summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris MacNaughton <chris.macnaughton@ubuntu.com>2020-10-09 06:33:57 +0000
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2020-10-09 08:43:50 +0000
commitd84dabb3d5f3991862932c45f06e6d322b5361ab (patch)
treeccbac0cee88eafeb28283f3e603e20e058951dae
parentb4a4c2922e520d08a8c14bba5f2d906a8cd3baef (diff)
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.
-rw-r--r--.zuul.yaml1
-rw-r--r--AUTHORS12
-rw-r--r--ChangeLog21
-rw-r--r--PKG-INFO2
-rw-r--r--bindep.txt1
-rw-r--r--debian/changelog7
-rw-r--r--debian/control4
-rw-r--r--doc/source/contributor/developing.rst2
-rw-r--r--lower-constraints.txt4
-rw-r--r--openstackclient/compute/v2/flavor.py2
-rw-r--r--openstackclient/compute/v2/server.py47
-rw-r--r--openstackclient/compute/v2/server_group.py17
-rw-r--r--openstackclient/identity/common.py2
-rw-r--r--openstackclient/identity/v3/role.py68
-rw-r--r--openstackclient/network/v2/port.py48
-rw-r--r--openstackclient/tests/functional/base.py4
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py58
-rw-r--r--openstackclient/tests/functional/examples/__init__.py0
-rw-r--r--openstackclient/tests/functional/examples/test_examples.py28
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py35
-rw-r--r--openstackclient/tests/unit/compute/v2/test_flavor.py36
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py70
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_group.py120
-rw-r--r--openstackclient/tests/unit/identity/v3/test_role.py379
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py1
-rw-r--r--openstackclient/tests/unit/network/v2/test_port.py153
-rw-r--r--python_openstackclient.egg-info/PKG-INFO2
-rw-r--r--python_openstackclient.egg-info/SOURCES.txt6
-rw-r--r--python_openstackclient.egg-info/pbr.json2
-rw-r--r--releasenotes/notes/add-port-numa-affinity-policy-4706b0f9485a5d4d.yaml5
-rw-r--r--releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml7
-rw-r--r--releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml6
-rw-r--r--releasenotes/notes/security-grp-json-fix.yaml-2af1f48a48034d64.yaml4
-rw-r--r--tox.ini3
34 files changed, 1025 insertions, 132 deletions
diff --git a/.zuul.yaml b/.zuul.yaml
index e566ceb..c04f344 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -91,6 +91,7 @@
neutron-segments: true
q-metering: true
q-qos: true
+ neutron-tag-ports-during-bulk-creation: true
tox_envlist: functional
- job:
diff --git a/AUTHORS b/AUTHORS
index fa123d4..a31532d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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>
diff --git a/ChangeLog b/ChangeLog
index bf4523d..9c8ffa6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/PKG-INFO b/PKG-INFO
index 8e0a1bd..016d65f 100644
--- a/PKG-INFO
+++ b/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/bindep.txt b/bindep.txt
index cf94d2a..4c90a02 100644
--- a/bindep.txt
+++ b/bindep.txt
@@ -5,7 +5,6 @@ gcc [compile test]
libc6-dev [compile test platform:dpkg]
libffi-devel [platform:rpm]
libffi-dev [compile test platform:dpkg]
-libffi6 [platform:dpkg]
libssl-dev [compile test platform:dpkg]
python3-dev [compile test platform:dpkg]
python3-devel [compile test platform:rpm]
diff --git a/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.
diff --git a/tox.ini b/tox.ini
index 3a2a307..c0f6dd5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
minversion = 3.2.0
-envlist = py37,pep8
+envlist = py38,pep8
skipdist = True
# Automatic envs (pyXX) will only use the python version appropriate to that
# env and ignore basepython inherited from [testenv] if we set
@@ -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