diff options
| author | Thomas Goirand <zigo@debian.org> | 2019-02-21 09:20:30 +0100 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2019-02-21 16:40:38 +0000 |
| commit | f6e5ea4b4aad1f2ae797be1c623e7f861aa5b688 (patch) | |
| tree | c972a89b675f184e931d4304192177fbd85e661e | |
| parent | df63d3be8514ca60093cc461b7c83fc05e2226e4 (diff) | |
| parent | 613e5ab1174167bfb91141bd0dd09ba84626782e (diff) | |
3.16.2-1 (patches applied)applied/3.16.2-1applied/debian/buster
Imported using git-ubuntu import.
24 files changed, 480 insertions, 220 deletions
@@ -2,3 +2,4 @@ host=review.openstack.org port=29418 project=openstack/python-openstackclient.git +defaultbranch=stable/rocky @@ -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 diff --git a/debian/changelog b/debian/changelog index 8767c2e..4dcf550 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +python-openstackclient (3.16.2-1) unstable; urgency=medium + + [ Ondřej Nový ] + * Running wrap-and-sort -bast + * Use 'python3 -m sphinx' instead of sphinx-build for building docs + + [ Thomas Goirand ] + * New upstream point release. + + -- Thomas Goirand <zigo@debian.org> Thu, 21 Feb 2019 09:20:30 +0100 + python-openstackclient (3.16.0-3) unstable; urgency=high * Remove the test suite which is doing apt-get, and that isn't advised. diff --git a/debian/control b/debian/control index 6ff1983..7ac2448 100644 --- a/debian/control +++ b/debian/control @@ -86,10 +86,10 @@ Build-Depends-Indep: python3-searchlightclient (>= 1.0.0) <!nodoc>, python3-senlinclient (>= 1.1.0) <!nodoc>, python3-six, + python3-sphinxcontrib.apidoc <!nodoc>, python3-stestr <!nocheck>, python3-stevedore (>= 1.20.0), python3-subunit <!nocheck>, - python3-sphinxcontrib.apidoc <!nodoc>, python3-tempest (>= 1:17.1.0) <!nocheck>, python3-testtools (>= 2.2.0) <!nocheck>, python3-troveclient (>= 2.2.0) <!nodoc>, diff --git a/debian/rules b/debian/rules index 55e2865..ed57127 100755 --- a/debian/rules +++ b/debian/rules @@ -22,14 +22,14 @@ endif override_dh_sphinxdoc: ifeq (,$(findstring nodoc, $(DEB_BUILD_OPTIONS))) - sphinx-build -b man doc/source doc/build/man + python3 -m sphinx -b man doc/source doc/build/man PYTHONPATH=. python3 -m sphinx -b html doc/source $(CURDIR)/debian/python-openstackclient-doc/usr/share/doc/python-openstackclient-doc/html dh_sphinxdoc -O--buildsystem=python2 endif override_dh_installman: ifeq (,$(findstring nodoc, $(DEB_BUILD_OPTIONS))) - sphinx-build -b man doc/source doc/build/man + python3 -m sphinx -b man doc/source doc/build/man dh_installman endif diff --git a/doc/source/cli/command-objects/volume.rst b/doc/source/cli/command-objects/volume.rst index a06a5d4..8df48d4 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 <size>] [--type <volume-type>] - [--image <image> | --snapshot <snapshot> | --source <volume> | --source-replicated <replicated-volume>] + [--image <image> | --snapshot <snapshot> | --source <volume> ] [--description <description>] [--user <user>] [--project <project>] @@ -31,7 +31,7 @@ Create new volume .. option:: --size <size> Volume size in GB - (Required unless --snapshot or --source or --source-replicated is specified) + (Required unless --snapshot or --source is specified) .. option:: --type <volume-type> @@ -54,10 +54,6 @@ Create new volume Volume to clone (name or ID) -.. option:: --source-replicated <replicated-volume> - - Replicated volume to clone (name or ID) - .. option:: --description <description> Volume description diff --git a/openstackclient/common/project_purge.py b/openstackclient/common/project_purge.py index 5b1d007..76ed456 100644 --- a/openstackclient/common/project_purge.py +++ b/openstackclient/common/project_purge.py @@ -85,7 +85,7 @@ class ProjectPurge(command.Command): # 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 @@ class ProjectPurge(command.Command): # 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/compute/v2/server.py b/openstackclient/compute/v2/server.py index a6a5908..86ac1f8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -23,6 +23,7 @@ import os 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 @@ class AddFloatingIP(network_common.NetworkAndComputeCommand): parser.add_argument( "ip_address", metavar="<ip-address>", - 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="<ip-address>", 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 @@ class AddFloatingIP(network_common.NetworkAndComputeCommand): 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( @@ -809,9 +846,14 @@ class CreateServer(command.ShowOne): 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/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 3229240..858b503 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -199,7 +199,7 @@ class ListEndpoint(command.Lister): metavar='<project>', 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/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index bba16f6..3cb72d9 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -618,7 +618,9 @@ class ServerTests(common.ComputeTestCase): 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/functional/image/base.py b/openstackclient/tests/functional/image/base.py new file mode 100644 index 0000000..4b2ab64 --- /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 fa073f9..30490bf 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -15,50 +15,47 @@ import uuid 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 @@ class ImageTests(base.TestCase): '--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 @@ class ImageTests(base.TestCase): '--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 3037b90..3185c3b 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -16,60 +16,55 @@ import uuid 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 @@ class ImageTests(base.TestCase): '--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 @@ class ImageTests(base.TestCase): '--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 @@ class ImageTests(base.TestCase): 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 @@ class ImageTests(base.TestCase): 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 @@ class ImageTests(base.TestCase): 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 @@ class ImageTests(base.TestCase): 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 @@ class ImageTests(base.TestCase): # 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 73191fc..0399e6c 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 @@ class TransferRequestTests(common.BaseVolumeTests): '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 @@ class TransferRequestTests(common.BaseVolumeTests): '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 33495af..33d8ce7 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 @@ class TransferRequestTests(common.BaseVolumeTests): '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 b325323..f16dfaf 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 @@ from openstackclient.tests.functional.volume.v3 import common class TransferRequestTests(common.BaseVolumeTests, v2.TransferRequestTests): """Functional tests for transfer request. """ + + API_VERSION = '3' diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py index 2385eae..6e8ce18 100644 --- a/openstackclient/tests/unit/common/test_project_purge.py +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -117,10 +117,11 @@ class TestProjectPurge(TestProjectPurgeInit): 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 @@ class TestProjectPurge(TestProjectPurgeInit): 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 @@ class TestProjectPurge(TestProjectPurgeInit): 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 @@ class TestProjectPurge(TestProjectPurgeInit): 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 @@ class TestProjectPurge(TestProjectPurgeInit): 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 @@ class TestProjectPurge(TestProjectPurgeInit): 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( diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 9a065be..234bbd9 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -17,6 +17,7 @@ import copy import uuid import mock +from novaclient import api_versions from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -201,6 +202,8 @@ class FakeComputev2Client(object): 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 b3d3268..45fb400 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -19,6 +19,7 @@ import getpass 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 @@ class TestServerAddFloatingIPNetwork( 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 @@ class TestServerAddFloatingIPNetwork( } 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 @@ class TestServerAddFloatingIPNetwork( **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 @@ class TestServerAddFloatingIPNetwork( _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 @@ class TestServerAddFloatingIPNetwork( **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): @@ -845,6 +943,55 @@ class TestServerCreate(TestServer): 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', diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index bfe930d..62dcf58 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -439,6 +439,47 @@ class TestEndpointList(TestEndpoint): ) 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): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 304aa91..971567c 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -133,7 +133,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -185,7 +184,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=consistency_group.id, - source_replica=None, multiattach=True, scheduler_hints={'k': 'v'}, ) @@ -231,7 +229,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -277,7 +274,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -317,7 +313,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -359,7 +354,6 @@ class TestVolumeCreate(TestVolume): imageRef=image.id, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -401,7 +395,6 @@ class TestVolumeCreate(TestVolume): imageRef=image.id, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -442,7 +435,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -484,7 +476,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -530,7 +521,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -585,7 +575,6 @@ class TestVolumeCreate(TestVolume): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -598,40 +587,6 @@ class TestVolumeCreate(TestVolume): 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 @@ class TestVolumeCreate(TestVolume): '--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 @@ class TestVolumeCreate(TestVolume): ('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 ee3d2f2..fc648ce 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 @@ class CreateVolume(command.ShowOne): metavar="<size>", 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 @@ class CreateVolume(command.ShowOne): source_group.add_argument( "--source-replicated", metavar="<replicated-volume>", - help=_("Replicated volume to clone (name or ID)"), + help=argparse.SUPPRESS, ) parser.add_argument( "--description", @@ -168,12 +169,6 @@ class CreateVolume(command.ShowOne): 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 @@ class CreateVolume(command.ShowOne): 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, ) 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 0000000..738190f --- /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. @@ -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://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky} -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://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky} -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://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -119,7 +119,7 @@ commands = [testenv:releasenotes] deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html |
