diff options
| author | Chris MacNaughton <chris.macnaughton@ubuntu.com> | 2020-10-09 06:33:57 +0000 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2020-10-09 08:43:50 +0000 |
| commit | d84dabb3d5f3991862932c45f06e6d322b5361ab (patch) | |
| tree | ccbac0cee88eafeb28283f3e603e20e058951dae | |
| parent | b4a4c2922e520d08a8c14bba5f2d906a8cd3baef (diff) | |
5.4.0-0ubuntu1 (patches unapplied)import/5.4.0-0ubuntu1ubuntu/groovy-proposedubuntu/groovy-develubuntu/groovy
Imported using git-ubuntu import.
Notes
Notes:
* d/control: Update VCS paths for move to lp:~ubuntu-openstack-dev.
* New upstream release for OpenStack Victoria.
34 files changed, 1025 insertions, 132 deletions
@@ -91,6 +91,7 @@ neutron-segments: true q-metering: true q-qos: true + neutron-tag-ports-during-bulk-creation: true tox_envlist: functional - job: @@ -32,6 +32,7 @@ Ankur Gupta <ankur.gupta@intel.com> Anne Gentle <agentle@cisco.com> Anton Frolov <af9740@att.com> Aradhana Singh <aradhana1.singh> +Artem Goncharov <Artem.goncharov@gmail.com> Artem Goncharov <artem.goncharov@gmail.com> Artom Lifshitz <alifshit@redhat.com> Asha Saravanamohan <assarava@cisco.com> @@ -111,6 +112,7 @@ Fei Long Wang <flwang@catalyst.net.nz> Felix Yan <felixonmars@archlinux.org> Flavio Percoco <flaper87@gmail.com> Florent Flament <florent.flament-ext@cloudwatt.com> +Gabriel Ramirez <gabrielramirez1109@gmail.com> Gage Hugo <gagehugo@gmail.com> Gary Kotton <gkotton@vmware.com> Georgina Shippey <georgina.shippey@bbc.co.uk> @@ -201,6 +203,7 @@ Kyrylo Romanenko <kromanenko@mirantis.com> LIU Yulong <i@liuyulong.me> Lajos Katona <lajos.katona@ericsson.com> Lance Bragstad <lbragstad@gmail.com> +Lewis Denny <ldenny@redhat.com> Lin Yang <lin.a.yang@intel.com> Lingxian Kong <anlin.kong@gmail.com> LiuNanke <nanke.liu@easystack.cn> @@ -231,6 +234,7 @@ Matthieu Huin <mhu@enovance.com> Michael Gugino <michael.gugino@walmart.com> Michael Johnson <johnsomor@gmail.com> Michael McCune <msm@redhat.com> +Miguel Lavalle <miguel.lavalle@verizonmedia.com> Mike Fedosin <mfedosin@redhat.com> Mikhail Feoktistov <mfeoktistov@virtuozzo.com> Min Min Ren <rminmin@cn.ibm.com> @@ -238,6 +242,7 @@ Mohammed Naser <mnaser@vexxhost.com> Mohan Muppidi <mkumar2301@gmail.com> Monty Taylor <mordred@inaugust.com> Mouad Benchchaoui <m.benchchaoui@x-ion.de> +Myeongchul Chae <cocahack@naver.com> Nakul Dahiwade <nakul.dahiwade@intel.com> Nam Nguyen Hoai <namnh@vn.fujitsu.com> Natal Ngétal <hobbestigrou@erakis.eu> @@ -282,6 +287,7 @@ Rodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com> Rodrigo Duarte <rodrigods@lsd.ufcg.edu.br> Rodrigo Duarte Sousa <rduartes@redhat.com> Rodrigo Duarte Sousa <rodrigods@lsd.ufcg.edu.br> +Roger Luethi <rl@patchworkscience.org> Roxana Gherle <roxana.gherle@hp.com> Ruby Loo <rloo@oath.com> Rudolf Vriend <rudolf.vriend@sap.com> @@ -381,6 +387,7 @@ heha <zhanghanqun@unitedstack.com> henriquetruta <henrique@lsd.ufcg.edu.br> huangshan <huangshan@fiberhome.com> huangtianhua <huangtianhua@huawei.com> +jay <jayadityagupta11@gmail.com> jeckxie <xiexiaozhe@inspur.com> ji-xuepeng <ji.xuepeng@zte.com.cn> jiahui.qiang <jiahui.qiang@easystack.cn> @@ -392,6 +399,7 @@ judy-yu <yujie@cmss.chinamobile.com> kafka <guowang@unitedstack.com> licanwei <li.canwei2@zte.com.cn> lihaijing <lihaijing@fiberhome.com> +likui <likui@yovole.com> lin-hua-cheng <os.lcheng@gmail.com> lingyongxu <lyxu@fiberhome.com> liujunpeng <liujunpeng@inspur.com> @@ -402,6 +410,8 @@ liyingjun <liyingjun1988@gmail.com> lrqrun <lrqrun@gmail.com> lvjiawei <lvjiawei@cmss.chinamobile.com> lvxianguo <lvxianguo@inspur.com> +maaoyu <maaoyu@inspur.com> +mb711d <mb711d@att.com> melanie witt <melwittt@gmail.com> melissaml <ma.lei@99cloud.net> nidhimittalhada <nidhimittal19@gmail.com> @@ -431,9 +441,11 @@ venkata anil <anilvenkata@redhat.com> venkatamahesh <venkatamaheshkotha@gmail.com> wanghong <w.wanghong@huawei.com> wangxiyuan <wangxiyuan@huawei.com> +wangzihao <wangzihao@yovole.com> whoami-rajat <rajatdhasmana@gmail.com> wingwj <wingwj@gmail.com> wu.chunyang <wu.chunyang@99cloud.net> +wu.shiming <wushiming@yovole.com> wuyuting <wytdahu@gmail.com> xiexs <xiexs@cn.fujitsu.com> yang wang <wangy@rc.inesa.com> @@ -1,10 +1,30 @@ CHANGES ======= +5.4.0 +----- + +* Remove install unnecessary packages +* bump py37 to py38 in tox.ini +* Output correct json for security groups in 'openstack server show' +* Remove install unnecessary packages +* Fix gate due to switch to focal +* Fix --image-property option in 'create server' +* Update developing.rst +* Support tagging Neutron ports on creation +* Show words indicating booted from volume for server image +* Add API check for server\_groups.create +* Add API check for server\_groups.list +* Bypass user and group verification in RemoveRole +* Add NUMA affinity policy parameter to "port" +* Delete the testcases that arent needed anymore +* Fix reverted osc-lib interface change + 5.3.1 ----- * Fix compatibility issue in 5.3 +* Add system role assignment tests for users and groups 5.3.0 ----- @@ -19,6 +39,7 @@ CHANGES * port: add --host to list command * Don't display Munch objects in the output * Revert "Format location columns in network commands" +* Allow openstack flavor set to update flavor description using name * image: autogenerate docs * identity: autogenerate docs * Remove os-client-config references @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: python-openstackclient -Version: 5.3.1 +Version: 5.4.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -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/debian/changelog b/debian/changelog index a97af9f..2fb5714 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +python-openstackclient (5.4.0-0ubuntu1) groovy; urgency=medium + + * d/control: Update VCS paths for move to lp:~ubuntu-openstack-dev. + * New upstream release for OpenStack Victoria. + + -- Chris MacNaughton <chris.macnaughton@ubuntu.com> Fri, 09 Oct 2020 06:33:57 +0000 + python-openstackclient (5.3.1-0ubuntu1) groovy; urgency=medium * New upstream release for OpenStack Victoria. diff --git a/debian/control b/debian/control index 6fd01da..292a3e7 100644 --- a/debian/control +++ b/debian/control @@ -68,8 +68,8 @@ Build-Depends-Indep: python3-zunclient (>= 3.6.0), subunit, Standards-Version: 4.5.0 -Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-openstackclient -Vcs-Git: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-openstackclient +Vcs-Browser: https://git.launchpad.net/~ubuntu-openstack-dev/ubuntu/+source/python-openstackclient +Vcs-Git: https://git.launchpad.net/~ubuntu-openstack-dev/ubuntu/+source/python-openstackclient Homepage: https://opendev.org/openstack/python-openstackclient Testsuite: autopkgtest-pkg-python diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 5b85919..35c7c7b 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 diff --git a/lower-constraints.txt b/lower-constraints.txt index 0a453da..403ba4e 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 diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 42649db..805e919 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -402,7 +402,7 @@ class SetFlavor(command.Command): 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/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f96..1d1fc74 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -24,6 +24,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 format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -38,6 +39,8 @@ from openstackclient.network import common as network_common 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 +150,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', {}) @@ -166,14 +175,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 +191,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: @@ -751,19 +765,27 @@ class CreateServer(command.ShowOne): 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) @@ -1520,8 +1542,12 @@ class ListServer(command.Lister): 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: @@ -2530,7 +2556,6 @@ class ShowServer(command.ShowOne): data = _prep_server_detail(compute_client, self.app.client_manager.image, server, refresh=False) - return zip(*sorted(data.items())) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index c49a552..1af6e28 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 @@ class CreateServerGroup(command.ShowOne): 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) @@ -136,11 +141,15 @@ class ListServerGroup(command.Lister): 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 +158,7 @@ class ListServerGroup(command.Lister): column_headers = columns = ( 'ID', 'Name', - 'Policies', + policy_key, ) return (column_headers, diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index e70d87d..a75db4f 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}) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 980ebf1..a674564 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 @@ class RemoveRole(command.Command): ) 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/network/v2/port.py b/openstackclient/network/v2/port.py index a21324a..02ab06c 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 @@ -454,12 +480,23 @@ class CreatePort(command.ShowOne): 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) @@ -904,6 +941,11 @@ class UnsetPort(command.Command): 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 +1001,8 @@ class UnsetPort(command.Command): 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/functional/base.py b/openstackclient/tests/functional/base.py index 08e9390..3542a82 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -19,10 +19,6 @@ from tempest.lib import exceptions 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/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6e080e9..44d9c61 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -16,6 +16,7 @@ import uuid 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 @@ -230,7 +231,7 @@ class ServerTests(common.ComputeTestCase): )) # Really, shouldn't this be a list? self.assertEqual( - "a='b', c='d'", + {'a': 'b', 'c': 'd'}, cmd_output['properties'], ) @@ -244,7 +245,7 @@ class ServerTests(common.ComputeTestCase): name )) self.assertEqual( - "c='d'", + {'c': 'd'}, cmd_output['properties'], ) @@ -509,6 +510,20 @@ class ServerTests(common.ComputeTestCase): 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 ' + @@ -619,8 +634,8 @@ class ServerTests(common.ComputeTestCase): 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 +714,8 @@ class ServerTests(common.ComputeTestCase): 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,19 +788,21 @@ class ServerTests(common.ComputeTestCase): 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=''. - 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( '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 +896,21 @@ class ServerTests(common.ComputeTestCase): 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/functional/examples/__init__.py b/openstackclient/tests/functional/examples/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/openstackclient/tests/functional/examples/__init__.py +++ /dev/null diff --git a/openstackclient/tests/functional/examples/test_examples.py b/openstackclient/tests/functional/examples/test_examples.py deleted file mode 100644 index 031f036..0000000 --- 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') diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 6e12f73..3143098 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 @@ class FakeServerGroup(object): '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 @@ class FakeServerGroup(object): 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_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index fe7ce17..4732cc8 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -749,6 +749,42 @@ class TestFlavorSet(TestFlavor): 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): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c..02bb406 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2048,6 +2048,65 @@ class TestServerCreate(TestServer): 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 = [ @@ -2609,7 +2668,7 @@ class TestServerList(TestServer): 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 +2681,8 @@ class TestServerList(TestServer): ), 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 +2695,7 @@ class TestServerList(TestServer): 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'] )) @@ -5166,6 +5225,8 @@ class TestServerGeneral(TestServer): '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 +5243,7 @@ class TestServerGeneral(TestServer): '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/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 9cd876e..359cd2b 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 @@ class TestServerGroup(compute_fakes.TestComputev2): 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): @@ -80,6 +108,28 @@ class TestServerGroupCreate(TestServerGroup): 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): @@ -230,6 +280,76 @@ class TestServerGroupList(TestServerGroup): 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): diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 544da7c..774b2c2 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -19,6 +19,7 @@ from unittest import mock 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 @@ -102,6 +103,40 @@ class TestRoleAdd(TestRole): # 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 +203,40 @@ class TestRoleAdd(TestRole): ) 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 +813,81 @@ class TestRoleRemove(TestRole): # 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) + + @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, @@ -777,6 +921,46 @@ class TestRoleRemove(TestRole): ) 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, @@ -810,6 +994,121 @@ class TestRoleRemove(TestRole): ) 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, + '--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) + + @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, @@ -844,6 +1143,46 @@ class TestRoleRemove(TestRole): ) 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, @@ -877,6 +1216,46 @@ class TestRoleRemove(TestRole): ) 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/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cef0a11..3df4042 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -634,6 +634,7 @@ class FakePort(object): '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 87aea61..f729b55 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -59,6 +59,7 @@ class TestPort(network_fakes.TestNetworkV2): 'mac_address', 'name', 'network_id', + 'numa_affinity_policy', 'port_security_enabled', 'project_id', 'qos_network_policy_id', @@ -90,6 +91,7 @@ class TestPort(network_fakes.TestNetworkV2): 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, @@ -119,6 +121,7 @@ class TestCreatePort(TestPort): 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 +537,7 @@ class TestCreatePort(TestPort): '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 +556,59 @@ class TestCreatePort(TestPort): 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 = [ @@ -655,6 +689,50 @@ class TestCreatePort(TestPort): '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 +1746,32 @@ class TestSetPort(TestPort): 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 +2027,26 @@ class TestUnsetPort(TestPort): 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/python_openstackclient.egg-info/PKG-INFO b/python_openstackclient.egg-info/PKG-INFO index 8e0a1bd..016d65f 100644 --- a/python_openstackclient.egg-info/PKG-INFO +++ b/python_openstackclient.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: python-openstackclient -Version: 5.3.1 +Version: 5.4.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack diff --git a/python_openstackclient.egg-info/SOURCES.txt b/python_openstackclient.egg-info/SOURCES.txt index d9c2dc0..66c0e73 100644 --- a/python_openstackclient.egg-info/SOURCES.txt +++ b/python_openstackclient.egg-info/SOURCES.txt @@ -332,8 +332,6 @@ openstackclient/tests/functional/compute/v2/test_keypair.py openstackclient/tests/functional/compute/v2/test_server.py openstackclient/tests/functional/compute/v2/test_server_event.py openstackclient/tests/functional/compute/v2/test_server_group.py -openstackclient/tests/functional/examples/__init__.py -openstackclient/tests/functional/examples/test_examples.py openstackclient/tests/functional/identity/__init__.py openstackclient/tests/functional/identity/v2/__init__.py openstackclient/tests/functional/identity/v2/common.py @@ -646,6 +644,7 @@ releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml releasenotes/notes/add-parent-list-option-to-projects-10382a7176993366.yaml releasenotes/notes/add-port-commands-a3580662721a6312.yaml releasenotes/notes/add-port-delete-command-4789d3881b186cfc.yaml +releasenotes/notes/add-port-numa-affinity-policy-4706b0f9485a5d4d.yaml releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml releasenotes/notes/add-port-show-command-de0a599017189a21.yaml releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml @@ -964,6 +963,7 @@ releasenotes/notes/bug-1798744-5512256baf4dc633.yaml releasenotes/notes/bug-1827844-8f1de120087c6a22.yaml releasenotes/notes/bug-2005246-3fb70206bafc5444.yaml releasenotes/notes/bug-2005521-0274fc26bd9b3842.yaml +releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml releasenotes/notes/bug-2006761-9041d1b25e845cfb.yaml releasenotes/notes/bug-27882-402ced7ffe930058.yaml releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml @@ -1010,6 +1010,7 @@ releasenotes/notes/osc4-identity-6564257c67d43106.yaml releasenotes/notes/osc4-image-e922ee6f8e028648.yaml releasenotes/notes/osc4-network-db2aed696d964ca6.yaml releasenotes/notes/osc4-volume-470422e5a453310e.yaml +releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml releasenotes/notes/rbac-add-address-scope-7f6409ab70d36306.yaml releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml releasenotes/notes/rbac-add-subnetpool-f1fc0e728ff61654.yaml @@ -1024,6 +1025,7 @@ releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml releasenotes/notes/router-port-add-0afe7392c080bcb8.yaml releasenotes/notes/router-remove-port-058078c93819b0f4.yaml releasenotes/notes/router-subnet-469d095ae0bac884.yaml +releasenotes/notes/security-grp-json-fix.yaml-2af1f48a48034d64.yaml releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml releasenotes/notes/server-description-ae9618fc09544cac.yaml releasenotes/notes/server-rebuild-property-e8c6439b04e27c80.yaml diff --git a/python_openstackclient.egg-info/pbr.json b/python_openstackclient.egg-info/pbr.json index a09307c..1d3f669 100644 --- a/python_openstackclient.egg-info/pbr.json +++ b/python_openstackclient.egg-info/pbr.json @@ -1 +1 @@ -{"git_version": "82ebddca", "is_release": true}
\ No newline at end of file +{"git_version": "70465629", "is_release": true}
\ No newline at end of file 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 0000000..0fbc54b --- /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. diff --git a/releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml b/releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml new file mode 100644 index 0000000..ecc58c8 --- /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 <https://storyboard.openstack.org/#!/story/2006635>`_] 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 0000000..cf082f4 --- /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. 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 0000000..3a0155a --- /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. @@ -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 @@ -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 |
