summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/command-objects/consistency-group.rst46
-rw-r--r--doc/source/command-objects/flavor.rst4
-rw-r--r--doc/source/command-objects/image.rst31
-rw-r--r--doc/source/command-objects/network-qos-rule.rst165
-rw-r--r--doc/source/commands.rst1
-rw-r--r--openstackclient/common/quota.py40
-rw-r--r--openstackclient/compute/v2/flavor.py4
-rw-r--r--openstackclient/identity/v2_0/project.py24
-rw-r--r--openstackclient/identity/v2_0/role.py23
-rw-r--r--openstackclient/identity/v2_0/user.py24
-rw-r--r--openstackclient/identity/v3/group.py22
-rw-r--r--openstackclient/identity/v3/project.py30
-rw-r--r--openstackclient/identity/v3/role.py27
-rw-r--r--openstackclient/identity/v3/trust.py26
-rw-r--r--openstackclient/identity/v3/user.py30
-rw-r--r--openstackclient/image/v2/image.py36
-rw-r--r--openstackclient/network/v2/floating_ip.py81
-rw-r--r--openstackclient/network/v2/network_agent.py28
-rw-r--r--openstackclient/network/v2/network_qos_rule.py356
-rw-r--r--openstackclient/network/v2/security_group_rule.py45
-rw-r--r--openstackclient/tests/functional/base.py5
-rw-r--r--openstackclient/tests/functional/common/test_quota.py3
-rw-r--r--openstackclient/tests/functional/image/v2/test_image.py22
-rw-r--r--openstackclient/tests/functional/network/v2/test_floating_ip.py8
-rw-r--r--openstackclient/tests/functional/network/v2/test_network.py229
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_agent.py4
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_qos_rule.py181
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py6
-rw-r--r--openstackclient/tests/functional/network/v2/test_port.py3
-rw-r--r--openstackclient/tests/functional/network/v2/test_security_group_rule.py3
-rwxr-xr-xopenstackclient/tests/functional/post_test_hook_tips.sh47
-rw-r--r--openstackclient/tests/unit/fakes.py6
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_project.py29
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_role.py27
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_user.py27
-rw-r--r--openstackclient/tests/unit/identity/v3/test_group.py27
-rw-r--r--openstackclient/tests/unit/identity/v3/test_project.py27
-rw-r--r--openstackclient/tests/unit/identity/v3/test_role.py34
-rw-r--r--openstackclient/tests/unit/identity/v3/test_trust.py31
-rw-r--r--openstackclient/tests/unit/identity/v3/test_user.py29
-rw-r--r--openstackclient/tests/unit/image/v2/test_image.py100
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py165
-rw-r--r--openstackclient/tests/unit/network/v2/test_floating_ip.py90
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_agent.py4
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_qos_rule.py1049
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py20
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group.py206
-rw-r--r--openstackclient/tests/unit/volume/v2/test_type.py58
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py117
-rw-r--r--openstackclient/volume/v2/consistency_group.py92
-rw-r--r--openstackclient/volume/v2/volume.py9
-rw-r--r--releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml8
-rw-r--r--releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml5
-rw-r--r--releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml6
-rw-r--r--releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml6
-rw-r--r--releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml6
-rw-r--r--releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml6
-rw-r--r--releasenotes/notes/bug-1656572-b40303ae50a41000.yaml6
-rw-r--r--releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml5
-rw-r--r--releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml4
-rw-r--r--requirements.txt4
-rw-r--r--setup.cfg8
-rw-r--r--test-requirements.txt2
-rw-r--r--tox.ini10
64 files changed, 3462 insertions, 315 deletions
diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst
index 658d9425..e54ac65f 100644
--- a/doc/source/command-objects/consistency-group.rst
+++ b/doc/source/command-objects/consistency-group.rst
@@ -4,6 +4,28 @@ consistency group
Block Storage v2
+consistency group add volume
+----------------------------
+
+Add volume(s) to consistency group.
+
+.. program:: consistency group add volume
+.. code:: bash
+
+ os consistency group add volume
+ <consistency-group>
+ <volume> [<volume> ...]
+
+.. _consistency_group_add_volume:
+.. describe:: <consistency-group>
+
+ Consistency group to contain <volume> (name or ID)
+
+.. describe:: <volume>
+
+ Volume(s) to add to <consistency-group> (name or ID)
+ (repeat option to add multiple volumes)
+
consistency group create
------------------------
@@ -86,13 +108,35 @@ List consistency groups.
List additional fields in output
+consistency group remove volume
+-------------------------------
+
+Remove volume(s) from consistency group.
+
+.. program:: consistency group remove volume
+.. code:: bash
+
+ os consistency group remove volume
+ <consistency-group>
+ <volume> [<volume> ...]
+
+.. _consistency_group_remove_volume:
+.. describe:: <consistency-group>
+
+ Consistency group containing <volume> (name or ID)
+
+.. describe:: <volume>
+
+ Volume(s) to remove from <consistency-group> (name or ID)
+ (repeat option to remove multiple volumes)
+
consistency group set
---------------------
Set consistency group properties.
.. program:: consistency group set
- .. code:: bash
+.. code:: bash
openstack consistency group set
[--name <name>]
diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst
index 0900f2ed..971628d7 100644
--- a/doc/source/command-objects/flavor.rst
+++ b/doc/source/command-objects/flavor.rst
@@ -42,9 +42,9 @@ Create new flavor
Ephemeral disk size in GB (default 0G)
-.. option:: --swap <size-gb>
+.. option:: --swap <size-mb>
- Swap space size in GB (default 0G)
+ Swap space size in MB (default 0M)
.. option:: --vcpus <num-cpu>
diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst
index 7ebcb54a..999842af 100644
--- a/doc/source/command-objects/image.rst
+++ b/doc/source/command-objects/image.rst
@@ -325,6 +325,7 @@ Set image properties
[--ramdisk-id <ramdisk-id>]
[--activate|--deactivate]
[--project <project> [--project-domain <project-domain>]]
+ [--accept | --reject | --pending]
<image>
.. option:: --name <name>
@@ -490,6 +491,36 @@ Set image properties
.. versionadded:: 2
+.. option:: --accept
+
+ Accept the image membership.
+
+ If `--project` is passed, this will update the membership status for the
+ given project, otherwise `--project` will default to the project the user
+ is authenticated to.
+
+ .. versionadded:: 2
+
+.. option:: --reject
+
+ Reject the image membership.
+
+ If `--project` is passed, this will update the membership status for the
+ given project, otherwise `--project` will default to the project the user
+ is authenticated to.
+
+ .. versionadded:: 2
+
+.. option:: --pending
+
+ Reset the image membership to 'pending'.
+
+ If `--project` is passed, this will update the membership status for the
+ given project, otherwise `--project` will default to the project the user
+ is authenticated to.
+
+ .. versionadded:: 2
+
.. _image_set-image:
.. describe:: <image>
diff --git a/doc/source/command-objects/network-qos-rule.rst b/doc/source/command-objects/network-qos-rule.rst
new file mode 100644
index 00000000..b98244b1
--- /dev/null
+++ b/doc/source/command-objects/network-qos-rule.rst
@@ -0,0 +1,165 @@
+================
+network qos rule
+================
+
+A **Network QoS rule** specifies a rule defined in a Network QoS policy; its
+type is defined by the parameter 'type'. Can be assigned, within a Network QoS
+policy, to a port or a network. Each Network QoS policy can contain several
+rules, each of them
+
+Network v2
+
+network qos rule create
+-----------------------
+
+Create new Network QoS rule
+
+.. program:: network qos rule create
+.. code:: bash
+
+ os network qos rule create
+ --type <type>
+ [--max-kbps <max-kbps>]
+ [--max-burst-kbits <max-burst-kbits>]
+ [--dscp-marks <dscp-marks>]
+ [--min-kbps <min-kbps>]
+ [--ingress | --egress]
+ <qos-policy>
+
+.. option:: --type <type>
+
+ QoS rule type (minimum-bandwidth, dscp-marking, bandwidth-limit)
+
+.. option:: --max-kbps <min-kbps>
+
+ Maximum bandwidth in kbps
+
+.. option:: --max-burst-kbits <max-burst-kbits>
+
+ Maximum burst in kilobits, 0 means automatic
+
+.. option:: --dscp-mark <dscp-mark>
+
+ DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50,
+ 52, and 54
+
+.. option:: --min-kbps <min-kbps>
+
+ Minimum guaranteed bandwidth in kbps
+
+.. option:: --ingress
+
+ Ingress traffic direction from the project point of view
+
+.. option:: --egress
+
+ Egress traffic direction from the project point of view
+
+.. describe:: <qos-policy>
+
+ QoS policy that contains the rule (name or ID)
+
+network qos rule delete
+-----------------------
+
+Delete Network QoS rule
+
+.. program:: network qos rule delete
+.. code:: bash
+
+ os network qos rule delete
+ <qos-policy>
+ <rule-id>
+
+.. describe:: <qos-policy>
+
+ QoS policy that contains the rule (name or ID)
+
+.. describe:: <rule-id>
+
+ Network QoS rule to delete (ID)
+
+network qos rule list
+---------------------
+
+List Network QoS rules
+
+.. program:: network qos rule list
+.. code:: bash
+
+ os network qos rule list
+ <qos-policy>
+
+.. describe:: <qos-policy>
+
+ QoS policy that contains the rule (name or ID)
+
+network qos rule set
+--------------------
+
+Set Network QoS rule properties
+
+.. program:: network qos rule set
+.. code:: bash
+
+ os network qos rule set
+ [--max-kbps <max-kbps>]
+ [--max-burst-kbits <max-burst-kbits>]
+ [--dscp-marks <dscp-marks>]
+ [--min-kbps <min-kbps>]
+ [--ingress | --egress]
+ <qos-policy>
+ <rule-id>
+
+.. option:: --max-kbps <min-kbps>
+
+ Maximum bandwidth in kbps
+
+.. option:: --max-burst-kbits <max-burst-kbits>
+
+ Maximum burst in kilobits, 0 means automatic
+
+.. option:: --dscp-mark <dscp-mark>
+
+ DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50,
+ 52, and 54
+
+.. option:: --min-kbps <min-kbps>
+
+ Minimum guaranteed bandwidth in kbps
+
+.. option:: --ingress
+
+ Ingress traffic direction from the project point of view
+
+.. option:: --egress
+
+ Egress traffic direction from the project point of view
+
+.. describe:: <qos-policy>
+
+ QoS policy that contains the rule (name or ID)
+
+.. describe:: <rule-id>
+
+ Network QoS rule to delete (ID)
+
+network qos rule show
+---------------------
+
+Display Network QoS rule details
+
+.. program:: network qos rule show
+.. code:: bash
+
+ os network qos rule show
+ <qos-policy>
+ <rule-id>
+
+.. describe:: <qos-policy>
+
+ QoS policy that contains the rule (name or ID)
+
+.. describe:: <rule-id>
+
+ Network QoS rule to delete (ID)
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index b81c121b..78668a0f 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -113,6 +113,7 @@ referring to both Compute and Volume quotas.
* ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks
* ``network meter``: (**Network**) - allow traffic metering in a network
* ``network rbac``: (**Network**) - an RBAC policy for network resources
+* ``network qos rule``: (**Network**) - a QoS rule for network resources
* ``network qos policy``: (**Network**) - a QoS policy for network resources
* ``network qos rule type``: (**Network**) - list of QoS available rule types
* ``network segment``: (**Network**) - a segment of a virtual network
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 58368c56..fa6c5765 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -18,6 +18,8 @@
import itertools
import sys
+from openstack import exceptions as sdk_exceptions
+from openstack.network.v2 import quota as _quota
from osc_lib.command import command
from osc_lib import utils
import six
@@ -251,7 +253,39 @@ class ShowQuota(command.ShowOne):
project = self._get_project(parsed_args)
client = self.app.client_manager.network
if parsed_args.default:
- network_quota = client.get_quota_default(project)
+ # TODO(dtroyer): Remove the top of this if block once the
+ # fixed SDK QuotaDefault class is the minimum
+ # required version. This is expected to be
+ # SDK release 0.9.13
+ if hasattr(_quota.QuotaDefault, 'project'):
+ # hack 0.9.11+
+ quotadef_obj = client._get_resource(
+ _quota.QuotaDefault,
+ project,
+ )
+ quotadef_obj.base_path = quotadef_obj.base_path % {
+ 'project': project,
+ }
+ try:
+ network_quota = quotadef_obj.get(
+ client.session,
+ requires_id=False,
+ )
+ except sdk_exceptions.NotFoundException as e:
+ raise sdk_exceptions.ResourceNotFound(
+ message="No %s found for %s" %
+ (_quota.QuotaDefault.__name__, project),
+ details=e.details,
+ response=e.response,
+ request_id=e.request_id,
+ url=e.url,
+ method=e.method,
+ http_status=e.http_status,
+ cause=e.cause,
+ )
+ # end hack-around
+ else:
+ network_quota = client.get_quota_default(project)
else:
network_quota = client.get_quota(project)
return network_quota
@@ -273,6 +307,10 @@ class ShowQuota(command.ShowOne):
volume_quota_info = self.get_compute_volume_quota(volume_client,
parsed_args)
network_quota_info = self.get_network_quota(parsed_args)
+ # NOTE(reedip): Remove the below check once requirement for
+ # Openstack SDK is fixed to version 0.9.12 and above
+ if type(network_quota_info) is not dict:
+ network_quota_info = network_quota_info.to_dict()
info = {}
info.update(compute_quota_info)
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index e562cd40..7cd22ed7 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -89,9 +89,9 @@ class CreateFlavor(command.ShowOne):
parser.add_argument(
"--swap",
type=int,
- metavar="<size-gb>",
+ metavar="<size-mb>",
default=0,
- help=_("Swap space size in GB (default 0G)")
+ help=_("Swap space size in MB (default 0M)")
)
parser.add_argument(
"--vcpus",
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index 8526d6bd..ca565d4d 100644
--- a/openstackclient/identity/v2_0/project.py
+++ b/openstackclient/identity/v2_0/project.py
@@ -20,6 +20,7 @@ import logging
from keystoneauth1 import exceptions as ks_exc
from osc_lib.cli import parseractions
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -117,12 +118,25 @@ class DeleteProject(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ errors = 0
for project in parsed_args.projects:
- project_obj = utils.find_resource(
- identity_client.tenants,
- project,
- )
- identity_client.tenants.delete(project_obj.id)
+ try:
+ project_obj = utils.find_resource(
+ identity_client.tenants,
+ project,
+ )
+ identity_client.tenants.delete(project_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete project with "
+ "name or ID '%(project)s': %(e)s"),
+ {'project': project, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.projects)
+ msg = (_("%(errors)s of %(total)s projects failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListProject(command.Lister):
diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py
index 0a28a70a..e254e05f 100644
--- a/openstackclient/identity/v2_0/role.py
+++ b/openstackclient/identity/v2_0/role.py
@@ -124,12 +124,25 @@ class DeleteRole(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ errors = 0
for role in parsed_args.roles:
- role_obj = utils.find_resource(
- identity_client.roles,
- role,
- )
- identity_client.roles.delete(role_obj.id)
+ try:
+ role_obj = utils.find_resource(
+ identity_client.roles,
+ role,
+ )
+ identity_client.roles.delete(role_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete role with "
+ "name or ID '%(role)s': %(e)s"),
+ {'role': role, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.roles)
+ msg = (_("%(errors)s of %(total)s roles failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListRole(command.Lister):
diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py
index ddd5b981..2a3dde6b 100644
--- a/openstackclient/identity/v2_0/user.py
+++ b/openstackclient/identity/v2_0/user.py
@@ -19,6 +19,7 @@ import logging
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -145,12 +146,25 @@ class DeleteUser(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ errors = 0
for user in parsed_args.users:
- user_obj = utils.find_resource(
- identity_client.users,
- user,
- )
- identity_client.users.delete(user_obj.id)
+ try:
+ user_obj = utils.find_resource(
+ identity_client.users,
+ user,
+ )
+ identity_client.users.delete(user_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete user with "
+ "name or ID '%(user)s': %(e)s"),
+ {'user': user, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.users)
+ msg = (_("%(errors)s of %(total)s users failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListUser(command.Lister):
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index df684c12..a03a86eb 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -20,6 +20,7 @@ import sys
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -194,11 +195,24 @@ class DeleteGroup(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ errors = 0
for group in parsed_args.groups:
- group_obj = common.find_group(identity_client,
- group,
- parsed_args.domain)
- identity_client.groups.delete(group_obj.id)
+ try:
+ group_obj = common.find_group(identity_client,
+ group,
+ parsed_args.domain)
+ identity_client.groups.delete(group_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete group with "
+ "name or ID '%(group)s': %(e)s"),
+ {'group': group, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.groups)
+ msg = (_("%(errors)s of %(total)s groups failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListGroup(command.Lister):
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index a6348659..12197cdd 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -20,6 +20,7 @@ import logging
from keystoneauth1 import exceptions as ks_exc
from osc_lib.cli import parseractions
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -148,15 +149,28 @@ class DeleteProject(command.Command):
domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client, parsed_args.domain)
+ errors = 0
for project in parsed_args.projects:
- if domain is not None:
- project_obj = utils.find_resource(identity_client.projects,
- project,
- domain_id=domain.id)
- else:
- project_obj = utils.find_resource(identity_client.projects,
- project)
- identity_client.projects.delete(project_obj.id)
+ try:
+ if domain is not None:
+ project_obj = utils.find_resource(identity_client.projects,
+ project,
+ domain_id=domain.id)
+ else:
+ project_obj = utils.find_resource(identity_client.projects,
+ project)
+ identity_client.projects.delete(project_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete project with "
+ "name or ID '%(project)s': %(e)s"),
+ {'project': project, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.projects)
+ msg = (_("%(errors)s of %(total)s projects failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListProject(command.Lister):
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index c9d0fbf3..994ecc9c 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -20,6 +20,7 @@ import sys
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -223,14 +224,26 @@ class DeleteRole(command.Command):
if parsed_args.domain:
domain_id = common.find_domain(identity_client,
parsed_args.domain).id
-
+ errors = 0
for role in parsed_args.roles:
- role_obj = utils.find_resource(
- identity_client.roles,
- role,
- domain_id=domain_id
- )
- identity_client.roles.delete(role_obj.id)
+ try:
+ role_obj = utils.find_resource(
+ identity_client.roles,
+ role,
+ domain_id=domain_id
+ )
+ identity_client.roles.delete(role_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete role with "
+ "name or ID '%(role)s': %(e)s"),
+ {'role': role, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.roles)
+ msg = (_("%(errors)s of %(total)s roles failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListRole(command.Lister):
diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py
index 62d72ea1..04ee4dce 100644
--- a/openstackclient/identity/v3/trust.py
+++ b/openstackclient/identity/v3/trust.py
@@ -14,8 +14,10 @@
"""Identity v3 Trust action implementations"""
import datetime
+import logging
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -23,6 +25,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateTrust(command.ShowOne):
_description = _("Create new trust")
@@ -145,9 +150,24 @@ class DeleteTrust(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- for t in parsed_args.trust:
- trust_obj = utils.find_resource(identity_client.trusts, t)
- identity_client.trusts.delete(trust_obj.id)
+
+ errors = 0
+ for trust in parsed_args.trust:
+ try:
+ trust_obj = utils.find_resource(identity_client.trusts,
+ trust)
+ identity_client.trusts.delete(trust_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete trust with "
+ "name or ID '%(trust)s': %(e)s"),
+ {'trust': trust, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.trust)
+ msg = (_("%(errors)s of %(total)s trusts failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListTrust(command.Lister):
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index 796cf28c..19a4c298 100644
--- a/openstackclient/identity/v3/user.py
+++ b/openstackclient/identity/v3/user.py
@@ -20,6 +20,7 @@ import logging
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -161,15 +162,28 @@ class DeleteUser(command.Command):
domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client, parsed_args.domain)
+ errors = 0
for user in parsed_args.users:
- if domain is not None:
- user_obj = utils.find_resource(identity_client.users,
- user,
- domain_id=domain.id)
- else:
- user_obj = utils.find_resource(identity_client.users,
- user)
- identity_client.users.delete(user_obj.id)
+ try:
+ if domain is not None:
+ user_obj = utils.find_resource(identity_client.users,
+ user,
+ domain_id=domain.id)
+ else:
+ user_obj = utils.find_resource(identity_client.users,
+ user)
+ identity_client.users.delete(user_obj.id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete user with "
+ "name or ID '%(user)s': %(e)s"),
+ {'user': user, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.users)
+ msg = (_("%(errors)s of %(total)s users failed "
+ "to delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
class ListUser(command.Lister):
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 55eb7eb1..418cc397 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -777,6 +777,23 @@ class SetImage(command.Command):
dest=deadopt.replace('-', '_'),
help=argparse.SUPPRESS,
)
+
+ membership_group = parser.add_mutually_exclusive_group()
+ membership_group.add_argument(
+ "--accept",
+ action="store_true",
+ help=_("Accept the image membership"),
+ )
+ membership_group.add_argument(
+ "--reject",
+ action="store_true",
+ help=_("Reject the image membership"),
+ )
+ membership_group.add_argument(
+ "--pending",
+ action="store_true",
+ help=_("Reset the image membership to 'pending'"),
+ )
return parser
def take_action(self, parsed_args):
@@ -828,12 +845,14 @@ class SetImage(command.Command):
project_arg = parsed_args.owner
LOG.warning(_('The --owner option is deprecated, '
'please use --project instead.'))
+ project_id = None
if project_arg:
- kwargs['owner'] = common.find_project(
+ project_id = common.find_project(
identity_client,
project_arg,
parsed_args.project_domain,
).id
+ kwargs['owner'] = project_id
image = utils.find_resource(
image_client.images, parsed_args.image)
@@ -846,6 +865,21 @@ class SetImage(command.Command):
image_client.images.reactivate(image.id)
activation_status = "activated"
+ membership_group_args = ('accept', 'reject', 'pending')
+ membership_status = [status for status in membership_group_args
+ if getattr(parsed_args, status)]
+ if membership_status:
+ # If a specific project is not passed, assume we want to update
+ # our own membership
+ if not project_id:
+ project_id = self.app.client_manager.auth_ref.project_id
+ # The mutually exclusive group of the arg parser ensure we have at
+ # most one item in the membership_status list.
+ if membership_status[0] != 'pending':
+ membership_status[0] += 'ed' # Glance expects the past form
+ image_client.image_members.update(
+ image.id, project_id, membership_status[0])
+
if parsed_args.tags:
# Tags should be extended, but duplicates removed
kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags)))
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index 8202b3fa..980c41c7 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -15,6 +15,8 @@
import logging
+from openstack import exceptions as sdk_exceptions
+from openstack.network.v2 import floating_ip as _floating_ip
from osc_lib import utils
from openstackclient.i18n import _
@@ -79,6 +81,58 @@ def _get_attrs(client_manager, parsed_args):
return attrs
+def _find_floating_ip(
+ session,
+ ip_cache,
+ name_or_id,
+ ignore_missing=True,
+ **params
+):
+ """Find a floating IP by IP or ID
+
+ The SDK's find_ip() can only locate a floating IP by ID so we have
+ to do this ourselves.
+ """
+
+ def _get_one_match(name_or_id):
+ """Given a list of results, return the match"""
+ the_result = None
+ for maybe_result in ip_cache:
+ id_value = maybe_result.id
+ ip_value = maybe_result.floating_ip_address
+
+ if (id_value == name_or_id) or (ip_value == name_or_id):
+ # Only allow one resource to be found. If we already
+ # found a match, raise an exception to show it.
+ if the_result is None:
+ the_result = maybe_result
+ else:
+ msg = "More than one %s exists with the name '%s'."
+ msg = (msg % (_floating_ip.FloatingIP, name_or_id))
+ raise sdk_exceptions.DuplicateResource(msg)
+
+ return the_result
+
+ # Try to short-circuit by looking directly for a matching ID.
+ try:
+ match = _floating_ip.FloatingIP.existing(id=name_or_id, **params)
+ return (match.get(session), ip_cache)
+ except sdk_exceptions.NotFoundException:
+ pass
+
+ if len(ip_cache) == 0:
+ ip_cache = list(_floating_ip.FloatingIP.list(session, **params))
+
+ result = _get_one_match(name_or_id)
+ if result is not None:
+ return (result, ip_cache)
+
+ if ignore_missing:
+ return (None, ip_cache)
+ raise sdk_exceptions.ResourceNotFound(
+ "No %s found for %s" % (_floating_ip.FloatingIP.__name__, name_or_id))
+
+
class CreateFloatingIP(common.NetworkAndComputeShowOne):
_description = _("Create floating IP")
@@ -186,13 +240,28 @@ class DeleteFloatingIP(common.NetworkAndComputeDelete):
return parser
def take_action_network(self, client, parsed_args):
- obj = client.find_ip(self.r, ignore_missing=False)
+ (obj, self.ip_cache) = _find_floating_ip(
+ client.session,
+ self.ip_cache,
+ self.r,
+ ignore_missing=False,
+ )
client.delete_ip(obj)
def take_action_compute(self, client, parsed_args):
obj = utils.find_resource(client.floating_ips, self.r)
client.floating_ips.delete(obj.id)
+ def take_action(self, parsed_args):
+ """Implements a naive cache for the list of floating IPs"""
+
+ # NOTE(dtroyer): This really only prevents multiple list()
+ # calls when performing multiple resource deletes
+ # in a single command. In an interactive session
+ # each delete command will call list().
+ self.ip_cache = []
+ super(DeleteFloatingIP, self).take_action(parsed_args)
+
class DeleteIPFloating(DeleteFloatingIP):
_description = _("Delete floating IP(s)")
@@ -390,6 +459,9 @@ class ListIPFloating(ListFloatingIP):
class ShowFloatingIP(common.NetworkAndComputeShowOne):
_description = _("Display floating IP details")
+ # ip_cache is unused here but is a side effect of _find_floating_ip()
+ ip_cache = []
+
def update_parser_common(self, parser):
parser.add_argument(
'floating_ip',
@@ -399,7 +471,12 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne):
return parser
def take_action_network(self, client, parsed_args):
- obj = client.find_ip(parsed_args.floating_ip, ignore_missing=False)
+ (obj, self.ip_cache) = _find_floating_ip(
+ client.session,
+ [],
+ parsed_args.floating_ip,
+ ignore_missing=False,
+ )
display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns)
return (display_columns, data)
diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py
index b3411166..d429fa08 100644
--- a/openstackclient/network/v2/network_agent.py
+++ b/openstackclient/network/v2/network_agent.py
@@ -20,6 +20,7 @@ from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
@@ -31,10 +32,19 @@ def _format_admin_state(state):
_formatters = {
'admin_state_up': _format_admin_state,
+ 'is_admin_state_up': _format_admin_state,
'configurations': utils.format_dict,
}
+def _get_network_columns(item):
+ column_map = {
+ 'is_admin_state_up': 'admin_state_up',
+ 'is_alive': 'alive',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
class DeleteNetworkAgent(command.Command):
_description = _("Delete network agent(s)")
@@ -69,6 +79,8 @@ class DeleteNetworkAgent(command.Command):
raise exceptions.CommandError(msg)
+# TODO(huanxuan): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListNetworkAgent(command.Lister):
_description = _("List network agents")
@@ -98,8 +110,8 @@ class ListNetworkAgent(command.Lister):
'agent_type',
'host',
'availability_zone',
- 'alive',
- 'admin_state_up',
+ 'is_alive',
+ 'is_admin_state_up',
'binary'
)
column_headers = (
@@ -138,6 +150,8 @@ class ListNetworkAgent(command.Lister):
) for s in data))
+# TODO(huanxuan): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetNetworkAgent(command.Command):
_description = _("Set network agent properties")
@@ -168,10 +182,12 @@ class SetNetworkAgent(command.Command):
def take_action(self, parsed_args):
client = self.app.client_manager.network
- obj = client.get_agent(parsed_args.network_agent, ignore_missing=False)
+ obj = client.get_agent(parsed_args.network_agent)
attrs = {}
if parsed_args.description is not None:
attrs['description'] = str(parsed_args.description)
+ # TODO(huanxuan): Also update by the new attribute name
+ # "is_admin_state_up" after sdk 0.9.12
if parsed_args.enable:
attrs['admin_state_up'] = True
if parsed_args.disable:
@@ -193,7 +209,7 @@ class ShowNetworkAgent(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.network
- obj = client.get_agent(parsed_args.network_agent, ignore_missing=False)
- columns = tuple(sorted(list(obj.keys())))
+ obj = client.get_agent(parsed_args.network_agent)
+ display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters,)
- return columns, data
+ return display_columns, data
diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py
new file mode 100644
index 00000000..a662ca18
--- /dev/null
+++ b/openstackclient/network/v2/network_qos_rule.py
@@ -0,0 +1,356 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import itertools
+import logging
+import six
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.network import sdk_utils
+
+
+LOG = logging.getLogger(__name__)
+
+RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit'
+RULE_TYPE_DSCP_MARKING = 'dscp-marking'
+RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth'
+REQUIRED_PARAMETERS = {
+ RULE_TYPE_MINIMUM_BANDWIDTH: ['min_kbps', 'direction'],
+ RULE_TYPE_DSCP_MARKING: ['dscp_mark'],
+ RULE_TYPE_BANDWIDTH_LIMIT: ['max_kbps', 'max_burst_kbps']}
+DIRECTION_EGRESS = 'egress'
+DIRECTION_INGRESS = 'ingress'
+DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
+ 34, 36, 38, 40, 46, 48, 56]
+
+ACTION_CREATE = 'create'
+ACTION_DELETE = 'delete'
+ACTION_FIND = 'find'
+ACTION_SET = 'update'
+ACTION_SHOW = 'get'
+
+
+def _get_columns(item):
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _check_type_parameters(attrs, type, is_create):
+ req_params = REQUIRED_PARAMETERS[type]
+ notreq_params = list(itertools.chain(
+ *[v for k, v in six.iteritems(REQUIRED_PARAMETERS) if k != type]))
+ if is_create and None in map(attrs.get, req_params):
+ msg = (_('"Create" rule command for type "%(rule_type)s" requires '
+ 'arguments %(args)s') % {'rule_type': type,
+ 'args': ", ".join(req_params)})
+ raise exceptions.CommandError(msg)
+ if set(six.iterkeys(attrs)) & set(notreq_params):
+ msg = (_('Rule type "%(rule_type)s" only requires arguments %(args)s')
+ % {'rule_type': type, 'args': ", ".join(req_params)})
+ raise exceptions.CommandError(msg)
+
+
+def _get_attrs(network_client, parsed_args, is_create=False):
+ attrs = {}
+ qos = network_client.find_qos_policy(parsed_args.qos_policy,
+ ignore_missing=False)
+ attrs['qos_policy_id'] = qos.id
+ if not is_create:
+ attrs['id'] = parsed_args.id
+ rule_type = _find_rule_type(qos, parsed_args.id)
+ if not rule_type:
+ msg = (_('Rule ID %(rule_id)s not found') %
+ {'rule_id': parsed_args.id})
+ raise exceptions.CommandError(msg)
+ else:
+ if not parsed_args.type:
+ msg = _('"Create" rule command requires argument "type"')
+ raise exceptions.CommandError(msg)
+ rule_type = parsed_args.type
+ if parsed_args.max_kbps is not None:
+ attrs['max_kbps'] = parsed_args.max_kbps
+ if parsed_args.max_burst_kbits is not None:
+ # NOTE(ralonsoh): this parameter must be changed in SDK and then in
+ # Neutron API, from 'max_burst_kbps' to
+ # 'max_burst_kbits'
+ attrs['max_burst_kbps'] = parsed_args.max_burst_kbits
+ if parsed_args.dscp_mark is not None:
+ attrs['dscp_mark'] = parsed_args.dscp_mark
+ if parsed_args.min_kbps is not None:
+ attrs['min_kbps'] = parsed_args.min_kbps
+ if parsed_args.ingress:
+ attrs['direction'] = 'ingress'
+ if parsed_args.egress:
+ attrs['direction'] = 'egress'
+ _check_type_parameters(attrs, rule_type, is_create)
+ return attrs
+
+
+def _get_item_properties(item, fields):
+ """Return a tuple containing the item properties."""
+ row = []
+ for field in fields:
+ row.append(item.get(field, ''))
+ return tuple(row)
+
+
+def _rule_action_call(client, action, rule_type):
+ rule_type = rule_type.replace('-', '_')
+ func_name = '%(action)s_qos_%(rule_type)s_rule' % {'action': action,
+ 'rule_type': rule_type}
+ return getattr(client, func_name)
+
+
+def _find_rule_type(qos, rule_id):
+ for rule in (r for r in qos.rules if r['id'] == rule_id):
+ return rule['type'].replace('_', '-')
+ return None
+
+
+def _add_rule_arguments(parser):
+ parser.add_argument(
+ '--max-kbps',
+ dest='max_kbps',
+ metavar='<max-kbps>',
+ type=int,
+ help=_('Maximum bandwidth in kbps')
+ )
+ parser.add_argument(
+ '--max-burst-kbits',
+ dest='max_burst_kbits',
+ metavar='<max-burst-kbits>',
+ type=int,
+ help=_('Maximum burst in kilobits, 0 means automatic')
+ )
+ parser.add_argument(
+ '--dscp-mark',
+ dest='dscp_mark',
+ metavar='<dscp-mark>',
+ type=int,
+ help=_('DSCP mark: value can be 0, even numbers from 8-56, '
+ 'excluding 42, 44, 50, 52, and 54')
+ )
+ parser.add_argument(
+ '--min-kbps',
+ dest='min_kbps',
+ metavar='<min-kbps>',
+ type=int,
+ help=_('Minimum guaranteed bandwidth in kbps')
+ )
+ direction_group = parser.add_mutually_exclusive_group()
+ direction_group.add_argument(
+ '--ingress',
+ action='store_true',
+ help=_("Ingress traffic direction from the project point of view")
+ )
+ direction_group.add_argument(
+ '--egress',
+ action='store_true',
+ help=_("Egress traffic direction from the project point of view")
+ )
+
+
+class CreateNetworkQosRule(command.ShowOne):
+ _description = _("Create new Network QoS rule")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateNetworkQosRule, self).get_parser(
+ prog_name)
+ parser.add_argument(
+ 'qos_policy',
+ metavar='<qos-policy>',
+ help=_('QoS policy that contains the rule (name or ID)')
+ )
+ parser.add_argument(
+ '--type',
+ metavar='<type>',
+ choices=[RULE_TYPE_MINIMUM_BANDWIDTH,
+ RULE_TYPE_DSCP_MARKING,
+ RULE_TYPE_BANDWIDTH_LIMIT],
+ help=(_('QoS rule type (%s)') %
+ ", ".join(six.iterkeys(REQUIRED_PARAMETERS)))
+ )
+ _add_rule_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ network_client = self.app.client_manager.network
+ attrs = _get_attrs(network_client, parsed_args, is_create=True)
+ try:
+ obj = _rule_action_call(
+ network_client, ACTION_CREATE, parsed_args.type)(
+ attrs.pop('qos_policy_id'), **attrs)
+ except Exception as e:
+ msg = (_('Failed to create Network QoS rule: %(e)s') % {'e': e})
+ raise exceptions.CommandError(msg)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteNetworkQosRule(command.Command):
+ _description = _("Delete Network QoS rule")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkQosRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'qos_policy',
+ metavar='<qos-policy>',
+ help=_('QoS policy that contains the rule (name or ID)')
+ )
+ parser.add_argument(
+ 'id',
+ metavar='<rule-id>',
+ help=_('Network QoS rule to delete (ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ network_client = self.app.client_manager.network
+ rule_id = parsed_args.id
+ try:
+ qos = network_client.find_qos_policy(parsed_args.qos_policy,
+ ignore_missing=False)
+ rule_type = _find_rule_type(qos, rule_id)
+ if not rule_type:
+ raise Exception('Rule %s not found' % rule_id)
+ _rule_action_call(network_client, ACTION_DELETE, rule_type)(
+ rule_id, qos.id)
+ except Exception as e:
+ msg = (_('Failed to delete Network QoS rule ID "%(rule)s": %(e)s')
+ % {'rule': rule_id, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ListNetworkQosRule(command.Lister):
+ _description = _("List Network QoS rules")
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworkQosRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'qos_policy',
+ metavar='<qos-policy>',
+ help=_('QoS policy that contains the rule (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ columns = (
+ 'id',
+ 'qos_policy_id',
+ 'type',
+ 'max_kbps',
+ 'max_burst_kbps',
+ 'min_kbps',
+ 'dscp_mark',
+ 'direction',
+ )
+ column_headers = (
+ 'ID',
+ 'QoS Policy ID',
+ 'Type',
+ 'Max Kbps',
+ 'Max Burst Kbits',
+ 'Min Kbps',
+ 'DSCP mark',
+ 'Direction',
+ )
+ qos = client.find_qos_policy(parsed_args.qos_policy,
+ ignore_missing=False)
+ data = qos.rules
+ return (column_headers,
+ (_get_item_properties(s, columns) for s in data))
+
+
+class SetNetworkQosRule(command.Command):
+ _description = _("Set Network QoS rule properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkQosRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'qos_policy',
+ metavar='<qos-policy>',
+ help=_('QoS policy that contains the rule (name or ID)')
+ )
+ parser.add_argument(
+ 'id',
+ metavar='<rule-id>',
+ help=_('Network QoS rule to delete (ID)')
+ )
+ _add_rule_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ network_client = self.app.client_manager.network
+ try:
+ qos = network_client.find_qos_policy(parsed_args.qos_policy,
+ ignore_missing=False)
+ rule_type = _find_rule_type(qos, parsed_args.id)
+ if not rule_type:
+ raise Exception('Rule not found')
+ attrs = _get_attrs(network_client, parsed_args)
+ qos_id = attrs.pop('qos_policy_id')
+ qos_rule = _rule_action_call(network_client, ACTION_FIND,
+ rule_type)(attrs.pop('id'), qos_id)
+ _rule_action_call(network_client, ACTION_SET, rule_type)(
+ qos_rule, qos_id, **attrs)
+ except Exception as e:
+ msg = (_('Failed to set Network QoS rule ID "%(rule)s": %(e)s') %
+ {'rule': parsed_args.id, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowNetworkQosRule(command.ShowOne):
+ _description = _("Display Network QoS rule details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkQosRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'qos_policy',
+ metavar='<qos-policy>',
+ help=_('QoS policy that contains the rule (name or ID)')
+ )
+ parser.add_argument(
+ 'id',
+ metavar='<rule-id>',
+ help=_('Network QoS rule to delete (ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ network_client = self.app.client_manager.network
+ rule_id = parsed_args.id
+ try:
+ qos = network_client.find_qos_policy(parsed_args.qos_policy,
+ ignore_missing=False)
+ rule_type = _find_rule_type(qos, rule_id)
+ if not rule_type:
+ raise Exception('Rule not found')
+ obj = _rule_action_call(network_client, ACTION_SHOW, rule_type)(
+ rule_id, qos.id)
+ except Exception as e:
+ msg = (_('Failed to set Network QoS rule ID "%(rule)s": %(e)s') %
+ {'rule': rule_id, 'e': e})
+ raise exceptions.CommandError(msg)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return display_columns, data
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index b878d875..4fb62c7b 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -51,17 +51,17 @@ def _format_network_port_range(rule):
# - Single port: '80:80'
# - No port range: ''
port_range = ''
- if _is_icmp_protocol(rule.protocol):
- if rule.port_range_min:
- port_range += 'type=' + str(rule.port_range_min)
- if rule.port_range_max:
- port_range += ':code=' + str(rule.port_range_max)
- elif rule.port_range_min or rule.port_range_max:
- port_range_min = str(rule.port_range_min)
- port_range_max = str(rule.port_range_max)
- if rule.port_range_min is None:
+ if _is_icmp_protocol(rule['protocol']):
+ if rule['port_range_min']:
+ port_range += 'type=' + str(rule['port_range_min'])
+ if rule['port_range_max']:
+ port_range += ':code=' + str(rule['port_range_max'])
+ elif rule['port_range_min'] or rule['port_range_max']:
+ port_range_min = str(rule['port_range_min'])
+ port_range_max = str(rule['port_range_max'])
+ if rule['port_range_min'] is None:
port_range_min = port_range_max
- if rule.port_range_max is None:
+ if rule['port_range_max'] is None:
port_range_max = port_range_min
port_range = port_range_min + ':' + port_range_max
return port_range
@@ -423,6 +423,16 @@ class DeleteSecurityGroupRule(common.NetworkAndComputeDelete):
class ListSecurityGroupRule(common.NetworkAndComputeLister):
_description = _("List security group rules")
+ def _format_network_security_group_rule(self, rule):
+ """Transform the SDK SecurityGroupRule object to a dict
+
+ The SDK object gets in the way of reformatting columns...
+ Create port_range column from port_range_min and port_range_max
+ """
+ rule = rule.to_dict()
+ rule['port_range'] = _format_network_port_range(rule)
+ return rule
+
def update_parser_common(self, parser):
parser.add_argument(
'group',
@@ -508,7 +518,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
'id',
'protocol',
'remote_ip_prefix',
- 'port_range_min',
+ 'port_range',
)
if parsed_args.long:
columns = columns + ('direction', 'ethertype',)
@@ -535,16 +545,13 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
if parsed_args.protocol is not None:
query['protocol'] = parsed_args.protocol
- rules = list(client.security_group_rules(**query))
-
- # Reformat the rules to display a port range instead
- # of just the port range minimum. This maintains
- # output compatibility with compute.
- for rule in rules:
- rule.port_range_min = _format_network_port_range(rule)
+ rules = [
+ self._format_network_security_group_rule(r)
+ for r in client.security_group_rules(**query)
+ ]
return (column_headers,
- (utils.get_item_properties(
+ (utils.get_dict_properties(
s, columns,
) for s in rules))
diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py
index 885abc02..fb78ea62 100644
--- a/openstackclient/tests/functional/base.py
+++ b/openstackclient/tests/functional/base.py
@@ -77,6 +77,11 @@ class TestCase(testtools.TestCase):
if expected not in actual:
raise Exception(expected + ' not in ' + actual)
+ @classmethod
+ def assertsOutputNotNone(cls, observed):
+ if observed is None:
+ raise Exception('No output observed')
+
def assert_table_structure(self, items, field_names):
"""Verify that all items have keys listed in field_names."""
for item in items:
diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py
index fbb8e563..b2b198af 100644
--- a/openstackclient/tests/functional/common/test_quota.py
+++ b/openstackclient/tests/functional/common/test_quota.py
@@ -35,19 +35,16 @@ class QuotaTests(base.TestCase):
raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts)
self.assertEqual("11\n11\n11\n", raw_output)
- @testtools.skip('broken SDK testing')
def test_quota_show(self):
raw_output = self.openstack('quota show ' + self.PROJECT_NAME)
for expected_field in self.EXPECTED_FIELDS:
self.assertIn(expected_field, raw_output)
- @testtools.skip('broken SDK testing')
def test_quota_show_default_project(self):
raw_output = self.openstack('quota show')
for expected_field in self.EXPECTED_FIELDS:
self.assertIn(expected_field, raw_output)
- @testtools.skip('broken SDK testing')
def test_quota_show_with_default_option(self):
raw_output = self.openstack('quota show --default')
for expected_field in self.EXPECTED_FIELDS:
diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py
index 3f432b02..6faff94a 100644
--- a/openstackclient/tests/functional/image/v2/test_image.py
+++ b/openstackclient/tests/functional/image/v2/test_image.py
@@ -74,3 +74,25 @@ class ImageTests(base.TestCase):
self.openstack('image unset --property a --property c ' + self.NAME)
raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n\n", raw_output)
+
+ def test_image_members(self):
+ opts = self.get_opts(['project_id'])
+ my_project_id = self.openstack('token issue' + opts).strip()
+ self.openstack(
+ 'image add project {} {}'.format(self.NAME, my_project_id))
+
+ self.openstack(
+ 'image set --accept ' + self.NAME)
+ shared_img_list = self.parse_listing(
+ self.openstack('image list --shared', self.get_opts(['name']))
+ )
+ self.assertIn(self.NAME, [img['Name'] for img in shared_img_list])
+
+ self.openstack(
+ 'image set --reject ' + self.NAME)
+ shared_img_list = self.parse_listing(
+ self.openstack('image list --shared', self.get_opts(['name']))
+ )
+
+ self.openstack(
+ 'image remove project {} {}'.format(self.NAME, my_project_id))
diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py
index 5f642f04..fa9607a0 100644
--- a/openstackclient/tests/functional/network/v2/test_floating_ip.py
+++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py
@@ -14,8 +14,6 @@ import random
import re
import uuid
-import testtools
-
from openstackclient.tests.functional import base
@@ -25,7 +23,6 @@ class FloatingIpTests(base.TestCase):
NETWORK_NAME = uuid.uuid4().hex
@classmethod
- @testtools.skip('broken SDK testing')
def setUpClass(cls):
# Set up some regex for matching below
cls.re_id = re.compile("id\s+\|\s+(\S+)")
@@ -37,7 +34,7 @@ class FloatingIpTests(base.TestCase):
# Make a random subnet
cls.subnet = ".".join(map(
str,
- (random.randint(0, 255) for _ in range(3))
+ (random.randint(0, 223) for _ in range(3))
)) + ".0/26"
# Create a network for the floating ip
@@ -62,7 +59,6 @@ class FloatingIpTests(base.TestCase):
raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME)
cls.assertOutput('', raw_output)
- @testtools.skip('broken SDK testing')
def test_floating_ip_delete(self):
"""Test create, delete multiple"""
raw_output = self.openstack(
@@ -93,7 +89,6 @@ class FloatingIpTests(base.TestCase):
raw_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2)
self.assertOutput('', raw_output)
- @testtools.skip('broken SDK testing')
def test_floating_ip_list(self):
"""Test create defaults, list filters, delete"""
raw_output = self.openstack(
@@ -135,7 +130,6 @@ class FloatingIpTests(base.TestCase):
# TODO(dtroyer): add more filter tests
- @testtools.skip('broken SDK testing')
def test_floating_ip_show(self):
"""Test show"""
raw_output = self.openstack(
diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py
index ef42dcce..c55d70f9 100644
--- a/openstackclient/tests/functional/network/v2/test_network.py
+++ b/openstackclient/tests/functional/network/v2/test_network.py
@@ -10,164 +10,173 @@
# License for the specific language governing permissions and limitations
# under the License.
-import re
+import json
import uuid
-import testtools
-
from openstackclient.tests.functional import base
class NetworkTests(base.TestCase):
"""Functional tests for network"""
- @classmethod
- def setUpClass(cls):
- # Set up some regex for matching below
- cls.re_id = re.compile("id\s+\|\s+(\S+)")
- cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|")
- cls.re_enabled = re.compile("admin_state_up\s+\|\s+(\S+)")
- cls.re_shared = re.compile("shared\s+\|\s+(\S+)")
- cls.re_external = re.compile("router:external\s+\|\s+(\S+)")
- cls.re_default = re.compile("is_default\s+\|\s+(\S+)")
- cls.re_port_security = re.compile(
- "port_security_enabled\s+\|\s+(\S+)"
- )
-
def test_network_delete(self):
"""Test create, delete multiple"""
name1 = uuid.uuid4().hex
- raw_output = self.openstack(
- 'network create ' +
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
'--description aaaa ' +
name1
- )
+ ))
+ self.assertIsNotNone(cmd_output["id"])
self.assertEqual(
'aaaa',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
+
name2 = uuid.uuid4().hex
- raw_output = self.openstack(
- 'network create ' +
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
'--description bbbb ' +
name2
- )
+ ))
+ self.assertIsNotNone(cmd_output["id"])
self.assertEqual(
'bbbb',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
del_output = self.openstack('network delete ' + name1 + ' ' + name2)
self.assertOutput('', del_output)
- @testtools.skip('broken SDK testing')
def test_network_list(self):
"""Test create defaults, list filters, delete"""
name1 = uuid.uuid4().hex
- raw_output = self.openstack(
- 'network create ' +
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
'--description aaaa ' +
name1
- )
+ ))
self.addCleanup(self.openstack, 'network delete ' + name1)
+ self.assertIsNotNone(cmd_output["id"])
self.assertEqual(
'aaaa',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
# Check the default values
self.assertEqual(
'UP',
- re.search(self.re_enabled, raw_output).group(1),
+ cmd_output["admin_state_up"],
)
self.assertEqual(
- 'False',
- re.search(self.re_shared, raw_output).group(1),
+ False,
+ cmd_output["shared"],
)
self.assertEqual(
'Internal',
- re.search(self.re_external, raw_output).group(1),
+ cmd_output["router:external"],
)
# NOTE(dtroyer): is_default is not present in the create output
# so make sure it stays that way.
- self.assertIsNone(re.search(self.re_default, raw_output))
+ # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer,
+ # but the value seems to always be None, regardless
+ # of the --default or --no-default value.
+ # self.assertEqual('x', cmd_output)
+ if ('is_default' in cmd_output):
+ self.assertEqual(
+ None,
+ cmd_output["is_default"],
+ )
self.assertEqual(
- 'True',
- re.search(self.re_port_security, raw_output).group(1),
+ True,
+ cmd_output["port_security_enabled"],
)
name2 = uuid.uuid4().hex
- raw_output = self.openstack(
- 'network create ' +
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
'--description bbbb ' +
'--disable ' +
'--share ' +
name2
- )
+ ))
self.addCleanup(self.openstack, 'network delete ' + name2)
+ self.assertIsNotNone(cmd_output["id"])
self.assertEqual(
'bbbb',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
self.assertEqual(
'DOWN',
- re.search(self.re_enabled, raw_output).group(1),
+ cmd_output["admin_state_up"],
)
self.assertEqual(
- 'True',
- re.search(self.re_shared, raw_output).group(1),
+ True,
+ cmd_output["shared"],
+ )
+ if ('is_default' in cmd_output):
+ self.assertEqual(
+ None,
+ cmd_output["is_default"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["port_security_enabled"],
)
# Test list --long
- raw_output = self.openstack('network list --long')
- self.assertIsNotNone(
- re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output)
- )
- self.assertIsNotNone(
- re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output)
- )
+ cmd_output = json.loads(self.openstack(
+ "network list -f json " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertIn(name2, col_name)
# Test list --long --enable
- raw_output = self.openstack('network list --long --enable')
- self.assertIsNotNone(
- re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output)
- )
- self.assertIsNone(
- re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output)
- )
+ cmd_output = json.loads(self.openstack(
+ "network list -f json " +
+ "--enable " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertNotIn(name2, col_name)
# Test list --long --disable
- raw_output = self.openstack('network list --long --disable')
- self.assertIsNone(
- re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output)
- )
- self.assertIsNotNone(
- re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output)
- )
+ cmd_output = json.loads(self.openstack(
+ "network list -f json " +
+ "--disable " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, col_name)
+ self.assertIn(name2, col_name)
# Test list --long --share
- raw_output = self.openstack('network list --long --share')
- self.assertIsNone(
- re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output)
- )
- self.assertIsNotNone(
- re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output)
- )
+ cmd_output = json.loads(self.openstack(
+ "network list -f json " +
+ "--share " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, col_name)
+ self.assertIn(name2, col_name)
# Test list --long --no-share
- raw_output = self.openstack('network list --long --no-share')
- self.assertIsNotNone(
- re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output)
- )
- self.assertIsNone(
- re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output)
- )
+ cmd_output = json.loads(self.openstack(
+ "network list -f json " +
+ "--no-share " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertNotIn(name2, col_name)
- @testtools.skip('broken SDK testing')
def test_network_set(self):
"""Tests create options, set, show, delete"""
name = uuid.uuid4().hex
- raw_output = self.openstack(
- 'network create ' +
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
'--description aaaa ' +
'--enable ' +
'--no-share ' +
@@ -175,30 +184,38 @@ class NetworkTests(base.TestCase):
'--no-default ' +
'--enable-port-security ' +
name
- )
+ ))
self.addCleanup(self.openstack, 'network delete ' + name)
+ self.assertIsNotNone(cmd_output["id"])
self.assertEqual(
'aaaa',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
self.assertEqual(
'UP',
- re.search(self.re_enabled, raw_output).group(1),
+ cmd_output["admin_state_up"],
)
self.assertEqual(
- 'False',
- re.search(self.re_shared, raw_output).group(1),
+ False,
+ cmd_output["shared"],
)
self.assertEqual(
'Internal',
- re.search(self.re_external, raw_output).group(1),
+ cmd_output["router:external"],
)
# NOTE(dtroyer): is_default is not present in the create output
# so make sure it stays that way.
- self.assertIsNone(re.search(self.re_default, raw_output))
+ # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer,
+ # but the value seems to always be None, regardless
+ # of the --default or --no-default value.
+ if ('is_default' in cmd_output):
+ self.assertEqual(
+ None,
+ cmd_output["is_default"],
+ )
self.assertEqual(
- 'True',
- re.search(self.re_port_security, raw_output).group(1),
+ True,
+ cmd_output["port_security_enabled"],
)
raw_output = self.openstack(
@@ -212,32 +229,34 @@ class NetworkTests(base.TestCase):
)
self.assertOutput('', raw_output)
- raw_output = self.openstack('network show ' + name)
+ cmd_output = json.loads(self.openstack(
+ 'network show -f json ' + name
+ ))
self.assertEqual(
'cccc',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
self.assertEqual(
'DOWN',
- re.search(self.re_enabled, raw_output).group(1),
+ cmd_output["admin_state_up"],
)
self.assertEqual(
- 'True',
- re.search(self.re_shared, raw_output).group(1),
+ True,
+ cmd_output["shared"],
)
self.assertEqual(
'External',
- re.search(self.re_external, raw_output).group(1),
+ cmd_output["router:external"],
)
# why not 'None' like above??
self.assertEqual(
- 'False',
- re.search(self.re_default, raw_output).group(1),
+ False,
+ cmd_output["is_default"],
)
self.assertEqual(
- 'False',
- re.search(self.re_port_security, raw_output).group(1),
+ False,
+ cmd_output["port_security_enabled"],
)
# NOTE(dtroyer): There is ambiguity around is_default in that
@@ -252,14 +271,16 @@ class NetworkTests(base.TestCase):
)
self.assertOutput('', raw_output)
- raw_output = self.openstack('network show ' + name)
+ cmd_output = json.loads(self.openstack(
+ 'network show -f json ' + name
+ ))
self.assertEqual(
'cccc',
- re.search(self.re_description, raw_output).group(1),
+ cmd_output["description"],
)
# NOTE(dtroyer): This should be 'True'
self.assertEqual(
- 'False',
- re.search(self.re_default, raw_output).group(1),
+ False,
+ cmd_output["port_security_enabled"],
)
diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py
index e99dcef6..dd6112e7 100644
--- a/openstackclient/tests/functional/network/v2/test_network_agent.py
+++ b/openstackclient/tests/functional/network/v2/test_network_agent.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from openstackclient.tests.functional import base
@@ -28,13 +26,11 @@ class NetworkAgentTests(base.TestCase):
# get the list of network agent IDs.
cls.IDs = raw_output.split('\n')
- @testtools.skip('broken SDK testing')
def test_network_agent_show(self):
opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('network agent show ' + self.IDs[0] + opts)
self.assertEqual(self.IDs[0] + "\n", raw_output)
- @testtools.skip('broken SDK testing')
def test_network_agent_set(self):
opts = self.get_opts(['admin_state_up'])
self.openstack('network agent set --disable ' + self.IDs[0])
diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py
new file mode 100644
index 00000000..af0c9bac
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from openstackclient.tests.functional import base
+
+
+class NetworkQosRuleTestsMinimumBandwidth(base.TestCase):
+ """Functional tests for QoS minimum bandwidth rule."""
+ RULE_ID = None
+ QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex
+ MIN_KBPS = 2800
+ MIN_KBPS_MODIFIED = 7500
+ DIRECTION = '--egress'
+ HEADERS = ['ID']
+ FIELDS = ['id']
+ TYPE = 'minimum-bandwidth'
+
+ @classmethod
+ def setUpClass(cls):
+ opts = cls.get_opts(cls.FIELDS)
+ cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME)
+ cls.RULE_ID = cls.openstack('network qos rule create --type ' +
+ cls.TYPE + ' --min-kbps ' +
+ str(cls.MIN_KBPS) + ' ' + cls.DIRECTION +
+ ' ' + cls.QOS_POLICY_NAME + opts)
+ cls.assertsOutputNotNone(cls.RULE_ID)
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output = cls.openstack('network qos rule delete ' +
+ cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID)
+ cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME)
+ cls.assertOutput('', raw_output)
+
+ def test_qos_policy_list(self):
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('network qos rule list '
+ + self.QOS_POLICY_NAME + opts)
+ self.assertIn(self.RULE_ID, raw_output)
+
+ def test_qos_policy_show(self):
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(self.RULE_ID, raw_output)
+
+ def test_qos_policy_set(self):
+ self.openstack('network qos rule set --min-kbps ' +
+ str(self.MIN_KBPS_MODIFIED) + ' ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ opts = self.get_opts(['min_kbps'])
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(str(self.MIN_KBPS_MODIFIED) + "\n", raw_output)
+
+
+class NetworkQosRuleTestsDSCPMarking(base.TestCase):
+ """Functional tests for QoS DSCP marking rule."""
+ RULE_ID = None
+ QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex
+ DSCP_MARK = 8
+ DSCP_MARK_MODIFIED = 32
+ HEADERS = ['ID']
+ FIELDS = ['id']
+ TYPE = 'dscp-marking'
+
+ @classmethod
+ def setUpClass(cls):
+ opts = cls.get_opts(cls.FIELDS)
+ cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME)
+ cls.RULE_ID = cls.openstack('network qos rule create --type ' +
+ cls.TYPE + ' --dscp-mark ' +
+ str(cls.DSCP_MARK) + ' ' +
+ cls.QOS_POLICY_NAME + opts)
+ cls.assertsOutputNotNone(cls.RULE_ID)
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output = cls.openstack('network qos rule delete ' +
+ cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID)
+ cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME)
+ cls.assertOutput('', raw_output)
+
+ def test_qos_policy_list(self):
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('network qos rule list '
+ + self.QOS_POLICY_NAME + opts)
+ self.assertIn(self.RULE_ID, raw_output)
+
+ def test_qos_policy_show(self):
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(self.RULE_ID, raw_output)
+
+ def test_qos_policy_set(self):
+ self.openstack('network qos rule set --dscp-mark ' +
+ str(self.DSCP_MARK_MODIFIED) + ' ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ opts = self.get_opts(['dscp_mark'])
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(str(self.DSCP_MARK_MODIFIED) + "\n", raw_output)
+
+
+class NetworkQosRuleTestsBandwidthLimit(base.TestCase):
+ """Functional tests for QoS bandwidth limit rule."""
+ RULE_ID = None
+ QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex
+ MAX_KBPS = 10000
+ MAX_KBPS_MODIFIED = 15000
+ MAX_BURST_KBITS = 1400
+ MAX_BURST_KBITS_MODIFIED = 1800
+ HEADERS = ['ID']
+ FIELDS = ['id']
+ TYPE = 'bandwidth-limit'
+
+ @classmethod
+ def setUpClass(cls):
+ opts = cls.get_opts(cls.FIELDS)
+ cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME)
+ cls.RULE_ID = cls.openstack('network qos rule create --type ' +
+ cls.TYPE + ' --max-kbps ' +
+ str(cls.MAX_KBPS) + ' --max-burst-kbits ' +
+ str(cls.MAX_BURST_KBITS) + ' ' +
+ cls.QOS_POLICY_NAME + opts)
+ cls.assertsOutputNotNone(cls.RULE_ID)
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output = cls.openstack('network qos rule delete ' +
+ cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID)
+ cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME)
+ cls.assertOutput('', raw_output)
+
+ def test_qos_policy_list(self):
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('network qos rule list '
+ + self.QOS_POLICY_NAME + opts)
+ self.assertIn(self.RULE_ID, raw_output)
+
+ def test_qos_policy_show(self):
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(self.RULE_ID, raw_output)
+
+ def test_qos_policy_set(self):
+ self.openstack('network qos rule set --max-kbps ' +
+ str(self.MAX_KBPS_MODIFIED) + ' --max-burst-kbits ' +
+ str(self.MAX_BURST_KBITS_MODIFIED) + ' ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ opts = self.get_opts(['max_kbps'])
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(str(self.MAX_KBPS_MODIFIED) + "\n", raw_output)
+ opts = self.get_opts(['max_burst_kbps'])
+ raw_output = self.openstack('network qos rule show ' +
+ self.QOS_POLICY_NAME + ' ' + self.RULE_ID +
+ opts)
+ self.assertEqual(str(self.MAX_BURST_KBITS_MODIFIED) + "\n", raw_output)
diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py
index 2bb04a9d..7dff0cbd 100644
--- a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py
+++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from openstackclient.tests.functional import base
@@ -22,10 +20,8 @@ class NetworkQosRuleTypeTests(base.TestCase):
"""Functional tests for Network QoS rule type. """
AVAILABLE_RULE_TYPES = ['dscp_marking',
- 'bandwidth_limit',
- 'minimum_bandwidth']
+ 'bandwidth_limit']
- @testtools.skip('broken SDK testing')
def test_qos_rule_type_list(self):
raw_output = self.openstack('network qos rule type list')
for rule_type in self.AVAILABLE_RULE_TYPES:
diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py
index 976fbedb..decd9553 100644
--- a/openstackclient/tests/functional/network/v2/test_port.py
+++ b/openstackclient/tests/functional/network/v2/test_port.py
@@ -12,8 +12,6 @@
import uuid
-import testtools
-
from openstackclient.tests.functional import base
@@ -25,7 +23,6 @@ class PortTests(base.TestCase):
FIELDS = ['name']
@classmethod
- @testtools.skip('broken SDK testing')
def setUpClass(cls):
# Create a network for the subnet.
cls.openstack('network create ' + cls.NETWORK_NAME)
diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py
index ec3731eb..c91de1a5 100644
--- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py
+++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py
@@ -12,8 +12,6 @@
import uuid
-import testtools
-
from openstackclient.tests.functional import base
@@ -54,7 +52,6 @@ class SecurityGroupRuleTests(base.TestCase):
cls.SECURITY_GROUP_NAME)
cls.assertOutput('', raw_output)
- @testtools.skip('broken SDK testing')
def test_security_group_rule_list(self):
opts = self.get_opts(self.ID_HEADER)
raw_output = self.openstack('security group rule list ' +
diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh
new file mode 100755
index 00000000..28ab9580
--- /dev/null
+++ b/openstackclient/tests/functional/post_test_hook_tips.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+# This is a script that kicks off a series of functional tests against an
+# OpenStack cloud. It will attempt to create an instance if one is not
+# available. Do not run this script unless you know what you're doing.
+# For more information refer to:
+# http://docs.openstack.org/developer/python-openstackclient/
+
+# This particular script differs from the normal post_test_hook because
+# it installs the master (tip) version of osc-lib, os-client-config
+# and openstacksdk, OSCs most important dependencies.
+
+function generate_testr_results {
+ if [ -f .testrepository/0 ]; then
+ sudo .tox/functional-tips/bin/testr last --subunit > $WORKSPACE/testrepository.subunit
+ sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
+ sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
+ sudo gzip -9 $BASE/logs/testrepository.subunit
+ sudo gzip -9 $BASE/logs/testr_results.html
+ sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
+ sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
+ fi
+}
+
+export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient"
+sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR
+
+# Go to the openstackclient dir
+cd $OPENSTACKCLIENT_DIR
+
+# Run tests
+echo "Running openstackclient functional-tips test suite"
+set +e
+
+# Source environment variables to kick things off
+source ~stack/devstack/openrc admin admin
+echo 'Running tests with:'
+env | grep OS
+
+# Preserve env for OS_ credentials
+sudo -E -H -u jenkins tox -e functional-tips
+EXIT_CODE=$?
+set -e
+
+# Collect and parse result
+generate_testr_results
+exit $EXIT_CODE
diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py
index ca6b1d31..626b466d 100644
--- a/openstackclient/tests/unit/fakes.py
+++ b/openstackclient/tests/unit/fakes.py
@@ -219,6 +219,12 @@ class FakeResource(object):
def info(self):
return self._info
+ def __getitem__(self, item):
+ return self._info.get(item)
+
+ def get(self, item, default=None):
+ return self._info.get(item, default)
+
class FakeResponse(requests.Response):
diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py
index c1f00762..4e1077db 100644
--- a/openstackclient/tests/unit/identity/v2_0/test_project.py
+++ b/openstackclient/tests/unit/identity/v2_0/test_project.py
@@ -13,8 +13,11 @@
# under the License.
#
+import mock
+
from keystoneauth1 import exceptions as ks_exc
from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v2_0 import project
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
@@ -302,6 +305,32 @@ class TestProjectDelete(TestProject):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_projects_with_exception(self, find_mock):
+ find_mock.side_effect = [self.fake_project,
+ exceptions.CommandError]
+ arglist = [
+ self.fake_project.id,
+ 'unexist_project',
+ ]
+ verifylist = [
+ ('projects', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 projects failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.projects_mock, self.fake_project.id)
+ find_mock.assert_any_call(self.projects_mock, 'unexist_project')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.projects_mock.delete.assert_called_once_with(self.fake_project.id)
+
class TestProjectList(TestProject):
diff --git a/openstackclient/tests/unit/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py
index 68ebf141..684ce803 100644
--- a/openstackclient/tests/unit/identity/v2_0/test_role.py
+++ b/openstackclient/tests/unit/identity/v2_0/test_role.py
@@ -17,6 +17,7 @@ import mock
from keystoneauth1 import exceptions as ks_exc
from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v2_0 import role
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
@@ -240,6 +241,32 @@ class TestRoleDelete(TestRole):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_roles_with_exception(self, find_mock):
+ find_mock.side_effect = [self.fake_role,
+ exceptions.CommandError]
+ arglist = [
+ self.fake_role.id,
+ 'unexist_role',
+ ]
+ verifylist = [
+ ('roles', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 roles failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.roles_mock, self.fake_role.id)
+ find_mock.assert_any_call(self.roles_mock, 'unexist_role')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.roles_mock.delete.assert_called_once_with(self.fake_role.id)
+
class TestRoleList(TestRole):
diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py
index 765f8559..a8b9497e 100644
--- a/openstackclient/tests/unit/identity/v2_0/test_user.py
+++ b/openstackclient/tests/unit/identity/v2_0/test_user.py
@@ -17,6 +17,7 @@ import mock
from keystoneauth1 import exceptions as ks_exc
from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v2_0 import user
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
@@ -411,6 +412,32 @@ class TestUserDelete(TestUser):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_users_with_exception(self, find_mock):
+ find_mock.side_effect = [self.fake_user,
+ exceptions.CommandError]
+ arglist = [
+ self.fake_user.id,
+ 'unexist_user',
+ ]
+ verifylist = [
+ ('users', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 users failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.users_mock, self.fake_user.id)
+ find_mock.assert_any_call(self.users_mock, 'unexist_user')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.users_mock.delete.assert_called_once_with(self.fake_user.id)
+
class TestUserList(TestUser):
diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py
index eb50adb5..8558de95 100644
--- a/openstackclient/tests/unit/identity/v3/test_group.py
+++ b/openstackclient/tests/unit/identity/v3/test_group.py
@@ -16,6 +16,7 @@ from mock import call
from keystoneauth1 import exceptions as ks_exc
from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v3 import group
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@@ -257,6 +258,32 @@ class TestGroupDelete(TestGroup):
self.groups_mock.delete.assert_called_once_with(self.groups[0].id)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_groups_with_exception(self, find_mock):
+ find_mock.side_effect = [self.groups[0],
+ exceptions.CommandError]
+ arglist = [
+ self.groups[0].id,
+ 'unexist_group',
+ ]
+ verifylist = [
+ ('groups', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 groups failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.groups_mock, self.groups[0].id)
+ find_mock.assert_any_call(self.groups_mock, 'unexist_group')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.groups_mock.delete.assert_called_once_with(self.groups[0].id)
+
class TestGroupList(TestGroup):
diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py
index 702d9209..2b898090 100644
--- a/openstackclient/tests/unit/identity/v3/test_project.py
+++ b/openstackclient/tests/unit/identity/v3/test_project.py
@@ -16,6 +16,7 @@
import mock
from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v3 import project
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@@ -445,6 +446,32 @@ class TestProjectDelete(TestProject):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_projects_with_exception(self, find_mock):
+ find_mock.side_effect = [self.project,
+ exceptions.CommandError]
+ arglist = [
+ self.project.id,
+ 'unexist_project',
+ ]
+ verifylist = [
+ ('projects', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 projects failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.projects_mock, self.project.id)
+ find_mock.assert_any_call(self.projects_mock, 'unexist_project')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.projects_mock.delete.assert_called_once_with(self.project.id)
+
class TestProjectList(TestProject):
diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py
index 448e18d3..c0b68bdf 100644
--- a/openstackclient/tests/unit/identity/v3/test_role.py
+++ b/openstackclient/tests/unit/identity/v3/test_role.py
@@ -14,6 +14,10 @@
#
import copy
+import mock
+
+from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v3 import role
from openstackclient.tests.unit import fakes
@@ -428,6 +432,36 @@ class TestRoleDelete(TestRole):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_roles_with_exception(self, find_mock):
+ find_mock.side_effect = [self.roles_mock.get.return_value,
+ exceptions.CommandError]
+ arglist = [
+ identity_fakes.role_name,
+ 'unexist_role',
+ ]
+ verifylist = [
+ ('roles', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 roles failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.roles_mock,
+ identity_fakes.role_name,
+ domain_id=None)
+ find_mock.assert_any_call(self.roles_mock,
+ 'unexist_role',
+ domain_id=None)
+
+ self.assertEqual(2, find_mock.call_count)
+ self.roles_mock.delete.assert_called_once_with(identity_fakes.role_id)
+
class TestRoleList(TestRole):
diff --git a/openstackclient/tests/unit/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py
index 4eeb8bfe..93e8f63d 100644
--- a/openstackclient/tests/unit/identity/v3/test_trust.py
+++ b/openstackclient/tests/unit/identity/v3/test_trust.py
@@ -12,6 +12,10 @@
#
import copy
+import mock
+
+from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.identity.v3 import trust
from openstackclient.tests.unit import fakes
@@ -148,6 +152,33 @@ class TestTrustDelete(TestTrust):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_trusts_with_exception(self, find_mock):
+ find_mock.side_effect = [self.trusts_mock.get.return_value,
+ exceptions.CommandError]
+ arglist = [
+ identity_fakes.trust_id,
+ 'unexist_trust',
+ ]
+ verifylist = [
+ ('trust', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 trusts failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.trusts_mock, identity_fakes.trust_id)
+ find_mock.assert_any_call(self.trusts_mock, 'unexist_trust')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.trusts_mock.delete.assert_called_once_with(
+ identity_fakes.trust_id)
+
class TestTrustList(TestTrust):
diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py
index 6150a5f3..3c1f49a6 100644
--- a/openstackclient/tests/unit/identity/v3/test_user.py
+++ b/openstackclient/tests/unit/identity/v3/test_user.py
@@ -16,6 +16,9 @@
import contextlib
import mock
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.identity.v3 import user
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@@ -465,6 +468,32 @@ class TestUserDelete(TestUser):
)
self.assertIsNone(result)
+ @mock.patch.object(utils, 'find_resource')
+ def test_delete_multi_users_with_exception(self, find_mock):
+ find_mock.side_effect = [self.user,
+ exceptions.CommandError]
+ arglist = [
+ self.user.id,
+ 'unexist_user',
+ ]
+ verifylist = [
+ ('users', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 users failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.users_mock, self.user.id)
+ find_mock.assert_any_call(self.users_mock, 'unexist_user')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.users_mock.delete.assert_called_once_with(self.user.id)
+
class TestUserList(TestUser):
diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py
index a054e513..164185df 100644
--- a/openstackclient/tests/unit/image/v2/test_image.py
+++ b/openstackclient/tests/unit/image/v2/test_image.py
@@ -829,6 +829,11 @@ class TestImageSet(TestImage):
self.images_mock.get.return_value = self.model(**image_fakes.IMAGE)
self.images_mock.update.return_value = self.model(**image_fakes.IMAGE)
+
+ self.app.client_manager.auth_ref = mock.Mock(
+ project_id=self.project.id,
+ )
+
# Get the command object to test
self.cmd = image.SetImage(self.app, None)
@@ -845,6 +850,101 @@ class TestImageSet(TestImage):
self.assertIsNone(result)
+ self.image_members_mock.update.assert_not_called()
+
+ def test_image_set_membership_option_accept(self):
+ membership = image_fakes.FakeImage.create_one_image_member(
+ attrs={'image_id': image_fakes.image_id,
+ 'member_id': self.project.id}
+ )
+ self.image_members_mock.update.return_value = membership
+
+ arglist = [
+ '--accept',
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('accept', True),
+ ('reject', False),
+ ('pending', False),
+ ('image', image_fakes.image_id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ self.image_members_mock.update.assert_called_once_with(
+ image_fakes.image_id,
+ self.app.client_manager.auth_ref.project_id,
+ 'accepted',
+ )
+
+ # Assert that the 'update image" route is also called, in addition to
+ # the 'update membership' route.
+ self.images_mock.update.assert_called_with(image_fakes.image_id)
+
+ def test_image_set_membership_option_reject(self):
+ membership = image_fakes.FakeImage.create_one_image_member(
+ attrs={'image_id': image_fakes.image_id,
+ 'member_id': self.project.id}
+ )
+ self.image_members_mock.update.return_value = membership
+
+ arglist = [
+ '--reject',
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('accept', False),
+ ('reject', True),
+ ('pending', False),
+ ('image', image_fakes.image_id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ self.image_members_mock.update.assert_called_once_with(
+ image_fakes.image_id,
+ self.app.client_manager.auth_ref.project_id,
+ 'rejected',
+ )
+
+ # Assert that the 'update image" route is also called, in addition to
+ # the 'update membership' route.
+ self.images_mock.update.assert_called_with(image_fakes.image_id)
+
+ def test_image_set_membership_option_pending(self):
+ membership = image_fakes.FakeImage.create_one_image_member(
+ attrs={'image_id': image_fakes.image_id,
+ 'member_id': self.project.id}
+ )
+ self.image_members_mock.update.return_value = membership
+
+ arglist = [
+ '--pending',
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('accept', False),
+ ('reject', False),
+ ('pending', True),
+ ('image', image_fakes.image_id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ self.image_members_mock.update.assert_called_once_with(
+ image_fakes.image_id,
+ self.app.client_manager.auth_ref.project_id,
+ 'pending',
+ )
+
+ # Assert that the 'update image" route is also called, in addition to
+ # the 'update membership' route.
+ self.images_mock.update.assert_called_with(image_fakes.image_id)
+
def test_image_set_options(self):
arglist = [
'--name', 'new-name',
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index b931cb55..524285ab 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -14,6 +14,8 @@
import argparse
import copy
import mock
+from random import choice
+from random import randint
import uuid
from openstackclient.tests.unit import fakes
@@ -37,10 +39,20 @@ QUOTA = {
"l7policy": 5,
}
+RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit'
+RULE_TYPE_DSCP_MARKING = 'dscp-marking'
+RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth'
+VALID_QOS_RULES = [RULE_TYPE_BANDWIDTH_LIMIT,
+ RULE_TYPE_DSCP_MARKING,
+ RULE_TYPE_MINIMUM_BANDWIDTH]
+VALID_DSCP_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
+ 34, 36, 38, 40, 46, 48, 56]
+
class FakeNetworkV2Client(object):
def __init__(self, **kwargs):
+ self.session = mock.Mock()
self.extensions = mock.Mock()
self.extensions.resource_class = fakes.FakeResource(None, {})
@@ -553,6 +565,8 @@ class FakeNetworkAgent(object):
agent_attrs.update(attrs)
agent = fakes.FakeResource(info=copy.deepcopy(agent_attrs),
loaded=True)
+ agent.is_admin_state_up = agent_attrs['admin_state_up']
+ agent.is_alive = agent_attrs['alive']
return agent
@staticmethod
@@ -662,83 +676,90 @@ class FakeNetworkRBAC(object):
return mock.Mock(side_effect=rbac_policies)
-class FakeNetworkQosBandwidthLimitRule(object):
- """Fake one or more QoS bandwidth limit rules."""
+class FakeNetworkQosPolicy(object):
+ """Fake one or more QoS policies."""
@staticmethod
- def create_one_qos_bandwidth_limit_rule(attrs=None):
- """Create a fake QoS bandwidth limit rule.
+ def create_one_qos_policy(attrs=None):
+ """Create a fake QoS policy.
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object with id, qos_policy_id, max_kbps and
- max_burst_kbps attributes.
+ A FakeResource object with name, id, etc.
"""
attrs = attrs or {}
+ qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex
+ rule_attrs = {'qos_policy_id': qos_id}
+ rules = [FakeNetworkQosRule.create_one_qos_rule(rule_attrs)]
# Set default attributes.
- qos_bandwidth_limit_rule_attrs = {
- 'id': 'qos-bandwidth-limit-rule-id-' + uuid.uuid4().hex,
- 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
- 'max_kbps': 1500,
- 'max_burst_kbps': 1200,
+ qos_policy_attrs = {
+ 'name': 'qos-policy-name-' + uuid.uuid4().hex,
+ 'id': qos_id,
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ 'shared': False,
+ 'description': 'qos-policy-description-' + uuid.uuid4().hex,
+ 'rules': rules,
}
# Overwrite default attributes.
- qos_bandwidth_limit_rule_attrs.update(attrs)
+ qos_policy_attrs.update(attrs)
- qos_bandwidth_limit_rule = fakes.FakeResource(
- info=copy.deepcopy(qos_bandwidth_limit_rule_attrs),
+ qos_policy = fakes.FakeResource(
+ info=copy.deepcopy(qos_policy_attrs),
loaded=True)
- return qos_bandwidth_limit_rule
+ # Set attributes with special mapping in OpenStack SDK.
+ qos_policy.is_shared = qos_policy_attrs['shared']
+ qos_policy.project_id = qos_policy_attrs['tenant_id']
+
+ return qos_policy
@staticmethod
- def create_qos_bandwidth_limit_rules(attrs=None, count=2):
- """Create multiple fake QoS bandwidth limit rules.
+ def create_qos_policies(attrs=None, count=2):
+ """Create multiple fake QoS policies.
:param Dictionary attrs:
A dictionary with all attributes
:param int count:
- The number of QoS bandwidth limit rules to fake
+ The number of QoS policies to fake
:return:
- A list of FakeResource objects faking the QoS bandwidth limit rules
+ A list of FakeResource objects faking the QoS policies
"""
qos_policies = []
for i in range(0, count):
- qos_policies.append(FakeNetworkQosBandwidthLimitRule.
- create_one_qos_bandwidth_limit_rule(attrs))
+ qos_policies.append(
+ FakeNetworkQosPolicy.create_one_qos_policy(attrs))
return qos_policies
@staticmethod
- def get_qos_bandwidth_limit_rules(qos_rules=None, count=2):
- """Get a list of faked QoS bandwidth limit rules.
+ def get_qos_policies(qos_policies=None, count=2):
+ """Get an iterable MagicMock object with a list of faked QoS policies.
- If QoS bandwidth limit rules list is provided, then initialize the
- Mock object with the list. Otherwise create one.
+ If qos policies list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
:param List address scopes:
- A list of FakeResource objects faking QoS bandwidth limit rules
+ A list of FakeResource objects faking qos policies
:param int count:
- The number of QoS bandwidth limit rules to fake
+ The number of QoS policies to fake
:return:
An iterable Mock object with side_effect set to a list of faked
- qos bandwidth limit rules
+ QoS policies
"""
- if qos_rules is None:
- qos_rules = (FakeNetworkQosBandwidthLimitRule.
- create_qos_bandwidth_limit_rules(count))
- return mock.Mock(side_effect=qos_rules)
+ if qos_policies is None:
+ qos_policies = FakeNetworkQosPolicy.create_qos_policies(count)
+ return mock.Mock(side_effect=qos_policies)
-class FakeNetworkQosPolicy(object):
- """Fake one or more QoS policies."""
+class FakeNetworkQosRule(object):
+ """Fake one or more Network QoS rules."""
@staticmethod
- def create_one_qos_policy(attrs=None):
- """Create a fake QoS policy.
+ def create_one_qos_rule(attrs=None):
+ """Create a fake Network QoS rule.
:param Dictionary attrs:
A dictionary with all attributes
@@ -746,71 +767,69 @@ class FakeNetworkQosPolicy(object):
A FakeResource object with name, id, etc.
"""
attrs = attrs or {}
- qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex
- rule_attrs = {'qos_policy_id': qos_id}
- rules = [
- FakeNetworkQosBandwidthLimitRule.
- create_one_qos_bandwidth_limit_rule(rule_attrs)]
# Set default attributes.
- qos_policy_attrs = {
- 'name': 'qos-policy-name-' + uuid.uuid4().hex,
- 'id': qos_id,
+ type = attrs.get('type') or choice(VALID_QOS_RULES)
+ qos_rule_attrs = {
+ 'id': 'qos-rule-id-' + uuid.uuid4().hex,
+ 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
'tenant_id': 'project-id-' + uuid.uuid4().hex,
- 'shared': False,
- 'description': 'qos-policy-description-' + uuid.uuid4().hex,
- 'rules': rules,
+ 'type': type,
}
+ if type == RULE_TYPE_BANDWIDTH_LIMIT:
+ qos_rule_attrs['max_kbps'] = randint(1, 10000)
+ qos_rule_attrs['max_burst_kbits'] = randint(1, 10000)
+ elif type == RULE_TYPE_DSCP_MARKING:
+ qos_rule_attrs['dscp_mark'] = choice(VALID_DSCP_MARKS)
+ elif type == RULE_TYPE_MINIMUM_BANDWIDTH:
+ qos_rule_attrs['min_kbps'] = randint(1, 10000)
+ qos_rule_attrs['direction'] = 'egress'
# Overwrite default attributes.
- qos_policy_attrs.update(attrs)
+ qos_rule_attrs.update(attrs)
- qos_policy = fakes.FakeResource(
- info=copy.deepcopy(qos_policy_attrs),
- loaded=True)
+ qos_rule = fakes.FakeResource(info=copy.deepcopy(qos_rule_attrs),
+ loaded=True)
# Set attributes with special mapping in OpenStack SDK.
- qos_policy.is_shared = qos_policy_attrs['shared']
- qos_policy.project_id = qos_policy_attrs['tenant_id']
+ qos_rule.project_id = qos_rule['tenant_id']
- return qos_policy
+ return qos_rule
@staticmethod
- def create_qos_policies(attrs=None, count=2):
- """Create multiple fake QoS policies.
+ def create_qos_rules(attrs=None, count=2):
+ """Create multiple fake Network QoS rules.
:param Dictionary attrs:
A dictionary with all attributes
:param int count:
- The number of QoS policies to fake
+ The number of Network QoS rule to fake
:return:
- A list of FakeResource objects faking the QoS policies
+ A list of FakeResource objects faking the Network QoS rules
"""
- qos_policies = []
+ qos_rules = []
for i in range(0, count):
- qos_policies.append(
- FakeNetworkQosPolicy.create_one_qos_policy(attrs))
-
- return qos_policies
+ qos_rules.append(FakeNetworkQosRule.create_one_qos_rule(attrs))
+ return qos_rules
@staticmethod
- def get_qos_policies(qos_policies=None, count=2):
- """Get an iterable MagicMock object with a list of faked QoS policies.
+ def get_qos_rules(qos_rules=None, count=2):
+ """Get a list of faked Network QoS rules.
- If qos policies list is provided, then initialize the Mock object
- with the list. Otherwise create one.
+ If Network QoS rules list is provided, then initialize the Mock
+ object with the list. Otherwise create one.
:param List address scopes:
- A list of FakeResource objects faking qos policies
+ A list of FakeResource objects faking Network QoS rules
:param int count:
- The number of QoS policies to fake
+ The number of QoS minimum bandwidth rules to fake
:return:
An iterable Mock object with side_effect set to a list of faked
- QoS policies
+ qos minimum bandwidth rules
"""
- if qos_policies is None:
- qos_policies = FakeNetworkQosPolicy.create_qos_policies(count)
- return mock.Mock(side_effect=qos_policies)
+ if qos_rules is None:
+ qos_rules = (FakeNetworkQosRule.create_qos_rules(count))
+ return mock.Mock(side_effect=qos_rules)
class FakeNetworkQosRuleType(object):
diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py
index 63d22bf8..e395300d 100644
--- a/openstackclient/tests/unit/network/v2/test_floating_ip.py
+++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py
@@ -208,13 +208,19 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
super(TestDeleteFloatingIPNetwork, self).setUp()
self.network.delete_ip = mock.Mock(return_value=None)
- self.network.find_ip = (
- network_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips))
# Get the command object to test
self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace)
- def test_floating_ip_delete(self):
+ @mock.patch(
+ "openstackclient.tests.unit.network.v2.test_floating_ip." +
+ "floating_ip._find_floating_ip"
+ )
+ def test_floating_ip_delete(self, find_floating_ip_mock):
+ find_floating_ip_mock.side_effect = [
+ (self.floating_ips[0], []),
+ (self.floating_ips[1], []),
+ ]
arglist = [
self.floating_ips[0].id,
]
@@ -225,12 +231,24 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
result = self.cmd.take_action(parsed_args)
- self.network.find_ip.assert_called_once_with(
- self.floating_ips[0].id, ignore_missing=False)
+ find_floating_ip_mock.assert_called_once_with(
+ mock.ANY,
+ [],
+ self.floating_ips[0].id,
+ ignore_missing=False,
+ )
self.network.delete_ip.assert_called_once_with(self.floating_ips[0])
self.assertIsNone(result)
- def test_multi_floating_ips_delete(self):
+ @mock.patch(
+ "openstackclient.tests.unit.network.v2.test_floating_ip." +
+ "floating_ip._find_floating_ip"
+ )
+ def test_floating_ip_delete_multi(self, find_floating_ip_mock):
+ find_floating_ip_mock.side_effect = [
+ (self.floating_ips[0], []),
+ (self.floating_ips[1], []),
+ ]
arglist = []
verifylist = []
@@ -243,13 +261,37 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
result = self.cmd.take_action(parsed_args)
+ calls = [
+ call(
+ mock.ANY,
+ [],
+ self.floating_ips[0].id,
+ ignore_missing=False,
+ ),
+ call(
+ mock.ANY,
+ [],
+ self.floating_ips[1].id,
+ ignore_missing=False,
+ ),
+ ]
+ find_floating_ip_mock.assert_has_calls(calls)
+
calls = []
for f in self.floating_ips:
calls.append(call(f))
self.network.delete_ip.assert_has_calls(calls)
self.assertIsNone(result)
- def test_multi_floating_ips_delete_with_exception(self):
+ @mock.patch(
+ "openstackclient.tests.unit.network.v2.test_floating_ip." +
+ "floating_ip._find_floating_ip"
+ )
+ def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock):
+ find_floating_ip_mock.side_effect = [
+ (self.floating_ips[0], []),
+ exceptions.CommandError,
+ ]
arglist = [
self.floating_ips[0].id,
'unexist_floating_ip',
@@ -260,21 +302,24 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- find_mock_result = [self.floating_ips[0], exceptions.CommandError]
- self.network.find_ip = (
- mock.Mock(side_effect=find_mock_result)
- )
-
try:
self.cmd.take_action(parsed_args)
self.fail('CommandError should be raised.')
except exceptions.CommandError as e:
self.assertEqual('1 of 2 floating_ips failed to delete.', str(e))
- self.network.find_ip.assert_any_call(
- self.floating_ips[0].id, ignore_missing=False)
- self.network.find_ip.assert_any_call(
- 'unexist_floating_ip', ignore_missing=False)
+ find_floating_ip_mock.assert_any_call(
+ mock.ANY,
+ [],
+ self.floating_ips[0].id,
+ ignore_missing=False,
+ )
+ find_floating_ip_mock.assert_any_call(
+ mock.ANY,
+ [],
+ 'unexist_floating_ip',
+ ignore_missing=False,
+ )
self.network.delete_ip.assert_called_once_with(
self.floating_ips[0]
)
@@ -534,7 +579,12 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
# Get the command object to test
self.cmd = floating_ip.ShowFloatingIP(self.app, self.namespace)
- def test_floating_ip_show(self):
+ @mock.patch(
+ "openstackclient.tests.unit.network.v2.test_floating_ip." +
+ "floating_ip._find_floating_ip"
+ )
+ def test_floating_ip_show(self, find_floating_ip_mock):
+ find_floating_ip_mock.return_value = (self.floating_ip, [])
arglist = [
self.floating_ip.id,
]
@@ -545,9 +595,11 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
columns, data = self.cmd.take_action(parsed_args)
- self.network.find_ip.assert_called_once_with(
+ find_floating_ip_mock.assert_called_once_with(
+ mock.ANY,
+ [],
self.floating_ip.id,
- ignore_missing=False
+ ignore_missing=False,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py
index 9fd395b4..2fc0c043 100644
--- a/openstackclient/tests/unit/network/v2/test_network_agent.py
+++ b/openstackclient/tests/unit/network/v2/test_network_agent.py
@@ -195,6 +195,8 @@ class TestListNetworkAgent(TestNetworkAgent):
self.assertEqual(self.data, list(data))
+# TODO(huanxuan): Also update by the new attribute name
+# "is_admin_state_up" after sdk 0.9.12
class TestSetNetworkAgent(TestNetworkAgent):
_network_agent = (
@@ -324,6 +326,6 @@ class TestShowNetworkAgent(TestNetworkAgent):
columns, data = self.cmd.take_action(parsed_args)
self.network.get_agent.assert_called_once_with(
- self._network_agent.id, ignore_missing=False)
+ self._network_agent.id)
self.assertEqual(self.columns, columns)
self.assertEqual(list(self.data), list(data))
diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py
new file mode 100644
index 00000000..41ccae32
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py
@@ -0,0 +1,1049 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_qos_rule
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+from openstackclient.tests.unit import utils as tests_utils
+
+
+RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit'
+RULE_TYPE_DSCP_MARKING = 'dscp-marking'
+RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth'
+DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
+ 34, 36, 38, 40, 46, 48, 56]
+
+
+class TestNetworkQosRule(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkQosRule, self).setUp()
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+ self.qos_policy = (network_fakes.FakeNetworkQosPolicy.
+ create_one_qos_policy())
+ self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy)
+
+
+class TestCreateNetworkQosRuleMinimumBandwidth(TestNetworkQosRule):
+
+ def test_check_type_parameters(self):
+ pass
+
+ def setUp(self):
+ super(TestCreateNetworkQosRuleMinimumBandwidth, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_MINIMUM_BANDWIDTH}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.columns = (
+ 'direction',
+ 'id',
+ 'min_kbps',
+ 'project_id',
+ 'qos_policy_id',
+ 'type'
+ )
+
+ self.data = (
+ self.new_rule.direction,
+ self.new_rule.id,
+ self.new_rule.min_kbps,
+ self.new_rule.project_id,
+ self.new_rule.qos_policy_id,
+ self.new_rule.type,
+ )
+ self.network.create_qos_minimum_bandwidth_rule = mock.Mock(
+ return_value=self.new_rule)
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.CreateNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ '--type', RULE_TYPE_MINIMUM_BANDWIDTH,
+ '--min-kbps', str(self.new_rule.min_kbps),
+ '--egress',
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_MINIMUM_BANDWIDTH),
+ ('min_kbps', self.new_rule.min_kbps),
+ ('egress', True),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_qos_minimum_bandwidth_rule.assert_called_once_with(
+ self.qos_policy.id,
+ **{'min_kbps': self.new_rule.min_kbps,
+ 'direction': self.new_rule.direction}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_wrong_options(self):
+ arglist = [
+ '--type', RULE_TYPE_MINIMUM_BANDWIDTH,
+ '--max-kbps', '10000',
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_MINIMUM_BANDWIDTH),
+ ('max_kbps', 10000),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('"Create" rule command for type "minimum-bandwidth" '
+ 'requires arguments min_kbps, direction')
+ self.assertEqual(msg, str(e))
+
+
+class TestCreateNetworkQosRuleDSCPMarking(TestNetworkQosRule):
+
+ def test_check_type_parameters(self):
+ pass
+
+ def setUp(self):
+ super(TestCreateNetworkQosRuleDSCPMarking, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_DSCP_MARKING}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.columns = (
+ 'dscp_mark',
+ 'id',
+ 'project_id',
+ 'qos_policy_id',
+ 'type'
+ )
+
+ self.data = (
+ self.new_rule.dscp_mark,
+ self.new_rule.id,
+ self.new_rule.project_id,
+ self.new_rule.qos_policy_id,
+ self.new_rule.type,
+ )
+ self.network.create_qos_dscp_marking_rule = mock.Mock(
+ return_value=self.new_rule)
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.CreateNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ '--type', RULE_TYPE_DSCP_MARKING,
+ '--dscp-mark', str(self.new_rule.dscp_mark),
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_DSCP_MARKING),
+ ('dscp_mark', self.new_rule.dscp_mark),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_qos_dscp_marking_rule.assert_called_once_with(
+ self.qos_policy.id,
+ **{'dscp_mark': self.new_rule.dscp_mark}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_wrong_options(self):
+ arglist = [
+ '--type', RULE_TYPE_DSCP_MARKING,
+ '--max-kbps', '10000',
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_DSCP_MARKING),
+ ('max_kbps', 10000),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('"Create" rule command for type "dscp-marking" '
+ 'requires arguments dscp_mark')
+ self.assertEqual(msg, str(e))
+
+
+class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule):
+
+ def test_check_type_parameters(self):
+ pass
+
+ def setUp(self):
+ super(TestCreateNetworkQosRuleBandwidtLimit, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_BANDWIDTH_LIMIT}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.columns = (
+ 'id',
+ 'max_burst_kbits',
+ 'max_kbps',
+ 'project_id',
+ 'qos_policy_id',
+ 'type'
+ )
+
+ self.data = (
+ self.new_rule.id,
+ self.new_rule.max_burst_kbits,
+ self.new_rule.max_kbps,
+ self.new_rule.project_id,
+ self.new_rule.qos_policy_id,
+ self.new_rule.type,
+ )
+ self.network.create_qos_bandwidth_limit_rule = mock.Mock(
+ return_value=self.new_rule)
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.CreateNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ '--type', RULE_TYPE_BANDWIDTH_LIMIT,
+ '--max-kbps', str(self.new_rule.max_kbps),
+ '--max-burst-kbits', str(self.new_rule.max_burst_kbits),
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_BANDWIDTH_LIMIT),
+ ('max_kbps', self.new_rule.max_kbps),
+ ('max_burst_kbits', self.new_rule.max_burst_kbits),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_qos_bandwidth_limit_rule.assert_called_once_with(
+ self.qos_policy.id,
+ **{'max_kbps': self.new_rule.max_kbps,
+ 'max_burst_kbps': self.new_rule.max_burst_kbits}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_wrong_options(self):
+ arglist = [
+ '--type', RULE_TYPE_BANDWIDTH_LIMIT,
+ '--min-kbps', '10000',
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_BANDWIDTH_LIMIT),
+ ('min_kbps', 10000),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('"Create" rule command for type "bandwidth-limit" '
+ 'requires arguments max_kbps, max_burst_kbps')
+ self.assertEqual(msg, str(e))
+
+
+class TestDeleteNetworkQosRuleMinimumBandwidth(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestDeleteNetworkQosRuleMinimumBandwidth, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_MINIMUM_BANDWIDTH}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.network.delete_qos_minimum_bandwidth_rule = mock.Mock(
+ return_value=None)
+ self.network.find_qos_minimum_bandwidth_rule = (
+ network_fakes.FakeNetworkQosRule.get_qos_rules(
+ qos_rules=self.new_rule)
+ )
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_qos_policy_delete(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_qos_policy.assert_called_once_with(
+ self.qos_policy.id, ignore_missing=False)
+ self.network.delete_qos_minimum_bandwidth_rule.assert_called_once_with(
+ self.new_rule.id, self.qos_policy.id)
+ self.assertIsNone(result)
+
+ def test_qos_policy_delete_error(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ self.network.delete_qos_minimum_bandwidth_rule.side_effect = \
+ Exception('Error message')
+ try:
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' %
+ {'rule': self.new_rule.id, 'e': 'Error message'})
+ self.assertEqual(msg, str(e))
+
+
+class TestDeleteNetworkQosRuleDSCPMarking(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestDeleteNetworkQosRuleDSCPMarking, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_DSCP_MARKING}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.network.delete_qos_dscp_marking_rule = mock.Mock(
+ return_value=None)
+ self.network.find_qos_dscp_marking_rule = (
+ network_fakes.FakeNetworkQosRule.get_qos_rules(
+ qos_rules=self.new_rule)
+ )
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_qos_policy_delete(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_qos_policy.assert_called_once_with(
+ self.qos_policy.id, ignore_missing=False)
+ self.network.delete_qos_dscp_marking_rule.assert_called_once_with(
+ self.new_rule.id, self.qos_policy.id)
+ self.assertIsNone(result)
+
+ def test_qos_policy_delete_error(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ self.network.delete_qos_dscp_marking_rule.side_effect = \
+ Exception('Error message')
+ try:
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' %
+ {'rule': self.new_rule.id, 'e': 'Error message'})
+ self.assertEqual(msg, str(e))
+
+
+class TestDeleteNetworkQosRuleBandwidthLimit(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestDeleteNetworkQosRuleBandwidthLimit, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_BANDWIDTH_LIMIT}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.network.delete_qos_bandwidth_limit_rule = mock.Mock(
+ return_value=None)
+ self.network.find_qos_bandwidth_limit_rule = (
+ network_fakes.FakeNetworkQosRule.get_qos_rules(
+ qos_rules=self.new_rule)
+ )
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_qos_policy_delete(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_qos_policy.assert_called_once_with(
+ self.qos_policy.id, ignore_missing=False)
+ self.network.delete_qos_bandwidth_limit_rule.assert_called_once_with(
+ self.new_rule.id, self.qos_policy.id)
+ self.assertIsNone(result)
+
+ def test_qos_policy_delete_error(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ self.network.delete_qos_bandwidth_limit_rule.side_effect = \
+ Exception('Error message')
+ try:
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' %
+ {'rule': self.new_rule.id, 'e': 'Error message'})
+ self.assertEqual(msg, str(e))
+
+
+class TestSetNetworkQosRuleMinimumBandwidth(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestSetNetworkQosRuleMinimumBandwidth, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_MINIMUM_BANDWIDTH}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs=attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.network.update_qos_minimum_bandwidth_rule = mock.Mock(
+ return_value=None)
+ self.network.find_qos_minimum_bandwidth_rule = mock.Mock(
+ return_value=self.new_rule)
+ self.network.find_qos_policy = mock.Mock(
+ return_value=self.qos_policy)
+
+ # Get the command object to test
+ self.cmd = (network_qos_rule.SetNetworkQosRule(self.app,
+ self.namespace))
+
+ def test_set_nothing(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.update_qos_minimum_bandwidth_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id)
+ self.assertIsNone(result)
+
+ def test_set_min_kbps(self):
+ self._set_min_kbps()
+
+ def test_set_min_kbps_to_zero(self):
+ self._set_min_kbps(min_kbps=0)
+
+ def _set_min_kbps(self, min_kbps=None):
+ if min_kbps:
+ previous_min_kbps = self.new_rule.min_kbps
+ self.new_rule.min_kbps = min_kbps
+
+ arglist = [
+ '--min-kbps', str(self.new_rule.min_kbps),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('min_kbps', self.new_rule.min_kbps),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'min_kbps': self.new_rule.min_kbps,
+ }
+ self.network.update_qos_minimum_bandwidth_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id, **attrs)
+ self.assertIsNone(result)
+
+ if min_kbps:
+ self.new_rule.min_kbps = previous_min_kbps
+
+ def test_set_wrong_options(self):
+ arglist = [
+ '--max-kbps', str(10000),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('max_kbps', 10000),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type '
+ '"minimum-bandwidth" only requires arguments min_kbps, '
+ 'direction' % {'rule': self.new_rule.id})
+ self.assertEqual(msg, str(e))
+
+
+class TestSetNetworkQosRuleDSCPMarking(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestSetNetworkQosRuleDSCPMarking, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_DSCP_MARKING}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs=attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.network.update_qos_dscp_marking_rule = mock.Mock(
+ return_value=None)
+ self.network.find_qos_dscp_marking_rule = mock.Mock(
+ return_value=self.new_rule)
+ self.network.find_qos_policy = mock.Mock(
+ return_value=self.qos_policy)
+
+ # Get the command object to test
+ self.cmd = (network_qos_rule.SetNetworkQosRule(self.app,
+ self.namespace))
+
+ def test_set_nothing(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.update_qos_dscp_marking_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id)
+ self.assertIsNone(result)
+
+ def test_set_dscp_mark(self):
+ self._set_dscp_mark()
+
+ def test_set_dscp_mark_to_zero(self):
+ self._set_dscp_mark(dscp_mark=0)
+
+ def _set_dscp_mark(self, dscp_mark=None):
+ if dscp_mark:
+ previous_dscp_mark = self.new_rule.dscp_mark
+ self.new_rule.dscp_mark = dscp_mark
+
+ arglist = [
+ '--dscp-mark', str(self.new_rule.dscp_mark),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('dscp_mark', self.new_rule.dscp_mark),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'dscp_mark': self.new_rule.dscp_mark,
+ }
+ self.network.update_qos_dscp_marking_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id, **attrs)
+ self.assertIsNone(result)
+
+ if dscp_mark:
+ self.new_rule.dscp_mark = previous_dscp_mark
+
+ def test_set_wrong_options(self):
+ arglist = [
+ '--max-kbps', str(10000),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('max_kbps', 10000),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type '
+ '"dscp-marking" only requires arguments dscp_mark' %
+ {'rule': self.new_rule.id})
+ self.assertEqual(msg, str(e))
+
+
+class TestSetNetworkQosRuleBandwidthLimit(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestSetNetworkQosRuleBandwidthLimit, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_BANDWIDTH_LIMIT}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs=attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.network.update_qos_bandwidth_limit_rule = mock.Mock(
+ return_value=None)
+ self.network.find_qos_bandwidth_limit_rule = mock.Mock(
+ return_value=self.new_rule)
+ self.network.find_qos_policy = mock.Mock(
+ return_value=self.qos_policy)
+
+ # Get the command object to test
+ self.cmd = (network_qos_rule.SetNetworkQosRule(self.app,
+ self.namespace))
+
+ def test_set_nothing(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.update_qos_bandwidth_limit_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id)
+ self.assertIsNone(result)
+
+ def test_set_max_kbps(self):
+ self._set_max_kbps()
+
+ def test_set_max_kbps_to_zero(self):
+ self._set_max_kbps(max_kbps=0)
+
+ def _set_max_kbps(self, max_kbps=None):
+ if max_kbps:
+ previous_max_kbps = self.new_rule.max_kbps
+ self.new_rule.max_kbps = max_kbps
+
+ arglist = [
+ '--max-kbps', str(self.new_rule.max_kbps),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('max_kbps', self.new_rule.max_kbps),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'max_kbps': self.new_rule.max_kbps,
+ }
+ self.network.update_qos_bandwidth_limit_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id, **attrs)
+ self.assertIsNone(result)
+
+ if max_kbps:
+ self.new_rule.max_kbps = previous_max_kbps
+
+ def test_set_max_burst_kbits(self):
+ self._set_max_burst_kbits()
+
+ def test_set_max_burst_kbits_to_zero(self):
+ self._set_max_burst_kbits(max_burst_kbits=0)
+
+ def _set_max_burst_kbits(self, max_burst_kbits=None):
+ if max_burst_kbits:
+ previous_max_burst_kbits = self.new_rule.max_burst_kbits
+ self.new_rule.max_burst_kbits = max_burst_kbits
+
+ arglist = [
+ '--max-burst-kbits', str(self.new_rule.max_burst_kbits),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('max_burst_kbits', self.new_rule.max_burst_kbits),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'max_burst_kbps': self.new_rule.max_burst_kbits,
+ }
+ self.network.update_qos_bandwidth_limit_rule.assert_called_with(
+ self.new_rule, self.qos_policy.id, **attrs)
+ self.assertIsNone(result)
+
+ if max_burst_kbits:
+ self.new_rule.max_burst_kbits = previous_max_burst_kbits
+
+ def test_set_wrong_options(self):
+ arglist = [
+ '--min-kbps', str(10000),
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('min_kbps', 10000),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ except exceptions.CommandError as e:
+ msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type '
+ '"bandwidth-limit" only requires arguments max_kbps, '
+ 'max_burst_kbps' % {'rule': self.new_rule.id})
+ self.assertEqual(msg, str(e))
+
+
+class TestListNetworkQosRule(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestListNetworkQosRule, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_MINIMUM_BANDWIDTH}
+ self.new_rule_min_bw = (network_fakes.FakeNetworkQosRule.
+ create_one_qos_rule(attrs=attrs))
+ attrs['type'] = RULE_TYPE_DSCP_MARKING
+ self.new_rule_dscp_mark = (network_fakes.FakeNetworkQosRule.
+ create_one_qos_rule(attrs=attrs))
+ attrs['type'] = RULE_TYPE_BANDWIDTH_LIMIT
+ self.new_rule_max_bw = (network_fakes.FakeNetworkQosRule.
+ create_one_qos_rule(attrs=attrs))
+ self.qos_policy.rules = [self.new_rule_min_bw,
+ self.new_rule_dscp_mark,
+ self.new_rule_max_bw]
+ self.network.find_qos_minimum_bandwidth_rule = mock.Mock(
+ return_value=self.new_rule_min_bw)
+ self.network.find_qos_dscp_marking_rule = mock.Mock(
+ return_value=self.new_rule_dscp_mark)
+ self.network.find_qos_bandwidth_limit_rule = mock.Mock(
+ return_value=self.new_rule_max_bw)
+ self.columns = (
+ 'ID',
+ 'QoS Policy ID',
+ 'Type',
+ 'Max Kbps',
+ 'Max Burst Kbits',
+ 'Min Kbps',
+ 'DSCP mark',
+ 'Direction',
+ )
+ self.data = []
+ for index in range(len(self.qos_policy.rules)):
+ self.data.append((
+ self.qos_policy.rules[index].id,
+ self.qos_policy.rules[index].qos_policy_id,
+ self.qos_policy.rules[index].type,
+ getattr(self.qos_policy.rules[index], 'max_kbps', ''),
+ getattr(self.qos_policy.rules[index], 'max_burst_kbps', ''),
+ getattr(self.qos_policy.rules[index], 'min_kbps', ''),
+ getattr(self.qos_policy.rules[index], 'dscp_mark', ''),
+ getattr(self.qos_policy.rules[index], 'direction', ''),
+ ))
+ # Get the command object to test
+ self.cmd = network_qos_rule.ListNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_qos_rule_list(self):
+ arglist = [
+ self.qos_policy.id
+ ]
+ verifylist = [
+ ('qos_policy', self.qos_policy.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_qos_policy.assert_called_once_with(
+ self.qos_policy.id, ignore_missing=False)
+ self.assertEqual(self.columns, columns)
+ list_data = list(data)
+ self.assertEqual(len(self.data), len(list_data))
+ for index in range(len(list_data)):
+ self.assertEqual(self.data[index], list_data[index])
+
+
+class TestShowNetworkQosRuleMinimumBandwidth(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestShowNetworkQosRuleMinimumBandwidth, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_MINIMUM_BANDWIDTH}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.columns = (
+ 'direction',
+ 'id',
+ 'min_kbps',
+ 'project_id',
+ 'qos_policy_id',
+ 'type'
+ )
+ self.data = (
+ self.new_rule.direction,
+ self.new_rule.id,
+ self.new_rule.min_kbps,
+ self.new_rule.project_id,
+ self.new_rule.qos_policy_id,
+ self.new_rule.type,
+ )
+
+ self.network.get_qos_minimum_bandwidth_rule = mock.Mock(
+ return_value=self.new_rule)
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.ShowNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.get_qos_minimum_bandwidth_rule.assert_called_once_with(
+ self.new_rule.id, self.qos_policy.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(list(self.data), list(data))
+
+
+class TestShowNetworkQosDSCPMarking(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestShowNetworkQosDSCPMarking, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_DSCP_MARKING}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.columns = (
+ 'dscp_mark',
+ 'id',
+ 'project_id',
+ 'qos_policy_id',
+ 'type'
+ )
+ self.data = (
+ self.new_rule.dscp_mark,
+ self.new_rule.id,
+ self.new_rule.project_id,
+ self.new_rule.qos_policy_id,
+ self.new_rule.type,
+ )
+
+ self.network.get_qos_dscp_marking_rule = mock.Mock(
+ return_value=self.new_rule)
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.ShowNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.get_qos_dscp_marking_rule.assert_called_once_with(
+ self.new_rule.id, self.qos_policy.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(list(self.data), list(data))
+
+
+class TestShowNetworkQosBandwidthLimit(TestNetworkQosRule):
+
+ def setUp(self):
+ super(TestShowNetworkQosBandwidthLimit, self).setUp()
+ attrs = {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_BANDWIDTH_LIMIT}
+ self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ attrs)
+ self.qos_policy.rules = [self.new_rule]
+ self.columns = (
+ 'id',
+ 'max_burst_kbits',
+ 'max_kbps',
+ 'project_id',
+ 'qos_policy_id',
+ 'type'
+ )
+ self.data = (
+ self.new_rule.id,
+ self.new_rule.max_burst_kbits,
+ self.new_rule.max_kbps,
+ self.new_rule.project_id,
+ self.new_rule.qos_policy_id,
+ self.new_rule.type,
+ )
+
+ self.network.get_qos_bandwidth_limit_rule = mock.Mock(
+ return_value=self.new_rule)
+
+ # Get the command object to test
+ self.cmd = network_qos_rule.ShowNetworkQosRule(self.app,
+ self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self.new_rule.qos_policy_id,
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ('id', self.new_rule.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.get_qos_bandwidth_limit_rule.assert_called_once_with(
+ self.new_rule.id, self.qos_policy.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(list(self.data), list(data))
diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py
index d5cd72ec..a6676403 100644
--- a/openstackclient/tests/unit/volume/v2/fakes.py
+++ b/openstackclient/tests/unit/volume/v2/fakes.py
@@ -903,3 +903,23 @@ class FakeType(object):
volume_types.append(volume_type)
return volume_types
+
+ @staticmethod
+ def get_types(types=None, count=2):
+ """Get an iterable MagicMock object with a list of faked types.
+
+ If types list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List types:
+ A list of FakeResource objects faking types
+ :param Integer count:
+ The number of types to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ types
+ """
+ if types is None:
+ types = FakeType.create_types(count)
+
+ return mock.Mock(side_effect=types)
diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
index bc99ca8d..6eeeae39 100644
--- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py
+++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
@@ -36,10 +36,115 @@ class TestConsistencyGroup(volume_fakes.TestVolume):
self.app.client_manager.volume.cgsnapshots)
self.cgsnapshots_mock.reset_mock()
+ self.volumes_mock = (
+ self.app.client_manager.volume.volumes)
+ self.volumes_mock.reset_mock()
+
self.types_mock = self.app.client_manager.volume.volume_types
self.types_mock.reset_mock()
+class TestConsistencyGroupAddVolume(TestConsistencyGroup):
+
+ _consistency_group = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
+
+ def setUp(self):
+ super(TestConsistencyGroupAddVolume, self).setUp()
+
+ self.consistencygroups_mock.get.return_value = (
+ self._consistency_group)
+ # Get the command object to test
+ self.cmd = \
+ consistency_group.AddVolumeToConsistencyGroup(self.app, None)
+
+ def test_add_one_volume_to_consistency_group(self):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ self.volumes_mock.get.return_value = volume
+ arglist = [
+ self._consistency_group.id,
+ volume.id,
+ ]
+ verifylist = [
+ ('consistency_group', self._consistency_group.id),
+ ('volumes', [volume.id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'add_volumes': volume.id,
+ }
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self._consistency_group.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ def test_add_multiple_volumes_to_consistency_group(self):
+ volumes = volume_fakes.FakeVolume.create_volumes(count=2)
+ self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes(volumes)
+ arglist = [
+ self._consistency_group.id,
+ volumes[0].id,
+ volumes[1].id,
+ ]
+ verifylist = [
+ ('consistency_group', self._consistency_group.id),
+ ('volumes', [volumes[0].id, volumes[1].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'add_volumes': volumes[0].id + ',' + volumes[1].id,
+ }
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self._consistency_group.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ @mock.patch.object(consistency_group.LOG, 'error')
+ def test_add_multiple_volumes_to_consistency_group_with_exception(
+ self, mock_error):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ arglist = [
+ self._consistency_group.id,
+ volume.id,
+ 'unexist_volume',
+ ]
+ verifylist = [
+ ('consistency_group', self._consistency_group.id),
+ ('volumes', [volume.id, 'unexist_volume']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [volume,
+ exceptions.CommandError,
+ self._consistency_group]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ result = self.cmd.take_action(parsed_args)
+ mock_error.assert_called_with("1 of 2 volumes failed to add.")
+ self.assertIsNone(result)
+ find_mock.assert_any_call(self.consistencygroups_mock,
+ self._consistency_group.id)
+ find_mock.assert_any_call(self.volumes_mock,
+ volume.id)
+ find_mock.assert_any_call(self.volumes_mock,
+ 'unexist_volume')
+ self.assertEqual(3, find_mock.call_count)
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self._consistency_group.id, add_volumes=volume.id
+ )
+
+
class TestConsistencyGroupCreate(TestConsistencyGroup):
volume_type = volume_fakes.FakeType.create_one_type()
@@ -394,6 +499,107 @@ class TestConsistencyGroupList(TestConsistencyGroup):
self.assertEqual(self.data_long, list(data))
+class TestConsistencyGroupRemoveVolume(TestConsistencyGroup):
+
+ _consistency_group = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
+
+ def setUp(self):
+ super(TestConsistencyGroupRemoveVolume, self).setUp()
+
+ self.consistencygroups_mock.get.return_value = (
+ self._consistency_group)
+ # Get the command object to test
+ self.cmd = \
+ consistency_group.RemoveVolumeFromConsistencyGroup(self.app, None)
+
+ def test_remove_one_volume_from_consistency_group(self):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ self.volumes_mock.get.return_value = volume
+ arglist = [
+ self._consistency_group.id,
+ volume.id,
+ ]
+ verifylist = [
+ ('consistency_group', self._consistency_group.id),
+ ('volumes', [volume.id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'remove_volumes': volume.id,
+ }
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self._consistency_group.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ def test_remove_multi_volumes_from_consistency_group(self):
+ volumes = volume_fakes.FakeVolume.create_volumes(count=2)
+ self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes(volumes)
+ arglist = [
+ self._consistency_group.id,
+ volumes[0].id,
+ volumes[1].id,
+ ]
+ verifylist = [
+ ('consistency_group', self._consistency_group.id),
+ ('volumes', [volumes[0].id, volumes[1].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'remove_volumes': volumes[0].id + ',' + volumes[1].id,
+ }
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self._consistency_group.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ @mock.patch.object(consistency_group.LOG, 'error')
+ def test_remove_multiple_volumes_from_consistency_group_with_exception(
+ self, mock_error):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ arglist = [
+ self._consistency_group.id,
+ volume.id,
+ 'unexist_volume',
+ ]
+ verifylist = [
+ ('consistency_group', self._consistency_group.id),
+ ('volumes', [volume.id, 'unexist_volume']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [volume,
+ exceptions.CommandError,
+ self._consistency_group]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ result = self.cmd.take_action(parsed_args)
+ mock_error.assert_called_with("1 of 2 volumes failed to remove.")
+ self.assertIsNone(result)
+ find_mock.assert_any_call(self.consistencygroups_mock,
+ self._consistency_group.id)
+ find_mock.assert_any_call(self.volumes_mock,
+ volume.id)
+ find_mock.assert_any_call(self.volumes_mock,
+ 'unexist_volume')
+ self.assertEqual(3, find_mock.call_count)
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self._consistency_group.id, remove_volumes=volume.id
+ )
+
+
class TestConsistencyGroupSet(TestConsistencyGroup):
consistency_group = (
diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py
index 0d556e13..cec01bd8 100644
--- a/openstackclient/tests/unit/volume/v2/test_type.py
+++ b/openstackclient/tests/unit/volume/v2/test_type.py
@@ -13,6 +13,7 @@
#
import mock
+from mock import call
from osc_lib import exceptions
from osc_lib import utils
@@ -133,12 +134,13 @@ class TestTypeCreate(TestType):
class TestTypeDelete(TestType):
- volume_type = volume_fakes.FakeType.create_one_type()
+ volume_types = volume_fakes.FakeType.create_types(count=2)
def setUp(self):
super(TestTypeDelete, self).setUp()
- self.types_mock.get.return_value = self.volume_type
+ self.types_mock.get = volume_fakes.FakeType.get_types(
+ self.volume_types)
self.types_mock.delete.return_value = None
# Get the command object to mock
@@ -146,18 +148,64 @@ class TestTypeDelete(TestType):
def test_type_delete(self):
arglist = [
- self.volume_type.id
+ self.volume_types[0].id
]
verifylist = [
- ("volume_types", [self.volume_type.id])
+ ("volume_types", [self.volume_types[0].id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.types_mock.delete.assert_called_with(self.volume_type)
+ self.types_mock.delete.assert_called_with(self.volume_types[0])
self.assertIsNone(result)
+ def test_delete_multiple_types(self):
+ arglist = []
+ for t in self.volume_types:
+ arglist.append(t.id)
+ verifylist = [
+ ('volume_types', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for t in self.volume_types:
+ calls.append(call(t))
+ self.types_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_types_with_exception(self):
+ arglist = [
+ self.volume_types[0].id,
+ 'unexist_type',
+ ]
+ verifylist = [
+ ('volume_types', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.volume_types[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 volume types failed to delete.',
+ str(e))
+ find_mock.assert_any_call(
+ self.types_mock, self.volume_types[0].id)
+ find_mock.assert_any_call(self.types_mock, 'unexist_type')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.types_mock.delete.assert_called_once_with(
+ self.volume_types[0]
+ )
+
class TestTypeList(TestType):
diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py
index 1529c2e2..4fef9dd9 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume.py
@@ -823,6 +823,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': False,
+ 'project_id': None,
+ 'user_id': None,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -853,6 +866,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': True,
+ 'project_id': self.project.id,
+ 'user_id': None,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -885,6 +911,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': True,
+ 'project_id': self.project.id,
+ 'user_id': None,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -915,6 +954,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': False,
+ 'project_id': None,
+ 'user_id': self.user.id,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
device = self.mock_volume.attachments[0]['device']
@@ -946,6 +998,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': False,
+ 'project_id': None,
+ 'user_id': self.user.id,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -976,6 +1041,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': False,
+ 'project_id': None,
+ 'user_id': None,
+ 'display_name': self.mock_volume.name,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -1006,6 +1084,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': False,
+ 'project_id': None,
+ 'user_id': None,
+ 'display_name': None,
+ 'status': self.mock_volume.status,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -1036,6 +1127,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': True,
+ 'project_id': None,
+ 'user_id': None,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
self.assertEqual(self.columns, columns)
server = self.mock_volume.attachments[0]['server_id']
@@ -1067,6 +1171,19 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ 'all_tenants': False,
+ 'project_id': None,
+ 'user_id': None,
+ 'display_name': None,
+ 'status': None,
+ }
+ self.volumes_mock.list.assert_called_once_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+
collist = [
'ID',
'Display Name',
diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py
index 2f4f3c95..0a932f84 100644
--- a/openstackclient/volume/v2/consistency_group.py
+++ b/openstackclient/volume/v2/consistency_group.py
@@ -27,6 +27,60 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
+def _find_volumes(parsed_args_volumes, volume_client):
+ result = 0
+ uuid = ''
+ for volume in parsed_args_volumes:
+ try:
+ volume_id = utils.find_resource(
+ volume_client.volumes, volume).id
+ uuid += volume_id + ','
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to find volume with "
+ "name or ID '%(volume)s':%(e)s")
+ % {'volume': volume, 'e': e})
+
+ return result, uuid
+
+
+class AddVolumeToConsistencyGroup(command.Command):
+ _description = _("Add volume(s) to consistency group")
+
+ def get_parser(self, prog_name):
+ parser = super(AddVolumeToConsistencyGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'consistency_group',
+ metavar="<consistency-group>",
+ help=_('Consistency group to contain <volume> (name or ID)'),
+ )
+ parser.add_argument(
+ 'volumes',
+ metavar='<volume>',
+ nargs='+',
+ help=_('Volume(s) to add to <consistency-group> (name or ID) '
+ '(repeat option to add multiple volumes)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ result, add_uuid = _find_volumes(parsed_args.volumes, volume_client)
+
+ if result > 0:
+ total = len(parsed_args.volumes)
+ LOG.error(_("%(result)s of %(total)s volumes failed "
+ "to add.") % {'result': result, 'total': total})
+
+ if add_uuid:
+ add_uuid = add_uuid.rstrip(',')
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups,
+ parsed_args.consistency_group).id
+ volume_client.consistencygroups.update(
+ consistency_group_id, add_volumes=add_uuid)
+
+
class CreateConsistencyGroup(command.ShowOne):
_description = _("Create new consistency group.")
@@ -188,6 +242,44 @@ class ListConsistencyGroup(command.Lister):
for s in consistency_groups))
+class RemoveVolumeFromConsistencyGroup(command.Command):
+ _description = _("Remove volume(s) from consistency group")
+
+ def get_parser(self, prog_name):
+ parser = \
+ super(RemoveVolumeFromConsistencyGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'consistency_group',
+ metavar="<consistency-group>",
+ help=_('Consistency group containing <volume> (name or ID)'),
+ )
+ parser.add_argument(
+ 'volumes',
+ metavar='<volume>',
+ nargs='+',
+ help=_('Volume(s) to remove from <consistency-group> (name or ID) '
+ '(repeat option to remove multiple volumes)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ result, remove_uuid = _find_volumes(parsed_args.volumes, volume_client)
+
+ if result > 0:
+ total = len(parsed_args.volumes)
+ LOG.error(_("%(result)s of %(total)s volumes failed "
+ "to remove.") % {'result': result, 'total': total})
+
+ if remove_uuid:
+ remove_uuid = remove_uuid.rstrip(',')
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups,
+ parsed_args.consistency_group).id
+ volume_client.consistencygroups.update(
+ consistency_group_id, remove_volumes=remove_uuid)
+
+
class SetConsistencyGroup(command.Command):
_description = _("Set consistency group properties")
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index 301bf5e4..78db261b 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -417,16 +417,19 @@ class ListVolume(command.Lister):
project_id = identity_common.find_project(
identity_client,
parsed_args.project,
- parsed_args.project_domain)
+ parsed_args.project_domain).id
user_id = None
if parsed_args.user:
user_id = identity_common.find_user(identity_client,
parsed_args.user,
- parsed_args.user_domain)
+ parsed_args.user_domain).id
+
+ # set value of 'all_tenants' when using project option
+ all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
- 'all_tenants': parsed_args.all_projects,
+ 'all_tenants': all_projects,
'project_id': project_id,
'user_id': user_id,
'display_name': parsed_args.name,
diff --git a/releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml b/releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml
new file mode 100644
index 00000000..812d510f
--- /dev/null
+++ b/releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Add support for Network QoS rule commands:
+ ``network qos rule create``, ``network qos rule delete``,
+ ``network qos rule list``, ``network qos rule show`` and
+ ``network qos rule set``
+ [Bug `1609472 <https://bugs.launchpad.net/bugs/1609472>`_]
diff --git a/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml b/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml
index ac83b7e8..1b022cf1 100644
--- a/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml
+++ b/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml
@@ -1,7 +1,6 @@
---
features:
- |
- Add support to overwrite routes in a router instance, using ``--router``
- and ``--no-router`` option in ``osc router set``.
+ Add ``--router`` and ``--no-router`` options to ``osc router set`` command to
+ modify routes in a router instance.
[ Blueprint `allow-overwrite-set-options <https://blueprints.launchpad.net/python-openstackclient/+spec/allow-overwrite-set-options>`_]
-
diff --git a/releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml b/releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml
new file mode 100644
index 00000000..b11d0c1f
--- /dev/null
+++ b/releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Add ``consistency group add volume`` and ``consistency group remove volume`` commands
+ in volume v2.
+ [Bug `1642238 <https://bugs.launchpad.net/python-openstackclient/+bug/1642238>`_]
+
diff --git a/releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml b/releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml
new file mode 100644
index 00000000..24991cbd
--- /dev/null
+++ b/releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Fix an endpoint version problem with Image endpoints that contained the
+ substring 'v2'.
+ [Bug `1652827 <https://bugs.launchpad.net/bugs/1652827>`_]
diff --git a/releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml b/releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml
new file mode 100644
index 00000000..77cb3df0
--- /dev/null
+++ b/releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Fixed a ``'Quota' object is not iterable`` error in the ``quota show`` command
+ that appeared with the initial release of openstacksdk v0.9.11 and v0.9.12.
+ [Bug `1655537 <https://bugs.launchpad.net/bugs/1655537>`_]
diff --git a/releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml b/releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml
new file mode 100644
index 00000000..c8a30298
--- /dev/null
+++ b/releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Fix ``floating ip delete`` and ``floating ip show`` to accept IP addresses
+ in addition to IDs to select floating IPs to delete or show.
+ [Bug `1656402 <https://bugs.launchpad.net/bugs/1656402>`_
diff --git a/releasenotes/notes/bug-1656572-b40303ae50a41000.yaml b/releasenotes/notes/bug-1656572-b40303ae50a41000.yaml
new file mode 100644
index 00000000..e6d0bcc5
--- /dev/null
+++ b/releasenotes/notes/bug-1656572-b40303ae50a41000.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Work around a bug in OpenStackSDK 0.9.11 and 0.9.12 that causes
+ ``quota show --default`` to fail.
+ [Bug `1656572 <https://bugs.launchpad.net/python-openstackclient/+bug/1656572>`_]
diff --git a/releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml b/releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml
new file mode 100644
index 00000000..f2245201
--- /dev/null
+++ b/releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Fix a bug of unable to filter volume list by ``--project``
+ and ``--user`` options in the ``openstack volume list``.
diff --git a/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml
new file mode 100644
index 00000000..6db63f87
--- /dev/null
+++ b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Add support to update image membership with the ``--accept``,
+ ``--reject`` and ``--pending`` options of the ``image set command``.
diff --git a/requirements.txt b/requirements.txt
index 81a129e0..e5ca3c7b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,8 +6,8 @@ six>=1.9.0 # MIT
Babel>=2.3.4 # BSD
cliff>=2.3.0 # Apache-2.0
-keystoneauth1>=2.16.0 # Apache-2.0
-openstacksdk!=0.9.11,>=0.9.10 # Apache-2.0
+keystoneauth1>=2.17.0 # Apache-2.0
+openstacksdk!=0.9.11,!=0.9.12,>=0.9.10 # Apache-2.0
osc-lib>=1.2.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.utils>=3.18.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index c88a4cb5..7aa3f198 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -377,6 +377,12 @@ openstack.network.v2 =
network_qos_policy_set = openstackclient.network.v2.network_qos_policy:SetNetworkQosPolicy
network_qos_policy_show = openstackclient.network.v2.network_qos_policy:ShowNetworkQosPolicy
+ network_qos_rule_create = openstackclient.network.v2.network_qos_rule:CreateNetworkQosRule
+ network_qos_rule_delete = openstackclient.network.v2.network_qos_rule:DeleteNetworkQosRule
+ network_qos_rule_list = openstackclient.network.v2.network_qos_rule:ListNetworkQosRule
+ network_qos_rule_set = openstackclient.network.v2.network_qos_rule:SetNetworkQosRule
+ network_qos_rule_show = openstackclient.network.v2.network_qos_rule:ShowNetworkQosRule
+
network_qos_rule_type_list = openstackclient.network.v2.network_qos_rule_type:ListNetworkQosRuleType
network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC
@@ -522,9 +528,11 @@ openstack.volume.v2 =
backup_restore = openstackclient.volume.v2.backup:RestoreBackup
backup_show = openstackclient.volume.v2.backup:ShowBackup
+ consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup
consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup
consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup
consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup
+ consistency_group_remove_volume = openstackclient.volume.v2.consistency_group:RemoveVolumeFromConsistencyGroup
consistency_group_set = openstackclient.volume.v2.consistency_group:SetConsistencyGroup
consistency_group_show = openstackclient.volume.v2.consistency_group:ShowConsistencyGroup
diff --git a/test-requirements.txt b/test-requirements.txt
index 08b49c98..b53d08dd 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -28,7 +28,7 @@ python-barbicanclient>=4.0.0 # Apache-2.0
python-congressclient<2000,>=1.3.0 # Apache-2.0
python-designateclient>=1.5.0 # Apache-2.0
python-heatclient>=1.6.1 # Apache-2.0
-python-ironicclient>=1.6.0 # Apache-2.0
+python-ironicclient>=1.9.0 # Apache-2.0
python-ironic-inspector-client>=1.5.0 # Apache-2.0
python-mistralclient>=2.0.0 # Apache-2.0
python-muranoclient>=0.8.2 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index af7120e1..130b32fe 100644
--- a/tox.ini
+++ b/tox.ini
@@ -54,6 +54,16 @@ commands =
setenv = OS_TEST_PATH=./openstackclient/tests/functional
passenv = OS_*
+[testenv:functional-tips]
+setenv = OS_TEST_PATH=./openstackclient/tests/functional
+passenv = OS_*
+commands =
+ pip install -q -U -e "git+file:///opt/stack/new/osc-lib#egg=osc_lib"
+ pip install -q -U -e "git+file:///opt/stack/new/python-openstacksdk#egg=openstacksdk"
+ pip install -q -U -e "git+file:///opt/stack/new/os-client-config#egg=os_client_config"
+ pip freeze
+ ostestr {posargs}
+
[testenv:venv]
commands = {posargs}