summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/image_v1.py6
-rw-r--r--openstackclient/api/image_v2.py6
-rw-r--r--openstackclient/api/object_store_v1.py2
-rw-r--r--openstackclient/common/availability_zone.py2
-rw-r--r--openstackclient/common/client_config.py196
-rw-r--r--openstackclient/common/clientmanager.py27
-rw-r--r--openstackclient/common/configuration.py2
-rw-r--r--openstackclient/common/extension.py2
-rw-r--r--openstackclient/common/limits.py2
-rw-r--r--openstackclient/common/module.py52
-rw-r--r--openstackclient/common/quota.py183
-rw-r--r--openstackclient/compute/v2/agent.py8
-rw-r--r--openstackclient/compute/v2/aggregate.py20
-rw-r--r--openstackclient/compute/v2/console.py17
-rw-r--r--openstackclient/compute/v2/fixedip.py4
-rw-r--r--openstackclient/compute/v2/flavor.py42
-rw-r--r--openstackclient/compute/v2/floatingip.py4
-rw-r--r--openstackclient/compute/v2/host.py7
-rw-r--r--openstackclient/compute/v2/hypervisor.py16
-rw-r--r--openstackclient/compute/v2/hypervisor_stats.py4
-rw-r--r--openstackclient/compute/v2/keypair.py57
-rw-r--r--openstackclient/compute/v2/server.py325
-rw-r--r--openstackclient/compute/v2/server_backup.py2
-rw-r--r--openstackclient/compute/v2/server_event.py117
-rw-r--r--openstackclient/compute/v2/server_group.py17
-rw-r--r--openstackclient/compute/v2/server_image.py2
-rw-r--r--openstackclient/compute/v2/service.py9
-rw-r--r--openstackclient/compute/v2/usage.py6
-rw-r--r--openstackclient/identity/v2_0/catalog.py4
-rw-r--r--openstackclient/identity/v2_0/ec2creds.py12
-rw-r--r--openstackclient/identity/v2_0/endpoint.py12
-rw-r--r--openstackclient/identity/v2_0/project.py45
-rw-r--r--openstackclient/identity/v2_0/role.py37
-rw-r--r--openstackclient/identity/v2_0/role_assignment.py2
-rw-r--r--openstackclient/identity/v2_0/service.py12
-rw-r--r--openstackclient/identity/v2_0/token.py4
-rw-r--r--openstackclient/identity/v2_0/user.py42
-rw-r--r--openstackclient/identity/v3/catalog.py4
-rw-r--r--openstackclient/identity/v3/consumer.py13
-rw-r--r--openstackclient/identity/v3/credential.py14
-rw-r--r--openstackclient/identity/v3/domain.py13
-rw-r--r--openstackclient/identity/v3/ec2creds.py12
-rw-r--r--openstackclient/identity/v3/endpoint.py15
-rw-r--r--openstackclient/identity/v3/federation_protocol.py14
-rw-r--r--openstackclient/identity/v3/group.py135
-rw-r--r--openstackclient/identity/v3/identity_provider.py14
-rw-r--r--openstackclient/identity/v3/mapping.py16
-rw-r--r--openstackclient/identity/v3/policy.py13
-rw-r--r--openstackclient/identity/v3/project.py86
-rw-r--r--openstackclient/identity/v3/region.py13
-rw-r--r--openstackclient/identity/v3/role.py72
-rw-r--r--openstackclient/identity/v3/role_assignment.py4
-rw-r--r--openstackclient/identity/v3/service.py14
-rw-r--r--openstackclient/identity/v3/service_provider.py14
-rw-r--r--openstackclient/identity/v3/token.py12
-rw-r--r--openstackclient/identity/v3/trust.py46
-rw-r--r--openstackclient/identity/v3/unscoped_saml.py26
-rw-r--r--openstackclient/identity/v3/user.py77
-rw-r--r--openstackclient/image/client.py2
-rw-r--r--openstackclient/image/v1/image.py51
-rw-r--r--openstackclient/image/v2/image.py105
-rw-r--r--openstackclient/network/client.py5
-rw-r--r--openstackclient/network/common.py17
-rw-r--r--openstackclient/network/sdk_utils.py59
-rw-r--r--openstackclient/network/v2/address_scope.py97
-rw-r--r--openstackclient/network/v2/floating_ip.py223
-rw-r--r--openstackclient/network/v2/floating_ip_pool.py4
-rw-r--r--openstackclient/network/v2/ip_availability.py26
-rw-r--r--openstackclient/network/v2/network.py177
-rw-r--r--openstackclient/network/v2/network_agent.py183
-rw-r--r--openstackclient/network/v2/network_auto_allocated_topology.py136
-rw-r--r--openstackclient/network/v2/network_flavor.py247
-rw-r--r--openstackclient/network/v2/network_flavor_profile.py250
-rw-r--r--openstackclient/network/v2/network_meter.py190
-rw-r--r--openstackclient/network/v2/network_meter_rule.py203
-rw-r--r--openstackclient/network/v2/network_qos_policy.py259
-rw-r--r--openstackclient/network/v2/network_qos_rule.py353
-rw-r--r--openstackclient/network/v2/network_qos_rule_type.py38
-rw-r--r--openstackclient/network/v2/network_rbac.py81
-rw-r--r--openstackclient/network/v2/network_segment.py27
-rw-r--r--openstackclient/network/v2/network_service_provider.py43
-rw-r--r--openstackclient/network/v2/port.py425
-rw-r--r--openstackclient/network/v2/router.py225
-rw-r--r--openstackclient/network/v2/security_group.py96
-rw-r--r--openstackclient/network/v2/security_group_rule.py201
-rw-r--r--openstackclient/network/v2/subnet.py71
-rw-r--r--openstackclient/network/v2/subnet_pool.py70
-rw-r--r--openstackclient/object/v1/account.py16
-rw-r--r--openstackclient/object/v1/container.py48
-rw-r--r--openstackclient/object/v1/object.py68
-rw-r--r--openstackclient/shell.py16
-rw-r--r--openstackclient/tests/functional/base.py36
-rw-r--r--openstackclient/tests/functional/common/test_availability_zone.py19
-rw-r--r--openstackclient/tests/functional/common/test_configuration.py43
-rw-r--r--openstackclient/tests/functional/common/test_help.py7
-rw-r--r--openstackclient/tests/functional/common/test_module.py79
-rw-r--r--openstackclient/tests/functional/common/test_quota.py21
-rw-r--r--openstackclient/tests/functional/compute/v2/common.py145
-rw-r--r--openstackclient/tests/functional/compute/v2/test_agent.py233
-rw-r--r--openstackclient/tests/functional/compute/v2/test_aggregate.py179
-rw-r--r--openstackclient/tests/functional/compute/v2/test_flavor.py227
-rw-r--r--openstackclient/tests/functional/compute/v2/test_keypair.py21
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py617
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server_event.py84
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server_group.py112
-rw-r--r--openstackclient/tests/functional/identity/v2/common.py13
-rw-r--r--openstackclient/tests/functional/identity/v3/common.py15
-rw-r--r--openstackclient/tests/functional/identity/v3/test_group.py25
-rw-r--r--openstackclient/tests/functional/identity/v3/test_idp.py3
-rw-r--r--openstackclient/tests/functional/identity/v3/test_project.py13
-rw-r--r--openstackclient/tests/functional/image/v2/test_image.py22
-rw-r--r--openstackclient/tests/functional/network/v2/test_address_scope.py171
-rw-r--r--openstackclient/tests/functional/network/v2/test_floating_ip.py158
-rw-r--r--openstackclient/tests/functional/network/v2/test_ip_availability.py37
-rw-r--r--openstackclient/tests/functional/network/v2/test_network.py369
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_agent.py52
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_flavor.py176
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_flavor_profile.py151
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_meter.py158
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_meter_rule.py123
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_qos_policy.py55
-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.py28
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_service_provider.py26
-rw-r--r--openstackclient/tests/functional/network/v2/test_port.py158
-rw-r--r--openstackclient/tests/functional/network/v2/test_router.py232
-rw-r--r--openstackclient/tests/functional/network/v2/test_subnet.py279
-rwxr-xr-xopenstackclient/tests/functional/post_test_hook_tips.sh47
-rw-r--r--openstackclient/tests/functional/volume/v1/test_qos.py134
-rw-r--r--openstackclient/tests/functional/volume/v1/test_snapshot.py260
-rw-r--r--openstackclient/tests/functional/volume/v1/test_volume.py247
-rw-r--r--openstackclient/tests/functional/volume/v1/test_volume_type.py71
-rw-r--r--openstackclient/tests/functional/volume/v2/test_qos.py141
-rw-r--r--openstackclient/tests/functional/volume/v2/test_snapshot.py271
-rw-r--r--openstackclient/tests/functional/volume/v2/test_volume.py319
-rw-r--r--openstackclient/tests/functional/volume/v2/test_volume_type.py92
-rw-r--r--openstackclient/tests/functional/volume/v3/__init__.py0
-rw-r--r--openstackclient/tests/functional/volume/v3/common.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_qos.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_snapshot.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_transfer_request.py24
-rw-r--r--openstackclient/tests/functional/volume/v3/test_volume.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_volume_type.py23
-rw-r--r--openstackclient/tests/unit/api/test_image_v1.py2
-rw-r--r--openstackclient/tests/unit/api/test_image_v2.py2
-rw-r--r--openstackclient/tests/unit/common/test_module.py78
-rw-r--r--openstackclient/tests/unit/common/test_quota.py159
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py173
-rw-r--r--openstackclient/tests/unit/compute/v2/test_console.py28
-rw-r--r--openstackclient/tests/unit/compute/v2/test_flavor.py22
-rw-r--r--openstackclient/tests/unit/compute/v2/test_hypervisor.py67
-rw-r--r--openstackclient/tests/unit/compute/v2/test_keypair.py34
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py611
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_event.py167
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_group.py37
-rw-r--r--openstackclient/tests/unit/compute/v2/test_usage.py179
-rw-r--r--openstackclient/tests/unit/fakes.py12
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_project.py65
-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/fakes.py55
-rw-r--r--openstackclient/tests/unit/identity/v3/test_group.py170
-rw-r--r--openstackclient/tests/unit/identity/v3/test_mappings.py8
-rw-r--r--openstackclient/tests/unit/identity/v3/test_project.py140
-rw-r--r--openstackclient/tests/unit/identity/v3/test_role.py77
-rw-r--r--openstackclient/tests/unit/identity/v3/test_trust.py31
-rw-r--r--openstackclient/tests/unit/identity/v3/test_unscoped_saml.py26
-rw-r--r--openstackclient/tests/unit/identity/v3/test_user.py65
-rw-r--r--openstackclient/tests/unit/image/v1/fakes.py44
-rw-r--r--openstackclient/tests/unit/image/v1/test_image.py242
-rw-r--r--openstackclient/tests/unit/image/v2/test_image.py135
-rw-r--r--openstackclient/tests/unit/network/test_common.py14
-rw-r--r--openstackclient/tests/unit/network/test_sdk_utils.py59
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py592
-rw-r--r--openstackclient/tests/unit/network/v2/test_address_scope.py98
-rw-r--r--openstackclient/tests/unit/network/v2/test_floating_ip.py319
-rw-r--r--openstackclient/tests/unit/network/v2/test_ip_availability.py11
-rw-r--r--openstackclient/tests/unit/network/v2/test_network.py160
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_agent.py168
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py267
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_flavor.py407
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_flavor_profile.py448
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_meter.py304
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_meter_rule.py321
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_qos_policy.py433
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_qos_rule.py1049
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py62
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_rbac.py104
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_service_provider.py71
-rw-r--r--openstackclient/tests/unit/network/v2/test_port.py774
-rw-r--r--openstackclient/tests/unit/network/v2/test_router.py278
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group.py40
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_rule.py262
-rw-r--r--openstackclient/tests/unit/network/v2/test_subnet.py16
-rw-r--r--openstackclient/tests/unit/network/v2/test_subnet_pool.py8
-rw-r--r--openstackclient/tests/unit/test_shell.py2
-rw-r--r--openstackclient/tests/unit/volume/v1/fakes.py166
-rw-r--r--openstackclient/tests/unit/volume/v1/test_backup.py45
-rw-r--r--openstackclient/tests/unit/volume/v1/test_qos_specs.py266
-rw-r--r--openstackclient/tests/unit/volume/v1/test_snapshot.py159
-rw-r--r--openstackclient/tests/unit/volume/v1/test_type.py243
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume.py302
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py214
-rw-r--r--openstackclient/tests/unit/volume/v2/test_backup.py82
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group.py585
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py351
-rw-r--r--openstackclient/tests/unit/volume/v2/test_qos_specs.py28
-rw-r--r--openstackclient/tests/unit/volume/v2/test_snapshot.py281
-rw-r--r--openstackclient/tests/unit/volume/v2/test_type.py366
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py480
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume_host.py117
-rw-r--r--openstackclient/tests/unit/volume/v3/__init__.py0
-rw-r--r--openstackclient/volume/client.py3
-rw-r--r--openstackclient/volume/v1/backup.py63
-rw-r--r--openstackclient/volume/v1/qos_specs.py27
-rw-r--r--openstackclient/volume/v1/service.py4
-rw-r--r--openstackclient/volume/v1/snapshot.py30
-rw-r--r--openstackclient/volume/v1/volume.py161
-rw-r--r--openstackclient/volume/v1/volume_snapshot.py355
-rw-r--r--openstackclient/volume/v1/volume_transfer_request.py10
-rw-r--r--openstackclient/volume/v1/volume_type.py233
-rw-r--r--openstackclient/volume/v2/backup.py84
-rw-r--r--openstackclient/volume/v2/consistency_group.py284
-rw-r--r--openstackclient/volume/v2/consistency_group_snapshot.py190
-rw-r--r--openstackclient/volume/v2/qos_specs.py27
-rw-r--r--openstackclient/volume/v2/service.py4
-rw-r--r--openstackclient/volume/v2/snapshot.py30
-rw-r--r--openstackclient/volume/v2/volume.py183
-rw-r--r--openstackclient/volume/v2/volume_host.py75
-rw-r--r--openstackclient/volume/v2/volume_snapshot.py440
-rw-r--r--openstackclient/volume/v2/volume_transfer_request.py10
-rw-r--r--openstackclient/volume/v2/volume_type.py258
-rw-r--r--openstackclient/volume/v3/__init__.py0
233 files changed, 23912 insertions, 3129 deletions
diff --git a/openstackclient/api/image_v1.py b/openstackclient/api/image_v1.py
index 534c7750..e15d825a 100644
--- a/openstackclient/api/image_v1.py
+++ b/openstackclient/api/image_v1.py
@@ -19,7 +19,7 @@ from openstackclient.api import api
class APIv1(api.BaseAPI):
"""Image v1 API"""
- _endpoint_suffix = 'v1'
+ _endpoint_suffix = '/v1'
def __init__(self, endpoint=None, **kwargs):
super(APIv1, self).__init__(endpoint=endpoint, **kwargs)
@@ -29,8 +29,8 @@ class APIv1(api.BaseAPI):
def _munge_url(self):
# Hack this until discovery is up
- if self._endpoint_suffix not in self.endpoint.split('/')[-1]:
- self.endpoint = '/'.join([self.endpoint, self._endpoint_suffix])
+ if not self.endpoint.endswith(self._endpoint_suffix):
+ self.endpoint = self.endpoint + self._endpoint_suffix
def image_list(
self,
diff --git a/openstackclient/api/image_v2.py b/openstackclient/api/image_v2.py
index 026498fa..c3628121 100644
--- a/openstackclient/api/image_v2.py
+++ b/openstackclient/api/image_v2.py
@@ -19,12 +19,12 @@ from openstackclient.api import image_v1
class APIv2(image_v1.APIv1):
"""Image v2 API"""
- _endpoint_suffix = 'v2'
+ _endpoint_suffix = '/v2'
def _munge_url(self):
# Hack this until discovery is up, and ignore parent endpoint setting
- if 'v2' not in self.endpoint.split('/')[-1]:
- self.endpoint = '/'.join([self.endpoint, 'v2'])
+ if not self.endpoint.endswith(self._endpoint_suffix):
+ self.endpoint = self.endpoint + self._endpoint_suffix
def image_list(
self,
diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py
index 184814c6..74c4a46f 100644
--- a/openstackclient/api/object_store_v1.py
+++ b/openstackclient/api/object_store_v1.py
@@ -380,7 +380,7 @@ class APIv1(api.BaseAPI):
if len(os.path.dirname(file)) > 0:
os.makedirs(os.path.dirname(file))
with open(file, 'wb') as f:
- for chunk in response.iter_content():
+ for chunk in response.iter_content(64 * 1024):
f.write(chunk)
def object_set(
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py
index 63c55370..b2385ef7 100644
--- a/openstackclient/common/availability_zone.py
+++ b/openstackclient/common/availability_zone.py
@@ -88,7 +88,7 @@ def _xform_network_availability_zone(az):
class ListAvailabilityZone(command.Lister):
- """List availability zones and their status"""
+ _description = _("List availability zones and their status")
def get_parser(self, prog_name):
parser = super(ListAvailabilityZone, self).get_parser(prog_name)
diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py
index 30286df8..a22dd0cb 100644
--- a/openstackclient/common/client_config.py
+++ b/openstackclient/common/client_config.py
@@ -13,122 +13,15 @@
"""OpenStackConfig subclass for argument compatibility"""
-import logging
-
-from os_client_config import config
-from os_client_config import exceptions as occ_exceptions
-
-
-LOG = logging.getLogger(__name__)
+from osc_lib.cli import client_config
# Sublcass OpenStackConfig in order to munge config values
# before auth plugins are loaded
-class OSC_Config(config.OpenStackConfig):
-
- # TODO(dtroyer): Once os-client-config with pw_func argument is in
- # global-requirements we can remove __init()__
- def __init__(
- self,
- config_files=None,
- vendor_files=None,
- override_defaults=None,
- force_ipv4=None,
- envvar_prefix=None,
- secure_files=None,
- pw_func=None,
- ):
- ret = super(OSC_Config, self).__init__(
- config_files=config_files,
- vendor_files=vendor_files,
- override_defaults=override_defaults,
- force_ipv4=force_ipv4,
- envvar_prefix=envvar_prefix,
- secure_files=secure_files,
- )
-
- # NOTE(dtroyer): This will be pushed down into os-client-config
- # The default is there is no callback, the calling
- # application must specify what to use, typically
- # it will be osc_lib.shell.prompt_for_password()
- if '_pw_callback' not in vars(self):
- # Set the default if it doesn't already exist
- self._pw_callback = None
- if pw_func is not None:
- # Set the passed in value
- self._pw_callback = pw_func
-
- return ret
-
- def _auth_select_default_plugin(self, config):
- """Select a default plugin based on supplied arguments
-
- Migrated from auth.select_auth_plugin()
- """
-
- identity_version = config.get('identity_api_version', '')
-
- if config.get('username', None) and not config.get('auth_type', None):
- if identity_version == '3':
- config['auth_type'] = 'v3password'
- elif identity_version.startswith('2'):
- config['auth_type'] = 'v2password'
- else:
- # let keystoneauth figure it out itself
- config['auth_type'] = 'password'
- elif config.get('token', None) and not config.get('auth_type', None):
- if identity_version == '3':
- config['auth_type'] = 'v3token'
- elif identity_version.startswith('2'):
- config['auth_type'] = 'v2token'
- else:
- # let keystoneauth figure it out itself
- config['auth_type'] = 'token'
- else:
- # The ultimate default is similar to the original behaviour,
- # but this time with version discovery
- if not config.get('auth_type', None):
- config['auth_type'] = 'password'
-
- LOG.debug("Auth plugin %s selected" % config['auth_type'])
- return config
-
- def _auth_v2_arguments(self, config):
- """Set up v2-required arguments from v3 info
-
- Migrated from auth.build_auth_params()
- """
-
- if ('auth_type' in config and config['auth_type'].startswith("v2")):
- if 'project_id' in config['auth']:
- config['auth']['tenant_id'] = config['auth']['project_id']
- if 'project_name' in config['auth']:
- config['auth']['tenant_name'] = config['auth']['project_name']
- return config
-
- def _auth_v2_ignore_v3(self, config):
- """Remove v3 arguemnts if present for v2 plugin
-
- Migrated from clientmanager.setup_auth()
- """
-
- # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID
- # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then
- # ignore all domain related configs.
- if (config.get('identity_api_version', '').startswith('2') and
- config.get('auth_type', None).endswith('password')):
- domain_props = [
- 'project_domain_id',
- 'project_domain_name',
- 'user_domain_id',
- 'user_domain_name',
- ]
- for prop in domain_props:
- if config['auth'].pop(prop, None) is not None:
- LOG.warning("Ignoring domain related config " +
- prop + " because identity API version is 2.0")
- return config
+class OSC_Config(client_config.OSC_Config):
+ # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is
+ # backported to osc-lib, should be in release 1.3.0
def _auth_default_domain(self, config):
"""Set a default domain from available arguments
@@ -149,6 +42,7 @@ class OSC_Config(config.OpenStackConfig):
# present, then do not change the behaviour. Otherwise, set the
# PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
if (
+ auth_type in ("password", "v3password", "v3totp") and
not config['auth'].get('project_domain_id') and
not config['auth'].get('project_domain_name')
):
@@ -168,21 +62,6 @@ class OSC_Config(config.OpenStackConfig):
config['auth']['user_domain_id'] = default_domain
return config
- def auth_config_hook(self, config):
- """Allow examination of config values before loading auth plugin
-
- OpenStackClient will override this to perform additional chacks
- on auth_type.
- """
-
- config = self._auth_select_default_plugin(config)
- config = self._auth_v2_arguments(config)
- config = self._auth_v2_ignore_v3(config)
- config = self._auth_default_domain(config)
-
- LOG.debug("auth_config_hook(): %s" % config)
- return config
-
def load_auth_plugin(self, config):
"""Get auth plugin and validate args"""
@@ -190,68 +69,3 @@ class OSC_Config(config.OpenStackConfig):
config = self._validate_auth(config, loader)
auth_plugin = loader.load_from_options(**config['auth'])
return auth_plugin
-
- def _validate_auth_ksc(self, config, cloud, fixed_argparse=None):
- """Old compatibility hack for OSC, no longer needed/wanted"""
- return config
-
- def _validate_auth(self, config, loader, fixed_argparse=None):
- """Validate auth plugin arguments"""
- # May throw a keystoneauth1.exceptions.NoMatchingPlugin
-
- plugin_options = loader.get_options()
-
- msgs = []
- prompt_options = []
- for p_opt in plugin_options:
- # if it's in config, win, move it and kill it from config dict
- # if it's in config.auth but not in config we're good
- # deprecated loses to current
- # provided beats default, deprecated or not
- winning_value = self._find_winning_auth_value(p_opt, config)
- if not winning_value:
- winning_value = self._find_winning_auth_value(
- p_opt, config['auth'])
-
- # if the plugin tells us that this value is required
- # then error if it's doesn't exist now
- if not winning_value and p_opt.required:
- msgs.append(
- 'Missing value {auth_key}'
- ' required for auth plugin {plugin}'.format(
- auth_key=p_opt.name, plugin=config.get('auth_type'),
- )
- )
-
- # Clean up after ourselves
- for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]:
- opt = opt.replace('-', '_')
- config.pop(opt, None)
- config['auth'].pop(opt, None)
-
- if winning_value:
- # Prefer the plugin configuration dest value if the value's key
- # is marked as depreciated.
- if p_opt.dest is None:
- config['auth'][p_opt.name.replace('-', '_')] = (
- winning_value)
- else:
- config['auth'][p_opt.dest] = winning_value
-
- # See if this needs a prompting
- if (
- 'prompt' in vars(p_opt) and
- p_opt.prompt is not None and
- p_opt.dest not in config['auth'] and
- self._pw_callback is not None
- ):
- # Defer these until we know all required opts are present
- prompt_options.append(p_opt)
-
- if msgs:
- raise occ_exceptions.OpenStackConfigException('\n'.join(msgs))
- else:
- for p_opt in prompt_options:
- config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt)
-
- return config
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 23c35a3b..3e1a50e3 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -59,6 +59,8 @@ class ClientManager(clientmanager.ClientManager):
self._interface = self.interface
self._cacert = self.cacert
self._insecure = not self.verify
+ # store original auth_type
+ self._original_auth_type = cli_options.auth_type
def setup_auth(self):
"""Set up authentication"""
@@ -73,12 +75,33 @@ class ClientManager(clientmanager.ClientManager):
if self._cli_options._openstack_config is not None:
self._cli_options._openstack_config._pw_callback = \
shell.prompt_for_password
+ try:
+ self._cli_options._auth = \
+ self._cli_options._openstack_config.load_auth_plugin(
+ self._cli_options.config,
+ )
+ except TypeError as e:
+ self._fallback_load_auth_plugin(e)
+
+ return super(ClientManager, self).setup_auth()
+
+ def _fallback_load_auth_plugin(self, e):
+ # NOTES(RuiChen): Hack to avoid auth plugins choking on data they don't
+ # expect, delete fake token and endpoint, then try to
+ # load auth plugin again with user specified options.
+ # We know it looks ugly, but it's necessary.
+ if self._cli_options.config['auth']['token'] == 'x':
+ # restore original auth_type
+ self._cli_options.config['auth_type'] = \
+ self._original_auth_type
+ del self._cli_options.config['auth']['token']
+ del self._cli_options.config['auth']['endpoint']
self._cli_options._auth = \
self._cli_options._openstack_config.load_auth_plugin(
self._cli_options.config,
)
-
- return super(ClientManager, self).setup_auth()
+ else:
+ raise e
def is_network_endpoint_enabled(self):
"""Check if the network endpoint is enabled"""
diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py
index 016e9191..57825bb0 100644
--- a/openstackclient/common/configuration.py
+++ b/openstackclient/common/configuration.py
@@ -23,7 +23,7 @@ REDACTED = "<redacted>"
class ShowConfiguration(command.ShowOne):
- """Display configuration details"""
+ _description = _("Display configuration details")
def get_parser(self, prog_name):
parser = super(ShowConfiguration, self).get_parser(prog_name)
diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
index 07c407f6..d5b72238 100644
--- a/openstackclient/common/extension.py
+++ b/openstackclient/common/extension.py
@@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__)
class ListExtension(command.Lister):
- """List API extensions"""
+ _description = _("List API extensions")
def get_parser(self, prog_name):
parser = super(ListExtension, self).get_parser(prog_name)
diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py
index f7aa82f6..957f1d02 100644
--- a/openstackclient/common/limits.py
+++ b/openstackclient/common/limits.py
@@ -25,7 +25,7 @@ from openstackclient.identity import common as identity_common
class ShowLimits(command.Lister):
- """Show compute and block storage limits"""
+ _description = _("Show compute and block storage limits")
def get_parser(self, prog_name):
parser = super(ShowLimits, self).get_parser(prog_name)
diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py
index 7c5fcd55..20497f21 100644
--- a/openstackclient/common/module.py
+++ b/openstackclient/common/module.py
@@ -25,16 +25,30 @@ from openstackclient.i18n import _
class ListCommand(command.Lister):
- """List recognized commands by group"""
+ _description = _("List recognized commands by group")
auth_required = False
+ def get_parser(self, prog_name):
+ parser = super(ListCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ '--group',
+ metavar='<group-keyword>',
+ help=_('Show commands filtered by a command group, for example: '
+ 'identity, volume, compute, image, network and '
+ 'other keywords'),
+ )
+ return parser
+
def take_action(self, parsed_args):
cm = self.app.command_manager
groups = cm.get_command_groups()
groups = sorted(groups)
columns = ('Command Group', 'Commands')
+ if parsed_args.group:
+ groups = (group for group in groups if parsed_args.group in group)
+
commands = []
for group in groups:
command_names = cm.get_command_names(group)
@@ -53,7 +67,7 @@ class ListCommand(command.Lister):
class ListModule(command.ShowOne):
- """List module versions"""
+ _description = _("List module versions")
auth_required = False
@@ -74,15 +88,29 @@ class ListModule(command.ShowOne):
mods = sys.modules
for k in mods.keys():
k = k.split('.')[0]
- # TODO(dtroyer): Need a better way to decide which modules to
- # show for the default (not --all) invocation.
- # It should be just the things we actually care
- # about like client and plugin modules...
- if (parsed_args.all or 'client' in k):
- try:
- data[k] = mods[k].__version__
- except AttributeError:
- # aw, just skip it
- pass
+ # Skip private modules and the modules that had been added,
+ # like: keystoneclient, keystoneclient.exceptions and
+ # keystoneclient.auth
+ if not k.startswith('_') and k not in data:
+ # TODO(dtroyer): Need a better way to decide which modules to
+ # show for the default (not --all) invocation.
+ # It should be just the things we actually care
+ # about like client and plugin modules...
+ if (parsed_args.all or
+ # Handle xxxclient and openstacksdk
+ (k.endswith('client') or k == 'openstack')):
+ try:
+ # NOTE(RuiChen): openstacksdk bug/1588823 exist,
+ # no good way to add __version__ for
+ # openstack module properly, hard code
+ # looks bad, but openstacksdk module
+ # information is important.
+ if k == 'openstack':
+ data[k] = mods[k].version.__version__
+ else:
+ data[k] = mods[k].__version__
+ except Exception:
+ # Catch all exceptions, just skip it
+ pass
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 8f099cc9..ec4c8b51 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -16,6 +16,7 @@
"""Quota action implementations"""
import itertools
+import logging
import sys
from osc_lib.command import command
@@ -25,6 +26,8 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
# List the quota items, map the internal argument name to the option
# name that the user sees.
@@ -78,9 +81,179 @@ NETWORK_QUOTAS = {
'l7policy': 'l7policies',
}
+NETWORK_KEYS = ['floating_ips', 'networks', 'rbac_policies', 'routers',
+ 'ports', 'security_group_rules', 'security_groups',
+ 'subnet_pools', 'subnets']
+
+
+def _xform_get_quota(data, value, keys):
+ res = []
+ res_info = {}
+ for key in keys:
+ res_info[key] = getattr(data, key, '')
+
+ res_info['id'] = value
+ res.append(res_info)
+ return res
+
+
+class ListQuota(command.Lister):
+ _description = _("List quotas for all projects "
+ "with non-default quota values")
+
+ def get_parser(self, prog_name):
+ parser = super(ListQuota, self).get_parser(prog_name)
+ option = parser.add_mutually_exclusive_group(required=True)
+ option.add_argument(
+ '--compute',
+ action='store_true',
+ default=False,
+ help=_('List compute quota'),
+ )
+ option.add_argument(
+ '--volume',
+ action='store_true',
+ default=False,
+ help=_('List volume quota'),
+ )
+ option.add_argument(
+ '--network',
+ action='store_true',
+ default=False,
+ help=_('List network quota'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ projects = self.app.client_manager.identity.projects.list()
+ result = []
+ project_ids = [getattr(p, 'id', '') for p in projects]
+
+ if parsed_args.compute:
+ compute_client = self.app.client_manager.compute
+ for p in project_ids:
+ data = compute_client.quotas.get(p)
+ result_data = _xform_get_quota(data, p,
+ COMPUTE_QUOTAS.keys())
+ default_data = compute_client.quotas.defaults(p)
+ result_default = _xform_get_quota(default_data,
+ p,
+ COMPUTE_QUOTAS.keys())
+ if result_default != result_data:
+ result += result_data
+
+ columns = (
+ 'id',
+ 'cores',
+ 'fixed_ips',
+ 'injected_files',
+ 'injected_file_content_bytes',
+ 'injected_file_path_bytes',
+ 'instances',
+ 'key_pairs',
+ 'metadata_items',
+ 'ram',
+ 'server_groups',
+ 'server_group_members',
+ )
+ column_headers = (
+ 'Project ID',
+ 'Cores',
+ 'Fixed IPs',
+ 'Injected Files',
+ 'Injected File Content Bytes',
+ 'Injected File Path Bytes',
+ 'Instances',
+ 'Key Pairs',
+ 'Metadata Items',
+ 'Ram',
+ 'Server Groups',
+ 'Server Group Members',
+ )
+ return (column_headers,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in result))
+ if parsed_args.volume:
+ volume_client = self.app.client_manager.volume
+ for p in project_ids:
+ data = volume_client.quotas.get(p)
+ result_data = _xform_get_quota(data, p,
+ VOLUME_QUOTAS.keys())
+ default_data = volume_client.quotas.defaults(p)
+ result_default = _xform_get_quota(default_data,
+ p,
+ VOLUME_QUOTAS.keys())
+ if result_default != result_data:
+ result += result_data
+
+ columns = (
+ 'id',
+ 'backups',
+ 'backup_gigabytes',
+ 'gigabytes',
+ 'per_volume_gigabytes',
+ 'snapshots',
+ 'volumes',
+ )
+ column_headers = (
+ 'Project ID',
+ 'Backups',
+ 'Backup Gigabytes',
+ 'Gigabytes',
+ 'Per Volume Gigabytes',
+ 'Snapshots',
+ 'Volumes',
+ )
+ return (column_headers,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in result))
+ if parsed_args.network:
+ client = self.app.client_manager.network
+ for p in project_ids:
+ data = client.get_quota(p)
+ result_data = _xform_get_quota(data, p, NETWORK_KEYS)
+ default_data = client.get_quota_default(p)
+ result_default = _xform_get_quota(default_data,
+ p, NETWORK_KEYS)
+ if result_default != result_data:
+ result += result_data
+
+ columns = (
+ 'id',
+ 'floating_ips',
+ 'networks',
+ 'ports',
+ 'rbac_policies',
+ 'routers',
+ 'security_groups',
+ 'security_group_rules',
+ 'subnets',
+ 'subnet_pools',
+ )
+ column_headers = (
+ 'Project ID',
+ 'Floating IPs',
+ 'Networks',
+ 'Ports',
+ 'RBAC Policies',
+ 'Routers',
+ 'Security Groups',
+ 'Security Group Rules',
+ 'Subnets',
+ 'Subnet Pools'
+ )
+ return (column_headers,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in result))
+
+ return ((), ())
+
class SetQuota(command.Command):
- """Set quotas for project or class"""
+ _description = _("Set quotas for project or class")
def _build_options_list(self):
if self.app.client_manager.is_network_endpoint_enabled():
@@ -165,7 +338,7 @@ class SetQuota(command.Command):
**volume_kwargs)
if network_kwargs:
sys.stderr.write("Network quotas are ignored since quota class"
- "is not supported.")
+ " is not supported.")
else:
project = utils.find_resource(
identity_client.projects,
@@ -186,7 +359,7 @@ class SetQuota(command.Command):
class ShowQuota(command.ShowOne):
- """Show quotas for project or class"""
+ _description = _("Show quotas for project or class")
def get_parser(self, prog_name):
parser = super(ShowQuota, self).get_parser(prog_name)
@@ -273,6 +446,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/agent.py b/openstackclient/compute/v2/agent.py
index 76c1b3b7..151dcc1e 100644
--- a/openstackclient/compute/v2/agent.py
+++ b/openstackclient/compute/v2/agent.py
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class CreateAgent(command.ShowOne):
- """Create compute agent"""
+ _description = _("Create compute agent")
def get_parser(self, prog_name):
parser = super(CreateAgent, self).get_parser(prog_name)
@@ -81,7 +81,7 @@ class CreateAgent(command.ShowOne):
class DeleteAgent(command.Command):
- """Delete compute agent(s)"""
+ _description = _("Delete compute agent(s)")
def get_parser(self, prog_name):
parser = super(DeleteAgent, self).get_parser(prog_name)
@@ -112,7 +112,7 @@ class DeleteAgent(command.Command):
class ListAgent(command.Lister):
- """List compute agents"""
+ _description = _("List compute agents")
def get_parser(self, prog_name):
parser = super(ListAgent, self).get_parser(prog_name)
@@ -142,7 +142,7 @@ class ListAgent(command.Lister):
class SetAgent(command.Command):
- """Set compute agent properties"""
+ _description = _("Set compute agent properties")
def get_parser(self, prog_name):
parser = super(SetAgent, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
index 58d529e9..7f9161a9 100644
--- a/openstackclient/compute/v2/aggregate.py
+++ b/openstackclient/compute/v2/aggregate.py
@@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
class AddAggregateHost(command.ShowOne):
- """Add host to aggregate"""
+ _description = _("Add host to aggregate")
def get_parser(self, prog_name):
parser = super(AddAggregateHost, self).get_parser(prog_name)
@@ -62,7 +62,7 @@ class AddAggregateHost(command.ShowOne):
class CreateAggregate(command.ShowOne):
- """Create a new aggregate"""
+ _description = _("Create a new aggregate")
def get_parser(self, prog_name):
parser = super(CreateAggregate, self).get_parser(prog_name)
@@ -105,7 +105,7 @@ class CreateAggregate(command.ShowOne):
class DeleteAggregate(command.Command):
- """Delete existing aggregate(s)"""
+ _description = _("Delete existing aggregate(s)")
def get_parser(self, prog_name):
parser = super(DeleteAggregate, self).get_parser(prog_name)
@@ -128,8 +128,8 @@ class DeleteAggregate(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete aggregate with name or "
- "ID '%(aggregate)s': %(e)s")
- % {'aggregate': a, 'e': e})
+ "ID '%(aggregate)s': %(e)s"),
+ {'aggregate': a, 'e': e})
if result > 0:
total = len(parsed_args.aggregate)
@@ -139,7 +139,7 @@ class DeleteAggregate(command.Command):
class ListAggregate(command.Lister):
- """List all aggregates"""
+ _description = _("List all aggregates")
def get_parser(self, prog_name):
parser = super(ListAggregate, self).get_parser(prog_name)
@@ -188,7 +188,7 @@ class ListAggregate(command.Lister):
class RemoveAggregateHost(command.ShowOne):
- """Remove host from aggregate"""
+ _description = _("Remove host from aggregate")
def get_parser(self, prog_name):
parser = super(RemoveAggregateHost, self).get_parser(prog_name)
@@ -222,7 +222,7 @@ class RemoveAggregateHost(command.ShowOne):
class SetAggregate(command.Command):
- """Set aggregate properties"""
+ _description = _("Set aggregate properties")
def get_parser(self, prog_name):
parser = super(SetAggregate, self).get_parser(prog_name)
@@ -298,7 +298,7 @@ class SetAggregate(command.Command):
class ShowAggregate(command.ShowOne):
- """Display aggregate details"""
+ _description = _("Display aggregate details")
def get_parser(self, prog_name):
parser = super(ShowAggregate, self).get_parser(prog_name)
@@ -334,7 +334,7 @@ class ShowAggregate(command.ShowOne):
class UnsetAggregate(command.Command):
- """Unset aggregate properties"""
+ _description = _("Unset aggregate properties")
def get_parser(self, prog_name):
parser = super(UnsetAggregate, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index 02be99d5..25f92108 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -26,7 +26,7 @@ from openstackclient.i18n import _
class ShowConsoleLog(command.Command):
- """Show server's console output"""
+ _description = _("Show server's console output")
def get_parser(self, prog_name):
parser = super(ShowConsoleLog, self).get_parser(prog_name)
@@ -64,7 +64,7 @@ class ShowConsoleLog(command.Command):
class ShowConsoleURL(command.ShowOne):
- """Show server's remote console URL"""
+ _description = _("Show server's remote console URL")
def get_parser(self, prog_name):
parser = super(ShowConsoleURL, self).get_parser(prog_name)
@@ -126,18 +126,7 @@ class ShowConsoleURL(command.ShowOne):
parsed_args.server,
)
- data = None
- if parsed_args.url_type in ['novnc', 'xvpvnc']:
- data = server.get_vnc_console(parsed_args.url_type)
- if parsed_args.url_type in ['spice-html5']:
- data = server.get_spice_console(parsed_args.url_type)
- if parsed_args.url_type in ['rdp-html5']:
- data = server.get_rdp_console(parsed_args.url_type)
- if parsed_args.url_type in ['serial']:
- data = server.get_serial_console(parsed_args.url_type)
- if parsed_args.url_type in ['webmks']:
- data = server.get_mks_console()
-
+ data = server.get_console_url(parsed_args.url_type)
if not data:
return ({}, {})
diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py
index c14d29fa..0c0b619e 100644
--- a/openstackclient/compute/v2/fixedip.py
+++ b/openstackclient/compute/v2/fixedip.py
@@ -24,7 +24,7 @@ from openstackclient.i18n import _
class AddFixedIP(command.Command):
- """Add fixed IP address to server"""
+ _description = _("Add fixed IP address to server")
# TODO(tangchen): Remove this class and ``ip fixed add`` command
# two cycles after Mitaka.
@@ -64,7 +64,7 @@ class AddFixedIP(command.Command):
class RemoveFixedIP(command.Command):
- """Remove fixed IP address from server"""
+ _description = _("Remove fixed IP address from server")
# TODO(tangchen): Remove this class and ``ip fixed remove`` command
# two cycles after Mitaka.
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index b3f09ce5..009c9bd1 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -49,7 +49,7 @@ def _find_flavor(compute_client, flavor):
class CreateFlavor(command.ShowOne):
- """Create new flavor"""
+ _description = _("Create new flavor")
def get_parser(self, prog_name):
parser = super(CreateFlavor, self).get_parser(prog_name)
@@ -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",
@@ -167,11 +167,11 @@ class CreateFlavor(command.ShowOne):
parsed_args.project_domain,
).id
compute_client.flavor_access.add_tenant_access(
- parsed_args.id, project_id)
+ flavor.id, project_id)
except Exception as e:
msg = _("Failed to add project %(project)s access to "
"flavor: %(e)s")
- LOG.error(msg % {'project': parsed_args.project, 'e': e})
+ LOG.error(msg, {'project': parsed_args.project, 'e': e})
if parsed_args.property:
try:
flavor.set_keys(parsed_args.property)
@@ -186,7 +186,7 @@ class CreateFlavor(command.ShowOne):
class DeleteFlavor(command.Command):
- """Delete flavor(s)"""
+ _description = _("Delete flavor(s)")
def get_parser(self, prog_name):
parser = super(DeleteFlavor, self).get_parser(prog_name)
@@ -208,8 +208,7 @@ class DeleteFlavor(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete flavor with name or "
- "ID '%(flavor)s': %(e)s")
- % {'flavor': f, 'e': e})
+ "ID '%(flavor)s': %(e)s"), {'flavor': f, 'e': e})
if result > 0:
total = len(parsed_args.flavor)
@@ -219,7 +218,7 @@ class DeleteFlavor(command.Command):
class ListFlavor(command.Lister):
- """List flavors"""
+ _description = _("List flavors")
def get_parser(self, prog_name):
parser = super(ListFlavor, self).get_parser(prog_name)
@@ -303,7 +302,7 @@ class ListFlavor(command.Lister):
class SetFlavor(command.Command):
- """Set flavor properties"""
+ _description = _("Set flavor properties")
def get_parser(self, prog_name):
parser = super(SetFlavor, self).get_parser(prog_name)
@@ -313,6 +312,14 @@ class SetFlavor(command.Command):
help=_("Flavor to modify (name or ID)")
)
parser.add_argument(
+ "--no-property",
+ action="store_true",
+ help=_("Remove all properties from this flavor "
+ "(specify both --no-property and --property"
+ " to remove the current properties before setting"
+ " new properties.)"),
+ )
+ parser.add_argument(
"--property",
metavar="<key=value>",
action=parseractions.KeyValueAction,
@@ -336,6 +343,15 @@ class SetFlavor(command.Command):
flavor = _find_flavor(compute_client, parsed_args.flavor)
result = 0
+ key_list = []
+ if parsed_args.no_property:
+ try:
+ for key in flavor.get_keys().keys():
+ key_list.append(key)
+ flavor.unset_keys(key_list)
+ except Exception as e:
+ LOG.error(_("Failed to clear flavor property: %s"), e)
+ result += 1
if parsed_args.property:
try:
flavor.set_keys(parsed_args.property)
@@ -366,7 +382,7 @@ class SetFlavor(command.Command):
class ShowFlavor(command.ShowOne):
- """Display flavor details"""
+ _description = _("Display flavor details")
def get_parser(self, prog_name):
parser = super(ShowFlavor, self).get_parser(prog_name)
@@ -395,7 +411,7 @@ class ShowFlavor(command.ShowOne):
except Exception as e:
msg = _("Failed to get access projects list "
"for flavor '%(flavor)s': %(e)s")
- LOG.error(msg % {'flavor': parsed_args.flavor, 'e': e})
+ LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e})
flavor = resource_flavor._info.copy()
flavor.update({
@@ -409,7 +425,7 @@ class ShowFlavor(command.ShowOne):
class UnsetFlavor(command.Command):
- """Unset flavor properties"""
+ _description = _("Unset flavor properties")
def get_parser(self, prog_name):
parser = super(UnsetFlavor, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py
index 8398ea57..69595bed 100644
--- a/openstackclient/compute/v2/floatingip.py
+++ b/openstackclient/compute/v2/floatingip.py
@@ -24,7 +24,7 @@ from openstackclient.i18n import _
class AddFloatingIP(command.Command):
- """Add floating IP address to server"""
+ _description = _("Add floating IP address to server")
# TODO(tangchen): Remove this class and ``ip floating add`` command
# two cycles after Mitaka.
@@ -61,7 +61,7 @@ class AddFloatingIP(command.Command):
class RemoveFloatingIP(command.Command):
- """Remove floating IP address from server"""
+ _description = _("Remove floating IP address from server")
# TODO(tangchen): Remove this class and ``ip floating remove`` command
# two cycles after Mitaka.
diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py
index 4785377e..a495b367 100644
--- a/openstackclient/compute/v2/host.py
+++ b/openstackclient/compute/v2/host.py
@@ -22,7 +22,7 @@ from openstackclient.i18n import _
class ListHost(command.Lister):
- """List hosts"""
+ _description = _("List hosts")
def get_parser(self, prog_name):
parser = super(ListHost, self).get_parser(prog_name)
@@ -48,7 +48,8 @@ class ListHost(command.Lister):
class SetHost(command.Command):
- """Set host properties"""
+ _description = _("Set host properties")
+
def get_parser(self, prog_name):
parser = super(SetHost, self).get_parser(prog_name)
parser.add_argument(
@@ -107,7 +108,7 @@ class SetHost(command.Command):
class ShowHost(command.Lister):
- """Display host details"""
+ _description = _("Display host details")
def get_parser(self, prog_name):
parser = super(ShowHost, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py
index 0222e899..406aa917 100644
--- a/openstackclient/compute/v2/hypervisor.py
+++ b/openstackclient/compute/v2/hypervisor.py
@@ -26,7 +26,7 @@ from openstackclient.i18n import _
class ListHypervisor(command.Lister):
- """List hypervisors"""
+ _description = _("List hypervisors")
def get_parser(self, prog_name):
parser = super(ListHypervisor, self).get_parser(prog_name)
@@ -35,14 +35,24 @@ class ListHypervisor(command.Lister):
metavar="<hostname>",
help=_("Filter hypervisors using <hostname> substring")
)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ help=_("List additional fields in output")
+ )
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
columns = (
"ID",
- "Hypervisor Hostname"
+ "Hypervisor Hostname",
+ "Hypervisor Type",
+ "Host IP",
+ "State"
)
+ if parsed_args.long:
+ columns += ("vCPUs Used", "vCPUs", "Memory MB Used", "Memory MB")
if parsed_args.matching:
data = compute_client.hypervisors.search(parsed_args.matching)
@@ -56,7 +66,7 @@ class ListHypervisor(command.Lister):
class ShowHypervisor(command.ShowOne):
- """Display hypervisor details"""
+ _description = _("Display hypervisor details")
def get_parser(self, prog_name):
parser = super(ShowHypervisor, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py
index c6fd2992..b0413005 100644
--- a/openstackclient/compute/v2/hypervisor_stats.py
+++ b/openstackclient/compute/v2/hypervisor_stats.py
@@ -17,9 +17,11 @@
from osc_lib.command import command
import six
+from openstackclient.i18n import _
+
class ShowHypervisorStats(command.ShowOne):
- """Display hypervisor stats details"""
+ _description = _("Display hypervisor stats details")
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py
index d30fd429..851cced0 100644
--- a/openstackclient/compute/v2/keypair.py
+++ b/openstackclient/compute/v2/keypair.py
@@ -32,19 +32,27 @@ LOG = logging.getLogger(__name__)
class CreateKeypair(command.ShowOne):
- """Create new public key"""
+ _description = _("Create new public or private key for server ssh access")
def get_parser(self, prog_name):
parser = super(CreateKeypair, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<name>',
- help=_("New public key name")
+ help=_("New public or private key name")
)
- parser.add_argument(
+ key_group = parser.add_mutually_exclusive_group()
+ key_group.add_argument(
'--public-key',
metavar='<file>',
- help=_("Filename for public key to add")
+ help=_("Filename for public key to add. If not used, "
+ "creates a private key.")
+ )
+ key_group.add_argument(
+ '--private-key',
+ metavar='<file>',
+ help=_("Filename for private key to save. If not used, "
+ "print private key in console.")
)
return parser
@@ -68,13 +76,31 @@ class CreateKeypair(command.ShowOne):
public_key=public_key,
)
+ private_key = parsed_args.private_key
+ # Save private key into specified file
+ if private_key:
+ try:
+ with io.open(
+ os.path.expanduser(parsed_args.private_key), 'w+'
+ ) as p:
+ p.write(keypair.private_key)
+ except IOError as e:
+ msg = _("Key file %(private_key)s can not be saved: "
+ "%(exception)s")
+ raise exceptions.CommandError(
+ msg % {"private_key": parsed_args.private_key,
+ "exception": e}
+ )
# NOTE(dtroyer): how do we want to handle the display of the private
# key when it needs to be communicated back to the user
# For now, duplicate nova keypair-add command output
info = {}
- if public_key:
+ if public_key or private_key:
info.update(keypair._info)
- del info['public_key']
+ if 'public_key' in info:
+ del info['public_key']
+ if 'private_key' in info:
+ del info['private_key']
return zip(*sorted(six.iteritems(info)))
else:
sys.stdout.write(keypair.private_key)
@@ -82,7 +108,7 @@ class CreateKeypair(command.ShowOne):
class DeleteKeypair(command.Command):
- """Delete public key(s)"""
+ _description = _("Delete public or private key(s)")
def get_parser(self, prog_name):
parser = super(DeleteKeypair, self).get_parser(prog_name)
@@ -90,7 +116,7 @@ class DeleteKeypair(command.Command):
'name',
metavar='<key>',
nargs='+',
- help=_("Public key(s) to delete (name only)")
+ help=_("Name of key(s) to delete (name only)")
)
return parser
@@ -104,19 +130,18 @@ class DeleteKeypair(command.Command):
compute_client.keypairs.delete(data.name)
except Exception as e:
result += 1
- LOG.error(_("Failed to delete public key with name "
- "'%(name)s': %(e)s")
- % {'name': n, 'e': e})
+ LOG.error(_("Failed to delete key with name "
+ "'%(name)s': %(e)s"), {'name': n, 'e': e})
if result > 0:
total = len(parsed_args.name)
- msg = (_("%(result)s of %(total)s public keys failed "
+ msg = (_("%(result)s of %(total)s keys failed "
"to delete.") % {'result': result, 'total': total})
raise exceptions.CommandError(msg)
class ListKeypair(command.Lister):
- """List public key fingerprints"""
+ _description = _("List key fingerprints")
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
@@ -133,20 +158,20 @@ class ListKeypair(command.Lister):
class ShowKeypair(command.ShowOne):
- """Display public key details"""
+ _description = _("Display key details")
def get_parser(self, prog_name):
parser = super(ShowKeypair, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<key>',
- help=_("Public key to display (name only)")
+ help=_("Public or private key to display (name only)")
)
parser.add_argument(
'--public-key',
action='store_true',
default=False,
- help=_("Show only bare public key (name only)")
+ help=_("Show only bare public key paired with the generated key")
)
return parser
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 1ca31497..1fe5bb0d 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -26,6 +26,7 @@ from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
+from oslo_utils import timeutils
import six
try:
@@ -113,7 +114,7 @@ def _get_ip_address(addresses, address_type, ip_address_family):
)
-def _prep_server_detail(compute_client, server):
+def _prep_server_detail(compute_client, image_client, server):
"""Prepare the detailed server dict for printing
:param compute_client: a compute client instance
@@ -130,7 +131,7 @@ def _prep_server_detail(compute_client, server):
if image_info:
image_id = image_info.get('id', '')
try:
- image = utils.find_resource(compute_client.images, image_id)
+ image = utils.find_resource(image_client.images, image_id)
info['image'] = "%s (%s)" % (image.name, image_id)
except Exception:
info['image'] = image_id
@@ -144,6 +145,20 @@ def _prep_server_detail(compute_client, server):
except Exception:
info['flavor'] = flavor_id
+ if 'os-extended-volumes:volumes_attached' in info:
+ info.update(
+ {
+ 'volumes_attached': utils.format_list_of_dicts(
+ info.pop('os-extended-volumes:volumes_attached'))
+ }
+ )
+ if 'security_groups' in info:
+ info.update(
+ {
+ 'security_groups': utils.format_list_of_dicts(
+ info.pop('security_groups'))
+ }
+ )
# NOTE(dtroyer): novaclient splits these into separate entries...
# Format addresses in a useful way
info['addresses'] = _format_servers_list_networks(server.networks)
@@ -175,7 +190,7 @@ def _show_progress(progress):
class AddFixedIP(command.Command):
- """Add fixed IP address to server"""
+ _description = _("Add fixed IP address to server")
def get_parser(self, prog_name):
parser = super(AddFixedIP, self).get_parser(prog_name)
@@ -205,7 +220,7 @@ class AddFixedIP(command.Command):
class AddFloatingIP(command.Command):
- """Add floating IP address to server"""
+ _description = _("Add floating IP address to server")
def get_parser(self, prog_name):
parser = super(AddFloatingIP, self).get_parser(prog_name)
@@ -220,6 +235,12 @@ class AddFloatingIP(command.Command):
help=_("Floating IP address (IP address only) to assign "
"to server"),
)
+ parser.add_argument(
+ "--fixed-ip-address",
+ metavar="<fixed-ip-address>",
+ help=_("Fixed IP address to associate with this floating IP "
+ "address"),
+ )
return parser
def take_action(self, parsed_args):
@@ -228,11 +249,12 @@ class AddFloatingIP(command.Command):
server = utils.find_resource(
compute_client.servers, parsed_args.server)
- server.add_floating_ip(parsed_args.ip_address)
+ server.add_floating_ip(parsed_args.ip_address,
+ parsed_args.fixed_ip_address)
class AddServerSecurityGroup(command.Command):
- """Add security group to server"""
+ _description = _("Add security group to server")
def get_parser(self, prog_name):
parser = super(AddServerSecurityGroup, self).get_parser(prog_name)
@@ -264,7 +286,7 @@ class AddServerSecurityGroup(command.Command):
class AddServerVolume(command.Command):
- """Add volume to server"""
+ _description = _("Add volume to server")
def get_parser(self, prog_name):
parser = super(AddServerVolume, self).get_parser(prog_name)
@@ -306,7 +328,7 @@ class AddServerVolume(command.Command):
class CreateServer(command.ShowOne):
- """Create a new server"""
+ _description = _("Create a new server")
def get_parser(self, prog_name):
parser = super(CreateServer, self).get_parser(prog_name)
@@ -321,12 +343,15 @@ class CreateServer(command.ShowOne):
disk_group.add_argument(
'--image',
metavar='<image>',
- help=_('Create server from this image (name or ID)'),
+ help=_('Create server boot disk from this image (name or ID)'),
)
disk_group.add_argument(
'--volume',
metavar='<volume>',
- help=_('Create server from this volume (name or ID)'),
+ help=_(
+ 'Create server using this volume as the boot disk '
+ '(name or ID)'
+ ),
)
parser.add_argument(
'--flavor',
@@ -384,7 +409,7 @@ class CreateServer(command.ShowOne):
parser.add_argument(
'--nic',
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
- "port-id=port-uuid>",
+ "port-id=port-uuid,auto,none>",
action='append',
default=[],
help=_("Create a NIC on the server. "
@@ -393,7 +418,11 @@ class CreateServer(command.ShowOne):
"net-id: attach NIC to network with this UUID, "
"port-id: attach NIC to port with this UUID, "
"v4-fixed-ip: IPv4 fixed address for NIC (optional), "
- "v6-fixed-ip: IPv6 fixed address for NIC (optional)."),
+ "v6-fixed-ip: IPv6 fixed address for NIC (optional), "
+ "none: (v2.37+) no network is attached, "
+ "auto: (v2.37+) the compute service will automatically "
+ "allocate a network. Specifying a --nic of auto or none "
+ "cannot be used with any other --nic value."),
)
parser.add_argument(
'--hint',
@@ -433,12 +462,13 @@ class CreateServer(command.ShowOne):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
+ image_client = self.app.client_manager.image
# Lookup parsed_args.image
image = None
if parsed_args.image:
image = utils.find_resource(
- compute_client.images,
+ image_client.images,
parsed_args.image,
)
@@ -489,60 +519,94 @@ class CreateServer(command.ShowOne):
"exception": e}
)
- block_device_mapping = {}
+ block_device_mapping_v2 = []
if volume:
- # When booting from volume, for now assume no other mappings
- # This device value is likely KVM-specific
- block_device_mapping = {'vda': volume}
- else:
- for dev_map in parsed_args.block_device_mapping:
- dev_key, dev_vol = dev_map.split('=', 1)
- block_volume = None
- if dev_vol:
- vol = dev_vol.split(':', 1)[0]
- if vol:
- vol_id = utils.find_resource(
+ block_device_mapping_v2 = [{'uuid': volume,
+ 'boot_index': '0',
+ 'source_type': 'volume',
+ 'destination_type': 'volume'
+ }]
+ for dev_map in parsed_args.block_device_mapping:
+ dev_name, dev_map = dev_map.split('=', 1)
+ if dev_map:
+ dev_map = dev_map.split(':')
+ if len(dev_map) > 0:
+ mapping = {
+ 'device_name': dev_name,
+ 'uuid': utils.find_resource(
volume_client.volumes,
- vol,
- ).id
- block_volume = dev_vol.replace(vol, vol_id)
+ dev_map[0],
+ ).id}
+ # Block device mapping v1 compatibility
+ if len(dev_map) > 1 and \
+ dev_map[1] in ('volume', 'snapshot'):
+ mapping['source_type'] = dev_map[1]
else:
- msg = _("Volume name or ID must be specified if "
- "--block-device-mapping is specified")
- raise exceptions.CommandError(msg)
- block_device_mapping.update({dev_key: block_volume})
+ mapping['source_type'] = 'volume'
+ mapping['destination_type'] = 'volume'
+ if len(dev_map) > 2:
+ mapping['volume_size'] = dev_map[2]
+ if len(dev_map) > 3:
+ mapping['delete_on_termination'] = dev_map[3]
+ else:
+ msg = _("Volume name or ID must be specified if "
+ "--block-device-mapping is specified")
+ raise exceptions.CommandError(msg)
+ block_device_mapping_v2.append(mapping)
nics = []
+ auto_or_none = False
for nic_str in parsed_args.nic:
- nic_info = {"net-id": "", "v4-fixed-ip": "",
- "v6-fixed-ip": "", "port-id": ""}
- nic_info.update(dict(kv_str.split("=", 1)
- for kv_str in nic_str.split(",")))
- if bool(nic_info["net-id"]) == bool(nic_info["port-id"]):
- msg = _("either net-id or port-id should be specified "
- "but not both")
- raise exceptions.CommandError(msg)
- if self.app.client_manager.is_network_endpoint_enabled():
- network_client = self.app.client_manager.network
- if nic_info["net-id"]:
- net = network_client.find_network(
- nic_info["net-id"], ignore_missing=False)
- nic_info["net-id"] = net.id
- if nic_info["port-id"]:
- port = network_client.find_port(
- nic_info["port-id"], ignore_missing=False)
- nic_info["port-id"] = port.id
+ # Handle the special auto/none cases
+ if nic_str in ('auto', 'none'):
+ auto_or_none = True
+ nics.append(nic_str)
else:
- if nic_info["net-id"]:
- nic_info["net-id"] = utils.find_resource(
- compute_client.networks,
- nic_info["net-id"]
- ).id
- if nic_info["port-id"]:
- msg = _("can't create server with port specified "
- "since network endpoint not enabled")
+ nic_info = {"net-id": "", "v4-fixed-ip": "",
+ "v6-fixed-ip": "", "port-id": ""}
+ try:
+ nic_info.update(dict(kv_str.split("=", 1)
+ for kv_str in nic_str.split(",")))
+ except ValueError:
+ msg = _('Invalid --nic argument %s.') % nic_str
raise exceptions.CommandError(msg)
- nics.append(nic_info)
+ if bool(nic_info["net-id"]) == bool(nic_info["port-id"]):
+ msg = _("either net-id or port-id should be specified "
+ "but not both")
+ raise exceptions.CommandError(msg)
+ if self.app.client_manager.is_network_endpoint_enabled():
+ network_client = self.app.client_manager.network
+ if nic_info["net-id"]:
+ net = network_client.find_network(
+ nic_info["net-id"], ignore_missing=False)
+ nic_info["net-id"] = net.id
+ if nic_info["port-id"]:
+ port = network_client.find_port(
+ nic_info["port-id"], ignore_missing=False)
+ nic_info["port-id"] = port.id
+ else:
+ if nic_info["net-id"]:
+ nic_info["net-id"] = utils.find_resource(
+ compute_client.networks,
+ nic_info["net-id"]
+ ).id
+ if nic_info["port-id"]:
+ msg = _("can't create server with port specified "
+ "since network endpoint not enabled")
+ raise exceptions.CommandError(msg)
+ nics.append(nic_info)
+
+ if nics:
+ if auto_or_none:
+ if len(nics) > 1:
+ msg = _('Specifying a --nic of auto or none cannot '
+ 'be used with any other --nic value.')
+ raise exceptions.CommandError(msg)
+ nics = nics[0]
+ else:
+ # Default to empty list if nothing was specified, let nova side to
+ # decide the default behavior.
+ nics = []
hints = {}
for hint in parsed_args.hint:
@@ -577,7 +641,7 @@ class CreateServer(command.ShowOne):
userdata=userdata,
key_name=parsed_args.key_name,
availability_zone=parsed_args.availability_zone,
- block_device_mapping=block_device_mapping,
+ block_device_mapping_v2=block_device_mapping_v2,
nics=nics,
scheduler_hints=hints,
config_drive=config_drive)
@@ -609,7 +673,7 @@ class CreateServer(command.ShowOne):
sys.stdout.write(_('Error creating server\n'))
raise SystemExit
- details = _prep_server_detail(compute_client, server)
+ details = _prep_server_detail(compute_client, image_client, server)
return zip(*sorted(six.iteritems(details)))
@@ -642,7 +706,7 @@ class CreateServerDump(command.Command):
class DeleteServer(command.Command):
- """Delete server(s)"""
+ _description = _("Delete server(s)")
def get_parser(self, prog_name):
parser = super(DeleteServer, self).get_parser(prog_name)
@@ -680,7 +744,7 @@ class DeleteServer(command.Command):
class ListServer(command.Lister):
- """List servers"""
+ _description = _("List servers")
def get_parser(self, prog_name):
parser = super(ListServer, self).get_parser(prog_name)
@@ -772,11 +836,26 @@ class ListServer(command.Lister):
" 'osapi_max_limit' option of Nova API,"
" 'osapi_max_limit' will be used instead."),
)
+ parser.add_argument(
+ '--deleted',
+ action="store_true",
+ default=False,
+ help=_('Only display deleted servers (Admin only).')
+ )
+ parser.add_argument(
+ '--changes-since',
+ metavar='<changes-since>',
+ default=None,
+ help=_("List only servers changed after a certain point of time."
+ " The provided time should be an ISO 8061 formatted time."
+ " ex 2016-03-04T06:27:59Z .")
+ )
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
identity_client = self.app.client_manager.identity
+ image_client = self.app.client_manager.image
project_id = None
if parsed_args.project:
@@ -806,7 +885,7 @@ class ListServer(command.Lister):
# image name is given, map it to ID.
image_id = None
if parsed_args.image:
- image_id = utils.find_resource(compute_client.images,
+ image_id = utils.find_resource(image_client.images,
parsed_args.image).id
search_opts = {
@@ -822,9 +901,19 @@ class ListServer(command.Lister):
'tenant_id': project_id,
'all_tenants': parsed_args.all_projects,
'user_id': user_id,
+ 'deleted': parsed_args.deleted,
+ 'changes_since': parsed_args.changes_since,
}
LOG.debug('search options: %s', search_opts)
+ if search_opts['changes_since']:
+ try:
+ timeutils.parse_isotime(search_opts['changes_since'])
+ except ValueError:
+ raise exceptions.CommandError(_('Invalid changes-since value:'
+ ' %s') % search_opts['changes'
+ '_since'])
+
if parsed_args.long:
columns = (
'ID',
@@ -835,6 +924,8 @@ class ListServer(command.Lister):
'Networks',
'Image Name',
'Image ID',
+ 'Flavor Name',
+ 'Flavor ID',
'OS-EXT-AZ:availability_zone',
'OS-EXT-SRV-ATTR:host',
'Metadata',
@@ -848,6 +939,8 @@ class ListServer(command.Lister):
'Networks',
'Image Name',
'Image ID',
+ 'Flavor Name',
+ 'Flavor ID',
'Availability Zone',
'Host',
'Properties',
@@ -895,8 +988,19 @@ class ListServer(command.Lister):
except Exception:
pass
- # Populate image_name and image_id attributes of server objects
- # so that we can display "Image Name" and "Image ID" columns.
+ flavors = {}
+ # Create a dict that maps flavor_id to flavor object.
+ # Needed so that we can display the "Flavor Name" column.
+ # "Flavor Name" is not crucial, so we swallow any exceptions.
+ try:
+ flavors_list = compute_client.flavors.list()
+ for i in flavors_list:
+ flavors[i.id] = i
+ except Exception:
+ pass
+
+ # Populate image_name, image_id, flavor_name and flavor_id attributes
+ # of server objects so that we can display those columns.
for s in data:
if 'id' in s.image:
image = images.get(s.image['id'])
@@ -906,6 +1010,14 @@ class ListServer(command.Lister):
else:
s.image_name = ''
s.image_id = ''
+ if 'id' in s.flavor:
+ flavor = flavors.get(s.flavor['id'])
+ if flavor:
+ s.flavor_name = flavor.name
+ s.flavor_id = s.flavor['id']
+ else:
+ s.flavor_name = ''
+ s.flavor_id = ''
table = (column_headers,
(utils.get_item_properties(
@@ -923,7 +1035,8 @@ class ListServer(command.Lister):
class LockServer(command.Command):
- """Lock server(s). A non-admin user will not be able to execute actions"""
+ _description = _("Lock server(s). A non-admin user will not be able to "
+ "execute actions")
def get_parser(self, prog_name):
parser = super(LockServer, self).get_parser(prog_name)
@@ -956,7 +1069,7 @@ class LockServer(command.Command):
# then adding the groups doesn't seem to work
class MigrateServer(command.Command):
- """Migrate server to different host"""
+ _description = _("Migrate server to different host")
def get_parser(self, prog_name):
parser = super(MigrateServer, self).get_parser(prog_name)
@@ -973,15 +1086,15 @@ class MigrateServer(command.Command):
migration_group = parser.add_mutually_exclusive_group()
migration_group.add_argument(
'--shared-migration',
- dest='shared_migration',
- action='store_true',
- default=True,
+ dest='block_migration',
+ action='store_false',
+ default=False,
help=_('Perform a shared live migration (default)'),
)
migration_group.add_argument(
'--block-migration',
- dest='shared_migration',
- action='store_false',
+ dest='block_migration',
+ action='store_true',
help=_('Perform a block live migration'),
)
disk_group = parser.add_mutually_exclusive_group()
@@ -1002,7 +1115,7 @@ class MigrateServer(command.Command):
parser.add_argument(
'--wait',
action='store_true',
- help=_('Wait for resize to complete'),
+ help=_('Wait for migrate to complete'),
)
return parser
@@ -1016,11 +1129,15 @@ class MigrateServer(command.Command):
)
if parsed_args.live:
server.live_migrate(
- parsed_args.live,
- parsed_args.shared_migration,
- parsed_args.disk_overcommit,
+ host=parsed_args.live,
+ block_migration=parsed_args.block_migration,
+ disk_over_commit=parsed_args.disk_overcommit,
)
else:
+ if parsed_args.block_migration or parsed_args.disk_overcommit:
+ raise exceptions.CommandError("--live must be specified if "
+ "--block-migration or "
+ "--disk-overcommit is specified")
server.migrate()
if parsed_args.wait:
@@ -1038,7 +1155,7 @@ class MigrateServer(command.Command):
class PauseServer(command.Command):
- """Pause server(s)"""
+ _description = _("Pause server(s)")
def get_parser(self, prog_name):
parser = super(PauseServer, self).get_parser(prog_name)
@@ -1060,7 +1177,7 @@ class PauseServer(command.Command):
class RebootServer(command.Command):
- """Perform a hard or soft server reboot"""
+ _description = _("Perform a hard or soft server reboot")
def get_parser(self, prog_name):
parser = super(RebootServer, self).get_parser(prog_name)
@@ -1114,7 +1231,7 @@ class RebootServer(command.Command):
class RebuildServer(command.ShowOne):
- """Rebuild server"""
+ _description = _("Rebuild server")
def get_parser(self, prog_name):
parser = super(RebuildServer, self).get_parser(prog_name)
@@ -1143,13 +1260,14 @@ class RebuildServer(command.ShowOne):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
+ image_client = self.app.client_manager.image
server = utils.find_resource(
compute_client.servers, parsed_args.server)
# If parsed_args.image is not set, default to the currently used one.
image_id = parsed_args.image or server._info.get('image', {}).get('id')
- image = utils.find_resource(compute_client.images, image_id)
+ image = utils.find_resource(image_client.images, image_id)
server = server.rebuild(image, parsed_args.password)
if parsed_args.wait:
@@ -1165,12 +1283,12 @@ class RebuildServer(command.ShowOne):
sys.stdout.write(_('Error rebuilding server\n'))
raise SystemExit
- details = _prep_server_detail(compute_client, server)
+ details = _prep_server_detail(compute_client, image_client, server)
return zip(*sorted(six.iteritems(details)))
class RemoveFixedIP(command.Command):
- """Remove fixed IP address from server"""
+ _description = _("Remove fixed IP address from server")
def get_parser(self, prog_name):
parser = super(RemoveFixedIP, self).get_parser(prog_name)
@@ -1197,7 +1315,7 @@ class RemoveFixedIP(command.Command):
class RemoveFloatingIP(command.Command):
- """Remove floating IP address from server"""
+ _description = _("Remove floating IP address from server")
def get_parser(self, prog_name):
parser = super(RemoveFloatingIP, self).get_parser(prog_name)
@@ -1225,7 +1343,7 @@ class RemoveFloatingIP(command.Command):
class RemoveServerSecurityGroup(command.Command):
- """Remove security group from server"""
+ _description = _("Remove security group from server")
def get_parser(self, prog_name):
parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name)
@@ -1257,7 +1375,7 @@ class RemoveServerSecurityGroup(command.Command):
class RemoveServerVolume(command.Command):
- """Remove volume from server"""
+ _description = _("Remove volume from server")
def get_parser(self, prog_name):
parser = super(RemoveServerVolume, self).get_parser(prog_name)
@@ -1293,7 +1411,7 @@ class RemoveServerVolume(command.Command):
class RescueServer(command.ShowOne):
- """Put server in rescue mode"""
+ _description = _("Put server in rescue mode")
def get_parser(self, prog_name):
parser = super(RescueServer, self).get_parser(prog_name)
@@ -1315,7 +1433,7 @@ class RescueServer(command.ShowOne):
class ResizeServer(command.Command):
- """Scale server to a new flavor"""
+ _description = _("Scale server to a new flavor")
def get_parser(self, prog_name):
parser = super(ResizeServer, self).get_parser(prog_name)
@@ -1380,7 +1498,7 @@ class ResizeServer(command.Command):
class RestoreServer(command.Command):
- """Restore server(s)"""
+ _description = _("Restore server(s)")
def get_parser(self, prog_name):
parser = super(RestoreServer, self).get_parser(prog_name)
@@ -1402,7 +1520,7 @@ class RestoreServer(command.Command):
class ResumeServer(command.Command):
- """Resume server(s)"""
+ _description = _("Resume server(s)")
def get_parser(self, prog_name):
parser = super(ResumeServer, self).get_parser(prog_name)
@@ -1425,7 +1543,7 @@ class ResumeServer(command.Command):
class SetServer(command.Command):
- """Set server properties"""
+ _description = _("Set server properties")
def get_parser(self, prog_name):
parser = super(SetServer, self).get_parser(prog_name)
@@ -1490,7 +1608,7 @@ class SetServer(command.Command):
class ShelveServer(command.Command):
- """Shelve server(s)"""
+ _description = _("Shelve server(s)")
def get_parser(self, prog_name):
parser = super(ShelveServer, self).get_parser(prog_name)
@@ -1512,7 +1630,7 @@ class ShelveServer(command.Command):
class ShowServer(command.ShowOne):
- """Show server details"""
+ _description = _("Show server details")
def get_parser(self, prog_name):
parser = super(ShowServer, self).get_parser(prog_name)
@@ -1540,13 +1658,14 @@ class ShowServer(command.ShowOne):
sys.stderr.write(_("Error retrieving diagnostics data\n"))
return ({}, {})
else:
- data = _prep_server_detail(compute_client, server)
+ data = _prep_server_detail(compute_client,
+ self.app.client_manager.image, server)
return zip(*sorted(six.iteritems(data)))
class SshServer(command.Command):
- """SSH to server"""
+ _description = _("SSH to server")
def get_parser(self, prog_name):
parser = super(SshServer, self).get_parser(prog_name)
@@ -1690,7 +1809,7 @@ class SshServer(command.Command):
class StartServer(command.Command):
- """Start server(s)."""
+ _description = _("Start server(s).")
def get_parser(self, prog_name):
parser = super(StartServer, self).get_parser(prog_name)
@@ -1712,7 +1831,7 @@ class StartServer(command.Command):
class StopServer(command.Command):
- """Stop server(s)."""
+ _description = _("Stop server(s).")
def get_parser(self, prog_name):
parser = super(StopServer, self).get_parser(prog_name)
@@ -1734,7 +1853,7 @@ class StopServer(command.Command):
class SuspendServer(command.Command):
- """Suspend server(s)"""
+ _description = _("Suspend server(s)")
def get_parser(self, prog_name):
parser = super(SuspendServer, self).get_parser(prog_name)
@@ -1757,7 +1876,7 @@ class SuspendServer(command.Command):
class UnlockServer(command.Command):
- """Unlock server(s)"""
+ _description = _("Unlock server(s)")
def get_parser(self, prog_name):
parser = super(UnlockServer, self).get_parser(prog_name)
@@ -1780,7 +1899,7 @@ class UnlockServer(command.Command):
class UnpauseServer(command.Command):
- """Unpause server(s)"""
+ _description = _("Unpause server(s)")
def get_parser(self, prog_name):
parser = super(UnpauseServer, self).get_parser(prog_name)
@@ -1803,7 +1922,7 @@ class UnpauseServer(command.Command):
class UnrescueServer(command.Command):
- """Restore server from rescue mode"""
+ _description = _("Restore server from rescue mode")
def get_parser(self, prog_name):
parser = super(UnrescueServer, self).get_parser(prog_name)
@@ -1824,7 +1943,7 @@ class UnrescueServer(command.Command):
class UnsetServer(command.Command):
- """Unset server properties"""
+ _description = _("Unset server properties")
def get_parser(self, prog_name):
parser = super(UnsetServer, self).get_parser(prog_name)
@@ -1858,7 +1977,7 @@ class UnsetServer(command.Command):
class UnshelveServer(command.Command):
- """Unshelve server(s)"""
+ _description = _("Unshelve server(s)")
def get_parser(self, prog_name):
parser = super(UnshelveServer, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py
index c0e2e5ee..ddcf9101 100644
--- a/openstackclient/compute/v2/server_backup.py
+++ b/openstackclient/compute/v2/server_backup.py
@@ -33,7 +33,7 @@ def _show_progress(progress):
class CreateServerBackup(command.ShowOne):
- """Create a server backup image"""
+ _description = _("Create a server backup image")
IMAGE_API_VERSIONS = {
"1": "openstackclient.image.v1.image",
diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py
new file mode 100644
index 00000000..ccb19ef7
--- /dev/null
+++ b/openstackclient/compute/v2/server_event.py
@@ -0,0 +1,117 @@
+# Copyright 2017 Huawei, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Compute v2 Server operation event implementations"""
+
+import logging
+import six
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ListServerEvent(command.Lister):
+ _description = _("List recent events of a server")
+
+ def get_parser(self, prog_name):
+ parser = super(ListServerEvent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ help=_('Server to list events (name or ID)'),
+ )
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ server_id = utils.find_resource(compute_client.servers,
+ parsed_args.server).id
+ data = compute_client.instance_action.list(server_id)
+
+ if parsed_args.long:
+ columns = (
+ 'request_id',
+ 'instance_uuid',
+ 'action',
+ 'start_time',
+ 'message',
+ 'project_id',
+ 'user_id',
+ )
+ column_headers = (
+ 'Request ID',
+ 'Server ID',
+ 'Action',
+ 'Start Time',
+ 'Message',
+ 'Project ID',
+ 'User ID',
+ )
+ else:
+ columns = (
+ 'request_id',
+ 'instance_uuid',
+ 'action',
+ 'start_time',
+ )
+ column_headers = (
+ 'Request ID',
+ 'Server ID',
+ 'Action',
+ 'Start Time',
+ )
+
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+class ShowServerEvent(command.ShowOne):
+ _description = _("Show server event details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowServerEvent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ help=_('Server to show event details (name or ID)'),
+ )
+ parser.add_argument(
+ 'request_id',
+ metavar='<request-id>',
+ help=_('Request ID of the event to show (ID only)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ server_id = utils.find_resource(compute_client.servers,
+ parsed_args.server).id
+ action_detail = compute_client.instance_action.get(
+ server_id, parsed_args.request_id)
+
+ return zip(*sorted(six.iteritems(action_detail._info)))
diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py
index d51b1ec2..c6e2161f 100644
--- a/openstackclient/compute/v2/server_group.py
+++ b/openstackclient/compute/v2/server_group.py
@@ -43,7 +43,7 @@ def _get_columns(info):
class CreateServerGroup(command.ShowOne):
- """Create a new server group."""
+ _description = _("Create a new server group.")
def get_parser(self, prog_name):
parser = super(CreateServerGroup, self).get_parser(prog_name)
@@ -55,10 +55,11 @@ class CreateServerGroup(command.ShowOne):
parser.add_argument(
'--policy',
metavar='<policy>',
- action='append',
- required=True,
+ choices=['affinity', 'anti-affinity'],
+ default='affinity',
help=_("Add a policy to <name> "
- "(repeat option to add multiple policies)")
+ "('affinity' or 'anti-affinity', "
+ "default to 'affinity')")
)
return parser
@@ -67,7 +68,7 @@ class CreateServerGroup(command.ShowOne):
info = {}
server_group = compute_client.server_groups.create(
name=parsed_args.name,
- policies=parsed_args.policy)
+ policies=[parsed_args.policy])
info.update(server_group._info)
columns = _get_columns(info)
@@ -77,7 +78,7 @@ class CreateServerGroup(command.ShowOne):
class DeleteServerGroup(command.Command):
- """Delete existing server group(s)."""
+ _description = _("Delete existing server group(s).")
def get_parser(self, prog_name):
parser = super(DeleteServerGroup, self).get_parser(prog_name)
@@ -112,7 +113,7 @@ class DeleteServerGroup(command.Command):
class ListServerGroup(command.Lister):
- """List all server groups."""
+ _description = _("List all server groups.")
def get_parser(self, prog_name):
parser = super(ListServerGroup, self).get_parser(prog_name)
@@ -161,7 +162,7 @@ class ListServerGroup(command.Lister):
class ShowServerGroup(command.ShowOne):
- """Display server group details."""
+ _description = _("Display server group details.")
def get_parser(self, prog_name):
parser = super(ShowServerGroup, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py
index 285c7fd2..c66e0674 100644
--- a/openstackclient/compute/v2/server_image.py
+++ b/openstackclient/compute/v2/server_image.py
@@ -37,7 +37,7 @@ def _show_progress(progress):
class CreateServerImage(command.ShowOne):
- """Create a new server disk image from an existing server"""
+ _description = _("Create a new server disk image from an existing server")
IMAGE_API_VERSIONS = {
"1": "openstackclient.image.v1.image",
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index 53624f55..7b18c2e5 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class DeleteService(command.Command):
- """Delete compute service(s)"""
+ _description = _("Delete compute service(s)")
def get_parser(self, prog_name):
parser = super(DeleteService, self).get_parser(prog_name)
@@ -50,8 +50,7 @@ class DeleteService(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete compute service with "
- "ID '%(service)s': %(e)s")
- % {'service': s, 'e': e})
+ "ID '%(service)s': %(e)s"), {'service': s, 'e': e})
if result > 0:
total = len(parsed_args.service)
@@ -61,7 +60,7 @@ class DeleteService(command.Command):
class ListService(command.Lister):
- """List compute services"""
+ _description = _("List compute services")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
@@ -115,7 +114,7 @@ class ListService(command.Lister):
class SetService(command.Command):
- """Set compute service properties"""
+ _description = _("Set compute service properties")
def get_parser(self, prog_name):
parser = super(SetService, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index 2f35b01b..3edcffe4 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -26,7 +26,7 @@ from openstackclient.i18n import _
class ListUsage(command.Lister):
- """List resource usage per project"""
+ _description = _("List resource usage per project")
def get_parser(self, prog_name):
parser = super(ListUsage, self).get_parser(prog_name)
@@ -89,7 +89,7 @@ class ListUsage(command.Lister):
# Cache the project list
project_cache = {}
try:
- for p in self.app.client_manager.identity.tenants.list():
+ for p in self.app.client_manager.identity.projects.list():
project_cache[p.id] = p
except Exception:
# Just forget it if there's any trouble
@@ -115,7 +115,7 @@ class ListUsage(command.Lister):
class ShowUsage(command.ShowOne):
- """Show resource usage for a single project"""
+ _description = _("Show resource usage for a single project")
def get_parser(self, prog_name):
parser = super(ShowUsage, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py
index 7a15cf3a..993bdd53 100644
--- a/openstackclient/identity/v2_0/catalog.py
+++ b/openstackclient/identity/v2_0/catalog.py
@@ -43,7 +43,7 @@ def _format_endpoints(eps=None):
class ListCatalog(command.Lister):
- """List services in the service catalog"""
+ _description = _("List services in the service catalog")
def take_action(self, parsed_args):
@@ -66,7 +66,7 @@ class ListCatalog(command.Lister):
class ShowCatalog(command.ShowOne):
- """Display service catalog details"""
+ _description = _("Display service catalog details")
def get_parser(self, prog_name):
parser = super(ShowCatalog, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py
index 4873cd55..0bc48322 100644
--- a/openstackclient/identity/v2_0/ec2creds.py
+++ b/openstackclient/identity/v2_0/ec2creds.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateEC2Creds(command.ShowOne):
- """Create EC2 credentials"""
+ _description = _("Create EC2 credentials")
def get_parser(self, prog_name):
parser = super(CreateEC2Creds, self).get_parser(prog_name)
@@ -86,7 +86,7 @@ class CreateEC2Creds(command.ShowOne):
class DeleteEC2Creds(command.Command):
- """Delete EC2 credentials"""
+ _description = _("Delete EC2 credentials")
def get_parser(self, prog_name):
parser = super(DeleteEC2Creds, self).get_parser(prog_name)
@@ -122,8 +122,8 @@ class DeleteEC2Creds(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete EC2 credentials with "
- "access key '%(access_key)s': %(e)s")
- % {'access_key': access_key, 'e': e})
+ "access key '%(access_key)s': %(e)s"),
+ {'access_key': access_key, 'e': e})
if result > 0:
total = len(parsed_args.access_keys)
@@ -133,7 +133,7 @@ class DeleteEC2Creds(command.Command):
class ListEC2Creds(command.Lister):
- """List EC2 credentials"""
+ _description = _("List EC2 credentials")
def get_parser(self, prog_name):
parser = super(ListEC2Creds, self).get_parser(prog_name)
@@ -168,7 +168,7 @@ class ListEC2Creds(command.Lister):
class ShowEC2Creds(command.ShowOne):
- """Display EC2 credentials details"""
+ _description = _("Display EC2 credentials details")
def get_parser(self, prog_name):
parser = super(ShowEC2Creds, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py
index 5a3b3186..1628e488 100644
--- a/openstackclient/identity/v2_0/endpoint.py
+++ b/openstackclient/identity/v2_0/endpoint.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateEndpoint(command.ShowOne):
- """Create new endpoint"""
+ _description = _("Create new endpoint")
def get_parser(self, prog_name):
parser = super(CreateEndpoint, self).get_parser(prog_name)
@@ -80,7 +80,7 @@ class CreateEndpoint(command.ShowOne):
class DeleteEndpoint(command.Command):
- """Delete endpoint(s)"""
+ _description = _("Delete endpoint(s)")
def get_parser(self, prog_name):
parser = super(DeleteEndpoint, self).get_parser(prog_name)
@@ -102,8 +102,8 @@ class DeleteEndpoint(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete endpoint with "
- "ID '%(endpoint)s': %(e)s")
- % {'endpoint': endpoint, 'e': e})
+ "ID '%(endpoint)s': %(e)s"),
+ {'endpoint': endpoint, 'e': e})
if result > 0:
total = len(parsed_args.endpoints)
@@ -113,7 +113,7 @@ class DeleteEndpoint(command.Command):
class ListEndpoint(command.Lister):
- """List endpoints"""
+ _description = _("List endpoints")
def get_parser(self, prog_name):
parser = super(ListEndpoint, self).get_parser(prog_name)
@@ -146,7 +146,7 @@ class ListEndpoint(command.Lister):
class ShowEndpoint(command.ShowOne):
- """Display endpoint details"""
+ _description = _("Display endpoint details")
def get_parser(self, prog_name):
parser = super(ShowEndpoint, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index fc5c9201..04d422ec 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
@@ -30,7 +31,7 @@ LOG = logging.getLogger(__name__)
class CreateProject(command.ShowOne):
- """Create new project"""
+ _description = _("Create new project")
def get_parser(self, prog_name):
parser = super(CreateProject, self).get_parser(prog_name)
@@ -102,7 +103,7 @@ class CreateProject(command.ShowOne):
class DeleteProject(command.Command):
- """Delete project(s)"""
+ _description = _("Delete project(s)")
def get_parser(self, prog_name):
parser = super(DeleteProject, self).get_parser(prog_name)
@@ -117,16 +118,29 @@ 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):
- """List projects"""
+ _description = _("List projects")
def get_parser(self, prog_name):
parser = super(ListProject, self).get_parser(prog_name)
@@ -136,6 +150,13 @@ class ListProject(command.Lister):
default=False,
help=_('List additional fields in output'),
)
+ parser.add_argument(
+ '--sort',
+ metavar='<key>[:<direction>]',
+ help=_('Sort output by selected keys and directions (asc or desc) '
+ '(default: asc), repeat this option to specify multiple '
+ 'keys and directions.'),
+ )
return parser
def take_action(self, parsed_args):
@@ -144,6 +165,8 @@ class ListProject(command.Lister):
else:
columns = ('ID', 'Name')
data = self.app.client_manager.identity.tenants.list()
+ if parsed_args.sort:
+ data = utils.sort_items(data, parsed_args.sort)
return (columns,
(utils.get_item_properties(
s, columns,
@@ -152,7 +175,7 @@ class ListProject(command.Lister):
class SetProject(command.Command):
- """Set project properties"""
+ _description = _("Set project properties")
def get_parser(self, prog_name):
parser = super(SetProject, self).get_parser(prog_name)
@@ -221,7 +244,7 @@ class SetProject(command.Command):
class ShowProject(command.ShowOne):
- """Display project details"""
+ _description = _("Display project details")
def get_parser(self, prog_name):
parser = super(ShowProject, self).get_parser(prog_name)
@@ -279,7 +302,7 @@ class ShowProject(command.ShowOne):
class UnsetProject(command.Command):
- """Unset project properties"""
+ _description = _("Unset project properties")
def get_parser(self, prog_name):
parser = super(UnsetProject, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py
index b4b67bad..e254e05f 100644
--- a/openstackclient/identity/v2_0/role.py
+++ b/openstackclient/identity/v2_0/role.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class AddRole(command.ShowOne):
- """Add role to project:user"""
+ _description = _("Add role to project:user")
def get_parser(self, prog_name):
parser = super(AddRole, self).get_parser(prog_name)
@@ -73,7 +73,7 @@ class AddRole(command.ShowOne):
class CreateRole(command.ShowOne):
- """Create new role"""
+ _description = _("Create new role")
def get_parser(self, prog_name):
parser = super(CreateRole, self).get_parser(prog_name)
@@ -109,7 +109,7 @@ class CreateRole(command.ShowOne):
class DeleteRole(command.Command):
- """Delete role(s)"""
+ _description = _("Delete role(s)")
def get_parser(self, prog_name):
parser = super(DeleteRole, self).get_parser(prog_name)
@@ -124,16 +124,29 @@ 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):
- """List roles"""
+ _description = _("List roles")
def get_parser(self, prog_name):
parser = super(ListRole, self).get_parser(prog_name)
@@ -223,7 +236,7 @@ class ListRole(command.Lister):
class ListUserRole(command.Lister):
- """List user-role assignments"""
+ _description = _("List user-role assignments")
def get_parser(self, prog_name):
parser = super(ListUserRole, self).get_parser(prog_name)
@@ -293,7 +306,7 @@ class ListUserRole(command.Lister):
class RemoveRole(command.Command):
- """Remove role from project : user"""
+ _description = _("Remove role from project : user")
def get_parser(self, prog_name):
parser = super(RemoveRole, self).get_parser(prog_name)
@@ -331,7 +344,7 @@ class RemoveRole(command.Command):
class ShowRole(command.ShowOne):
- """Display role details"""
+ _description = _("Display role details")
def get_parser(self, prog_name):
parser = super(ShowRole, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py
index 44f55c3b..8236bbfc 100644
--- a/openstackclient/identity/v2_0/role_assignment.py
+++ b/openstackclient/identity/v2_0/role_assignment.py
@@ -21,7 +21,7 @@ from openstackclient.i18n import _ # noqa
class ListRoleAssignment(command.Lister):
- """List role assignments"""
+ _description = _("List role assignments")
def get_parser(self, prog_name):
parser = super(ListRoleAssignment, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py
index e318643c..80f2d72a 100644
--- a/openstackclient/identity/v2_0/service.py
+++ b/openstackclient/identity/v2_0/service.py
@@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
class CreateService(command.ShowOne):
- """Create new service"""
+ _description = _("Create new service")
def get_parser(self, prog_name):
parser = super(CreateService, self).get_parser(prog_name)
@@ -91,7 +91,7 @@ class CreateService(command.ShowOne):
class DeleteService(command.Command):
- """Delete service(s)"""
+ _description = _("Delete service(s)")
def get_parser(self, prog_name):
parser = super(DeleteService, self).get_parser(prog_name)
@@ -114,8 +114,8 @@ class DeleteService(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete service with "
- "name or ID '%(service)s': %(e)s")
- % {'service': service, 'e': e})
+ "name or ID '%(service)s': %(e)s"),
+ {'service': service, 'e': e})
if result > 0:
total = len(parsed_args.services)
@@ -125,7 +125,7 @@ class DeleteService(command.Command):
class ListService(command.Lister):
- """List services"""
+ _description = _("List services")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
@@ -151,7 +151,7 @@ class ListService(command.Lister):
class ShowService(command.ShowOne):
- """Display service details"""
+ _description = _("Display service details")
def get_parser(self, prog_name):
parser = super(ShowService, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py
index 4b7e988b..3b08b475 100644
--- a/openstackclient/identity/v2_0/token.py
+++ b/openstackclient/identity/v2_0/token.py
@@ -23,7 +23,7 @@ from openstackclient.i18n import _
class IssueToken(command.ShowOne):
- """Issue new token"""
+ _description = _("Issue new token")
# scoped token is optional
required_scope = False
@@ -53,7 +53,7 @@ class IssueToken(command.ShowOne):
class RevokeToken(command.Command):
- """Revoke existing token"""
+ _description = _("Revoke existing token")
def get_parser(self, prog_name):
parser = super(RevokeToken, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py
index d2075150..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
@@ -29,7 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateUser(command.ShowOne):
- """Create new user"""
+ _description = _("Create new user")
def get_parser(self, prog_name):
parser = super(CreateUser, self).get_parser(prog_name)
@@ -94,6 +95,10 @@ class CreateUser(command.ShowOne):
if parsed_args.password_prompt:
parsed_args.password = utils.get_password(self.app.stdin)
+ if not parsed_args.password:
+ LOG.warning(_("No password was supplied, authentication will fail "
+ "when a user does not have a password."))
+
try:
user = identity_client.users.create(
parsed_args.name,
@@ -126,7 +131,7 @@ class CreateUser(command.ShowOne):
class DeleteUser(command.Command):
- """Delete user(s)"""
+ _description = _("Delete user(s)")
def get_parser(self, prog_name):
parser = super(DeleteUser, self).get_parser(prog_name)
@@ -141,16 +146,29 @@ 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):
- """List users"""
+ _description = _("List users")
def get_parser(self, prog_name):
parser = super(ListUser, self).get_parser(prog_name)
@@ -238,7 +256,7 @@ class ListUser(command.Lister):
class SetUser(command.Command):
- """Set user properties"""
+ _description = _("Set user properties")
def get_parser(self, prog_name):
parser = super(SetUser, self).get_parser(prog_name)
@@ -292,6 +310,10 @@ class SetUser(command.Command):
if parsed_args.password_prompt:
parsed_args.password = utils.get_password(self.app.stdin)
+ if '' == parsed_args.password:
+ LOG.warning(_("No password was supplied, authentication will fail "
+ "when a user does not have a password."))
+
user = utils.find_resource(
identity_client.users,
parsed_args.user,
@@ -328,7 +350,7 @@ class SetUser(command.Command):
class ShowUser(command.ShowOne):
- """Display user details"""
+ _description = _("Display user details")
def get_parser(self, prog_name):
parser = super(ShowUser, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py
index a62d0a93..28f4fada 100644
--- a/openstackclient/identity/v3/catalog.py
+++ b/openstackclient/identity/v3/catalog.py
@@ -38,7 +38,7 @@ def _format_endpoints(eps=None):
class ListCatalog(command.Lister):
- """List services in the service catalog"""
+ _description = _("List services in the service catalog")
def take_action(self, parsed_args):
@@ -61,7 +61,7 @@ class ListCatalog(command.Lister):
class ShowCatalog(command.ShowOne):
- """Display service catalog details"""
+ _description = _("Display service catalog details")
def get_parser(self, prog_name):
parser = super(ShowCatalog, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py
index b41a37ca..6dd24dcc 100644
--- a/openstackclient/identity/v3/consumer.py
+++ b/openstackclient/identity/v3/consumer.py
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class CreateConsumer(command.ShowOne):
- """Create new consumer"""
+ _description = _("Create new consumer")
def get_parser(self, prog_name):
parser = super(CreateConsumer, self).get_parser(prog_name)
@@ -50,7 +50,7 @@ class CreateConsumer(command.ShowOne):
class DeleteConsumer(command.Command):
- """Delete consumer(s)"""
+ _description = _("Delete consumer(s)")
def get_parser(self, prog_name):
parser = super(DeleteConsumer, self).get_parser(prog_name)
@@ -73,8 +73,7 @@ class DeleteConsumer(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete consumer with name or "
- "ID '%(consumer)s': %(e)s")
- % {'consumer': i, 'e': e})
+ "ID '%(consumer)s': %(e)s"), {'consumer': i, 'e': e})
if result > 0:
total = len(parsed_args.consumer)
@@ -84,7 +83,7 @@ class DeleteConsumer(command.Command):
class ListConsumer(command.Lister):
- """List consumers"""
+ _description = _("List consumers")
def take_action(self, parsed_args):
columns = ('ID', 'Description')
@@ -97,7 +96,7 @@ class ListConsumer(command.Lister):
class SetConsumer(command.Command):
- """Set consumer properties"""
+ _description = _("Set consumer properties")
def get_parser(self, prog_name):
parser = super(SetConsumer, self).get_parser(prog_name)
@@ -126,7 +125,7 @@ class SetConsumer(command.Command):
class ShowConsumer(command.ShowOne):
- """Display consumer details"""
+ _description = _("Display consumer details")
def get_parser(self, prog_name):
parser = super(ShowConsumer, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py
index 0ef94cf4..2fd00626 100644
--- a/openstackclient/identity/v3/credential.py
+++ b/openstackclient/identity/v3/credential.py
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class CreateCredential(command.ShowOne):
- """Create new credential"""
+ _description = _("Create new credential")
def get_parser(self, prog_name):
parser = super(CreateCredential, self).get_parser(prog_name)
@@ -78,7 +78,7 @@ class CreateCredential(command.ShowOne):
class DeleteCredential(command.Command):
- """Delete credential(s)"""
+ _description = _("Delete credential(s)")
def get_parser(self, prog_name):
parser = super(DeleteCredential, self).get_parser(prog_name)
@@ -99,8 +99,8 @@ class DeleteCredential(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete credentials with "
- "ID '%(credential)s': %(e)s")
- % {'credential': i, 'e': e})
+ "ID '%(credential)s': %(e)s"),
+ {'credential': i, 'e': e})
if result > 0:
total = len(parsed_args.credential)
@@ -110,7 +110,7 @@ class DeleteCredential(command.Command):
class ListCredential(command.Lister):
- """List credentials"""
+ _description = _("List credentials")
def take_action(self, parsed_args):
columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID')
@@ -124,7 +124,7 @@ class ListCredential(command.Lister):
class SetCredential(command.Command):
- """Set credential properties"""
+ _description = _("Set credential properties")
def get_parser(self, prog_name):
parser = super(SetCredential, self).get_parser(prog_name)
@@ -180,7 +180,7 @@ class SetCredential(command.Command):
class ShowCredential(command.ShowOne):
- """Display credential details"""
+ _description = _("Display credential details")
def get_parser(self, prog_name):
parser = super(ShowCredential, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py
index 76e47d32..064624ab 100644
--- a/openstackclient/identity/v3/domain.py
+++ b/openstackclient/identity/v3/domain.py
@@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
class CreateDomain(command.ShowOne):
- """Create new domain"""
+ _description = _("Create new domain")
def get_parser(self, prog_name):
parser = super(CreateDomain, self).get_parser(prog_name)
@@ -89,7 +89,7 @@ class CreateDomain(command.ShowOne):
class DeleteDomain(command.Command):
- """Delete domain(s)"""
+ _description = _("Delete domain(s)")
def get_parser(self, prog_name):
parser = super(DeleteDomain, self).get_parser(prog_name)
@@ -111,8 +111,7 @@ class DeleteDomain(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete domain with name or "
- "ID '%(domain)s': %(e)s")
- % {'domain': i, 'e': e})
+ "ID '%(domain)s': %(e)s"), {'domain': i, 'e': e})
if result > 0:
total = len(parsed_args.domain)
@@ -122,7 +121,7 @@ class DeleteDomain(command.Command):
class ListDomain(command.Lister):
- """List domains"""
+ _description = _("List domains")
def take_action(self, parsed_args):
columns = ('ID', 'Name', 'Enabled', 'Description')
@@ -135,7 +134,7 @@ class ListDomain(command.Lister):
class SetDomain(command.Command):
- """Set domain properties"""
+ _description = _("Set domain properties")
def get_parser(self, prog_name):
parser = super(SetDomain, self).get_parser(prog_name)
@@ -186,7 +185,7 @@ class SetDomain(command.Command):
class ShowDomain(command.ShowOne):
- """Display domain details"""
+ _description = _("Display domain details")
def get_parser(self, prog_name):
parser = super(ShowDomain, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py
index 7ad01719..44e9a2c7 100644
--- a/openstackclient/identity/v3/ec2creds.py
+++ b/openstackclient/identity/v3/ec2creds.py
@@ -55,7 +55,7 @@ def _determine_ec2_user(parsed_args, client_manager):
class CreateEC2Creds(command.ShowOne):
- """Create EC2 credentials"""
+ _description = _("Create EC2 credentials")
def get_parser(self, prog_name):
parser = super(CreateEC2Creds, self).get_parser(prog_name)
@@ -112,7 +112,7 @@ class CreateEC2Creds(command.ShowOne):
class DeleteEC2Creds(command.Command):
- """Delete EC2 credentials"""
+ _description = _("Delete EC2 credentials")
def get_parser(self, prog_name):
parser = super(DeleteEC2Creds, self).get_parser(prog_name)
@@ -140,8 +140,8 @@ class DeleteEC2Creds(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete EC2 credentials with "
- "access key '%(access_key)s': %(e)s")
- % {'access_key': i, 'e': e})
+ "access key '%(access_key)s': %(e)s"),
+ {'access_key': i, 'e': e})
if result > 0:
total = len(parsed_args.access_key)
@@ -151,7 +151,7 @@ class DeleteEC2Creds(command.Command):
class ListEC2Creds(command.Lister):
- """List EC2 credentials"""
+ _description = _("List EC2 credentials")
def get_parser(self, prog_name):
parser = super(ListEC2Creds, self).get_parser(prog_name)
@@ -179,7 +179,7 @@ class ListEC2Creds(command.Lister):
class ShowEC2Creds(command.ShowOne):
- """Display EC2 credentials details"""
+ _description = _("Display EC2 credentials details")
def get_parser(self, prog_name):
parser = super(ShowEC2Creds, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py
index 73b37a43..15760a17 100644
--- a/openstackclient/identity/v3/endpoint.py
+++ b/openstackclient/identity/v3/endpoint.py
@@ -37,7 +37,7 @@ def get_service_name(service):
class CreateEndpoint(command.ShowOne):
- """Create new endpoint"""
+ _description = _("Create new endpoint")
def get_parser(self, prog_name):
parser = super(CreateEndpoint, self).get_parser(prog_name)
@@ -99,7 +99,7 @@ class CreateEndpoint(command.ShowOne):
class DeleteEndpoint(command.Command):
- """Delete endpoint(s)"""
+ _description = _("Delete endpoint(s)")
def get_parser(self, prog_name):
parser = super(DeleteEndpoint, self).get_parser(prog_name)
@@ -122,8 +122,7 @@ class DeleteEndpoint(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete endpoint with "
- "ID '%(endpoint)s': %(e)s")
- % {'endpoint': i, 'e': e})
+ "ID '%(endpoint)s': %(e)s"), {'endpoint': i, 'e': e})
if result > 0:
total = len(parsed_args.endpoint)
@@ -133,14 +132,14 @@ class DeleteEndpoint(command.Command):
class ListEndpoint(command.Lister):
- """List endpoints"""
+ _description = _("List endpoints")
def get_parser(self, prog_name):
parser = super(ListEndpoint, self).get_parser(prog_name)
parser.add_argument(
'--service',
metavar='<service>',
- help=_('Filter by service (name or ID)'),
+ help=_('Filter by service (type, name or ID)'),
)
parser.add_argument(
'--interface',
@@ -181,7 +180,7 @@ class ListEndpoint(command.Lister):
class SetEndpoint(command.Command):
- """Set endpoint properties"""
+ _description = _("Set endpoint properties")
def get_parser(self, prog_name):
parser = super(SetEndpoint, self).get_parser(prog_name)
@@ -252,7 +251,7 @@ class SetEndpoint(command.Command):
class ShowEndpoint(command.ShowOne):
- """Display endpoint details"""
+ _description = _("Display endpoint details")
def get_parser(self, prog_name):
parser = super(ShowEndpoint, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py
index 3fde9027..6429d934 100644
--- a/openstackclient/identity/v3/federation_protocol.py
+++ b/openstackclient/identity/v3/federation_protocol.py
@@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__)
class CreateProtocol(command.ShowOne):
- """Create new federation protocol"""
+ _description = _("Create new federation protocol")
def get_parser(self, prog_name):
parser = super(CreateProtocol, self).get_parser(prog_name)
@@ -72,7 +72,7 @@ class CreateProtocol(command.ShowOne):
class DeleteProtocol(command.Command):
- """Delete federation protocol(s)"""
+ _description = _("Delete federation protocol(s)")
def get_parser(self, prog_name):
parser = super(DeleteProtocol, self).get_parser(prog_name)
@@ -102,8 +102,8 @@ class DeleteProtocol(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete federation protocol "
- "with name or ID '%(protocol)s': %(e)s")
- % {'protocol': i, 'e': e})
+ "with name or ID '%(protocol)s': %(e)s"),
+ {'protocol': i, 'e': e})
if result > 0:
total = len(parsed_args.federation_protocol)
@@ -113,7 +113,7 @@ class DeleteProtocol(command.Command):
class ListProtocols(command.Lister):
- """List federation protocols"""
+ _description = _("List federation protocols")
def get_parser(self, prog_name):
parser = super(ListProtocols, self).get_parser(prog_name)
@@ -139,7 +139,7 @@ class ListProtocols(command.Lister):
class SetProtocol(command.Command):
- """Set federation protocol properties"""
+ _description = _("Set federation protocol properties")
def get_parser(self, prog_name):
parser = super(SetProtocol, self).get_parser(prog_name)
@@ -179,7 +179,7 @@ class SetProtocol(command.Command):
class ShowProtocol(command.ShowOne):
- """Display federation protocol details"""
+ _description = _("Display federation protocol details")
def get_parser(self, prog_name):
parser = super(ShowProtocol, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index f780810a..39c8547c 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
@@ -31,7 +32,7 @@ LOG = logging.getLogger(__name__)
class AddUserToGroup(command.Command):
- """Add user to group"""
+ _description = _("Add user to group")
def get_parser(self, prog_name):
parser = super(AddUserToGroup, self).get_parser(prog_name)
@@ -43,7 +44,9 @@ class AddUserToGroup(command.Command):
parser.add_argument(
'user',
metavar='<user>',
- help=_('User to add to <group> (name or ID)'),
+ nargs='+',
+ help=_('User(s) to add to <group> (name or ID) '
+ '(repeat option to add multiple users)'),
)
common.add_group_domain_option_to_parser(parser)
common.add_user_domain_option_to_parser(parser)
@@ -52,31 +55,38 @@ class AddUserToGroup(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- user_id = common.find_user(identity_client,
- parsed_args.user,
- parsed_args.user_domain).id
group_id = common.find_group(identity_client,
parsed_args.group,
parsed_args.group_domain).id
- try:
- identity_client.users.add_to_group(user_id, group_id)
- except Exception:
- msg = _("%(user)s not added to group %(group)s\n") % {
- 'user': parsed_args.user,
- 'group': parsed_args.group,
- }
- sys.stderr.write(msg)
- else:
- msg = _("%(user)s added to group %(group)s\n") % {
- 'user': parsed_args.user,
+ result = 0
+ for i in parsed_args.user:
+ try:
+ user_id = common.find_user(identity_client,
+ i,
+ parsed_args.user_domain).id
+ identity_client.users.add_to_group(user_id, group_id)
+ except Exception as e:
+ result += 1
+ msg = _("%(user)s not added to group %(group)s: %(e)s") % {
+ 'user': i,
+ 'group': parsed_args.group,
+ 'e': e,
+ }
+ LOG.error(msg)
+ if result > 0:
+ total = len(parsed_args.user)
+ msg = (_("%(result)s of %(total)s users not added to group "
+ "%(group)s.")) % {
+ 'result': result,
+ 'total': total,
'group': parsed_args.group,
}
- sys.stdout.write(msg)
+ raise exceptions.CommandError(msg)
class CheckUserInGroup(command.Command):
- """Check user membership in group"""
+ _description = _("Check user membership in group")
def get_parser(self, prog_name):
parser = super(CheckUserInGroup, self).get_parser(prog_name)
@@ -106,12 +116,15 @@ class CheckUserInGroup(command.Command):
try:
identity_client.users.check_in_group(user_id, group_id)
- except Exception:
- msg = _("%(user)s not in group %(group)s\n") % {
- 'user': parsed_args.user,
- 'group': parsed_args.group,
- }
- sys.stderr.write(msg)
+ except ks_exc.http.HTTPClientError as e:
+ if e.http_status == 403 or e.http_status == 404:
+ msg = _("%(user)s not in group %(group)s\n") % {
+ 'user': parsed_args.user,
+ 'group': parsed_args.group,
+ }
+ sys.stderr.write(msg)
+ else:
+ raise e
else:
msg = _("%(user)s in group %(group)s\n") % {
'user': parsed_args.user,
@@ -121,7 +134,7 @@ class CheckUserInGroup(command.Command):
class CreateGroup(command.ShowOne):
- """Create new group"""
+ _description = _("Create new group")
def get_parser(self, prog_name):
parser = super(CreateGroup, self).get_parser(prog_name)
@@ -174,7 +187,7 @@ class CreateGroup(command.ShowOne):
class DeleteGroup(command.Command):
- """Delete group(s)"""
+ _description = _("Delete group(s)")
def get_parser(self, prog_name):
parser = super(DeleteGroup, self).get_parser(prog_name)
@@ -194,15 +207,28 @@ 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):
- """List groups"""
+ _description = _("List groups")
def get_parser(self, prog_name):
parser = super(ListGroup, self).get_parser(prog_name)
@@ -262,7 +288,7 @@ class ListGroup(command.Lister):
class RemoveUserFromGroup(command.Command):
- """Remove user from group"""
+ _description = _("Remove user from group")
def get_parser(self, prog_name):
parser = super(RemoveUserFromGroup, self).get_parser(prog_name)
@@ -274,7 +300,9 @@ class RemoveUserFromGroup(command.Command):
parser.add_argument(
'user',
metavar='<user>',
- help=_('User to remove from <group> (name or ID)'),
+ nargs='+',
+ help=_('User(s) to remove from <group> (name or ID) '
+ '(repeat option to remove multiple users)'),
)
common.add_group_domain_option_to_parser(parser)
common.add_user_domain_option_to_parser(parser)
@@ -283,31 +311,38 @@ class RemoveUserFromGroup(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- user_id = common.find_user(identity_client,
- parsed_args.user,
- parsed_args.user_domain).id
group_id = common.find_group(identity_client,
parsed_args.group,
parsed_args.group_domain).id
- try:
- identity_client.users.remove_from_group(user_id, group_id)
- except Exception:
- msg = _("%(user)s not removed from group %(group)s\n") % {
- 'user': parsed_args.user,
- 'group': parsed_args.group,
- }
- sys.stderr.write(msg)
- else:
- msg = _("%(user)s removed from group %(group)s\n") % {
- 'user': parsed_args.user,
+ result = 0
+ for i in parsed_args.user:
+ try:
+ user_id = common.find_user(identity_client,
+ i,
+ parsed_args.user_domain).id
+ identity_client.users.remove_from_group(user_id, group_id)
+ except Exception as e:
+ result += 1
+ msg = _("%(user)s not removed from group %(group)s: %(e)s") % {
+ 'user': i,
+ 'group': parsed_args.group,
+ 'e': e,
+ }
+ LOG.error(msg)
+ if result > 0:
+ total = len(parsed_args.user)
+ msg = (_("%(result)s of %(total)s users not removed from group "
+ "%(group)s.")) % {
+ 'result': result,
+ 'total': total,
'group': parsed_args.group,
}
- sys.stdout.write(msg)
+ raise exceptions.CommandError(msg)
class SetGroup(command.Command):
- """Set group properties"""
+ _description = _("Set group properties")
def get_parser(self, prog_name):
parser = super(SetGroup, self).get_parser(prog_name)
@@ -347,7 +382,7 @@ class SetGroup(command.Command):
class ShowGroup(command.ShowOne):
- """Display group details"""
+ _description = _("Display group details")
def get_parser(self, prog_name):
parser = super(ShowGroup, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
index b6b03188..e8b3a2f4 100644
--- a/openstackclient/identity/v3/identity_provider.py
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
class CreateIdentityProvider(command.ShowOne):
- """Create new identity provider"""
+ _description = _("Create new identity provider")
def get_parser(self, prog_name):
parser = super(CreateIdentityProvider, self).get_parser(prog_name)
@@ -94,7 +94,7 @@ class CreateIdentityProvider(command.ShowOne):
class DeleteIdentityProvider(command.Command):
- """Delete identity provider(s)"""
+ _description = _("Delete identity provider(s)")
def get_parser(self, prog_name):
parser = super(DeleteIdentityProvider, self).get_parser(prog_name)
@@ -115,8 +115,8 @@ class DeleteIdentityProvider(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete identity providers with "
- "name or ID '%(provider)s': %(e)s")
- % {'provider': i, 'e': e})
+ "name or ID '%(provider)s': %(e)s"),
+ {'provider': i, 'e': e})
if result > 0:
total = len(parsed_args.identity_provider)
@@ -126,7 +126,7 @@ class DeleteIdentityProvider(command.Command):
class ListIdentityProvider(command.Lister):
- """List identity providers"""
+ _description = _("List identity providers")
def take_action(self, parsed_args):
columns = ('ID', 'Enabled', 'Description')
@@ -140,7 +140,7 @@ class ListIdentityProvider(command.Lister):
class SetIdentityProvider(command.Command):
- """Set identity provider properties"""
+ _description = _("Set identity provider properties")
def get_parser(self, prog_name):
parser = super(SetIdentityProvider, self).get_parser(prog_name)
@@ -211,7 +211,7 @@ class SetIdentityProvider(command.Command):
class ShowIdentityProvider(command.ShowOne):
- """Display identity provider details"""
+ _description = _("Display identity provider details")
def get_parser(self, prog_name):
parser = super(ShowIdentityProvider, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py
index 09181a0b..e729c410 100644
--- a/openstackclient/identity/v3/mapping.py
+++ b/openstackclient/identity/v3/mapping.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class _RulesReader(object):
- """Helper class capable of reading rules from files"""
+ _description = _("Helper class capable of reading rules from files")
def _read_rules(self, path):
"""Read and parse rules from path
@@ -82,7 +82,7 @@ class _RulesReader(object):
class CreateMapping(command.ShowOne, _RulesReader):
- """Create new mapping"""
+ _description = _("Create new mapping")
def get_parser(self, prog_name):
parser = super(CreateMapping, self).get_parser(prog_name)
@@ -111,7 +111,7 @@ class CreateMapping(command.ShowOne, _RulesReader):
class DeleteMapping(command.Command):
- """Delete mapping(s)"""
+ _description = _("Delete mapping(s)")
def get_parser(self, prog_name):
parser = super(DeleteMapping, self).get_parser(prog_name)
@@ -132,8 +132,7 @@ class DeleteMapping(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete mapping with name or "
- "ID '%(mapping)s': %(e)s")
- % {'mapping': i, 'e': e})
+ "ID '%(mapping)s': %(e)s"), {'mapping': i, 'e': e})
if result > 0:
total = len(parsed_args.mapping)
@@ -143,7 +142,7 @@ class DeleteMapping(command.Command):
class ListMapping(command.Lister):
- """List mappings"""
+ _description = _("List mappings")
def take_action(self, parsed_args):
# NOTE(marek-denis): Since rules can be long and tedious I have decided
@@ -157,7 +156,7 @@ class ListMapping(command.Lister):
class SetMapping(command.Command, _RulesReader):
- """Set mapping properties"""
+ _description = _("Set mapping properties")
def get_parser(self, prog_name):
parser = super(SetMapping, self).get_parser(prog_name)
@@ -183,11 +182,10 @@ class SetMapping(command.Command, _RulesReader):
rules=rules)
mapping._info.pop('links', None)
- return zip(*sorted(six.iteritems(mapping._info)))
class ShowMapping(command.ShowOne):
- """Display mapping details"""
+ _description = _("Display mapping details")
def get_parser(self, prog_name):
parser = super(ShowMapping, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py
index 596eae01..3b644195 100644
--- a/openstackclient/identity/v3/policy.py
+++ b/openstackclient/identity/v3/policy.py
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class CreatePolicy(command.ShowOne):
- """Create new policy"""
+ _description = _("Create new policy")
def get_parser(self, prog_name):
parser = super(CreatePolicy, self).get_parser(prog_name)
@@ -61,7 +61,7 @@ class CreatePolicy(command.ShowOne):
class DeletePolicy(command.Command):
- """Delete policy(s)"""
+ _description = _("Delete policy(s)")
def get_parser(self, prog_name):
parser = super(DeletePolicy, self).get_parser(prog_name)
@@ -82,8 +82,7 @@ class DeletePolicy(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete policy with name or "
- "ID '%(policy)s': %(e)s")
- % {'policy': i, 'e': e})
+ "ID '%(policy)s': %(e)s"), {'policy': i, 'e': e})
if result > 0:
total = len(parsed_args.policy)
@@ -93,7 +92,7 @@ class DeletePolicy(command.Command):
class ListPolicy(command.Lister):
- """List policies"""
+ _description = _("List policies")
def get_parser(self, prog_name):
parser = super(ListPolicy, self).get_parser(prog_name)
@@ -121,7 +120,7 @@ class ListPolicy(command.Lister):
class SetPolicy(command.Command):
- """Set policy properties"""
+ _description = _("Set policy properties")
def get_parser(self, prog_name):
parser = super(SetPolicy, self).get_parser(prog_name)
@@ -159,7 +158,7 @@ class SetPolicy(command.Command):
class ShowPolicy(command.ShowOne):
- """Display policy details"""
+ _description = _("Display policy details")
def get_parser(self, prog_name):
parser = super(ShowPolicy, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index 56c4fbc8..873ee9c7 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
@@ -31,7 +32,7 @@ LOG = logging.getLogger(__name__)
class CreateProject(command.ShowOne):
- """Create new project"""
+ _description = _("Create new project")
def get_parser(self, prog_name):
parser = super(CreateProject, self).get_parser(prog_name)
@@ -125,7 +126,7 @@ class CreateProject(command.ShowOne):
class DeleteProject(command.Command):
- """Delete project(s)"""
+ _description = _("Delete project(s)")
def get_parser(self, prog_name):
parser = super(DeleteProject, self).get_parser(prog_name)
@@ -148,19 +149,32 @@ 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):
- """List projects"""
+ _description = _("List projects")
def get_parser(self, prog_name):
parser = super(ListProject, self).get_parser(prog_name)
@@ -175,11 +189,24 @@ class ListProject(command.Lister):
help=_('Filter projects by <user> (name or ID)'),
)
parser.add_argument(
+ '--my-projects',
+ action='store_true',
+ help=_('List projects for the authenticated user. '
+ 'Supersedes other filters.'),
+ )
+ parser.add_argument(
'--long',
action='store_true',
default=False,
help=_('List additional fields in output'),
)
+ parser.add_argument(
+ '--sort',
+ metavar='<key>[:<direction>]',
+ help=_('Sort output by selected keys and directions (asc or desc) '
+ '(default: asc), repeat this option to specify multiple '
+ 'keys and directions.'),
+ )
return parser
def take_action(self, parsed_args):
@@ -207,7 +234,25 @@ class ListProject(command.Lister):
kwargs['user'] = user_id
- data = identity_client.projects.list(**kwargs)
+ if parsed_args.my_projects:
+ # NOTE(adriant): my-projects supersedes all the other filters.
+ kwargs = {'user': self.app.client_manager.auth_ref.user_id}
+
+ try:
+ data = identity_client.projects.list(**kwargs)
+ except ks_exc.Forbidden:
+ # NOTE(adriant): if no filters, assume a forbidden is non-admin
+ # wanting their own project list.
+ if not kwargs:
+ user = self.app.client_manager.auth_ref.user_id
+ data = identity_client.projects.list(
+ user=user)
+ else:
+ raise
+
+ if parsed_args.sort:
+ data = utils.sort_items(data, parsed_args.sort)
+
return (columns,
(utils.get_item_properties(
s, columns,
@@ -216,7 +261,7 @@ class ListProject(command.Lister):
class SetProject(command.Command):
- """Set project properties"""
+ _description = _("Set project properties")
def get_parser(self, prog_name):
parser = super(SetProject, self).get_parser(prog_name)
@@ -283,7 +328,7 @@ class SetProject(command.Command):
class ShowProject(command.ShowOne):
- """Display project details"""
+ _description = _("Display project details")
def get_parser(self, prog_name):
parser = super(ShowProject, self).get_parser(prog_name)
@@ -322,13 +367,18 @@ class ShowProject(command.ShowOne):
project = utils.find_resource(
identity_client.projects,
project_str,
- domain_id=domain.id,
- parents_as_list=parsed_args.parents,
- subtree_as_list=parsed_args.children)
+ domain_id=domain.id)
else:
project = utils.find_resource(
identity_client.projects,
- project_str,
+ project_str)
+
+ if parsed_args.parents or parsed_args.children:
+ # NOTE(RuiChen): utils.find_resource() can't pass kwargs,
+ # if id query hit the result at first, so call
+ # identity manager.get() with kwargs directly.
+ project = identity_client.projects.get(
+ project.id,
parents_as_list=parsed_args.parents,
subtree_as_list=parsed_args.children)
diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py
index b7c51f93..69c8b506 100644
--- a/openstackclient/identity/v3/region.py
+++ b/openstackclient/identity/v3/region.py
@@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
class CreateRegion(command.ShowOne):
- """Create new region"""
+ _description = _("Create new region")
def get_parser(self, prog_name):
parser = super(CreateRegion, self).get_parser(prog_name)
@@ -66,7 +66,7 @@ class CreateRegion(command.ShowOne):
class DeleteRegion(command.Command):
- """Delete region(s)"""
+ _description = _("Delete region(s)")
def get_parser(self, prog_name):
parser = super(DeleteRegion, self).get_parser(prog_name)
@@ -87,8 +87,7 @@ class DeleteRegion(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete region with "
- "ID '%(region)s': %(e)s")
- % {'region': i, 'e': e})
+ "ID '%(region)s': %(e)s"), {'region': i, 'e': e})
if result > 0:
total = len(parsed_args.region)
@@ -98,7 +97,7 @@ class DeleteRegion(command.Command):
class ListRegion(command.Lister):
- """List regions"""
+ _description = _("List regions")
def get_parser(self, prog_name):
parser = super(ListRegion, self).get_parser(prog_name)
@@ -128,7 +127,7 @@ class ListRegion(command.Lister):
class SetRegion(command.Command):
- """Set region properties"""
+ _description = _("Set region properties")
def get_parser(self, prog_name):
parser = super(SetRegion, self).get_parser(prog_name)
@@ -162,7 +161,7 @@ class SetRegion(command.Command):
class ShowRegion(command.ShowOne):
- """Display region details"""
+ _description = _("Display region details")
def get_parser(self, prog_name):
parser = super(ShowRegion, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index 8b911746..1bbf5f07 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -16,10 +16,10 @@
"""Identity v3 Role action implementations"""
import logging
-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
@@ -109,7 +109,8 @@ def _process_identity_and_resource_options(parsed_args,
class AddRole(command.Command):
- """Adds a role assignment to a user or group on a domain or project"""
+ _description = _("Adds a role assignment to a user or group on a domain "
+ "or project")
def get_parser(self, prog_name):
parser = super(AddRole, self).get_parser(prog_name)
@@ -127,7 +128,9 @@ class AddRole(command.Command):
if (not parsed_args.user and not parsed_args.domain
and not parsed_args.group and not parsed_args.project):
- return
+ msg = _("Role not added, incorrect set of arguments "
+ "provided. See openstack --help for more details")
+ raise exceptions.CommandError(msg)
domain_id = None
if parsed_args.role_domain:
@@ -141,17 +144,12 @@ class AddRole(command.Command):
kwargs = _process_identity_and_resource_options(
parsed_args, self.app.client_manager.identity)
- if not kwargs:
- sys.stderr.write(_("Role not added, incorrect set of arguments "
- "provided. See openstack --help for more "
- "details\n"))
- return
identity_client.roles.grant(role.id, **kwargs)
class CreateRole(command.ShowOne):
- """Create new role"""
+ _description = _("Create new role")
def get_parser(self, prog_name):
parser = super(CreateRole, self).get_parser(prog_name)
@@ -198,7 +196,7 @@ class CreateRole(command.ShowOne):
class DeleteRole(command.Command):
- """Delete role(s)"""
+ _description = _("Delete role(s)")
def get_parser(self, prog_name):
parser = super(DeleteRole, self).get_parser(prog_name)
@@ -222,18 +220,30 @@ 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):
- """List roles"""
+ _description = _("List roles")
def get_parser(self, prog_name):
parser = super(ListRole, self).get_parser(prog_name)
@@ -358,10 +368,10 @@ class ListRole(command.Lister):
'<group-name> --project <project-name> --names '
'instead.'))
else:
- sys.stderr.write(_("Error: If a user or group is specified, "
- "either --domain or --project must also be "
- "specified to list role grants.\n"))
- return ([], [])
+ msg = _("Error: If a user or group is specified, "
+ "either --domain or --project must also be "
+ "specified to list role grants.")
+ raise exceptions.CommandError(msg)
return (columns,
(utils.get_item_properties(
@@ -371,7 +381,8 @@ class ListRole(command.Lister):
class RemoveRole(command.Command):
- """Removes a role assignment from domain/project : user/group"""
+ _description = _("Removes a role assignment from domain/project : "
+ "user/group")
def get_parser(self, prog_name):
parser = super(RemoveRole, self).get_parser(prog_name)
@@ -390,9 +401,9 @@ class RemoveRole(command.Command):
if (not parsed_args.user and not parsed_args.domain
and not parsed_args.group and not parsed_args.project):
- sys.stderr.write(_("Incorrect set of arguments provided. "
- "See openstack --help for more details\n"))
- return
+ msg = _("Incorrect set of arguments provided. "
+ "See openstack --help for more details")
+ raise exceptions.CommandError(msg)
domain_id = None
if parsed_args.role_domain:
@@ -406,16 +417,11 @@ class RemoveRole(command.Command):
kwargs = _process_identity_and_resource_options(
parsed_args, self.app.client_manager.identity)
- if not kwargs:
- sys.stderr.write(_("Role not removed, incorrect set of arguments "
- "provided. See openstack --help for more "
- "details\n"))
- return
identity_client.roles.revoke(role.id, **kwargs)
class SetRole(command.Command):
- """Set role properties"""
+ _description = _("Set role properties")
def get_parser(self, prog_name):
parser = super(SetRole, self).get_parser(prog_name)
@@ -452,7 +458,7 @@ class SetRole(command.Command):
class ShowRole(command.ShowOne):
- """Display role details"""
+ _description = _("Display role details")
def get_parser(self, prog_name):
parser = super(ShowRole, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py
index d25cc6ce..9da050de 100644
--- a/openstackclient/identity/v3/role_assignment.py
+++ b/openstackclient/identity/v3/role_assignment.py
@@ -11,7 +11,7 @@
# under the License.
#
-"""Identity v3 Assignment action implementations """
+"""Identity v3 Assignment action implementations"""
from osc_lib.command import command
from osc_lib import utils
@@ -21,7 +21,7 @@ from openstackclient.identity import common
class ListRoleAssignment(command.Lister):
- """List role assignments"""
+ _description = _("List role assignments")
def get_parser(self, prog_name):
parser = super(ListRoleAssignment, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py
index 97e64dc6..ac8d8d9e 100644
--- a/openstackclient/identity/v3/service.py
+++ b/openstackclient/identity/v3/service.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateService(command.ShowOne):
- """Create new service"""
+ _description = _("Create new service")
def get_parser(self, prog_name):
parser = super(CreateService, self).get_parser(prog_name)
@@ -81,7 +81,7 @@ class CreateService(command.ShowOne):
class DeleteService(command.Command):
- """Delete service(s)"""
+ _description = _("Delete service(s)")
def get_parser(self, prog_name):
parser = super(DeleteService, self).get_parser(prog_name)
@@ -103,8 +103,8 @@ class DeleteService(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete consumer with type, "
- "name or ID '%(service)s': %(e)s")
- % {'service': i, 'e': e})
+ "name or ID '%(service)s': %(e)s"),
+ {'service': i, 'e': e})
if result > 0:
total = len(parsed_args.service)
@@ -114,7 +114,7 @@ class DeleteService(command.Command):
class ListService(command.Lister):
- """List services"""
+ _description = _("List services")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
@@ -140,7 +140,7 @@ class ListService(command.Lister):
class SetService(command.Command):
- """Set service properties"""
+ _description = _("Set service properties")
def get_parser(self, prog_name):
parser = super(SetService, self).get_parser(prog_name)
@@ -201,7 +201,7 @@ class SetService(command.Command):
class ShowService(command.ShowOne):
- """Display service details"""
+ _description = _("Display service details")
def get_parser(self, prog_name):
parser = super(ShowService, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py
index 8548ae1f..bb2d9917 100644
--- a/openstackclient/identity/v3/service_provider.py
+++ b/openstackclient/identity/v3/service_provider.py
@@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
class CreateServiceProvider(command.ShowOne):
- """Create new service provider"""
+ _description = _("Create new service provider")
def get_parser(self, prog_name):
parser = super(CreateServiceProvider, self).get_parser(prog_name)
@@ -87,7 +87,7 @@ class CreateServiceProvider(command.ShowOne):
class DeleteServiceProvider(command.Command):
- """Delete service provider(s)"""
+ _description = _("Delete service provider(s)")
def get_parser(self, prog_name):
parser = super(DeleteServiceProvider, self).get_parser(prog_name)
@@ -108,8 +108,8 @@ class DeleteServiceProvider(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete service provider with "
- "name or ID '%(provider)s': %(e)s")
- % {'provider': i, 'e': e})
+ "name or ID '%(provider)s': %(e)s"),
+ {'provider': i, 'e': e})
if result > 0:
total = len(parsed_args.service_provider)
@@ -119,7 +119,7 @@ class DeleteServiceProvider(command.Command):
class ListServiceProvider(command.Lister):
- """List service providers"""
+ _description = _("List service providers")
def take_action(self, parsed_args):
service_client = self.app.client_manager.identity
@@ -134,7 +134,7 @@ class ListServiceProvider(command.Lister):
class SetServiceProvider(command.Command):
- """Set service provider properties"""
+ _description = _("Set service provider properties")
def get_parser(self, prog_name):
parser = super(SetServiceProvider, self).get_parser(prog_name)
@@ -192,7 +192,7 @@ class SetServiceProvider(command.Command):
class ShowServiceProvider(command.ShowOne):
- """Display service provider details"""
+ _description = _("Display service provider details")
def get_parser(self, prog_name):
parser = super(ShowServiceProvider, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py
index 2cd304e6..effb9e35 100644
--- a/openstackclient/identity/v3/token.py
+++ b/openstackclient/identity/v3/token.py
@@ -25,7 +25,7 @@ from openstackclient.identity import common
class AuthorizeRequestToken(command.ShowOne):
- """Authorize a request token"""
+ _description = _("Authorize a request token")
def get_parser(self, prog_name):
parser = super(AuthorizeRequestToken, self).get_parser(prog_name)
@@ -42,7 +42,7 @@ class AuthorizeRequestToken(command.ShowOne):
default=[],
required=True,
help=_('Roles to authorize (name or ID) '
- '(repeat option to set multiple values, required)'),
+ '(repeat option to set multiple values) (required)'),
)
return parser
@@ -66,7 +66,7 @@ class AuthorizeRequestToken(command.ShowOne):
class CreateAccessToken(command.ShowOne):
- """Create an access token"""
+ _description = _("Create an access token")
def get_parser(self, prog_name):
parser = super(CreateAccessToken, self).get_parser(prog_name)
@@ -112,7 +112,7 @@ class CreateAccessToken(command.ShowOne):
class CreateRequestToken(command.ShowOne):
- """Create a request token"""
+ _description = _("Create a request token")
def get_parser(self, prog_name):
parser = super(CreateRequestToken, self).get_parser(prog_name)
@@ -164,7 +164,7 @@ class CreateRequestToken(command.ShowOne):
class IssueToken(command.ShowOne):
- """Issue new token"""
+ _description = _("Issue new token")
# scoped token is optional
required_scope = False
@@ -196,7 +196,7 @@ class IssueToken(command.ShowOne):
class RevokeToken(command.Command):
- """Revoke existing token"""
+ _description = _("Revoke existing token")
def get_parser(self, prog_name):
parser = super(RevokeToken, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py
index bbc86adb..52daeb4d 100644
--- a/openstackclient/identity/v3/trust.py
+++ b/openstackclient/identity/v3/trust.py
@@ -14,8 +14,11 @@
"""Identity v3 Trust action implementations"""
import datetime
+import logging
+from keystoneclient import exceptions as identity_exc
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
import six
@@ -23,8 +26,11 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateTrust(command.ShowOne):
- """Create new trust"""
+ _description = _("Create new trust")
def get_parser(self, prog_name):
parser = super(CreateTrust, self).get_parser(prog_name)
@@ -100,10 +106,13 @@ class CreateTrust(command.ShowOne):
role_names = []
for role in parsed_args.role:
- role_name = utils.find_resource(
- identity_client.roles,
- role,
- ).name
+ try:
+ role_name = utils.find_resource(
+ identity_client.roles,
+ role,
+ ).name
+ except identity_exc.Forbidden:
+ role_name = role
role_names.append(role_name)
expires_at = None
@@ -131,7 +140,7 @@ class CreateTrust(command.ShowOne):
class DeleteTrust(command.Command):
- """Delete trust(s)"""
+ _description = _("Delete trust(s)")
def get_parser(self, prog_name):
parser = super(DeleteTrust, self).get_parser(prog_name)
@@ -145,13 +154,28 @@ 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):
- """List trusts"""
+ _description = _("List trusts")
def take_action(self, parsed_args):
columns = ('ID', 'Expires At', 'Impersonation', 'Project ID',
@@ -165,7 +189,7 @@ class ListTrust(command.Lister):
class ShowTrust(command.ShowOne):
- """Display trust details"""
+ _description = _("Display trust details")
def get_parser(self, prog_name):
parser = super(ShowTrust, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py
index f116174b..f7598f17 100644
--- a/openstackclient/identity/v3/unscoped_saml.py
+++ b/openstackclient/identity/v3/unscoped_saml.py
@@ -18,35 +18,14 @@ the user can list domains and projects they are allowed to access, and request
a scoped token."""
from osc_lib.command import command
-from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
-UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc']
-
-
-def auth_with_unscoped_saml(func):
- """Check the unscoped federated context"""
-
- def _decorated(self, parsed_args):
- auth_plugin_name = self.app.client_manager.auth_plugin_name
- if auth_plugin_name in UNSCOPED_AUTH_PLUGINS:
- return func(self, parsed_args)
- else:
- msg = (_('This command requires the use of an unscoped SAML '
- 'authentication plugin. Please use argument '
- '--os-auth-type with one of the following '
- 'plugins: %s') % ', '.join(UNSCOPED_AUTH_PLUGINS))
- raise exceptions.CommandError(msg)
- return _decorated
-
-
class ListAccessibleDomains(command.Lister):
- """List accessible domains"""
+ _description = _("List accessible domains")
- @auth_with_unscoped_saml
def take_action(self, parsed_args):
columns = ('ID', 'Enabled', 'Name', 'Description')
identity_client = self.app.client_manager.identity
@@ -59,9 +38,8 @@ class ListAccessibleDomains(command.Lister):
class ListAccessibleProjects(command.Lister):
- """List accessible projects"""
+ _description = _("List accessible projects")
- @auth_with_unscoped_saml
def take_action(self, parsed_args):
columns = ('ID', 'Domain ID', 'Enabled', 'Name')
identity_client = self.app.client_manager.identity
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index dc47ef8d..9c289a6d 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
@@ -31,7 +32,7 @@ LOG = logging.getLogger(__name__)
class CreateUser(command.ShowOne):
- """Create new user"""
+ _description = _("Create new user")
def get_parser(self, prog_name):
parser = super(CreateUser, self).get_parser(prog_name)
@@ -110,6 +111,10 @@ class CreateUser(command.ShowOne):
if parsed_args.password_prompt:
parsed_args.password = utils.get_password(self.app.stdin)
+ if not parsed_args.password:
+ LOG.warning(_("No password was supplied, authentication will fail "
+ "when a user does not have a password."))
+
try:
user = identity_client.users.create(
name=parsed_args.name,
@@ -134,7 +139,7 @@ class CreateUser(command.ShowOne):
class DeleteUser(command.Command):
- """Delete user(s)"""
+ _description = _("Delete user(s)")
def get_parser(self, prog_name):
parser = super(DeleteUser, self).get_parser(prog_name)
@@ -157,19 +162,32 @@ 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):
- """List users"""
+ _description = _("List users")
def get_parser(self, prog_name):
parser = super(ListUser, self).get_parser(prog_name)
@@ -269,7 +287,7 @@ class ListUser(command.Lister):
class SetUser(command.Command):
- """Set user properties"""
+ _description = _("Set user properties")
def get_parser(self, prog_name):
parser = super(SetUser, self).get_parser(prog_name)
@@ -284,6 +302,12 @@ class SetUser(command.Command):
help=_('Set user name'),
)
parser.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help=_('Domain the user belongs to (name or ID). This can be '
+ 'used in case collisions between user names exist.'),
+ )
+ parser.add_argument(
'--project',
metavar='<project>',
help=_('Set default project (name or ID)'),
@@ -329,10 +353,23 @@ class SetUser(command.Command):
if parsed_args.password_prompt:
parsed_args.password = utils.get_password(self.app.stdin)
- user = utils.find_resource(
- identity_client.users,
- parsed_args.user,
- )
+ if '' == parsed_args.password:
+ LOG.warning(_("No password was supplied, authentication will fail "
+ "when a user does not have a password."))
+
+ user_str = common._get_token_resource(identity_client, 'user',
+ parsed_args.user)
+ if parsed_args.domain:
+ domain = common.find_domain(identity_client, parsed_args.domain)
+ user = utils.find_resource(identity_client.users,
+ user_str,
+ domain_id=domain.id)
+ else:
+ user = utils.find_resource(
+ identity_client.users,
+ parsed_args.user,
+ )
+
kwargs = {}
if parsed_args.name:
kwargs['name'] = parsed_args.name
@@ -357,7 +394,7 @@ class SetUser(command.Command):
class SetPasswordUser(command.Command):
- """Change current user password"""
+ _description = _("Change current user password")
required_scope = False
@@ -408,11 +445,15 @@ class SetPasswordUser(command.Command):
password = utils.get_password(
self.app.stdin, prompt="New Password:")
+ if '' == password:
+ LOG.warning(_("No password was supplied, authentication will fail "
+ "when a user does not have a password."))
+
identity_client.users.update_password(current_password, password)
class ShowUser(command.ShowOne):
- """Display user details"""
+ _description = _("Display user details")
def get_parser(self, prog_name):
parser = super(ShowUser, self).get_parser(prog_name)
diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py
index 1be6c765..b67c291f 100644
--- a/openstackclient/image/client.py
+++ b/openstackclient/image/client.py
@@ -22,7 +22,7 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
-DEFAULT_API_VERSION = '1'
+DEFAULT_API_VERSION = '2'
API_VERSION_OPTION = 'os_image_api_version'
API_NAME = "image"
API_VERSIONS = {
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 5f669c64..60d61709 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -36,8 +36,11 @@ from openstackclient.api import utils as api_utils
from openstackclient.i18n import _
+CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"]
DEFAULT_CONTAINER_FORMAT = 'bare'
DEFAULT_DISK_FORMAT = 'raw'
+DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx",
+ "vdi", "iso", "ploop"]
LOG = logging.getLogger(__name__)
@@ -59,7 +62,7 @@ def _format_visibility(data):
class CreateImage(command.ShowOne):
- """Create/upload an image"""
+ _description = _("Create/upload an image")
def get_parser(self, prog_name):
parser = super(CreateImage, self).get_parser(prog_name)
@@ -82,15 +85,20 @@ class CreateImage(command.ShowOne):
"--container-format",
default=DEFAULT_CONTAINER_FORMAT,
metavar="<container-format>",
- help=_("Image container format "
- "(default: %s)") % DEFAULT_CONTAINER_FORMAT,
+ choices=CONTAINER_CHOICES,
+ help=(_("Image container format. "
+ "The supported options are: %(option_list)s. "
+ "The default format is: %(default_opt)s") %
+ {'option_list': ', '.join(CONTAINER_CHOICES),
+ 'default_opt': DEFAULT_CONTAINER_FORMAT})
)
parser.add_argument(
"--disk-format",
default=DEFAULT_DISK_FORMAT,
metavar="<disk-format>",
- help=_("Image disk format "
- "(default: %s)") % DEFAULT_DISK_FORMAT,
+ choices=DISK_CHOICES,
+ help=_("Image disk format. The supported options are: %s. "
+ "The default format is: raw") % ', '.join(DISK_CHOICES)
)
parser.add_argument(
"--size",
@@ -120,12 +128,13 @@ class CreateImage(command.ShowOne):
metavar="<image-url>",
help=_("Copy image from the data store (similar to --location)"),
)
- parser.add_argument(
+ source_group = parser.add_mutually_exclusive_group()
+ source_group.add_argument(
"--file",
metavar="<file>",
help=_("Upload image from local file"),
)
- parser.add_argument(
+ source_group.add_argument(
"--volume",
metavar="<volume>",
help=_("Create image from a volume"),
@@ -277,7 +286,7 @@ class CreateImage(command.ShowOne):
class DeleteImage(command.Command):
- """Delete image(s)"""
+ _description = _("Delete image(s)")
def get_parser(self, prog_name):
parser = super(DeleteImage, self).get_parser(prog_name)
@@ -300,7 +309,7 @@ class DeleteImage(command.Command):
class ListImage(command.Lister):
- """List available images"""
+ _description = _("List available images")
def get_parser(self, prog_name):
parser = super(ListImage, self).get_parser(prog_name)
@@ -350,8 +359,9 @@ class ListImage(command.Lister):
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
+ default='name:asc',
help=_("Sort output by selected keys and directions(asc or desc) "
- "(default: asc), multiple keys and directions can be "
+ "(default: name:asc), multiple keys and directions can be "
"specified separated by comma"),
)
return parser
@@ -440,7 +450,7 @@ class ListImage(command.Lister):
class SaveImage(command.Command):
- """Save an image locally"""
+ _description = _("Save an image locally")
def get_parser(self, prog_name):
parser = super(SaveImage, self).get_parser(prog_name)
@@ -468,7 +478,7 @@ class SaveImage(command.Command):
class SetImage(command.Command):
- """Set image properties"""
+ _description = _("Set image properties")
def get_parser(self, prog_name):
parser = super(SetImage, self).get_parser(prog_name)
@@ -494,22 +504,19 @@ class SetImage(command.Command):
type=int,
help=_("Minimum RAM size needed to boot image, in megabytes"),
)
- container_choices = ["ami", "ari", "aki", "bare", "ovf"]
parser.add_argument(
"--container-format",
metavar="<container-format>",
- help=_("Container format of image. Acceptable formats: %s") %
- container_choices,
- choices=container_choices
+ choices=CONTAINER_CHOICES,
+ help=_("Image container format. The supported options are: %s") %
+ ', '.join(CONTAINER_CHOICES)
)
- disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2",
- "vdi", "iso"]
parser.add_argument(
"--disk-format",
metavar="<disk-format>",
- help=_("Disk format of image. Acceptable formats: %s") %
- disk_choices,
- choices=disk_choices
+ choices=DISK_CHOICES,
+ help=_("Image disk format. The supported options are: %s.") %
+ ', '.join(DISK_CHOICES)
)
parser.add_argument(
"--size",
@@ -702,7 +709,7 @@ class SetImage(command.Command):
class ShowImage(command.ShowOne):
- """Display image details"""
+ _description = _("Display image details")
def get_parser(self, prog_name):
parser = super(ShowImage, self).get_parser(prog_name)
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 0712e09c..c4be69f0 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -30,15 +30,18 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"]
DEFAULT_CONTAINER_FORMAT = 'bare'
DEFAULT_DISK_FORMAT = 'raw'
+DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx",
+ "vdi", "iso", "ploop"]
LOG = logging.getLogger(__name__)
def _format_image(image):
- """Format an image to make it more consistent with OSC operations. """
+ """Format an image to make it more consistent with OSC operations."""
info = {}
properties = {}
@@ -69,7 +72,7 @@ def _format_image(image):
class AddProjectToImage(command.ShowOne):
- """Associate project with image"""
+ _description = _("Associate project with image")
def get_parser(self, prog_name):
parser = super(AddProjectToImage, self).get_parser(prog_name)
@@ -107,7 +110,7 @@ class AddProjectToImage(command.ShowOne):
class CreateImage(command.ShowOne):
- """Create/upload an image"""
+ _description = _("Create/upload an image")
deadopts = ('size', 'location', 'copy-from', 'checksum', 'store')
@@ -133,16 +136,21 @@ class CreateImage(command.ShowOne):
parser.add_argument(
"--container-format",
default=DEFAULT_CONTAINER_FORMAT,
+ choices=CONTAINER_CHOICES,
metavar="<container-format>",
- help=_("Image container format "
- "(default: %s)") % DEFAULT_CONTAINER_FORMAT,
+ help=(_("Image container format. "
+ "The supported options are: %(option_list)s. "
+ "The default format is: %(default_opt)s") %
+ {'option_list': ', '.join(CONTAINER_CHOICES),
+ 'default_opt': DEFAULT_CONTAINER_FORMAT})
)
parser.add_argument(
"--disk-format",
default=DEFAULT_DISK_FORMAT,
+ choices=DISK_CHOICES,
metavar="<disk-format>",
- help=_("Image disk format "
- "(default: %s)") % DEFAULT_DISK_FORMAT,
+ help=_("Image disk format. The supported options are: %s. "
+ "The default format is: raw") % ', '.join(DISK_CHOICES)
)
parser.add_argument(
"--min-disk",
@@ -156,12 +164,13 @@ class CreateImage(command.ShowOne):
type=int,
help=_("Minimum RAM size needed to boot image, in megabytes"),
)
- parser.add_argument(
+ source_group = parser.add_mutually_exclusive_group()
+ source_group.add_argument(
"--file",
metavar="<file>",
help=_("Upload image from local file"),
)
- parser.add_argument(
+ source_group.add_argument(
"--volume",
metavar="<volume>",
help=_("Create image from a volume"),
@@ -361,7 +370,7 @@ class CreateImage(command.ShowOne):
class DeleteImage(command.Command):
- """Delete image(s)"""
+ _description = _("Delete image(s)")
def get_parser(self, prog_name):
parser = super(DeleteImage, self).get_parser(prog_name)
@@ -398,7 +407,7 @@ class DeleteImage(command.Command):
class ListImage(command.Lister):
- """List available images"""
+ _description = _("List available images")
def get_parser(self, prog_name):
parser = super(ListImage, self).get_parser(prog_name)
@@ -447,8 +456,9 @@ class ListImage(command.Lister):
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
+ default='name:asc',
help=_("Sort output by selected keys and directions(asc or desc) "
- "(default: asc), multiple keys and directions can be "
+ "(default: name:asc), multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
@@ -482,7 +492,6 @@ class ListImage(command.Lister):
if parsed_args.marker:
kwargs['marker'] = utils.find_resource(image_client.images,
parsed_args.marker).id
-
if parsed_args.long:
columns = (
'ID',
@@ -515,7 +524,19 @@ class ListImage(command.Lister):
column_headers = columns
# List of image data received
- data = image_client.api.image_list(**kwargs)
+ data = []
+ if 'marker' in kwargs:
+ data = image_client.api.image_list(**kwargs)
+ else:
+ # No pages received yet, so start the page marker at None.
+ marker = None
+ while True:
+ page = image_client.api.image_list(marker=marker, **kwargs)
+ if not page:
+ break
+ data.extend(page)
+ # Set the marker to the id of the last item we received
+ marker = page[-1]['id']
if parsed_args.property:
# NOTE(dtroyer): coerce to a list to subscript it in py3
@@ -542,7 +563,7 @@ class ListImage(command.Lister):
class RemoveProjectImage(command.Command):
- """Disassociate project with image"""
+ _description = _("Disassociate project with image")
def get_parser(self, prog_name):
parser = super(RemoveProjectImage, self).get_parser(prog_name)
@@ -575,7 +596,7 @@ class RemoveProjectImage(command.Command):
class SaveImage(command.Command):
- """Save an image locally"""
+ _description = _("Save an image locally")
def get_parser(self, prog_name):
parser = super(SaveImage, self).get_parser(prog_name)
@@ -603,7 +624,7 @@ class SaveImage(command.Command):
class SetImage(command.Command):
- """Set image properties"""
+ _description = _("Set image properties")
deadopts = ('visibility',)
@@ -644,14 +665,16 @@ class SetImage(command.Command):
parser.add_argument(
"--container-format",
metavar="<container-format>",
- help=_("Image container format "
- "(default: %s)") % DEFAULT_CONTAINER_FORMAT,
+ choices=CONTAINER_CHOICES,
+ help=_("Image container format. The supported options are: %s") %
+ ', '.join(CONTAINER_CHOICES)
)
parser.add_argument(
"--disk-format",
metavar="<disk-format>",
- help=_("Image disk format "
- "(default: %s)") % DEFAULT_DISK_FORMAT,
+ choices=DISK_CHOICES,
+ help=_("Image disk format. The supported options are: %s") %
+ ', '.join(DISK_CHOICES)
)
protected_group = parser.add_mutually_exclusive_group()
protected_group.add_argument(
@@ -761,6 +784,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):
@@ -812,12 +852,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)
@@ -830,6 +872,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)))
@@ -844,7 +901,7 @@ class SetImage(command.Command):
class ShowImage(command.ShowOne):
- """Display image details"""
+ _description = _("Display image details")
def get_parser(self, prog_name):
parser = super(ShowImage, self).get_parser(prog_name)
@@ -867,7 +924,7 @@ class ShowImage(command.ShowOne):
class UnsetImage(command.Command):
- """Unset image tags and properties"""
+ _description = _("Unset image tags and properties")
def get_parser(self, prog_name):
parser = super(UnsetImage, self).get_parser(prog_name)
diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py
index c562058d..9525b947 100644
--- a/openstackclient/network/client.py
+++ b/openstackclient/network/client.py
@@ -44,6 +44,11 @@ def make_client(instance):
LOG.debug('Connection: %s', conn)
LOG.debug('Network client initialized using OpenStack SDK: %s',
conn.network)
+
+ # NOTE(dtroyer): Horrible ugly hack since we don't actually save
+ # the connection anywhere yet, so stash it in the
+ # instance directly from here for other uses
+ instance.sdk_connection = conn
return conn.network
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index 2b1a5656..a2e700be 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -14,6 +14,7 @@
import abc
import logging
+import openstack.exceptions
from osc_lib.command import command
from osc_lib import exceptions
import six
@@ -181,12 +182,16 @@ class NetworkAndComputeShowOne(command.ShowOne):
"""
def take_action(self, parsed_args):
- if self.app.client_manager.is_network_endpoint_enabled():
- return self.take_action_network(self.app.client_manager.network,
- parsed_args)
- else:
- return self.take_action_compute(self.app.client_manager.compute,
- parsed_args)
+ try:
+ if self.app.client_manager.is_network_endpoint_enabled():
+ return self.take_action_network(
+ self.app.client_manager.network, parsed_args)
+ else:
+ return self.take_action_compute(
+ self.app.client_manager.compute, parsed_args)
+ except openstack.exceptions.HttpException as exc:
+ msg = _("Error while executing command: %s") % exc.message
+ raise exceptions.CommandError(msg)
def get_parser(self, prog_name):
LOG.debug('get_parser(%s)', prog_name)
diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py
new file mode 100644
index 00000000..04f168be
--- /dev/null
+++ b/openstackclient/network/sdk_utils.py
@@ -0,0 +1,59 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import six
+
+
+def get_osc_show_columns_for_sdk_resource(
+ sdk_resource,
+ osc_column_map,
+ invisible_columns=[]
+):
+ """Get and filter the display and attribute columns for an SDK resource.
+
+ Common utility function for preparing the output of an OSC show command.
+ Some of the columns may need to get renamed, others made invisible.
+
+ :param sdk_resource: An SDK resource
+ :param osc_column_map: A hash of mappings for display column names
+ :param invisible_columns: A list of invisible column names
+
+ :returns: Two tuples containing the names of the display and attribute
+ columns
+ """
+
+ if getattr(sdk_resource, 'allow_get', None) is not None:
+ resource_dict = sdk_resource.to_dict(
+ body=True, headers=False, ignore_none=False)
+ else:
+ resource_dict = sdk_resource
+
+ # Build the OSC column names to display for the SDK resource.
+ attr_map = {}
+ display_columns = list(resource_dict.keys())
+ for col_name in invisible_columns:
+ if col_name in display_columns:
+ display_columns.remove(col_name)
+ for sdk_attr, osc_attr in six.iteritems(osc_column_map):
+ if sdk_attr in display_columns:
+ attr_map[osc_attr] = sdk_attr
+ display_columns.remove(sdk_attr)
+ if osc_attr not in display_columns:
+ display_columns.append(osc_attr)
+ sorted_display_columns = sorted(display_columns)
+
+ # Build the SDK attribute names for the OSC column names.
+ attr_columns = []
+ for column in sorted_display_columns:
+ new_column = attr_map[column] if column in attr_map else column
+ attr_columns.append(new_column)
+ return tuple(sorted_display_columns), tuple(attr_columns)
diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py
index 6cd13f8c..71c1a9af 100644
--- a/openstackclient/network/v2/address_scope.py
+++ b/openstackclient/network/v2/address_scope.py
@@ -21,18 +21,18 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
-
- return tuple(sorted(columns))
+ column_map = {
+ 'is_shared': 'shared',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
def _get_attrs(client_manager, parsed_args):
@@ -55,8 +55,10 @@ def _get_attrs(client_manager, parsed_args):
return attrs
+# TODO(rtheis): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateAddressScope(command.ShowOne):
- """Create a new Address Scope"""
+ _description = _("Create a new Address Scope")
def get_parser(self, prog_name):
parser = super(CreateAddressScope, self).get_parser(prog_name)
@@ -97,14 +99,14 @@ class CreateAddressScope(command.ShowOne):
client = self.app.client_manager.network
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_address_scope(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters={})
- return (columns, data)
+ return (display_columns, data)
class DeleteAddressScope(command.Command):
- """Delete address scope(s)"""
+ _description = _("Delete address scope(s)")
def get_parser(self, prog_name):
parser = super(DeleteAddressScope, self).get_parser(prog_name)
@@ -138,8 +140,47 @@ class DeleteAddressScope(command.Command):
raise exceptions.CommandError(msg)
+# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListAddressScope(command.Lister):
- """List address scopes"""
+ _description = _("List address scopes")
+
+ def get_parser(self, prog_name):
+ parser = super(ListAddressScope, self).get_parser(prog_name)
+
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help=_("List only address scopes of given name in output")
+ )
+ parser.add_argument(
+ '--ip-version',
+ type=int,
+ choices=[4, 6],
+ metavar='<ip-version>',
+ dest='ip_version',
+ help=_("List address scopes of given IP version networks (4 or 6)")
+ )
+ parser.add_argument(
+ '--project',
+ metavar="<project>",
+ help=_("List address scopes according to their project "
+ "(name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+
+ shared_group = parser.add_mutually_exclusive_group()
+ shared_group.add_argument(
+ '--share',
+ action='store_true',
+ help=_("List address scopes shared between projects")
+ )
+ shared_group.add_argument(
+ '--no-share',
+ action='store_true',
+ help=_("List address scopes not shared between projects")
+ )
+ return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
@@ -147,8 +188,8 @@ class ListAddressScope(command.Lister):
'id',
'name',
'ip_version',
- 'shared',
- 'tenant_id',
+ 'is_shared',
+ 'project_id',
)
column_headers = (
'ID',
@@ -157,7 +198,25 @@ class ListAddressScope(command.Lister):
'Shared',
'Project',
)
- data = client.address_scopes()
+ attrs = {}
+ if parsed_args.name:
+ attrs['name'] = parsed_args.name
+ if parsed_args.ip_version:
+ attrs['ip_version'] = parsed_args.ip_version
+ if parsed_args.share:
+ attrs['is_shared'] = True
+ if parsed_args.no_share:
+ attrs['is_shared'] = False
+ if 'project' in parsed_args and parsed_args.project is not None:
+ identity_client = self.app.client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+ attrs['project_id'] = project_id
+ data = client.address_scopes(**attrs)
return (column_headers,
(utils.get_item_properties(
@@ -165,8 +224,10 @@ class ListAddressScope(command.Lister):
) for s in data))
+# TODO(rtheis): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetAddressScope(command.Command):
- """Set address scope properties"""
+ _description = _("Set address scope properties")
def get_parser(self, prog_name):
parser = super(SetAddressScope, self).get_parser(prog_name)
@@ -210,7 +271,7 @@ class SetAddressScope(command.Command):
class ShowAddressScope(command.ShowOne):
- """Display address scope details"""
+ _description = _("Display address scope details")
def get_parser(self, prog_name):
parser = super(ShowAddressScope, self).get_parser(prog_name)
@@ -227,7 +288,7 @@ class ShowAddressScope(command.ShowOne):
obj = client.find_address_scope(
parsed_args.address_scope,
ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters={})
- return (columns, data)
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index bb75540c..41b208aa 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -15,10 +15,21 @@
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 _
+from openstackclient.identity import common as identity_common
from openstackclient.network import common
+from openstackclient.network import sdk_utils
+
+
+def _get_network_columns(item):
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
def _get_columns(item):
@@ -58,11 +69,72 @@ def _get_attrs(client_manager, parsed_args):
if parsed_args.description is not None:
attrs['description'] = parsed_args.description
+ if parsed_args.project:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
return attrs
+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):
- """Create floating IP"""
+ _description = _("Create floating IP")
def update_parser_common(self, parser):
# In Compute v2 network, floating IPs could be allocated from floating
@@ -105,14 +177,20 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne):
metavar='<description>',
help=_('Set floating IP description')
)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Owner's project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
return parser
def take_action_network(self, client, parsed_args):
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_ip(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns)
- return (columns, data)
+ return (display_columns, data)
def take_action_compute(self, client, parsed_args):
obj = client.floating_ips.create(parsed_args.network)
@@ -122,7 +200,7 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne):
class CreateIPFloating(CreateFloatingIP):
- """Create floating IP"""
+ _description = _("Create floating IP")
# TODO(tangchen): Remove this class and ``ip floating create`` command
# two cycles after Mitaka.
@@ -146,7 +224,7 @@ class CreateIPFloating(CreateFloatingIP):
class DeleteFloatingIP(common.NetworkAndComputeDelete):
- """Delete floating IP(s)"""
+ _description = _("Delete floating IP(s)")
# Used by base class to find resources in parsed_args.
resource = 'floating_ip'
@@ -162,16 +240,31 @@ 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(
+ self.app.client_manager.sdk_connection.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):
- """Delete floating IP(s)"""
+ _description = _("Delete floating IP(s)")
# TODO(tangchen): Remove this class and ``ip floating delete`` command
# two cycles after Mitaka.
@@ -195,23 +288,117 @@ class DeleteIPFloating(DeleteFloatingIP):
class ListFloatingIP(common.NetworkAndComputeLister):
- """List floating IP(s)"""
+ # TODO(songminglong): Use SDK resource mapped attribute names once
+ # the OSC minimum requirements include SDK 1.0
+ _description = _("List floating IP(s)")
+
+ def update_parser_network(self, parser):
+ parser.add_argument(
+ '--network',
+ metavar='<network>',
+ help=_("List floating IP(s) according to "
+ "given network (name or ID)")
+ )
+ parser.add_argument(
+ '--port',
+ metavar='<port>',
+ help=_("List floating IP(s) according to "
+ "given port (name or ID)")
+ )
+ parser.add_argument(
+ '--fixed-ip-address',
+ metavar='<fixed-ip-address>',
+ help=_("List floating IP(s) according to "
+ "given fixed IP address")
+ )
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ parser.add_argument(
+ '--status',
+ metavar='<status>',
+ choices=['ACTIVE', 'DOWN'],
+ help=_("List floating IP(s) according to "
+ "given status ('ACTIVE', 'DOWN')")
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("List floating IP(s) according to "
+ "given project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--router',
+ metavar='<router>',
+ help=_("List floating IP(s) according to "
+ "given router (name or ID)")
+ )
+
+ return parser
def take_action_network(self, client, parsed_args):
+ network_client = self.app.client_manager.network
+ identity_client = self.app.client_manager.identity
+
columns = (
'id',
'floating_ip_address',
'fixed_ip_address',
'port_id',
+ 'floating_network_id',
+ 'project_id',
)
headers = (
'ID',
'Floating IP Address',
'Fixed IP Address',
'Port',
+ 'Floating Network',
+ 'Project',
)
+ if parsed_args.long:
+ columns = columns + (
+ 'router_id',
+ 'status',
+ 'description',
+ )
+ headers = headers + (
+ 'Router',
+ 'Status',
+ 'Description',
+ )
query = {}
+
+ if parsed_args.network is not None:
+ network = network_client.find_network(parsed_args.network,
+ ignore_missing=False)
+ query['floating_network_id'] = network.id
+ if parsed_args.port is not None:
+ port = network_client.find_port(parsed_args.port,
+ ignore_missing=False)
+ query['port_id'] = port.id
+ if parsed_args.fixed_ip_address is not None:
+ query['fixed_ip_address'] = parsed_args.fixed_ip_address
+ if parsed_args.status:
+ query['status'] = parsed_args.status
+ if parsed_args.project is not None:
+ project = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ )
+ query['tenant_id'] = project.id
+ query['project_id'] = project.id
+ if parsed_args.router is not None:
+ router = network_client.find_router(parsed_args.router,
+ ignore_missing=False)
+ query['router_id'] = router.id
+
data = client.ips(**query)
return (headers,
@@ -246,7 +433,7 @@ class ListFloatingIP(common.NetworkAndComputeLister):
class ListIPFloating(ListFloatingIP):
- """List floating IP(s)"""
+ _description = _("List floating IP(s)")
# TODO(tangchen): Remove this class and ``ip floating list`` command
# two cycles after Mitaka.
@@ -270,7 +457,10 @@ class ListIPFloating(ListFloatingIP):
class ShowFloatingIP(common.NetworkAndComputeShowOne):
- """Display floating IP details"""
+ _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(
@@ -281,10 +471,15 @@ 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)
- columns = _get_columns(obj)
+ (obj, self.ip_cache) = _find_floating_ip(
+ self.app.client_manager.sdk_connection.session,
+ [],
+ parsed_args.floating_ip,
+ ignore_missing=False,
+ )
+ display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns)
- return (columns, data)
+ return (display_columns, data)
def take_action_compute(self, client, parsed_args):
obj = utils.find_resource(
@@ -297,7 +492,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne):
class ShowIPFloating(ShowFloatingIP):
- """Display floating IP details"""
+ _description = _("Display floating IP details")
# TODO(tangchen): Remove this class and ``ip floating show`` command
# two cycles after Mitaka.
diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py
index c78ca06a..73e94ead 100644
--- a/openstackclient/network/v2/floating_ip_pool.py
+++ b/openstackclient/network/v2/floating_ip_pool.py
@@ -23,7 +23,7 @@ from openstackclient.network import common
class ListFloatingIPPool(common.NetworkAndComputeLister):
- """List pools of floating IP addresses"""
+ _description = _("List pools of floating IP addresses")
def take_action_network(self, client, parsed_args):
msg = _("Floating ip pool operations are only available for "
@@ -43,7 +43,7 @@ class ListFloatingIPPool(common.NetworkAndComputeLister):
class ListIPFloatingPool(ListFloatingIPPool):
- """List pools of floating IP addresses"""
+ _description = _("List pools of floating IP addresses")
# TODO(tangchen): Remove this class and ``ip floating pool list`` command
# two cycles after Mitaka.
diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py
index 1d7b2aed..1d963580 100644
--- a/openstackclient/network/v2/ip_availability.py
+++ b/openstackclient/network/v2/ip_availability.py
@@ -18,7 +18,7 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
-
+from openstackclient.network import sdk_utils
_formatters = {
'subnet_ip_availability': utils.format_list_of_dicts,
@@ -26,15 +26,16 @@ _formatters = {
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once
+# the OSC minimum requirements include SDK 1.0.
class ListIPAvailability(command.Lister):
- """List IP availability for network"""
+ _description = _("List IP availability for network")
def get_parser(self, prog_name):
parser = super(ListIPAvailability, self).get_parser(prog_name)
@@ -84,6 +85,7 @@ class ListIPAvailability(command.Lister):
parsed_args.project_domain,
).id
filters['tenant_id'] = project_id
+ filters['project_id'] = project_id
data = client.network_ip_availabilities(**filters)
return (column_headers,
(utils.get_item_properties(
@@ -92,7 +94,7 @@ class ListIPAvailability(command.Lister):
class ShowIPAvailability(command.ShowOne):
- """Show network IP availability details"""
+ _description = _("Show network IP availability details")
def get_parser(self, prog_name):
parser = super(ShowIPAvailability, self).get_parser(prog_name)
@@ -105,8 +107,10 @@ class ShowIPAvailability(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.network
- obj = client.find_network_ip_availability(parsed_args.network,
+ network_id = client.find_network(parsed_args.network,
+ ignore_missing=False).id
+ obj = client.find_network_ip_availability(network_id,
ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py
index dbf1b601..3e0bb776 100644
--- a/openstackclient/network/v2/network.py
+++ b/openstackclient/network/v2/network.py
@@ -19,6 +19,7 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
+from openstackclient.network import sdk_utils
def _format_admin_state(item):
@@ -31,19 +32,38 @@ def _format_router_external(item):
_formatters = {
'subnets': utils.format_list,
+ 'subnet_ids': utils.format_list,
'admin_state_up': _format_admin_state,
+ 'is_admin_state_up': _format_admin_state,
'router:external': _format_router_external,
+ 'is_router_external': _format_router_external,
'availability_zones': utils.format_list,
'availability_zone_hints': utils.format_list,
}
+def _get_network_columns(item):
+ column_map = {
+ 'subnet_ids': 'subnets',
+ 'is_admin_state_up': 'admin_state_up',
+ 'is_router_external': 'router:external',
+ 'is_port_security_enabled': 'port_security_enabled',
+ 'provider_network_type': 'provider:network_type',
+ 'provider_physical_network': 'provider:physical_network',
+ 'provider_segmentation_id': 'provider:segmentation_id',
+ 'is_shared': 'shared',
+ 'ipv4_address_scope_id': 'ipv4_address_scope',
+ 'ipv6_address_scope_id': 'ipv6_address_scope',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
def _get_attrs(client_manager, parsed_args):
@@ -71,7 +91,9 @@ def _get_attrs(client_manager, parsed_args):
parsed_args.project,
parsed_args.project_domain,
).id
+ # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor
attrs['tenant_id'] = project_id
+ attrs['project_id'] = project_id
# "network set" command doesn't support setting availability zone hints.
if 'availability_zone_hints' in parsed_args and \
@@ -87,10 +109,10 @@ def _get_attrs(client_manager, parsed_args):
attrs['router:external'] = False
if parsed_args.external:
attrs['router:external'] = True
- if parsed_args.no_default:
- attrs['is_default'] = False
- if parsed_args.default:
- attrs['is_default'] = True
+ if parsed_args.no_default:
+ attrs['is_default'] = False
+ if parsed_args.default:
+ attrs['is_default'] = True
# Update Provider network options
if parsed_args.provider_network_type:
attrs['provider:network_type'] = parsed_args.provider_network_type
@@ -98,6 +120,13 @@ def _get_attrs(client_manager, parsed_args):
attrs['provider:physical_network'] = parsed_args.physical_network
if parsed_args.segmentation_id:
attrs['provider:segmentation_id'] = parsed_args.segmentation_id
+ if parsed_args.qos_policy is not None:
+ network_client = client_manager.network
+ _qos_policy = network_client.find_qos_policy(parsed_args.qos_policy,
+ ignore_missing=False)
+ attrs['qos_policy_id'] = _qos_policy.id
+ if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy:
+ attrs['qos_policy_id'] = None
# Update VLAN Transparency for networks
if parsed_args.transparent_vlan:
attrs['vlan_transparent'] = True
@@ -112,10 +141,8 @@ def _add_additional_network_options(parser):
parser.add_argument(
'--provider-network-type',
metavar='<provider-network-type>',
- choices=['flat', 'geneve', 'gre', 'local',
- 'vlan', 'vxlan'],
help=_("The physical mechanism by which the virtual network "
- "is implemented. The supported options are: "
+ "is implemented. For example: "
"flat, geneve, gre, local, vlan, vxlan."))
parser.add_argument(
'--provider-physical-network',
@@ -154,8 +181,10 @@ def _get_attrs_compute(client_manager, parsed_args):
return attrs
+# TODO(sindhu): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateNetwork(common.NetworkAndComputeShowOne):
- """Create new network"""
+ _description = _("Create new network")
def update_parser_common(self, parser):
parser.add_argument(
@@ -248,6 +277,11 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
help=_("Do not use the network as the default external network "
"(default)")
)
+ parser.add_argument(
+ '--qos-policy',
+ metavar='<qos-policy>',
+ help=_("QoS policy to attach to this network (name or ID)")
+ )
_add_additional_network_options(parser)
return parser
@@ -262,20 +296,20 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
def take_action_network(self, client, parsed_args):
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_network(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
def take_action_compute(self, client, parsed_args):
attrs = _get_attrs_compute(self.app.client_manager, parsed_args)
obj = client.networks.create(**attrs)
- columns = _get_columns(obj._info)
+ display_columns, columns = _get_columns(obj._info)
data = utils.get_dict_properties(obj._info, columns)
- return (columns, data)
+ return (display_columns, data)
class DeleteNetwork(common.NetworkAndComputeDelete):
- """Delete network(s)"""
+ _description = _("Delete network(s)")
# Used by base class to find resources in parsed_args.
resource = 'network'
@@ -300,10 +334,12 @@ class DeleteNetwork(common.NetworkAndComputeDelete):
client.networks.delete(network.id)
+# TODO(sindhu): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListNetwork(common.NetworkAndComputeLister):
- """List networks"""
+ _description = _("List networks")
- def update_parser_common(self, parser):
+ def update_parser_network(self, parser):
router_ext_group = parser.add_mutually_exclusive_group()
router_ext_group.add_argument(
'--external',
@@ -360,6 +396,33 @@ class ListNetwork(common.NetworkAndComputeLister):
help=_("List networks according to their status "
"('ACTIVE', 'BUILD', 'DOWN', 'ERROR')")
)
+ parser.add_argument(
+ '--provider-network-type',
+ metavar='<provider-network-type>',
+ choices=['flat', 'geneve', 'gre', 'local',
+ 'vlan', 'vxlan'],
+ help=_("List networks according to their physical mechanisms. "
+ "The supported options are: flat, geneve, gre, local, "
+ "vlan, vxlan.")
+ )
+ parser.add_argument(
+ '--provider-physical-network',
+ metavar='<provider-physical-network>',
+ dest='physical_network',
+ help=_("List networks according to name of the physical network")
+ )
+ parser.add_argument(
+ '--provider-segment',
+ metavar='<provider-segment>',
+ dest='segmentation_id',
+ help=_("List networks according to VLAN ID for VLAN networks "
+ "or Tunnel ID for GENEVE/GRE/VXLAN networks")
+ )
+ parser.add_argument(
+ '--agent',
+ metavar='<agent-id>',
+ dest='agent_id',
+ help=_('List networks hosted by agent (ID only)'))
return parser
def take_action_network(self, client, parsed_args):
@@ -369,12 +432,12 @@ class ListNetwork(common.NetworkAndComputeLister):
'id',
'name',
'status',
- 'tenant_id',
- 'admin_state_up',
- 'shared',
- 'subnets',
+ 'project_id',
+ 'is_admin_state_up',
+ 'is_shared',
+ 'subnet_ids',
'provider_network_type',
- 'router:external',
+ 'is_router_external',
'availability_zones',
)
column_headers = (
@@ -389,11 +452,31 @@ class ListNetwork(common.NetworkAndComputeLister):
'Router Type',
'Availability Zones',
)
+ elif parsed_args.agent_id:
+ columns = (
+ 'id',
+ 'name',
+ 'subnet_ids'
+ )
+ column_headers = (
+ 'ID',
+ 'Name',
+ 'Subnets',
+ )
+ client = self.app.client_manager.network
+ dhcp_agent = client.get_agent(parsed_args.agent_id)
+ data = client.dhcp_agent_hosting_networks(dhcp_agent)
+
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ formatters=_formatters,
+ ) for s in data))
else:
columns = (
'id',
'name',
- 'subnets'
+ 'subnet_ids'
)
column_headers = (
'ID',
@@ -405,16 +488,20 @@ class ListNetwork(common.NetworkAndComputeLister):
if parsed_args.external:
args['router:external'] = True
+ args['is_router_external'] = True
elif parsed_args.internal:
args['router:external'] = False
+ args['is_router_external'] = False
if parsed_args.name is not None:
args['name'] = parsed_args.name
if parsed_args.enable:
args['admin_state_up'] = True
+ args['is_admin_state_up'] = True
elif parsed_args.disable:
args['admin_state_up'] = False
+ args['is_admin_state_up'] = False
if parsed_args.project:
project = identity_common.find_project(
@@ -423,15 +510,28 @@ class ListNetwork(common.NetworkAndComputeLister):
parsed_args.project_domain,
)
args['tenant_id'] = project.id
+ args['project_id'] = project.id
if parsed_args.share:
args['shared'] = True
+ args['is_shared'] = True
elif parsed_args.no_share:
args['shared'] = False
+ args['is_shared'] = False
if parsed_args.status:
args['status'] = parsed_args.status
+ if parsed_args.provider_network_type:
+ args['provider:network_type'] = parsed_args.provider_network_type
+ args['provider_network_type'] = parsed_args.provider_network_type
+ if parsed_args.physical_network:
+ args['provider:physical_network'] = parsed_args.physical_network
+ args['provider_physical_network'] = parsed_args.physical_network
+ if parsed_args.segmentation_id:
+ args['provider:segmentation_id'] = parsed_args.segmentation_id
+ args['provider_segmentation_id'] = parsed_args.segmentation_id
+
data = client.networks(**args)
return (column_headers,
@@ -461,8 +561,10 @@ class ListNetwork(common.NetworkAndComputeLister):
) for s in data))
+# TODO(sindhu): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetNetwork(command.Command):
- """Set network properties"""
+ _description = _("Set network properties")
def get_parser(self, prog_name):
parser = super(SetNetwork, self).get_parser(prog_name)
@@ -541,6 +643,17 @@ class SetNetwork(command.Command):
action='store_true',
help=_("Do not use the network as the default external network")
)
+ qos_group = parser.add_mutually_exclusive_group()
+ qos_group.add_argument(
+ '--qos-policy',
+ metavar='<qos-policy>',
+ help=_("QoS policy to attach to this network (name or ID)")
+ )
+ qos_group.add_argument(
+ '--no-qos-policy',
+ action='store_true',
+ help=_("Remove the QoS policy attached to this network")
+ )
_add_additional_network_options(parser)
return parser
@@ -553,7 +666,7 @@ class SetNetwork(command.Command):
class ShowNetwork(common.NetworkAndComputeShowOne):
- """Show network details"""
+ _description = _("Show network details")
def update_parser_common(self, parser):
parser.add_argument(
@@ -565,15 +678,15 @@ class ShowNetwork(common.NetworkAndComputeShowOne):
def take_action_network(self, client, parsed_args):
obj = client.find_network(parsed_args.network, ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
def take_action_compute(self, client, parsed_args):
obj = utils.find_resource(
client.networks,
parsed_args.network,
)
- columns = _get_columns(obj._info)
+ display_columns, columns = _get_columns(obj._info)
data = utils.get_dict_properties(obj._info, columns)
- return (columns, data)
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py
index fdb34bb7..dd3fdc24 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__)
@@ -28,15 +29,57 @@ LOG = logging.getLogger(__name__)
def _format_admin_state(state):
return 'UP' if state else 'DOWN'
-
_formatters = {
'admin_state_up': _format_admin_state,
+ 'is_admin_state_up': _format_admin_state,
'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 AddNetworkToAgent(command.Command):
+ _description = _("Add network to an agent")
+
+ def get_parser(self, prog_name):
+ parser = super(AddNetworkToAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ '--dhcp',
+ action='store_true',
+ help=_('Add network to a DHCP agent'))
+ parser.add_argument(
+ 'agent_id',
+ metavar='<agent-id>',
+ help=_('Agent to which a network is added (ID only)'))
+ parser.add_argument(
+ 'network',
+ metavar='<network>',
+ help=_('Network to be added to an agent (ID or name)'))
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ agent = client.get_agent(parsed_args.agent_id)
+ network = client.find_network(
+ parsed_args.network, ignore_missing=False)
+ if parsed_args.dhcp:
+ try:
+ client.add_dhcp_agent_to_network(agent, network)
+ except Exception:
+ msg = 'Failed to add {} to {}'.format(
+ network.name, agent.agent_type)
+ exceptions.CommandError(msg)
+
+
class DeleteNetworkAgent(command.Command):
- """Delete network agent(s)"""
+ _description = _("Delete network agent(s)")
def get_parser(self, prog_name):
parser = super(DeleteNetworkAgent, self).get_parser(prog_name)
@@ -69,8 +112,35 @@ 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):
- """List network agents"""
+ _description = _("List network agents")
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworkAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ '--agent-type',
+ metavar='<agent-type>',
+ choices=["bgp", "dhcp", "open-vswitch", "linux-bridge", "ofa",
+ "l3", "loadbalancer", "metering", "metadata", "macvtap",
+ "nic"],
+ help=_("List only agents with the specified agent type. "
+ "The supported agent types are: bgp, dhcp, open-vswitch, "
+ "linux-bridge, ofa, l3, loadbalancer, metering, "
+ "metadata, macvtap, nic.")
+ )
+ parser.add_argument(
+ '--host',
+ metavar='<host>',
+ help=_("List only agents running on the specified host")
+ )
+ parser.add_argument(
+ '--network',
+ metavar='<network>',
+ help=_('List agents hosting a network (name or ID)')
+ )
+ return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
@@ -79,8 +149,8 @@ class ListNetworkAgent(command.Lister):
'agent_type',
'host',
'availability_zone',
- 'alive',
- 'admin_state_up',
+ 'is_alive',
+ 'is_admin_state_up',
'binary'
)
column_headers = (
@@ -92,15 +162,94 @@ class ListNetworkAgent(command.Lister):
'State',
'Binary'
)
- data = client.agents()
- return (column_headers,
- (utils.get_item_properties(
- s, columns, formatters=_formatters,
- ) for s in data))
+
+ key_value = {
+ 'bgp': 'BGP dynamic routing agent',
+ 'dhcp': 'DHCP agent',
+ 'open-vswitch': 'Open vSwitch agent',
+ 'linux-bridge': 'Linux bridge agent',
+ 'ofa': 'OFA driver agent',
+ 'l3': 'L3 agent',
+ 'loadbalancer': 'Loadbalancer agent',
+ 'metering': 'Metering agent',
+ 'metadata': 'Metadata agent',
+ 'macvtap': 'Macvtap agent',
+ 'nic': 'NIC Switch agent'
+ }
+
+ filters = {}
+ if parsed_args.network is not None:
+ columns = (
+ 'id',
+ 'host',
+ 'is_admin_state_up',
+ 'is_alive',
+ )
+ column_headers = (
+ 'ID',
+ 'Host',
+ 'Admin State Up',
+ 'Alive',
+ )
+ network = client.find_network(
+ parsed_args.network, ignore_missing=False)
+ data = client.network_hosting_dhcp_agents(network)
+
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ formatters=_formatters,
+ ) for s in data))
+ else:
+ if parsed_args.agent_type is not None:
+ filters['agent_type'] = key_value[parsed_args.agent_type]
+ if parsed_args.host is not None:
+ filters['host'] = parsed_args.host
+
+ data = client.agents(**filters)
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns, formatters=_formatters,
+ ) for s in data))
+
+
+class RemoveNetworkFromAgent(command.Command):
+ _description = _("Remove network from an agent.")
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveNetworkFromAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ '--dhcp',
+ action='store_true',
+ help=_('Remove network from DHCP agent'))
+ parser.add_argument(
+ 'agent_id',
+ metavar='<agent-id>',
+ help=_('Agent to which a network is removed (ID only)'))
+ parser.add_argument(
+ 'network',
+ metavar='<network>',
+ help=_('Network to be removed from an agent (ID or name)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ agent = client.get_agent(parsed_args.agent_id)
+ network = client.find_network(
+ parsed_args.network, ignore_missing=False)
+ if parsed_args.dhcp:
+ try:
+ client.remove_dhcp_agent_from_network(agent, network)
+ except Exception:
+ msg = 'Failed to remove {} to {}'.format(
+ network.name, agent.agent_type)
+ exceptions.CommandError(msg)
+# TODO(huanxuan): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetNetworkAgent(command.Command):
- """Set network agent properties"""
+ _description = _("Set network agent properties")
def get_parser(self, prog_name):
parser = super(SetNetworkAgent, self).get_parser(prog_name)
@@ -129,10 +278,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:
@@ -141,7 +292,7 @@ class SetNetworkAgent(command.Command):
class ShowNetworkAgent(command.ShowOne):
- """Display network agent details"""
+ _description = _("Display network agent details")
def get_parser(self, prog_name):
parser = super(ShowNetworkAgent, self).get_parser(prog_name)
@@ -154,7 +305,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_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py
new file mode 100644
index 00000000..36f39200
--- /dev/null
+++ b/openstackclient/network/v2/network_auto_allocated_topology.py
@@ -0,0 +1,136 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Auto-allocated Topology Implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _format_check_resource_columns():
+ return ('dry_run',)
+
+
+def _format_check_resource(item):
+ item_id = getattr(item, 'id', False)
+ if item_id == 'dry-run=pass':
+ item.check_resource = 'pass'
+ return item
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ if parsed_args.project:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+ if parsed_args.check_resources:
+ attrs['check_resources'] = True
+
+ return attrs
+
+
+# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class CreateAutoAllocatedTopology(command.ShowOne):
+ _description = _("Create the auto allocated topology for project")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateAutoAllocatedTopology, self).get_parser(prog_name)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Return the auto allocated topology for a given project. "
+ "Default is current project")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--check-resources',
+ action='store_true',
+ help=_("Validate the requirements for auto allocated topology. "
+ "Does not return a topology.")
+ )
+ parser.add_argument(
+ '--or-show',
+ action='store_true',
+ default=True,
+ help=_("If topology exists returns the topology's "
+ "information (Default)")
+ )
+
+ return parser
+
+ def check_resource_topology(self, client, parsed_args):
+ obj = client.validate_auto_allocated_topology(parsed_args.project)
+
+ columns = _format_check_resource_columns()
+ data = utils.get_item_properties(_format_check_resource(obj),
+ columns,
+ formatters={})
+
+ return (columns, data)
+
+ def get_topology(self, client, parsed_args):
+ obj = client.get_auto_allocated_topology(parsed_args.project)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters={})
+ return (display_columns, data)
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ if parsed_args.check_resources:
+ columns, data = self.check_resource_topology(client, parsed_args)
+ else:
+ columns, data = self.get_topology(client, parsed_args)
+ return (columns, data)
+
+
+# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class DeleteAutoAllocatedTopology(command.Command):
+ _description = _("Delete auto allocated topology for project")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteAutoAllocatedTopology, self).get_parser(prog_name)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_('Delete auto allocated topology for a given project. '
+ 'Default is the current project')
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ client.delete_auto_allocated_topology(parsed_args.project)
diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py
new file mode 100644
index 00000000..3a3324c0
--- /dev/null
+++ b/openstackclient/network/v2/network_flavor.py
@@ -0,0 +1,247 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Flavor action implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ column_map = {
+ 'is_enabled': 'enabled',
+ 'tenant_id': 'project_id',
+ }
+
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ attrs['name'] = parsed_args.name
+ attrs['service_type'] = parsed_args.service_type
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.enable:
+ attrs['enabled'] = True
+ if parsed_args.disable:
+ attrs['enabled'] = False
+ if 'project' in parsed_args and parsed_args.project is not None:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
+ return attrs
+
+
+# TODO(dasanind): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class CreateNetworkFlavor(command.ShowOne):
+ _description = _("Create new network flavor")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateNetworkFlavor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar="<name>",
+ help=_("Name for the flavor")
+ )
+ parser.add_argument(
+ '--service-type',
+ metavar="<service-type>",
+ required=True,
+ help=_('Service type to which the flavor applies to: e.g. VPN '
+ '(See openstack network service provider list for loaded '
+ 'examples.)')
+ )
+ parser.add_argument(
+ '--description',
+ help=_('Description for the flavor')
+ )
+ parser.add_argument(
+ '--project',
+ metavar="<project>",
+ help=_("Owner's project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable the flavor (default)")
+ )
+ enable_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable the flavor")
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_flavor(**attrs)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters={})
+
+ return (display_columns, data)
+
+
+class DeleteNetworkFlavor(command.Command):
+ _description = _("Delete network flavors")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkFlavor, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'flavor',
+ metavar='<flavor>',
+ nargs='+',
+ help=_('Flavor(s) to delete (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for flavor in parsed_args.flavor:
+ try:
+ obj = client.find_flavor(flavor, ignore_missing=False)
+ client.delete_flavor(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete flavor with "
+ "name or ID '%(flavor)s': %(e)s"),
+ {"flavor": flavor, "e": e})
+ if result > 0:
+ total = len(parsed_args.flavor)
+ msg = (_("%(result)s of %(total)s flavors failed "
+ "to delete.") % {"result": result, "total": total})
+ raise exceptions.CommandError(msg)
+
+
+class ListNetworkFlavor(command.Lister):
+ _description = _("List network flavors")
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'id',
+ 'name',
+ 'is_enabled',
+ 'service_type',
+ 'description'
+ )
+ column_headers = (
+ 'ID',
+ 'Name',
+ 'Enabled',
+ 'Service Type',
+ 'Description'
+ )
+
+ data = client.flavors()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+# TODO(dasanind): Use only the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class SetNetworkFlavor(command.Command):
+ _description = _("Set network flavor properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkFlavor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flavor',
+ metavar="<flavor>",
+ help=_("Flavor to update (name or ID)")
+ )
+ parser.add_argument(
+ '--description',
+ help=_('Set network flavor description')
+ )
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable network flavor")
+ )
+ enable_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable network flavor")
+ )
+ parser.add_argument(
+ '--name',
+ metavar="<name>",
+ help=_('Set flavor name')
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_flavor(
+ parsed_args.flavor,
+ ignore_missing=False)
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.enable:
+ attrs['enabled'] = True
+ if parsed_args.disable:
+ attrs['enabled'] = False
+ client.update_flavor(obj, **attrs)
+
+
+class ShowNetworkFlavor(command.ShowOne):
+ _description = _("Display network flavor details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkFlavor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flavor',
+ metavar='<flavor>',
+ help=_('Flavor to display (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_flavor(parsed_args.flavor, ignore_missing=False)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return display_columns, data
diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py
new file mode 100644
index 00000000..6cf0c412
--- /dev/null
+++ b/openstackclient/network/v2/network_flavor_profile.py
@@ -0,0 +1,250 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ column_map = {
+ 'is_enabled': 'enabled',
+ 'tenant_id': 'project_id',
+ }
+
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.driver is not None:
+ attrs['driver'] = parsed_args.driver
+ if parsed_args.metainfo is not None:
+ attrs['metainfo'] = parsed_args.metainfo
+ if parsed_args.enable:
+ attrs['enabled'] = True
+ if parsed_args.disable:
+ attrs['enabled'] = False
+ if 'project' in parsed_args and parsed_args.project is not None:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
+ return attrs
+
+
+# TODO(ndahiwade): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class CreateNetworkFlavorProfile(command.ShowOne):
+ _description = _("Create new network flavor profile")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateNetworkFlavorProfile, self).get_parser(prog_name)
+ parser.add_argument(
+ '--project',
+ metavar="<project>",
+ help=_("Owner's project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--description',
+ metavar="<description>",
+ help=_("Description for the flavor profile")
+ )
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable the flavor profile")
+ )
+ enable_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable the flavor profile")
+ )
+ parser.add_argument(
+ '--driver',
+ help=_("Python module path to driver. This becomes "
+ "required if --metainfo is missing and vice versa")
+ )
+ parser.add_argument(
+ '--metainfo',
+ help=_("Metainfo for the flavor profile. This becomes "
+ "required if --driver is missing and vice versa")
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+
+ if parsed_args.driver is None and parsed_args.metainfo is None:
+ msg = _("Either --driver or --metainfo or both are required")
+ raise exceptions.CommandError(msg)
+
+ obj = client.create_service_profile(**attrs)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters={})
+
+ return (display_columns, data)
+
+
+class DeleteNetworkFlavorProfile(command.Command):
+ _description = _("Delete network flavor profile")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkFlavorProfile, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'flavor_profile',
+ metavar='<flavor-profile>',
+ nargs='+',
+ help=_("Flavor profile(s) to delete (ID only)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for flavor_profile in parsed_args.flavor_profile:
+ try:
+ obj = client.find_service_profile(flavor_profile,
+ ignore_missing=False)
+ client.delete_service_profile(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete flavor profile with "
+ "ID '%(flavor_profile)s': %(e)s"),
+ {"flavor_profile": flavor_profile, "e": e})
+ if result > 0:
+ total = len(parsed_args.flavor_profile)
+ msg = (_("%(result)s of %(total)s flavor_profiles failed "
+ "to delete.") % {"result": result, "total": total})
+ raise exceptions.CommandError(msg)
+
+
+class ListNetworkFlavorProfile(command.Lister):
+ _description = _("List network flavor profile(s)")
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'id',
+ 'driver',
+ 'is_enabled',
+ 'metainfo',
+ 'description',
+ )
+ column_headers = (
+ 'ID',
+ 'Driver',
+ 'Enabled',
+ 'Metainfo',
+ 'Description',
+ )
+
+ data = client.service_profiles()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+# TODO(ndahiwade): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class SetNetworkFlavorProfile(command.Command):
+ _description = _("Set network flavor profile properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkFlavorProfile, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flavor_profile',
+ metavar="<flavor-profile>",
+ help=_("Flavor profile to update (ID only)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--description',
+ metavar="<description>",
+ help=_("Description for the flavor profile")
+ )
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable the flavor profile")
+ )
+ enable_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable the flavor profile")
+ )
+ parser.add_argument(
+ '--driver',
+ help=_("Python module path to driver. This becomes "
+ "required if --metainfo is missing and vice versa")
+ )
+ parser.add_argument(
+ '--metainfo',
+ help=_("Metainfo for the flavor profile. This becomes "
+ "required if --driver is missing and vice versa")
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_service_profile(parsed_args.flavor_profile,
+ ignore_missing=False)
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+
+ client.update_service_profile(obj, **attrs)
+
+
+class ShowNetworkFlavorProfile(command.ShowOne):
+ _description = _("Display network flavor profile details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkFlavorProfile, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flavor_profile',
+ metavar='<flavor-profile>',
+ help=_("Flavor profile to display (ID only)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_service_profile(parsed_args.flavor_profile,
+ ignore_missing=False)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py
new file mode 100644
index 00000000..df0e1da1
--- /dev/null
+++ b/openstackclient/network/v2/network_meter.py
@@ -0,0 +1,190 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Metering Label Implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ column_map = {
+ 'is_shared': 'shared',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.project is not None and 'project' in parsed_args:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+ if parsed_args.share:
+ attrs['shared'] = True
+ if parsed_args.no_share:
+ attrs['shared'] = False
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+
+ return attrs
+
+
+# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class CreateMeter(command.ShowOne):
+ _description = _("Create network meter")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateMeter, self).get_parser(prog_name)
+
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Create description for meter")
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Owner's project (name or ID)")
+ )
+
+ identity_common.add_project_domain_option_to_parser(parser)
+ share_group = parser.add_mutually_exclusive_group()
+ share_group.add_argument(
+ '--share',
+ action='store_true',
+ default=None,
+ help=_("Share meter between projects")
+ )
+ share_group.add_argument(
+ '--no-share',
+ action='store_true',
+ help=_("Do not share meter between projects")
+ )
+ parser.add_argument(
+ 'name',
+ metavar='<name>',
+ help=_('Name of meter'),
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_metering_label(**attrs)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters={})
+
+ return (display_columns, data)
+
+
+# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class DeleteMeter(command.Command):
+ _description = _("Delete network meter")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteMeter, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'meter',
+ metavar='<meter>',
+ nargs='+',
+ help=_('Meter to delete (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for meter in parsed_args.meter:
+ try:
+ obj = client.find_metering_label(meter, ignore_missing=False)
+ client.delete_metering_label(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete meter with "
+ "ID '%(meter)s': %(e)s"),
+ {"meter": meter, "e": e})
+ if result > 0:
+ total = len(parsed_args.meter)
+ msg = (_("%(result)s of %(total)s meters failed "
+ "to delete.") % {"result": result, "total": total})
+ raise exceptions.CommandError(msg)
+
+
+class ListMeter(command.Lister):
+ _description = _("List network meters")
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'id',
+ 'name',
+ 'description',
+ 'shared',
+ )
+ column_headers = (
+ 'ID',
+ 'Name',
+ 'Description',
+ 'Shared',
+ )
+
+ data = client.metering_labels()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+class ShowMeter(command.ShowOne):
+ _description = _("Show network meter")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowMeter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'meter',
+ metavar='<meter>',
+ help=_('Meter to display (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_metering_label(parsed_args.meter,
+ ignore_missing=False)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return display_columns, data
diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py
new file mode 100644
index 00000000..49ff9e1b
--- /dev/null
+++ b/openstackclient/network/v2/network_meter_rule.py
@@ -0,0 +1,203 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Meter Rule Implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+
+ if parsed_args.exclude:
+ attrs['excluded'] = True
+ if parsed_args.include:
+ attrs['excluded'] = False
+ if parsed_args.ingress or not parsed_args.egress:
+ attrs['direction'] = 'ingress'
+ if parsed_args.egress:
+ attrs['direction'] = 'egress'
+ if parsed_args.remote_ip_prefix is not None:
+ attrs['remote_ip_prefix'] = parsed_args.remote_ip_prefix
+ if parsed_args.meter is not None:
+ attrs['metering_label_id'] = parsed_args.meter
+ if parsed_args.project is not None:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
+ return attrs
+
+
+class CreateMeterRule(command.ShowOne):
+ _description = _("Create a new meter rule")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateMeterRule, self).get_parser(prog_name)
+
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Owner's project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ exclude_group = parser.add_mutually_exclusive_group()
+ exclude_group.add_argument(
+ '--exclude',
+ action='store_true',
+ help=_("Exclude remote IP prefix from traffic count")
+ )
+ exclude_group.add_argument(
+ '--include',
+ action='store_true',
+ help=_("Include remote IP prefix from traffic count (default)")
+ )
+ direction_group = parser.add_mutually_exclusive_group()
+ direction_group.add_argument(
+ '--ingress',
+ action='store_true',
+ help=_("Apply rule to incoming network traffic (default)")
+ )
+ direction_group.add_argument(
+ '--egress',
+ action='store_true',
+ help=_('Apply rule to outgoing network traffic')
+ )
+ parser.add_argument(
+ '--remote-ip-prefix',
+ metavar='<remote-ip-prefix>',
+ required=True,
+ help=_('The remote IP prefix to associate with this rule'),
+ )
+ parser.add_argument(
+ 'meter',
+ metavar='<meter>',
+ help=_('Label to associate with this metering rule (name or ID)'),
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ _meter = client.find_metering_label(parsed_args.meter,
+ ignore_missing=False)
+ parsed_args.meter = _meter.id
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_metering_label_rule(**attrs)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters={})
+
+ return (display_columns, data)
+
+
+class DeleteMeterRule(command.Command):
+ _description = _("Delete meter rule(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteMeterRule, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'meter_rule_id',
+ metavar='<meter-rule-id>',
+ nargs='+',
+ help=_('Meter rule to delete (ID only)')
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for id in parsed_args.meter_rule_id:
+ try:
+ obj = client.find_metering_label_rule(id, ignore_missing=False)
+ client.delete_metering_label_rule(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete meter rule with "
+ "ID '%(id)s': %(e)s"),
+ {"id": id, "e": e})
+
+ if result > 0:
+ total = len(parsed_args.meter_rule_id)
+ msg = (_("%(result)s of %(total)s meter rules failed "
+ "to delete.") % {"result": result, "total": total})
+ raise exceptions.CommandError(msg)
+
+
+class ListMeterRule(command.Lister):
+ _description = _("List meter rules")
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'id',
+ 'excluded',
+ 'direction',
+ 'remote_ip_prefix',
+ )
+ column_headers = (
+ 'ID',
+ 'Excluded',
+ 'Direction',
+ 'Remote IP Prefix',
+ )
+ data = client.metering_label_rules()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+class ShowMeterRule(command.ShowOne):
+ _description = _("Display meter rules details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowMeterRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'meter_rule_id',
+ metavar='<meter-rule-id>',
+ help=_('Meter rule (ID only)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_metering_label_rule(parsed_args.meter_rule_id,
+ ignore_missing=False)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return display_columns, data
diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py
new file mode 100644
index 00000000..fef3ec88
--- /dev/null
+++ b/openstackclient/network/v2/network_qos_policy.py
@@ -0,0 +1,259 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ column_map = {
+ 'is_shared': 'shared',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ if 'name' in parsed_args and parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
+ if 'description' in parsed_args and parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.share:
+ attrs['shared'] = True
+ if parsed_args.no_share:
+ attrs['shared'] = False
+ if parsed_args.project is not None:
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
+ return attrs
+
+
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class CreateNetworkQosPolicy(command.ShowOne):
+ _description = _("Create a QoS policy")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateNetworkQosPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='<name>',
+ help=_("Name of QoS policy to create")
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Description of the QoS policy")
+ )
+ share_group = parser.add_mutually_exclusive_group()
+ share_group.add_argument(
+ '--share',
+ action='store_true',
+ default=None,
+ help=_("Make the QoS policy accessible by other projects")
+ )
+ share_group.add_argument(
+ '--no-share',
+ action='store_true',
+ help=_("Make the QoS policy not accessible by other projects "
+ "(default)")
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Owner's project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_qos_policy(**attrs)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters={})
+ return (display_columns, data)
+
+
+class DeleteNetworkQosPolicy(command.Command):
+ _description = _("Delete Qos Policy(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkQosPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'policy',
+ metavar="<qos-policy>",
+ nargs="+",
+ help=_("QoS policy(s) to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for policy in parsed_args.policy:
+ try:
+ obj = client.find_qos_policy(policy, ignore_missing=False)
+ client.delete_qos_policy(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete QoS policy "
+ "name or ID '%(qos_policy)s': %(e)s"),
+ {'qos_policy': policy, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.policy)
+ msg = (_("%(result)s of %(total)s QoS policies failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+# TODO(abhiraut): Use only the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class ListNetworkQosPolicy(command.Lister):
+ _description = _("List QoS policies")
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworkQosPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("List qos policies according to their project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ shared_group = parser.add_mutually_exclusive_group()
+ shared_group.add_argument(
+ '--share',
+ action='store_true',
+ help=_("List qos policies shared between projects")
+ )
+ shared_group.add_argument(
+ '--no-share',
+ action='store_true',
+ help=_("List qos policies not shared between projects")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ columns = (
+ 'id',
+ 'name',
+ 'is_shared',
+ 'project_id',
+ )
+ column_headers = (
+ 'ID',
+ 'Name',
+ 'Shared',
+ 'Project',
+ )
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ data = client.qos_policies(**attrs)
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns, formatters={},
+ ) for s in data))
+
+
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class SetNetworkQosPolicy(command.Command):
+ _description = _("Set QoS policy properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkQosPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'policy',
+ metavar="<qos-policy>",
+ help=_("QoS policy to modify (name or ID)")
+ )
+ parser.add_argument(
+ '--name',
+ metavar="<name>",
+ help=_('Set QoS policy name')
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Description of the QoS policy")
+ )
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--share',
+ action='store_true',
+ help=_('Make the QoS policy accessible by other projects'),
+ )
+ enable_group.add_argument(
+ '--no-share',
+ action='store_true',
+ help=_('Make the QoS policy not accessible by other projects'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_qos_policy(
+ parsed_args.policy,
+ ignore_missing=False)
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if parsed_args.share:
+ attrs['shared'] = True
+ if parsed_args.no_share:
+ attrs['shared'] = False
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ client.update_qos_policy(obj, **attrs)
+
+
+class ShowNetworkQosPolicy(command.ShowOne):
+ _description = _("Display QoS policy details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkQosPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'policy',
+ metavar="<qos-policy>",
+ help=_("QoS policy to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_qos_policy(parsed_args.policy,
+ ignore_missing=False)
+ display_columns, columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py
new file mode 100644
index 00000000..baed0424
--- /dev/null
+++ b/openstackclient/network/v2/network_qos_rule.py
@@ -0,0 +1,353 @@
+# 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 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
+
+
+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/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py
new file mode 100644
index 00000000..52f8e235
--- /dev/null
+++ b/openstackclient/network/v2/network_qos_rule_type.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+class ListNetworkQosRuleType(command.Lister):
+ _description = _("List QoS rule types")
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ columns = (
+ 'type',
+ )
+ column_headers = (
+ 'Type',
+ )
+ data = client.qos_rule_types()
+
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns, formatters={},
+ ) for s in data))
diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py
index bb29579f..90754737 100644
--- a/openstackclient/network/v2/network_rbac.py
+++ b/openstackclient/network/v2/network_rbac.py
@@ -21,20 +21,18 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- if 'target_tenant' in columns:
- columns.remove('target_tenant')
- columns.append('target_project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'target_tenant': 'target_project_id',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
def _get_attrs(client_manager, parsed_args):
@@ -47,9 +45,9 @@ def _get_attrs(client_manager, parsed_args):
object_id = network_client.find_network(
parsed_args.rbac_object, ignore_missing=False).id
if parsed_args.type == 'qos_policy':
- # TODO(Huanxuan Ao): Support finding a object ID by obejct name
- # after qos policy finding supported in SDK.
- object_id = parsed_args.rbac_object
+ object_id = network_client.find_qos_policy(
+ parsed_args.rbac_object,
+ ignore_missing=False).id
attrs['object_id'] = object_id
identity_client = client_manager.identity
@@ -70,16 +68,17 @@ def _get_attrs(client_manager, parsed_args):
return attrs
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateNetworkRBAC(command.ShowOne):
- """Create network RBAC policy"""
+ _description = _("Create network RBAC policy")
def get_parser(self, prog_name):
parser = super(CreateNetworkRBAC, self).get_parser(prog_name)
parser.add_argument(
'rbac_object',
metavar="<rbac-object>",
- help=_("The object to which this RBAC policy affects (name or "
- "ID for network objects, ID only for QoS policy objects)")
+ help=_("The object to which this RBAC policy affects (name or ID)")
)
parser.add_argument(
'--type',
@@ -123,13 +122,13 @@ class CreateNetworkRBAC(command.ShowOne):
client = self.app.client_manager.network
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_rbac_policy(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
- return columns, data
+ return display_columns, data
class DeleteNetworkRBAC(command.Command):
- """Delete network RBAC policy(s)"""
+ _description = _("Delete network RBAC policy(s)")
def get_parser(self, prog_name):
parser = super(DeleteNetworkRBAC, self).get_parser(prog_name)
@@ -163,7 +162,31 @@ class DeleteNetworkRBAC(command.Command):
class ListNetworkRBAC(command.Lister):
- """List network RBAC policies"""
+ _description = _("List network RBAC policies")
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworkRBAC, self).get_parser(prog_name)
+ parser.add_argument(
+ '--type',
+ metavar='<type>',
+ choices=['qos_policy', 'network'],
+ help=_('List network RBAC policies according to '
+ 'given object type ("qos_policy" or "network")')
+ )
+ parser.add_argument(
+ '--action',
+ metavar='<action>',
+ choices=['access_as_external', 'access_as_shared'],
+ help=_('List network RBAC policies according to given '
+ 'action ("access_as_external" or "access_as_shared")')
+ )
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
@@ -179,15 +202,27 @@ class ListNetworkRBAC(command.Lister):
'Object ID',
)
- data = client.rbac_policies()
+ query = {}
+ if parsed_args.long:
+ columns += ('action',)
+ column_headers += ('Action',)
+ if parsed_args.type is not None:
+ query['object_type'] = parsed_args.type
+ if parsed_args.action is not None:
+ query['action'] = parsed_args.action
+
+ data = client.rbac_policies(**query)
+
return (column_headers,
(utils.get_item_properties(
s, columns,
) for s in data))
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetNetworkRBAC(command.Command):
- """Set network RBAC policy properties"""
+ _description = _("Set network RBAC policy properties")
def get_parser(self, prog_name):
parser = super(SetNetworkRBAC, self).get_parser(prog_name)
@@ -228,7 +263,7 @@ class SetNetworkRBAC(command.Command):
class ShowNetworkRBAC(command.ShowOne):
- """Display network RBAC policy details"""
+ _description = _("Display network RBAC policy details")
def get_parser(self, prog_name):
parser = super(ShowNetworkRBAC, self).get_parser(prog_name)
@@ -243,6 +278,6 @@ class ShowNetworkRBAC(command.ShowOne):
client = self.app.client_manager.network
obj = client.find_rbac_policy(parsed_args.rbac_policy,
ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
- return columns, data
+ return display_columns, data
diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py
index 94722f1e..c1a672e2 100644
--- a/openstackclient/network/v2/network_segment.py
+++ b/openstackclient/network/v2/network_segment.py
@@ -20,13 +20,18 @@ from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
+def _get_columns(item):
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {})
+
+
class CreateNetworkSegment(command.ShowOne):
- """Create new network segment"""
+ _description = _("Create new network segment")
def get_parser(self, prog_name):
parser = super(CreateNetworkSegment, self).get_parser(prog_name)
@@ -84,13 +89,13 @@ class CreateNetworkSegment(command.ShowOne):
if parsed_args.segment is not None:
attrs['segmentation_id'] = parsed_args.segment
obj = client.create_segment(**attrs)
- columns = tuple(sorted(obj.keys()))
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
- return (columns, data)
+ return (display_columns, data)
class DeleteNetworkSegment(command.Command):
- """Delete network segment(s)"""
+ _description = _("Delete network segment(s)")
def get_parser(self, prog_name):
parser = super(DeleteNetworkSegment, self).get_parser(prog_name)
@@ -114,8 +119,8 @@ class DeleteNetworkSegment(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete network segment with "
- "ID '%(network_segment)s': %(e)s")
- % {'network_segment': network_segment, 'e': e})
+ "ID '%(network_segment)s': %(e)s"),
+ {'network_segment': network_segment, 'e': e})
if result > 0:
total = len(parsed_args.network_segment)
@@ -125,7 +130,7 @@ class DeleteNetworkSegment(command.Command):
class ListNetworkSegment(command.Lister):
- """List network segments"""
+ _description = _("List network segments")
def get_parser(self, prog_name):
parser = super(ListNetworkSegment, self).get_parser(prog_name)
@@ -185,7 +190,7 @@ class ListNetworkSegment(command.Lister):
class SetNetworkSegment(command.Command):
- """Set network segment properties"""
+ _description = _("Set network segment properties")
def get_parser(self, prog_name):
parser = super(SetNetworkSegment, self).get_parser(prog_name)
@@ -219,7 +224,7 @@ class SetNetworkSegment(command.Command):
class ShowNetworkSegment(command.ShowOne):
- """Display network segment details"""
+ _description = _("Display network segment details")
def get_parser(self, prog_name):
parser = super(ShowNetworkSegment, self).get_parser(prog_name)
@@ -236,6 +241,6 @@ class ShowNetworkSegment(command.ShowOne):
parsed_args.network_segment,
ignore_missing=False
)
- columns = tuple(sorted(obj.keys()))
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
- return (columns, data)
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/network_service_provider.py b/openstackclient/network/v2/network_service_provider.py
new file mode 100644
index 00000000..157948cc
--- /dev/null
+++ b/openstackclient/network/v2/network_service_provider.py
@@ -0,0 +1,43 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Network Service Providers Implementation"""
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+class ListNetworkServiceProvider(command.Lister):
+ _description = _("List Service Providers")
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'service_type',
+ 'name',
+ 'is_default',
+ )
+ column_headers = (
+ 'Service Type',
+ 'Name',
+ 'Default',
+ )
+
+ data = client.service_providers()
+ return(column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 92b286a9..eced93ce 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -25,6 +25,7 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
@@ -34,35 +35,37 @@ def _format_admin_state(state):
return 'UP' if state else 'DOWN'
+def _format_dns_assignment(dns_assignment):
+ return utils.format_list_of_dicts(dns_assignment) \
+ if dns_assignment else None
+
_formatters = {
'admin_state_up': _format_admin_state,
+ 'is_admin_state_up': _format_admin_state,
'allowed_address_pairs': utils.format_list_of_dicts,
'binding_profile': utils.format_dict,
'binding_vif_details': utils.format_dict,
- 'dns_assignment': utils.format_list_of_dicts,
+ 'binding:profile': utils.format_dict,
+ 'binding:vif_details': utils.format_dict,
+ 'dns_assignment': _format_dns_assignment,
'extra_dhcp_opts': utils.format_list_of_dicts,
'fixed_ips': utils.format_list_of_dicts,
- 'security_groups': utils.format_list,
+ 'security_group_ids': utils.format_list,
}
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- binding_columns = [
- 'binding:host_id',
- 'binding:profile',
- 'binding:vif_details',
- 'binding:vif_type',
- 'binding:vnic_type',
- ]
- for binding_column in binding_columns:
- if binding_column in columns:
- columns.remove(binding_column)
- columns.append(binding_column.replace('binding:', 'binding_', 1))
- return tuple(sorted(columns))
+ column_map = {
+ 'binding:host_id': 'binding_host_id',
+ 'binding:profile': 'binding_profile',
+ 'binding:vif_details': 'binding_vif_details',
+ 'binding:vif_type': 'binding_vif_type',
+ 'binding:vnic_type': 'binding_vnic_type',
+ 'is_admin_state_up': 'admin_state_up',
+ 'is_port_security_enabled': 'port_security_enabled',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
class JSONKeyValueAction(argparse.Action):
@@ -109,6 +112,8 @@ def _get_attrs(client_manager, parsed_args):
'The --host-id option is deprecated, '
'please use --host instead.'
))
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
if parsed_args.fixed_ip is not None:
attrs['fixed_ips'] = parsed_args.fixed_ip
if parsed_args.device:
@@ -125,14 +130,16 @@ def _get_attrs(client_manager, parsed_args):
attrs['binding:vnic_type'] = parsed_args.vnic_type
if parsed_args.host:
attrs['binding:host_id'] = parsed_args.host
+ if parsed_args.mac_address is not None:
+ attrs['mac_address'] = parsed_args.mac_address
+ if parsed_args.dns_name is not None:
+ attrs['dns_name'] = parsed_args.dns_name
# It is possible that name is not updated during 'port set'
if parsed_args.name is not None:
attrs['name'] = str(parsed_args.name)
# The remaining options do not support 'port set' command, so they require
# additional check
- if 'mac_address' in parsed_args and parsed_args.mac_address is not None:
- attrs['mac_address'] = parsed_args.mac_address
if 'network' in parsed_args and parsed_args.network is not None:
attrs['network_id'] = parsed_args.network
if 'project' in parsed_args and parsed_args.project is not None:
@@ -146,6 +153,12 @@ def _get_attrs(client_manager, parsed_args):
).id
attrs['tenant_id'] = project_id
+ if parsed_args.disable_port_security:
+ attrs['port_security_enabled'] = False
+
+ if parsed_args.enable_port_security:
+ attrs['port_security_enabled'] = True
+
return attrs
@@ -179,51 +192,103 @@ def _prepare_fixed_ips(client_manager, parsed_args):
parsed_args.fixed_ip = ips
+def _prepare_filter_fixed_ips(client_manager, parsed_args):
+ """Fix and properly format fixed_ip option for filtering.
+
+ Appropriately convert any subnet names to their respective ids.
+ Convert fixed_ips in parsed args to be in valid list format for filter:
+ ['subnet_id=foo'].
+ """
+ client = client_manager.network
+ ips = []
+
+ for ip_spec in parsed_args.fixed_ip:
+ if 'subnet' in ip_spec:
+ subnet_name_id = ip_spec['subnet']
+ if subnet_name_id:
+ _subnet = client.find_subnet(subnet_name_id,
+ ignore_missing=False)
+ ips.append('subnet_id=%s' % _subnet.id)
+
+ if 'ip-address' in ip_spec:
+ ips.append('ip_address=%s' % ip_spec['ip-address'])
+ return ips
+
+
def _add_updatable_args(parser):
- # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not
- # remove before 3.x release or Mar 2017.
- device_group = parser.add_mutually_exclusive_group()
- device_group.add_argument(
- '--device',
- metavar='<device-id>',
- help=_("Port device ID")
- )
- device_group.add_argument(
- '--device-id',
- metavar='<device-id>',
- help=argparse.SUPPRESS,
- )
- parser.add_argument(
- '--device-owner',
- metavar='<device-owner>',
- help=_("Device owner of this port. This is the entity that uses "
- "the port (for example, network:dhcp).")
- )
- parser.add_argument(
- '--vnic-type',
- metavar='<vnic-type>',
- choices=['direct', 'direct-physical', 'macvtap',
- 'normal', 'baremetal'],
- help=_("VNIC type for this port (direct | direct-physical | "
- "macvtap | normal | baremetal, default: normal)")
- )
- # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not
- # remove before 3.x release or Mar 2017.
- host_group = parser.add_mutually_exclusive_group()
- host_group.add_argument(
- '--host',
- metavar='<host-id>',
- help=_("Allocate port on host <host-id> (ID only)")
- )
- host_group.add_argument(
- '--host-id',
- metavar='<host-id>',
- help=argparse.SUPPRESS,
- )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Description of this port")
+ )
+ # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not
+ # remove before 3.x release or Mar 2017.
+ device_group = parser.add_mutually_exclusive_group()
+ device_group.add_argument(
+ '--device',
+ metavar='<device-id>',
+ help=_("Port device ID")
+ )
+ device_group.add_argument(
+ '--device-id',
+ metavar='<device-id>',
+ help=argparse.SUPPRESS,
+ )
+ parser.add_argument(
+ '--mac-address',
+ metavar='<mac-address>',
+ help=_("MAC address of this port (admin only)")
+ )
+ parser.add_argument(
+ '--device-owner',
+ metavar='<device-owner>',
+ help=_("Device owner of this port. This is the entity that uses "
+ "the port (for example, network:dhcp).")
+ )
+ parser.add_argument(
+ '--vnic-type',
+ metavar='<vnic-type>',
+ choices=['direct', 'direct-physical', 'macvtap',
+ 'normal', 'baremetal'],
+ help=_("VNIC type for this port (direct | direct-physical | "
+ "macvtap | normal | baremetal, default: normal)")
+ )
+ # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not
+ # remove before 3.x release or Mar 2017.
+ host_group = parser.add_mutually_exclusive_group()
+ host_group.add_argument(
+ '--host',
+ metavar='<host-id>',
+ help=_("Allocate port on host <host-id> (ID only)")
+ )
+ host_group.add_argument(
+ '--host-id',
+ metavar='<host-id>',
+ help=argparse.SUPPRESS,
+ )
+ parser.add_argument(
+ '--dns-name',
+ metavar='dns-name',
+ help=_("Set DNS name to this port "
+ "(requires DNS integration extension)")
+ )
+
+
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+def _convert_address_pairs(parsed_args):
+ ops = []
+ for opt in parsed_args.allowed_address_pairs:
+ addr = {}
+ addr['ip_address'] = opt['ip-address']
+ if 'mac-address' in opt:
+ addr['mac_address'] = opt['mac-address']
+ ops.append(addr)
+ return ops
class CreatePort(command.ShowOne):
- """Create a new port"""
+ _description = _("Create a new port")
def get_parser(self, prog_name):
parser = super(CreatePort, self).get_parser(prog_name)
@@ -265,11 +330,6 @@ class CreatePort(command.ShowOne):
help=_("Disable port")
)
parser.add_argument(
- '--mac-address',
- metavar='<mac-address>',
- help=_("MAC address of this port")
- )
- parser.add_argument(
'--project',
metavar='<project>',
help=_("Owner's project (name or ID)")
@@ -281,7 +341,44 @@ class CreatePort(command.ShowOne):
help=_("Name of this port")
)
# TODO(singhj): Add support for extended options:
- # qos,security groups,dhcp, address pairs
+ # qos,dhcp
+ secgroups = parser.add_mutually_exclusive_group()
+ secgroups.add_argument(
+ '--security-group',
+ metavar='<security-group>',
+ action='append',
+ dest='security_group',
+ help=_("Security group to associate with this port (name or ID) "
+ "(repeat option to set multiple security groups)")
+ )
+ secgroups.add_argument(
+ '--no-security-group',
+ dest='no_security_group',
+ action='store_true',
+ help=_("Associate no security groups with this port")
+ )
+ port_security = parser.add_mutually_exclusive_group()
+ port_security.add_argument(
+ '--enable-port-security',
+ action='store_true',
+ help=_("Enable port security for this port (Default)")
+ )
+ port_security.add_argument(
+ '--disable-port-security',
+ action='store_true',
+ help=_("Disable port security for this port")
+ )
+ parser.add_argument(
+ '--allowed-address',
+ metavar='ip-address=<ip-address>[,mac-address=<mac-address>]',
+ action=parseractions.MultiKeyValueAction,
+ dest='allowed_address_pairs',
+ required_keys=['ip-address'],
+ optional_keys=['mac-address'],
+ help=_("Add allowed-address pair associated with this port: "
+ "ip-address=<ip-address>[,mac-address=<mac-address>] "
+ "(repeat option to set multiple allowed-address pairs)")
+ )
return parser
def take_action(self, parsed_args):
@@ -291,15 +388,27 @@ class CreatePort(command.ShowOne):
parsed_args.network = _network.id
_prepare_fixed_ips(self.app.client_manager, parsed_args)
attrs = _get_attrs(self.app.client_manager, parsed_args)
+
+ if parsed_args.security_group:
+ attrs['security_group_ids'] = [client.find_security_group(
+ sg, ignore_missing=False).id
+ for sg in
+ parsed_args.security_group]
+ elif parsed_args.no_security_group:
+ attrs['security_group_ids'] = []
+ if parsed_args.allowed_address_pairs:
+ attrs['allowed_address_pairs'] = (
+ _convert_address_pairs(parsed_args))
+
obj = client.create_port(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
class DeletePort(command.Command):
- """Delete port(s)"""
+ _description = _("Delete port(s)")
def get_parser(self, prog_name):
parser = super(DeletePort, self).get_parser(prog_name)
@@ -332,8 +441,10 @@ class DeletePort(command.Command):
raise exceptions.CommandError(msg)
+# TODO(abhiraut): Use only the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListPort(command.Lister):
- """List ports"""
+ _description = _("List ports")
def get_parser(self, prog_name):
parser = super(ListPort, self).get_parser(prog_name)
@@ -360,26 +471,58 @@ class ListPort(command.Lister):
metavar='<server>',
help=_("List only ports attached to this server (name or ID)"),
)
+ parser.add_argument(
+ '--mac-address',
+ metavar='<mac-address>',
+ help=_("List only ports with this MAC address")
+ )
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("List ports according to their project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--fixed-ip',
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['subnet', 'ip-address'],
+ help=_("Desired IP and/or subnet (name or ID) for filtering "
+ "ports: subnet=<subnet>,ip-address=<ip-address> "
+ "(repeat option to set multiple fixed IP addresses)")
+ )
return parser
def take_action(self, parsed_args):
network_client = self.app.client_manager.network
compute_client = self.app.client_manager.compute
+ identity_client = self.app.client_manager.identity
columns = (
'id',
'name',
'mac_address',
'fixed_ips',
+ 'status',
)
column_headers = (
'ID',
'Name',
'MAC Address',
'Fixed IP Addresses',
+ 'Status',
)
filters = {}
+ if parsed_args.long:
+ columns += ('security_group_ids', 'device_owner',)
+ column_headers += ('Security Groups', 'Device Owner',)
if parsed_args.device_owner is not None:
filters['device_owner'] = parsed_args.device_owner
if parsed_args.router:
@@ -394,6 +537,19 @@ class ListPort(command.Lister):
network = network_client.find_network(parsed_args.network,
ignore_missing=False)
filters['network_id'] = network.id
+ if parsed_args.mac_address:
+ filters['mac_address'] = parsed_args.mac_address
+ if parsed_args.project:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ filters['tenant_id'] = project_id
+ filters['project_id'] = project_id
+ if parsed_args.fixed_ip:
+ filters['fixed_ips'] = _prepare_filter_fixed_ips(
+ self.app.client_manager, parsed_args)
data = network_client.ports(**filters)
@@ -404,8 +560,10 @@ class ListPort(command.Lister):
) for s in data))
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetPort(command.Command):
- """Set port properties"""
+ _description = _("Set port properties")
def get_parser(self, prog_name):
parser = super(SetPort, self).get_parser(prog_name)
@@ -463,6 +621,51 @@ class SetPort(command.Command):
metavar="<port>",
help=_("Port to modify (name or ID)")
)
+ parser.add_argument(
+ '--security-group',
+ metavar='<security-group>',
+ action='append',
+ dest='security_group',
+ help=_("Security group to associate with this port (name or ID) "
+ "(repeat option to set multiple security groups)")
+ )
+ parser.add_argument(
+ '--no-security-group',
+ dest='no_security_group',
+ action='store_true',
+ help=_("Clear existing security groups associated with this port")
+ )
+ port_security = parser.add_mutually_exclusive_group()
+ port_security.add_argument(
+ '--enable-port-security',
+ action='store_true',
+ help=_("Enable port security for this port")
+ )
+ port_security.add_argument(
+ '--disable-port-security',
+ action='store_true',
+ help=_("Disable port security for this port")
+ )
+ parser.add_argument(
+ '--allowed-address',
+ metavar='ip-address=<ip-address>[,mac-address=<mac-address>]',
+ action=parseractions.MultiKeyValueAction,
+ dest='allowed_address_pairs',
+ required_keys=['ip-address'],
+ optional_keys=['mac-address'],
+ help=_("Add allowed-address pair associated with this port: "
+ "ip-address=<ip-address>[,mac-address=<mac-address>] "
+ "(repeat option to set multiple allowed-address pairs)")
+ )
+ parser.add_argument(
+ '--no-allowed-address',
+ dest='no_allowed_address_pair',
+ action='store_true',
+ help=_("Clear existing allowed-address pairs associated"
+ "with this port."
+ "(Specify both --allowed-address and --no-allowed-address"
+ "to overwrite the current allowed-address pairs)")
+ )
return parser
def take_action(self, parsed_args):
@@ -491,11 +694,34 @@ class SetPort(command.Command):
elif parsed_args.no_fixed_ip:
attrs['fixed_ips'] = []
+ if parsed_args.security_group:
+ attrs['security_group_ids'] = [
+ client.find_security_group(sg, ignore_missing=False).id for
+ sg in parsed_args.security_group]
+ if not parsed_args.no_security_group:
+ attrs['security_group_ids'] += obj.security_group_ids
+
+ elif parsed_args.no_security_group:
+ attrs['security_group_ids'] = []
+
+ if (parsed_args.allowed_address_pairs and
+ parsed_args.no_allowed_address_pair):
+ attrs['allowed_address_pairs'] = (
+ _convert_address_pairs(parsed_args))
+
+ elif parsed_args.allowed_address_pairs:
+ attrs['allowed_address_pairs'] = (
+ [addr for addr in obj.allowed_address_pairs if addr] +
+ _convert_address_pairs(parsed_args))
+
+ elif parsed_args.no_allowed_address_pair:
+ attrs['allowed_address_pairs'] = []
+
client.update_port(obj, **attrs)
class ShowPort(command.ShowOne):
- """Display port details"""
+ _description = _("Display port details")
def get_parser(self, prog_name):
parser = super(ShowPort, self).get_parser(prog_name)
@@ -509,13 +735,15 @@ class ShowPort(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.network
obj = client.find_port(parsed_args.port, ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class UnsetPort(command.Command):
- """Unset port properties"""
+ _description = _("Unset port properties")
def get_parser(self, prog_name):
parser = super(UnsetPort, self).get_parser(prog_name)
@@ -536,20 +764,44 @@ class UnsetPort(command.Command):
help=_("Desired key which should be removed from binding:profile"
"(repeat option to unset multiple binding:profile data)"))
parser.add_argument(
+ '--security-group',
+ metavar='<security-group>',
+ action='append',
+ dest='security_group_ids',
+ help=_("Security group which should be removed this port (name "
+ "or ID) (repeat option to unset multiple security groups)")
+ )
+
+ parser.add_argument(
'port',
metavar="<port>",
help=_("Port to modify (name or ID)")
)
+ parser.add_argument(
+ '--allowed-address',
+ metavar='ip-address=<ip-address>[,mac-address=<mac-address>]',
+ action=parseractions.MultiKeyValueAction,
+ dest='allowed_address_pairs',
+ required_keys=['ip-address'],
+ optional_keys=['mac-address'],
+ help=_("Desired allowed-address pair which should be removed "
+ "from this port: ip-address=<ip-address> "
+ "[,mac-address=<mac-address>] (repeat option to set "
+ "multiple allowed-address pairs)")
+ )
+
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
obj = client.find_port(parsed_args.port, ignore_missing=False)
- # SDK ignores update() if it recieves a modified obj and attrs
+ # SDK ignores update() if it receives a modified obj and attrs
# To handle the same tmp_obj is created in all take_action of
# Unset* classes
tmp_fixed_ips = copy.deepcopy(obj.fixed_ips)
tmp_binding_profile = copy.deepcopy(obj.binding_profile)
+ tmp_secgroups = copy.deepcopy(obj.security_group_ids)
+ tmp_addr_pairs = copy.deepcopy(obj.allowed_address_pairs)
_prepare_fixed_ips(self.app.client_manager, parsed_args)
attrs = {}
if parsed_args.fixed_ip:
@@ -568,5 +820,24 @@ class UnsetPort(command.Command):
msg = _("Port does not contain binding-profile %s") % key
raise exceptions.CommandError(msg)
attrs['binding:profile'] = tmp_binding_profile
+ if parsed_args.security_group_ids:
+ try:
+ for sg in parsed_args.security_group_ids:
+ sg_id = client.find_security_group(
+ sg, ignore_missing=False).id
+ tmp_secgroups.remove(sg_id)
+ except ValueError:
+ msg = _("Port does not contain security group %s") % sg
+ raise exceptions.CommandError(msg)
+ attrs['security_group_ids'] = tmp_secgroups
+ if parsed_args.allowed_address_pairs:
+ try:
+ for addr in _convert_address_pairs(parsed_args):
+ tmp_addr_pairs.remove(addr)
+ except ValueError:
+ msg = _("Port does not contain allowed-address-pair %s") % addr
+ raise exceptions.CommandError(msg)
+ attrs['allowed_address_pairs'] = tmp_addr_pairs
+
if attrs:
client.update_port(obj, **attrs)
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index 48a3a92c..f46c8696 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -25,6 +25,7 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
@@ -51,6 +52,7 @@ def _format_routes(routes):
_formatters = {
'admin_state_up': _format_admin_state,
+ 'is_admin_state_up': _format_admin_state,
'external_gateway_info': _format_external_gateway_info,
'availability_zones': utils.format_list,
'availability_zone_hints': utils.format_list,
@@ -59,11 +61,13 @@ _formatters = {
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'tenant_id': 'project_id',
+ 'is_ha': 'ha',
+ 'is_distributed': 'distributed',
+ 'is_admin_state_up': 'admin_state_up',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
def _get_attrs(client_manager, parsed_args):
@@ -94,13 +98,11 @@ def _get_attrs(client_manager, parsed_args):
).id
attrs['tenant_id'] = project_id
- # TODO(tangchen): Support getting 'external_gateway_info' property.
-
return attrs
class AddPortToRouter(command.Command):
- """Add a port to a router"""
+ _description = _("Add a port to a router")
def get_parser(self, prog_name):
parser = super(AddPortToRouter, self).get_parser(prog_name)
@@ -124,7 +126,7 @@ class AddPortToRouter(command.Command):
class AddSubnetToRouter(command.Command):
- """Add a subnet to a router"""
+ _description = _("Add a subnet to a router")
def get_parser(self, prog_name):
parser = super(AddSubnetToRouter, self).get_parser(prog_name)
@@ -150,8 +152,10 @@ class AddSubnetToRouter(command.Command):
subnet_id=subnet.id)
+# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateRouter(command.ShowOne):
- """Create a new router"""
+ _description = _("Create a new router")
def get_parser(self, prog_name):
parser = super(CreateRouter, self).get_parser(prog_name)
@@ -215,14 +219,14 @@ class CreateRouter(command.ShowOne):
attrs['ha'] = parsed_args.ha
obj = client.create_router(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
class DeleteRouter(command.Command):
- """Delete router(s)"""
+ _description = _("Delete router(s)")
def get_parser(self, prog_name):
parser = super(DeleteRouter, self).get_parser(prog_name)
@@ -245,8 +249,8 @@ class DeleteRouter(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete router with "
- "name or ID '%(router)s': %(e)s")
- % {'router': router, 'e': e})
+ "name or ID '%(router)s': %(e)s"),
+ {'router': router, 'e': e})
if result > 0:
total = len(parsed_args.router)
@@ -255,30 +259,54 @@ class DeleteRouter(command.Command):
raise exceptions.CommandError(msg)
+# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListRouter(command.Lister):
- """List routers"""
+ _description = _("List routers")
def get_parser(self, prog_name):
parser = super(ListRouter, self).get_parser(prog_name)
parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help=_("List routers according to their name")
+ )
+ admin_state_group = parser.add_mutually_exclusive_group()
+ admin_state_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("List enabled routers")
+ )
+ admin_state_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("List disabled routers")
+ )
+ parser.add_argument(
'--long',
action='store_true',
default=False,
help=_("List additional fields in output")
)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("List routers according to their project (name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
client = self.app.client_manager.network
-
columns = (
'id',
'name',
'status',
- 'admin_state_up',
- 'distributed',
- 'ha',
- 'tenant_id',
+ 'is_admin_state_up',
+ 'is_distributed',
+ 'is_ha',
+ 'project_id',
)
column_headers = (
'ID',
@@ -289,6 +317,27 @@ class ListRouter(command.Lister):
'HA',
'Project',
)
+
+ args = {}
+
+ if parsed_args.name is not None:
+ args['name'] = parsed_args.name
+
+ if parsed_args.enable:
+ args['admin_state_up'] = True
+ args['is_admin_state_up'] = True
+ elif parsed_args.disable:
+ args['admin_state_up'] = False
+ args['is_admin_state_up'] = False
+
+ if parsed_args.project:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ args['tenant_id'] = project_id
+ args['project_id'] = project_id
if parsed_args.long:
columns = columns + (
'routes',
@@ -308,7 +357,7 @@ class ListRouter(command.Lister):
'Availability zones',
)
- data = client.routers()
+ data = client.routers(**args)
return (column_headers,
(utils.get_item_properties(
s, columns,
@@ -317,7 +366,7 @@ class ListRouter(command.Lister):
class RemovePortFromRouter(command.Command):
- """Remove a port from a router"""
+ _description = _("Remove a port from a router")
def get_parser(self, prog_name):
parser = super(RemovePortFromRouter, self).get_parser(prog_name)
@@ -341,7 +390,7 @@ class RemovePortFromRouter(command.Command):
class RemoveSubnetFromRouter(command.Command):
- """Remove a subnet from a router"""
+ _description = _("Remove a subnet from a router")
def get_parser(self, prog_name):
parser = super(RemoveSubnetFromRouter, self).get_parser(prog_name)
@@ -367,8 +416,10 @@ class RemoveSubnetFromRouter(command.Command):
subnet_id=subnet.id)
+# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetRouter(command.Command):
- """Set router properties"""
+ _description = _("Set router properties")
def get_parser(self, prog_name):
parser = super(SetRouter, self).get_parser(prog_name)
@@ -411,7 +462,8 @@ class SetRouter(command.Command):
help=_("Set router to centralized mode (disabled router only)")
)
routes_group = parser.add_mutually_exclusive_group()
- routes_group.add_argument(
+ # ToDo(Reedip):Remove mutual exclusiveness once clear-routes is removed
+ parser.add_argument(
'--route',
metavar='destination=<subnet>,gateway=<ip-address>',
action=parseractions.MultiKeyValueAction,
@@ -426,21 +478,54 @@ class SetRouter(command.Command):
routes_group.add_argument(
'--no-route',
action='store_true',
- help=_("Clear routes associated with the router")
+ help=_("Clear routes associated with the router. "
+ "Specify both --route and --no-route to overwrite "
+ "current value of route.")
)
routes_group.add_argument(
'--clear-routes',
action='store_true',
help=argparse.SUPPRESS,
)
-
- # TODO(tangchen): Support setting 'ha' property in 'router set'
- # command. It appears that changing the ha state is supported by
- # neutron under certain conditions.
-
- # TODO(tangchen): Support setting 'external_gateway_info' property in
- # 'router set' command.
-
+ routes_ha = parser.add_mutually_exclusive_group()
+ routes_ha.add_argument(
+ '--ha',
+ action='store_true',
+ help=_("Set the router as highly available "
+ "(disabled router only)")
+ )
+ routes_ha.add_argument(
+ '--no-ha',
+ action='store_true',
+ help=_("Clear high availability attribute of the router "
+ "(disabled router only)")
+ )
+ parser.add_argument(
+ '--external-gateway',
+ metavar="<network>",
+ help=_("External Network used as router's gateway (name or ID)")
+ )
+ parser.add_argument(
+ '--fixed-ip',
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['subnet', 'ip-address'],
+ help=_("Desired IP and/or subnet (name or ID)"
+ "on external gateway: "
+ "subnet=<subnet>,ip-address=<ip-address> "
+ "(repeat option to set multiple fixed IP addresses)")
+ )
+ snat_group = parser.add_mutually_exclusive_group()
+ snat_group.add_argument(
+ '--enable-snat',
+ action='store_true',
+ help=_("Enable Source NAT on external gateway")
+ )
+ snat_group.add_argument(
+ '--disable-snat',
+ action='store_true',
+ help=_("Disable Source NAT on external gateway")
+ )
return parser
def take_action(self, parsed_args):
@@ -451,26 +536,59 @@ class SetRouter(command.Command):
attrs = _get_attrs(self.app.client_manager, parsed_args)
# Get the route attributes.
- if parsed_args.no_route:
- attrs['routes'] = []
- elif parsed_args.clear_routes:
- attrs['routes'] = []
+ if parsed_args.ha:
+ attrs['ha'] = True
+ elif parsed_args.no_ha:
+ attrs['ha'] = False
+ if parsed_args.clear_routes:
LOG.warning(_(
'The --clear-routes option is deprecated, '
'please use --no-route instead.'
))
- elif parsed_args.routes is not None:
- # Map the route keys and append to the current routes.
- # The REST API will handle route validation and duplicates.
+
+ if parsed_args.routes is not None:
for route in parsed_args.routes:
route['nexthop'] = route.pop('gateway')
- attrs['routes'] = obj.routes + parsed_args.routes
-
+ attrs['routes'] = parsed_args.routes
+ if not (parsed_args.no_route or parsed_args.clear_routes):
+ # Map the route keys and append to the current routes.
+ # The REST API will handle route validation and duplicates.
+ attrs['routes'] += obj.routes
+ elif parsed_args.no_route or parsed_args.clear_routes:
+ attrs['routes'] = []
+ if (parsed_args.disable_snat or parsed_args.enable_snat or
+ parsed_args.fixed_ip) and not parsed_args.external_gateway:
+ msg = (_("You must specify '--external-gateway' in order"
+ "to update the SNAT or fixed-ip values"))
+ raise exceptions.CommandError(msg)
+ if parsed_args.external_gateway:
+ gateway_info = {}
+ network = client.find_network(
+ parsed_args.external_gateway, ignore_missing=False)
+ gateway_info['network_id'] = network.id
+ if parsed_args.disable_snat:
+ gateway_info['enable_snat'] = False
+ if parsed_args.enable_snat:
+ gateway_info['enable_snat'] = True
+ if parsed_args.fixed_ip:
+ ips = []
+ for ip_spec in parsed_args.fixed_ip:
+ if ip_spec.get('subnet', False):
+ subnet_name_id = ip_spec.pop('subnet')
+ if subnet_name_id:
+ subnet = client.find_subnet(subnet_name_id,
+ ignore_missing=False)
+ ip_spec['subnet_id'] = subnet.id
+ if ip_spec.get('ip-address', False):
+ ip_spec['ip_address'] = ip_spec.pop('ip-address')
+ ips.append(ip_spec)
+ gateway_info['external_fixed_ips'] = ips
+ attrs['external_gateway_info'] = gateway_info
client.update_router(obj, **attrs)
class ShowRouter(command.ShowOne):
- """Display router details"""
+ _description = _("Display router details")
def get_parser(self, prog_name):
parser = super(ShowRouter, self).get_parser(prog_name)
@@ -484,13 +602,14 @@ class ShowRouter(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.network
obj = client.find_router(parsed_args.router, ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+
+ return (display_columns, data)
class UnsetRouter(command.Command):
- """Unset router properties"""
+ _description = _("Unset router properties")
def get_parser(self, prog_name):
parser = super(UnsetRouter, self).get_parser(prog_name)
@@ -506,6 +625,11 @@ class UnsetRouter(command.Command):
"gateway: nexthop IP address "
"(repeat option to unset multiple routes)"))
parser.add_argument(
+ '--external-gateway',
+ action='store_true',
+ default=False,
+ help=_("Remove external gateway information from the router"))
+ parser.add_argument(
'router',
metavar="<router>",
help=_("Router to modify (name or ID)")
@@ -520,12 +644,13 @@ class UnsetRouter(command.Command):
if parsed_args.routes:
try:
for route in parsed_args.routes:
+ route['nexthop'] = route.pop('gateway')
tmp_routes.remove(route)
except ValueError:
msg = (_("Router does not contain route %s") % route)
raise exceptions.CommandError(msg)
- for route in tmp_routes:
- route['nexthop'] = route.pop('gateway')
attrs['routes'] = tmp_routes
+ if parsed_args.external_gateway:
+ attrs['external_gateway_info'] = {}
if attrs:
client.update_router(obj, **attrs)
diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py
index f832f721..182d4817 100644
--- a/openstackclient/network/v2/security_group.py
+++ b/openstackclient/network/v2/security_group.py
@@ -21,6 +21,7 @@ import six
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
+from openstackclient.network import sdk_utils
from openstackclient.network import utils as network_utils
@@ -34,6 +35,7 @@ def _format_network_security_group_rules(sg_rules):
sg_rule.pop(key)
sg_rule.pop('security_group_id', None)
sg_rule.pop('tenant_id', None)
+ sg_rule.pop('project_id', None)
return utils.format_list_of_dicts(sg_rules)
@@ -72,30 +74,17 @@ _formatters_compute = {
def _get_columns(item):
- # Build the display columns and a list of the property columns
- # that need to be mapped (display column name, property name).
- columns = list(item.keys())
- property_column_mappings = []
- if 'security_group_rules' in columns:
- columns.append('rules')
- columns.remove('security_group_rules')
- property_column_mappings.append(('rules', 'security_group_rules'))
- if 'tenant_id' in columns:
- columns.append('project_id')
- columns.remove('tenant_id')
- property_column_mappings.append(('project_id', 'tenant_id'))
- display_columns = sorted(columns)
-
- # Build the property columns and apply any column mappings.
- property_columns = sorted(columns)
- for property_column_mapping in property_column_mappings:
- property_index = property_columns.index(property_column_mapping[0])
- property_columns[property_index] = property_column_mapping[1]
- return tuple(display_columns), property_columns
+ column_map = {
+ 'security_group_rules': 'rules',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateSecurityGroup(common.NetworkAndComputeShowOne):
- """Create a new security group"""
+ _description = _("Create a new security group")
def update_parser_common(self, parser):
parser.add_argument(
@@ -155,7 +144,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne):
parsed_args.name,
description,
)
- display_columns, property_columns = _get_columns(obj._info)
+ display_columns, property_columns = _get_columns(obj)
data = utils.get_dict_properties(
obj._info,
property_columns,
@@ -165,7 +154,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne):
class DeleteSecurityGroup(common.NetworkAndComputeDelete):
- """Delete security group(s)"""
+ _description = _("Delete security group(s)")
# Used by base class to find resources in parsed_args.
resource = 'group'
@@ -189,8 +178,10 @@ class DeleteSecurityGroup(common.NetworkAndComputeDelete):
client.security_groups.delete(data.id)
+# TODO(rauta): Use the SDK resource mapped attribute names once
+# the OSC minimum requirements include SDK 1.0.
class ListSecurityGroup(common.NetworkAndComputeLister):
- """List security groups"""
+ _description = _("List security groups")
def update_parser_network(self, parser):
# Maintain and hide the argument for backwards compatibility.
@@ -201,6 +192,13 @@ class ListSecurityGroup(common.NetworkAndComputeLister):
default=False,
help=argparse.SUPPRESS,
)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("List security groups according to the project "
+ "(name or ID)")
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
return parser
def update_parser_compute(self, parser):
@@ -212,33 +210,57 @@ class ListSecurityGroup(common.NetworkAndComputeLister):
)
return parser
- def _get_return_data(self, data, include_project=True):
+ def take_action_network(self, client, parsed_args):
+ filters = {}
+ if parsed_args.project:
+ identity_client = self.app.client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ filters['tenant_id'] = project_id
+ filters['project_id'] = project_id
+ data = client.security_groups(**filters)
+
columns = (
"ID",
"Name",
"Description",
+ "Project ID"
+ )
+ column_headers = (
+ "ID",
+ "Name",
+ "Description",
+ "Project"
)
- column_headers = columns
- if include_project:
- columns = columns + ('Tenant ID',)
- column_headers = column_headers + ('Project',)
return (column_headers,
(utils.get_item_properties(
s, columns,
) for s in data))
- def take_action_network(self, client, parsed_args):
- return self._get_return_data(client.security_groups())
-
def take_action_compute(self, client, parsed_args):
search = {'all_tenants': parsed_args.all_projects}
data = client.security_groups.list(search_opts=search)
- return self._get_return_data(data,
- include_project=parsed_args.all_projects)
+
+ columns = (
+ "ID",
+ "Name",
+ "Description",
+ )
+ column_headers = columns
+ if parsed_args.all_projects:
+ columns = columns + ('Tenant ID',)
+ column_headers = column_headers + ('Project',)
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
class SetSecurityGroup(common.NetworkAndComputeCommand):
- """Set security group properties"""
+ _description = _("Set security group properties")
def update_parser_common(self, parser):
parser.add_argument(
@@ -293,7 +315,7 @@ class SetSecurityGroup(common.NetworkAndComputeCommand):
class ShowSecurityGroup(common.NetworkAndComputeShowOne):
- """Display security group details"""
+ _description = _("Display security group details")
def update_parser_common(self, parser):
parser.add_argument(
@@ -319,7 +341,7 @@ class ShowSecurityGroup(common.NetworkAndComputeShowOne):
client.security_groups,
parsed_args.group,
)
- display_columns, property_columns = _get_columns(obj._info)
+ display_columns, property_columns = _get_columns(obj)
data = utils.get_dict_properties(
obj._info,
property_columns,
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index e3be44ec..8f07c5a4 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -14,6 +14,7 @@
"""Security Group Rule action implementations"""
import argparse
+import logging
try:
from novaclient.v2 import security_group_rules as compute_secgroup_rules
@@ -28,9 +29,13 @@ import six
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
+from openstackclient.network import sdk_utils
from openstackclient.network import utils as network_utils
+LOG = logging.getLogger(__name__)
+
+
def _format_security_group_rule_show(obj):
data = network_utils.transform_compute_security_group_rule(obj)
return zip(*sorted(six.iteritems(data)))
@@ -46,28 +51,27 @@ 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
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
def _convert_to_lowercase(string):
@@ -85,8 +89,10 @@ def _is_icmp_protocol(protocol):
return False
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
- """Create a new security group rule"""
+ _description = _("Create a new security group rule")
def update_parser_common(self, parser):
parser.add_argument(
@@ -94,22 +100,40 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
metavar='<group>',
help=_("Create rule in this security group (name or ID)")
)
- source_group = parser.add_mutually_exclusive_group()
- source_group.add_argument(
+ remote_group = parser.add_mutually_exclusive_group()
+ remote_group.add_argument(
+ "--remote-ip",
+ metavar="<ip-address>",
+ help=_("Remote IP address block (may use CIDR notation; "
+ "default for IPv4 rule: 0.0.0.0/0)"),
+ )
+ remote_group.add_argument(
+ "--remote-group",
+ metavar="<group>",
+ help=_("Remote security group (name or ID)"),
+ )
+ # Handle deprecated options
+ # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016.
+ # Do not remove before 4.x release or Nov 2017.
+ remote_group.add_argument(
"--src-ip",
metavar="<ip-address>",
- help=_("Source IP address block (may use CIDR notation; "
- "default for IPv4 rule: 0.0.0.0/0)")
+ help=argparse.SUPPRESS,
)
- source_group.add_argument(
+ remote_group.add_argument(
"--src-group",
metavar="<group>",
- help=_("Source security group (name or ID)")
+ help=argparse.SUPPRESS,
)
return parser
def update_parser_network(self, parser):
parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Set security group rule description")
+ )
+ parser.add_argument(
'--dst-port',
metavar='<port-range>',
action=parseractions.RangeAction,
@@ -235,6 +259,9 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
attrs = {}
attrs['protocol'] = self._get_protocol(parsed_args)
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+
# NOTE(rtheis): A direction must be specified and ingress
# is the default.
if parsed_args.ingress or not parsed_args.egress:
@@ -277,13 +304,31 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
if parsed_args.icmp_code:
attrs['port_range_max'] = parsed_args.icmp_code
- if parsed_args.src_group is not None:
+ # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016.
+ # Do not remove before 4.x release or Nov 2017.
+ if not (parsed_args.remote_group is None and
+ parsed_args.src_group is None):
attrs['remote_group_id'] = client.find_security_group(
- parsed_args.src_group,
+ parsed_args.remote_group or parsed_args.src_group,
ignore_missing=False
).id
- elif parsed_args.src_ip is not None:
- attrs['remote_ip_prefix'] = parsed_args.src_ip
+ if parsed_args.src_group:
+ LOG.warning(
+ _("The %(old)s option is deprecated, "
+ "please use %(new)s instead."),
+ {'old': '--src-group', 'new': '--remote-group'},
+ )
+ elif not (parsed_args.remote_ip is None and
+ parsed_args.src_ip is None):
+ attrs['remote_ip_prefix'] = (
+ parsed_args.remote_ip or parsed_args.src_ip
+ )
+ if parsed_args.src_ip:
+ LOG.warning(
+ _("The %(old)s option is deprecated, "
+ "please use %(new)s instead."),
+ {'old': '--src-ip', 'new': '--remote-ip'},
+ )
elif attrs['ethertype'] == 'IPv4':
attrs['remote_ip_prefix'] = '0.0.0.0/0'
attrs['security_group_id'] = security_group_id
@@ -298,9 +343,9 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
# Create and show the security group rule.
obj = client.create_security_group_rule(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
- return (columns, data)
+ return (display_columns, data)
def take_action_compute(self, client, parsed_args):
group = utils.find_resource(
@@ -312,29 +357,46 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
from_port, to_port = -1, -1
else:
from_port, to_port = parsed_args.dst_port
- src_ip = None
- if parsed_args.src_group is not None:
- parsed_args.src_group = utils.find_resource(
+
+ # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016.
+ # Do not remove before 4.x release or Nov 2017.
+ remote_ip = None
+ if not (parsed_args.remote_group is None and
+ parsed_args.src_group is None):
+ parsed_args.remote_group = utils.find_resource(
client.security_groups,
- parsed_args.src_group,
+ parsed_args.remote_group or parsed_args.src_group,
).id
- if parsed_args.src_ip is not None:
- src_ip = parsed_args.src_ip
+ if parsed_args.src_group:
+ LOG.warning(
+ _("The %(old)s option is deprecated, "
+ "please use %(new)s instead."),
+ {'old': '--src-group', 'new': '--remote-group'},
+ )
+ if not (parsed_args.remote_ip is None and
+ parsed_args.src_ip is None):
+ remote_ip = parsed_args.remote_ip or parsed_args.src_ip
+ if parsed_args.src_ip:
+ LOG.warning(
+ _("The %(old)s option is deprecated, "
+ "please use %(new)s instead."),
+ {'old': '--src-ip', 'new': '--remote-ip'},
+ )
else:
- src_ip = '0.0.0.0/0'
+ remote_ip = '0.0.0.0/0'
obj = client.security_group_rules.create(
group.id,
protocol,
from_port,
to_port,
- src_ip,
- parsed_args.src_group,
+ remote_ip,
+ parsed_args.remote_group,
)
return _format_security_group_rule_show(obj._info)
class DeleteSecurityGroupRule(common.NetworkAndComputeDelete):
- """Delete security group rule(s)"""
+ _description = _("Delete security group rule(s)")
# Used by base class to find resources in parsed_args.
resource = 'rule'
@@ -359,7 +421,17 @@ class DeleteSecurityGroupRule(common.NetworkAndComputeDelete):
class ListSecurityGroupRule(common.NetworkAndComputeLister):
- """List security group rules"""
+ _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(
@@ -380,6 +452,28 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
help=argparse.SUPPRESS
)
parser.add_argument(
+ '--protocol',
+ metavar='<protocol>',
+ type=_convert_to_lowercase,
+ help=_("List rules by the IP protocol ("
+ "ah, dhcp, egp, esp, gre, icmp, igmp, "
+ "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, "
+ "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, "
+ "udp, udplite, vrrp and integer representations [0-255])."
+ )
+ )
+ direction_group = parser.add_mutually_exclusive_group()
+ direction_group.add_argument(
+ '--ingress',
+ action='store_true',
+ help=_("List rules applied to incoming network traffic")
+ )
+ direction_group.add_argument(
+ '--egress',
+ action='store_true',
+ help=_("List rules applied to outgoing network traffic")
+ )
+ parser.add_argument(
'--long',
action='store_true',
default=False,
@@ -424,10 +518,10 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
'id',
'protocol',
'remote_ip_prefix',
- 'port_range_min',
+ 'port_range',
)
if parsed_args.long:
- columns = columns + ('direction', 'ethertype',)
+ columns = columns + ('direction', 'ether_type',)
columns = columns + ('remote_group_id',)
# Get the security group rules using the requested query.
@@ -443,16 +537,21 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
query = {'security_group_id': security_group_id}
else:
columns = columns + ('security_group_id',)
- rules = list(client.security_group_rules(**query))
- # Reformat the rules to display a port range instead
- # of just the port range minimum. This maintains
- # output compatibility with compute.
- for rule in rules:
- rule.port_range_min = _format_network_port_range(rule)
+ if parsed_args.ingress:
+ query['direction'] = 'ingress'
+ if parsed_args.egress:
+ query['direction'] = 'egress'
+ if parsed_args.protocol is not None:
+ query['protocol'] = parsed_args.protocol
+
+ rules = [
+ 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))
@@ -494,7 +593,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
class ShowSecurityGroupRule(common.NetworkAndComputeShowOne):
- """Display security group rule details"""
+ _description = _("Display security group rule details")
def update_parser_common(self, parser):
parser.add_argument(
@@ -507,9 +606,9 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne):
def take_action_network(self, client, parsed_args):
obj = client.find_security_group_rule(parsed_args.rule,
ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
- return (columns, data)
+ return (display_columns, data)
def take_action_compute(self, client, parsed_args):
# NOTE(rtheis): Unfortunately, compute does not have an API
diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py
index 1b778c91..403b4cd2 100644
--- a/openstackclient/network/v2/subnet.py
+++ b/openstackclient/network/v2/subnet.py
@@ -23,6 +23,7 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
@@ -84,6 +85,15 @@ def _get_common_parse_arguments(parser, is_create=True):
help=_("DNS server for this subnet "
"(repeat option to set multiple DNS servers)")
)
+
+ if not is_create:
+ parser.add_argument(
+ '--no-dns-nameservers',
+ action='store_true',
+ help=_("Clear existing information of DNS Nameservers. "
+ "Specify both --dns-nameserver and --no-dns-nameserver "
+ "to overwrite the current DNS Nameserver information.")
+ )
parser.add_argument(
'--host-route',
metavar='destination=<subnet>,gateway=<ip-address>',
@@ -117,11 +127,18 @@ def _get_common_parse_arguments(parser, is_create=True):
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'is_dhcp_enabled': 'enable_dhcp',
+ 'subnet_pool_id': 'subnetpool_id',
+ 'tenant_id': 'project_id',
+ }
+ # Do not show this column when displaying a subnet
+ invisible_columns = ['use_default_subnetpool']
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(
+ item,
+ column_map,
+ invisible_columns=invisible_columns
+ )
def convert_entries_to_nexthop(entries):
@@ -168,7 +185,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
ignore_missing=False)
attrs['subnetpool_id'] = subnet_pool.id
if parsed_args.use_default_subnet_pool:
- attrs['use_default_subnetpool'] = True
+ attrs['use_default_subnet_pool'] = True
if parsed_args.prefix_length is not None:
attrs['prefixlen'] = parsed_args.prefix_length
if parsed_args.subnet_range is not None:
@@ -217,8 +234,10 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
return attrs
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateSubnet(command.ShowOne):
- """Create a subnet"""
+ _description = _("Create a subnet")
def get_parser(self, prog_name):
parser = super(CreateSubnet, self).get_parser(prog_name)
@@ -323,13 +342,13 @@ class CreateSubnet(command.ShowOne):
client = self.app.client_manager.network
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_subnet(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
class DeleteSubnet(command.Command):
- """Delete subnet(s)"""
+ _description = _("Delete subnet(s)")
def get_parser(self, prog_name):
parser = super(DeleteSubnet, self).get_parser(prog_name)
@@ -352,8 +371,8 @@ class DeleteSubnet(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete subnet with "
- "name or ID '%(subnet)s': %(e)s")
- % {'subnet': subnet, 'e': e})
+ "name or ID '%(subnet)s': %(e)s"),
+ {'subnet': subnet, 'e': e})
if result > 0:
total = len(parsed_args.subnet)
@@ -362,8 +381,10 @@ class DeleteSubnet(command.Command):
raise exceptions.CommandError(msg)
+# TODO(abhiraut): Use only the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListSubnet(command.Lister):
- """List subnets"""
+ _description = _("List subnets")
def get_parser(self, prog_name):
parser = super(ListSubnet, self).get_parser(prog_name)
@@ -443,8 +464,10 @@ class ListSubnet(command.Lister):
filters['ip_version'] = parsed_args.ip_version
if parsed_args.dhcp:
filters['enable_dhcp'] = True
+ filters['is_dhcp_enabled'] = True
elif parsed_args.no_dhcp:
filters['enable_dhcp'] = False
+ filters['is_dhcp_enabled'] = False
if parsed_args.service_types:
filters['service_types'] = parsed_args.service_types
if parsed_args.project:
@@ -454,6 +477,7 @@ class ListSubnet(command.Lister):
parsed_args.project_domain,
).id
filters['tenant_id'] = project_id
+ filters['project_id'] = project_id
if parsed_args.network:
network_id = network_client.find_network(parsed_args.network,
ignore_missing=False).id
@@ -472,7 +496,7 @@ class ListSubnet(command.Lister):
headers += ('Project', 'DHCP', 'Name Servers',
'Allocation Pools', 'Host Routes', 'IP Version',
'Gateway', 'Service Types')
- columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers',
+ columns += ('project_id', 'is_dhcp_enabled', 'dns_nameservers',
'allocation_pools', 'host_routes', 'ip_version',
'gateway_ip', 'service_types')
@@ -483,8 +507,10 @@ class ListSubnet(command.Lister):
) for s in data))
+# TODO(abhiraut): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetSubnet(command.Command):
- """Set subnet properties"""
+ _description = _("Set subnet properties")
def get_parser(self, prog_name):
parser = super(SetSubnet, self).get_parser(prog_name)
@@ -532,7 +558,10 @@ class SetSubnet(command.Command):
attrs = _get_attrs(self.app.client_manager, parsed_args,
is_create=False)
if 'dns_nameservers' in attrs:
- attrs['dns_nameservers'] += obj.dns_nameservers
+ if not parsed_args.no_dns_nameservers:
+ attrs['dns_nameservers'] += obj.dns_nameservers
+ elif parsed_args.no_dns_nameservers:
+ attrs['dns_nameservers'] = []
if 'host_routes' in attrs:
if not parsed_args.no_host_route:
attrs['host_routes'] += obj.host_routes
@@ -542,7 +571,7 @@ class SetSubnet(command.Command):
if not parsed_args.no_allocation_pool:
attrs['allocation_pools'] += obj.allocation_pools
elif parsed_args.no_allocation_pool:
- attrs['allocation_pools'] = ''
+ attrs['allocation_pools'] = []
if 'service_types' in attrs:
attrs['service_types'] += obj.service_types
client.update_subnet(obj, **attrs)
@@ -550,7 +579,7 @@ class SetSubnet(command.Command):
class ShowSubnet(command.ShowOne):
- """Display subnet details"""
+ _description = _("Display subnet details")
def get_parser(self, prog_name):
parser = super(ShowSubnet, self).get_parser(prog_name)
@@ -564,13 +593,13 @@ class ShowSubnet(command.ShowOne):
def take_action(self, parsed_args):
obj = self.app.client_manager.network.find_subnet(parsed_args.subnet,
ignore_missing=False)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
class UnsetSubnet(command.Command):
- """Unset subnet properties"""
+ _description = _("Unset subnet properties")
def get_parser(self, prog_name):
parser = super(UnsetSubnet, self).get_parser(prog_name)
diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py
index a01d2f7b..04731111 100644
--- a/openstackclient/network/v2/subnet_pool.py
+++ b/openstackclient/network/v2/subnet_pool.py
@@ -12,6 +12,7 @@
#
"""Subnet pool action implementations"""
+
import copy
import logging
@@ -22,17 +23,21 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import sdk_utils
LOG = logging.getLogger(__name__)
def _get_columns(item):
- columns = list(item.keys())
- if 'tenant_id' in columns:
- columns.remove('tenant_id')
- columns.append('project_id')
- return tuple(sorted(columns))
+ column_map = {
+ 'default_prefix_length': 'default_prefixlen',
+ 'is_shared': 'shared',
+ 'maximum_prefix_length': 'max_prefixlen',
+ 'minimum_prefix_length': 'min_prefixlen',
+ 'tenant_id': 'project_id',
+ }
+ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
_formatters = {
@@ -134,8 +139,10 @@ def _add_default_options(parser):
)
+# TODO(rtheis): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class CreateSubnetPool(command.ShowOne):
- """Create subnet pool"""
+ _description = _("Create subnet pool")
def get_parser(self, prog_name):
parser = super(CreateSubnetPool, self).get_parser(prog_name)
@@ -184,13 +191,13 @@ class CreateSubnetPool(command.ShowOne):
if "prefixes" not in attrs:
attrs['prefixes'] = []
obj = client.create_subnet_pool(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
class DeleteSubnetPool(command.Command):
- """Delete subnet pool(s)"""
+ _description = _("Delete subnet pool(s)")
def get_parser(self, prog_name):
parser = super(DeleteSubnetPool, self).get_parser(prog_name)
@@ -213,8 +220,8 @@ class DeleteSubnetPool(command.Command):
except Exception as e:
result += 1
LOG.error(_("Failed to delete subnet pool with "
- "name or ID '%(pool)s': %(e)s")
- % {'pool': pool, 'e': e})
+ "name or ID '%(pool)s': %(e)s"),
+ {'pool': pool, 'e': e})
if result > 0:
total = len(parsed_args.subnet_pool)
@@ -223,8 +230,10 @@ class DeleteSubnetPool(command.Command):
raise exceptions.CommandError(msg)
+# TODO(rtheis): Use only the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class ListSubnetPool(command.Lister):
- """List subnet pools"""
+ _description = _("List subnet pools")
def get_parser(self, prog_name):
parser = super(ListSubnetPool, self).get_parser(prog_name)
@@ -238,40 +247,42 @@ class ListSubnetPool(command.Lister):
shared_group.add_argument(
'--share',
action='store_true',
- help=_("List subnets shared between projects"),
+ help=_("List subnet pools shared between projects"),
)
shared_group.add_argument(
'--no-share',
action='store_true',
- help=_("List subnets not shared between projects"),
+ help=_("List subnet pools not shared between projects"),
)
default_group = parser.add_mutually_exclusive_group()
default_group.add_argument(
'--default',
action='store_true',
- help=_("List subnets used as the default external subnet pool"),
+ help=_("List subnet pools used as the default external "
+ "subnet pool"),
)
default_group.add_argument(
'--no-default',
action='store_true',
- help=_("List subnets not used as the default external subnet pool")
+ help=_("List subnet pools not used as the default external "
+ "subnet pool")
)
parser.add_argument(
'--project',
metavar='<project>',
- help=_("List subnets according to their project (name or ID)")
+ help=_("List subnet pools according to their project (name or ID)")
)
identity_common.add_project_domain_option_to_parser(parser)
parser.add_argument(
'--name',
metavar='<name>',
- help=_("List only subnets of given name in output")
+ help=_("List only subnet pools of given name in output")
)
parser.add_argument(
'--address-scope',
metavar='<address-scope>',
- help=_("List only subnets of given address scope (name or ID) "
- "in output")
+ help=_("List only subnet pools of given address scope "
+ "(name or ID) in output")
)
return parser
@@ -281,8 +292,10 @@ class ListSubnetPool(command.Lister):
filters = {}
if parsed_args.share:
filters['shared'] = True
+ filters['is_shared'] = True
elif parsed_args.no_share:
filters['shared'] = False
+ filters['is_shared'] = False
if parsed_args.default:
filters['is_default'] = True
elif parsed_args.no_default:
@@ -294,6 +307,7 @@ class ListSubnetPool(command.Lister):
parsed_args.project_domain,
).id
filters['tenant_id'] = project_id
+ filters['project_id'] = project_id
if parsed_args.name is not None:
filters['name'] = parsed_args.name
if parsed_args.address_scope:
@@ -308,8 +322,8 @@ class ListSubnetPool(command.Lister):
if parsed_args.long:
headers += ('Default Prefix Length', 'Address Scope',
'Default Subnet Pool', 'Shared')
- columns += ('default_prefixlen', 'address_scope_id',
- 'is_default', 'shared')
+ columns += ('default_prefix_length', 'address_scope_id',
+ 'is_default', 'is_shared')
return (headers,
(utils.get_item_properties(
@@ -318,8 +332,10 @@ class ListSubnetPool(command.Lister):
) for s in data))
+# TODO(rtheis): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
class SetSubnetPool(command.Command):
- """Set subnet pool properties"""
+ _description = _("Set subnet pool properties")
def get_parser(self, prog_name):
parser = super(SetSubnetPool, self).get_parser(prog_name)
@@ -371,7 +387,7 @@ class SetSubnetPool(command.Command):
class ShowSubnetPool(command.ShowOne):
- """Display subnet pool details"""
+ _description = _("Display subnet pool details")
def get_parser(self, prog_name):
parser = super(ShowSubnetPool, self).get_parser(prog_name)
@@ -388,13 +404,13 @@ class ShowSubnetPool(command.ShowOne):
parsed_args.subnet_pool,
ignore_missing=False
)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return (columns, data)
+ return (display_columns, data)
class UnsetSubnetPool(command.Command):
- """Unset subnet pool properties"""
+ _description = _("Unset subnet pool properties")
def get_parser(self, prog_name):
parser = super(UnsetSubnetPool, self).get_parser(prog_name)
diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py
index 801fe450..4847f8bb 100644
--- a/openstackclient/object/v1/account.py
+++ b/openstackclient/object/v1/account.py
@@ -18,9 +18,11 @@ from osc_lib.command import command
from osc_lib import utils
import six
+from openstackclient.i18n import _
+
class SetAccount(command.Command):
- """Set account properties"""
+ _description = _("Set account properties")
def get_parser(self, prog_name):
parser = super(SetAccount, self).get_parser(prog_name)
@@ -29,8 +31,8 @@ class SetAccount(command.Command):
metavar="<key=value>",
required=True,
action=parseractions.KeyValueAction,
- help="Set a property on this account "
- "(repeat option to set multiple properties)"
+ help=_("Set a property on this account "
+ "(repeat option to set multiple properties)")
)
return parser
@@ -41,7 +43,7 @@ class SetAccount(command.Command):
class ShowAccount(command.ShowOne):
- """Display account details"""
+ _description = _("Display account details")
def take_action(self, parsed_args):
data = self.app.client_manager.object_store.account_show()
@@ -51,7 +53,7 @@ class ShowAccount(command.ShowOne):
class UnsetAccount(command.Command):
- """Unset account properties"""
+ _description = _("Unset account properties")
def get_parser(self, prog_name):
parser = super(UnsetAccount, self).get_parser(prog_name)
@@ -61,8 +63,8 @@ class UnsetAccount(command.Command):
required=True,
action='append',
default=[],
- help='Property to remove from account '
- '(repeat option to remove multiple properties)',
+ help=_('Property to remove from account '
+ '(repeat option to remove multiple properties)'),
)
return parser
diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py
index 2f0d4ac2..88fb8602 100644
--- a/openstackclient/object/v1/container.py
+++ b/openstackclient/object/v1/container.py
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class CreateContainer(command.Lister):
- """Create new container"""
+ _description = _("Create new container")
def get_parser(self, prog_name):
parser = super(CreateContainer, self).get_parser(prog_name)
@@ -37,7 +37,7 @@ class CreateContainer(command.Lister):
'containers',
metavar='<container-name>',
nargs="+",
- help='New container name(s)',
+ help=_('New container name(s)'),
)
return parser
@@ -63,7 +63,7 @@ class CreateContainer(command.Lister):
class DeleteContainer(command.Command):
- """Delete container"""
+ _description = _("Delete container")
def get_parser(self, prog_name):
parser = super(DeleteContainer, self).get_parser(prog_name)
@@ -71,13 +71,13 @@ class DeleteContainer(command.Command):
'--recursive', '-r',
action='store_true',
default=False,
- help='Recursively delete objects and container',
+ help=_('Recursively delete objects and container'),
)
parser.add_argument(
'containers',
metavar='<container>',
nargs="+",
- help='Container(s) to delete',
+ help=_('Container(s) to delete'),
)
return parser
@@ -98,42 +98,42 @@ class DeleteContainer(command.Command):
class ListContainer(command.Lister):
- """List containers"""
+ _description = _("List containers")
def get_parser(self, prog_name):
parser = super(ListContainer, self).get_parser(prog_name)
parser.add_argument(
"--prefix",
metavar="<prefix>",
- help="Filter list using <prefix>",
+ help=_("Filter list using <prefix>"),
)
parser.add_argument(
"--marker",
metavar="<marker>",
- help="Anchor for paging",
+ help=_("Anchor for paging"),
)
parser.add_argument(
"--end-marker",
metavar="<end-marker>",
- help="End anchor for paging",
+ help=_("End anchor for paging"),
)
parser.add_argument(
"--limit",
metavar="<limit>",
type=int,
- help="Limit the number of containers returned",
+ help=_("Limit the number of containers returned"),
)
parser.add_argument(
'--long',
action='store_true',
default=False,
- help='List additional fields in output',
+ help=_('List additional fields in output'),
)
parser.add_argument(
'--all',
action='store_true',
default=False,
- help='List all containers (default is 10000)',
+ help=_('List all containers (default is 10000)'),
)
return parser
@@ -168,14 +168,14 @@ class ListContainer(command.Lister):
class SaveContainer(command.Command):
- """Save container contents locally"""
+ _description = _("Save container contents locally")
def get_parser(self, prog_name):
parser = super(SaveContainer, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Container to save',
+ help=_('Container to save'),
)
return parser
@@ -186,22 +186,22 @@ class SaveContainer(command.Command):
class SetContainer(command.Command):
- """Set container properties"""
+ _description = _("Set container properties")
def get_parser(self, prog_name):
parser = super(SetContainer, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Container to modify',
+ help=_('Container to modify'),
)
parser.add_argument(
"--property",
metavar="<key=value>",
required=True,
action=parseractions.KeyValueAction,
- help="Set a property on this container "
- "(repeat option to set multiple properties)"
+ help=_("Set a property on this container "
+ "(repeat option to set multiple properties)")
)
return parser
@@ -213,14 +213,14 @@ class SetContainer(command.Command):
class ShowContainer(command.ShowOne):
- """Display container details"""
+ _description = _("Display container details")
def get_parser(self, prog_name):
parser = super(ShowContainer, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Container to display',
+ help=_('Container to display'),
)
return parser
@@ -236,14 +236,14 @@ class ShowContainer(command.ShowOne):
class UnsetContainer(command.Command):
- """Unset container properties"""
+ _description = _("Unset container properties")
def get_parser(self, prog_name):
parser = super(UnsetContainer, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Container to modify',
+ help=_('Container to modify'),
)
parser.add_argument(
'--property',
@@ -251,8 +251,8 @@ class UnsetContainer(command.Command):
required=True,
action='append',
default=[],
- help='Property to remove from container '
- '(repeat option to remove multiple properties)',
+ help=_('Property to remove from container '
+ '(repeat option to remove multiple properties)'),
)
return parser
diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py
index 88f6815e..71b6f520 100644
--- a/openstackclient/object/v1/object.py
+++ b/openstackclient/object/v1/object.py
@@ -30,26 +30,26 @@ LOG = logging.getLogger(__name__)
class CreateObject(command.Lister):
- """Upload object to container"""
+ _description = _("Upload object to container")
def get_parser(self, prog_name):
parser = super(CreateObject, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Container for new object',
+ help=_('Container for new object'),
)
parser.add_argument(
'objects',
metavar='<filename>',
nargs="+",
- help='Local filename(s) to upload',
+ help=_('Local filename(s) to upload'),
)
parser.add_argument(
'--name',
metavar='<name>',
- help='Upload a file and rename it. '
- 'Can only be used when uploading a single object'
+ help=_('Upload a file and rename it. '
+ 'Can only be used when uploading a single object')
)
return parser
@@ -81,20 +81,20 @@ class CreateObject(command.Lister):
class DeleteObject(command.Command):
- """Delete object from container"""
+ _description = _("Delete object from container")
def get_parser(self, prog_name):
parser = super(DeleteObject, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Delete object(s) from <container>',
+ help=_('Delete object(s) from <container>'),
)
parser.add_argument(
'objects',
metavar='<object>',
nargs="+",
- help='Object(s) to delete',
+ help=_('Object(s) to delete'),
)
return parser
@@ -108,52 +108,52 @@ class DeleteObject(command.Command):
class ListObject(command.Lister):
- """List objects"""
+ _description = _("List objects")
def get_parser(self, prog_name):
parser = super(ListObject, self).get_parser(prog_name)
parser.add_argument(
"container",
metavar="<container>",
- help="Container to list",
+ help=_("Container to list"),
)
parser.add_argument(
"--prefix",
metavar="<prefix>",
- help="Filter list using <prefix>",
+ help=_("Filter list using <prefix>"),
)
parser.add_argument(
"--delimiter",
metavar="<delimiter>",
- help="Roll up items with <delimiter>",
+ help=_("Roll up items with <delimiter>"),
)
parser.add_argument(
"--marker",
metavar="<marker>",
- help="Anchor for paging",
+ help=_("Anchor for paging"),
)
parser.add_argument(
"--end-marker",
metavar="<end-marker>",
- help="End anchor for paging",
+ help=_("End anchor for paging"),
)
parser.add_argument(
"--limit",
metavar="<limit>",
type=int,
- help="Limit the number of objects returned",
+ help=_("Limit the number of objects returned"),
)
parser.add_argument(
'--long',
action='store_true',
default=False,
- help='List additional fields in output',
+ help=_('List additional fields in output'),
)
parser.add_argument(
'--all',
action='store_true',
default=False,
- help='List all objects in container (default is 10000)',
+ help=_('List all objects in container (default is 10000)'),
)
return parser
@@ -197,24 +197,24 @@ class ListObject(command.Lister):
class SaveObject(command.Command):
- """Save object locally"""
+ _description = _("Save object locally")
def get_parser(self, prog_name):
parser = super(SaveObject, self).get_parser(prog_name)
parser.add_argument(
"--file",
metavar="<filename>",
- help="Destination filename (defaults to object name)",
+ help=_("Destination filename (defaults to object name)"),
)
parser.add_argument(
'container',
metavar='<container>',
- help='Download <object> from <container>',
+ help=_('Download <object> from <container>'),
)
parser.add_argument(
"object",
metavar="<object>",
- help="Object to save",
+ help=_("Object to save"),
)
return parser
@@ -227,27 +227,27 @@ class SaveObject(command.Command):
class SetObject(command.Command):
- """Set object properties"""
+ _description = _("Set object properties")
def get_parser(self, prog_name):
parser = super(SetObject, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Modify <object> from <container>',
+ help=_('Modify <object> from <container>'),
)
parser.add_argument(
'object',
metavar='<object>',
- help='Object to modify',
+ help=_('Object to modify'),
)
parser.add_argument(
"--property",
metavar="<key=value>",
required=True,
action=parseractions.KeyValueAction,
- help="Set a property on this object "
- "(repeat option to set multiple properties)"
+ help=_("Set a property on this object "
+ "(repeat option to set multiple properties)")
)
return parser
@@ -260,19 +260,19 @@ class SetObject(command.Command):
class ShowObject(command.ShowOne):
- """Display object details"""
+ _description = _("Display object details")
def get_parser(self, prog_name):
parser = super(ShowObject, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Display <object> from <container>',
+ help=_('Display <object> from <container>'),
)
parser.add_argument(
'object',
metavar='<object>',
- help='Object to display',
+ help=_('Object to display'),
)
return parser
@@ -289,19 +289,19 @@ class ShowObject(command.ShowOne):
class UnsetObject(command.Command):
- """Unset object properties"""
+ _description = _("Unset object properties")
def get_parser(self, prog_name):
parser = super(UnsetObject, self).get_parser(prog_name)
parser.add_argument(
'container',
metavar='<container>',
- help='Modify <object> from <container>',
+ help=_('Modify <object> from <container>'),
)
parser.add_argument(
'object',
metavar='<object>',
- help='Object to modify',
+ help=_('Object to modify'),
)
parser.add_argument(
'--property',
@@ -309,8 +309,8 @@ class UnsetObject(command.Command):
required=True,
action='append',
default=[],
- help='Property to remove from object '
- '(repeat option to remove multiple properties)',
+ help=_('Property to remove from object '
+ '(repeat option to remove multiple properties)'),
)
return parser
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 3971b6ef..d7fe6ac1 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -76,7 +76,9 @@ class OpenStackShell(shell.OpenStackShell):
# Loop through extensions to get API versions
for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
- option = mod.API_VERSION_OPTION.replace('os_', '')
+ # Only replace the first instance of "os", some service names will
+ # have "os" in their name, like: "antiddos"
+ option = mod.API_VERSION_OPTION.replace('os_', '', 1)
version_opt = str(self.cloud.config.get(option, default_version))
if version_opt:
api = mod.API_NAME
@@ -93,10 +95,12 @@ class OpenStackShell(shell.OpenStackShell):
mod_versions = getattr(mod, 'API_VERSIONS', None)
if not skip_old_check and mod_versions:
if version_opt not in mod_versions:
+ sorted_versions = sorted(
+ mod.API_VERSIONS.keys(),
+ key=lambda s: list(map(int, s.split('.'))))
self.log.warning(
- "%s version %s is not in supported versions %s"
- % (api, version_opt,
- ', '.join(list(mod.API_VERSIONS.keys()))))
+ "%s version %s is not in supported versions: %s"
+ % (api, version_opt, ', '.join(sorted_versions)))
# Command groups deal only with major versions
version = '.v' + version_opt.replace('.', '_').split('_')[0]
@@ -146,10 +150,10 @@ class OpenStackShell(shell.OpenStackShell):
'auth_type': self._auth_type,
},
)
- except (IOError, OSError) as e:
+ except (IOError, OSError):
self.log.critical("Could not read clouds.yaml configuration file")
self.print_help_if_requested()
- raise e
+ raise
if not self.options.debug:
self.options.debug = None
diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py
index 298b2454..85743296 100644
--- a/openstackclient/tests/functional/base.py
+++ b/openstackclient/tests/functional/base.py
@@ -16,7 +16,6 @@ import shlex
import subprocess
import testtools
-import six
from tempest.lib.cli import output_parser
from tempest.lib import exceptions
@@ -63,8 +62,8 @@ class TestCase(testtools.TestCase):
return cls.openstack('extension list ' + opts)
@classmethod
- def get_opts(cls, fields, format='value'):
- return ' -f {0} {1}'.format(format,
+ def get_opts(cls, fields, output_format='value'):
+ return ' -f {0} {1}'.format(output_format,
' '.join(['-c ' + it for it in fields]))
@classmethod
@@ -77,29 +76,28 @@ 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:
for field in field_names:
self.assertIn(field, item)
- def assert_show_fields(self, items, field_names):
+ def assert_show_fields(self, show_output, field_names):
"""Verify that all items have keys listed in field_names."""
- for item in items:
- for key in six.iterkeys(item):
- self.assertIn(key, field_names)
-
- def assert_show_structure(self, items, field_names):
- """Verify that all field_names listed in keys of all items."""
- if isinstance(items, list):
- o = {}
- for d in items:
- o.update(d)
- else:
- o = items
- item_keys = o.keys()
- for field in field_names:
- self.assertIn(field, item_keys)
+
+ # field_names = ['name', 'description']
+ # show_output = [{'name': 'fc2b98d8faed4126b9e371eda045ade2'},
+ # {'description': 'description-821397086'}]
+ # this next line creates a flattened list of all 'keys' (like 'name',
+ # and 'description' out of the output
+ all_headers = [item for sublist in show_output for item in sublist]
+ for field_name in field_names:
+ self.assertIn(field_name, all_headers)
def parse_show_as_object(self, raw_output):
"""Return a dict with values parsed from cli output."""
diff --git a/openstackclient/tests/functional/common/test_availability_zone.py b/openstackclient/tests/functional/common/test_availability_zone.py
index f73e1ed9..025da95c 100644
--- a/openstackclient/tests/functional/common/test_availability_zone.py
+++ b/openstackclient/tests/functional/common/test_availability_zone.py
@@ -10,16 +10,23 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
+
from openstackclient.tests.functional import base
class AvailabilityZoneTests(base.TestCase):
"""Functional tests for availability zone. """
- HEADERS = ["'Zone Name'"]
- # So far, all components have the same default availability zone name.
- DEFAULT_AZ_NAME = 'nova'
def test_availability_zone_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('availability zone list' + opts)
- self.assertIn(self.DEFAULT_AZ_NAME, raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'availability zone list -f json'))
+ zones = [x['Zone Name'] for x in cmd_output]
+ self.assertIn(
+ 'internal',
+ zones
+ )
+ self.assertIn(
+ 'nova',
+ zones
+ )
diff --git a/openstackclient/tests/functional/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py
index f47d3b00..63a17d0e 100644
--- a/openstackclient/tests/functional/common/test_configuration.py
+++ b/openstackclient/tests/functional/common/test_configuration.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import os
from openstackclient.common import configuration
@@ -20,25 +21,47 @@ BASIC_CONFIG_HEADERS = ['Field', 'Value']
class ConfigurationTests(base.TestCase):
-
- opts = "-f value -c auth.password"
+ """Functional test for configuration."""
def test_configuration_show(self):
+
+ # Test show without option
raw_output = self.openstack('configuration show')
items = self.parse_listing(raw_output)
self.assert_table_structure(items, BASIC_CONFIG_HEADERS)
- def test_configuration_show_unmask(self):
- raw_output = self.openstack('configuration show --unmask ' + self.opts)
+ cmd_output = json.loads(self.openstack(
+ 'configuration show -f json'
+ ))
+ self.assertEqual(
+ configuration.REDACTED,
+ cmd_output['auth.password']
+ )
+
+ # Test show --mask
+ cmd_output = json.loads(self.openstack(
+ 'configuration show --mask -f json'
+ ))
+ self.assertEqual(
+ configuration.REDACTED,
+ cmd_output['auth.password']
+ )
+
+ # Test show --unmask
+ cmd_output = json.loads(self.openstack(
+ 'configuration show --unmask -f json'
+ ))
# If we are using os-client-config, this will not be set. Rather than
# parse clouds.yaml to get the right value, just make sure
# we are not getting redacted.
passwd = os.environ.get('OS_PASSWORD')
if passwd:
- self.assertEqual(passwd + '\n', raw_output)
+ self.assertEqual(
+ passwd,
+ cmd_output['auth.password']
+ )
else:
- self.assertNotEqual(configuration.REDACTED + '\n', raw_output)
-
- def test_configuration_show_mask(self):
- raw_output = self.openstack('configuration show --mask ' + self.opts)
- self.assertEqual(configuration.REDACTED + '\n', raw_output)
+ self.assertNotEqual(
+ configuration.REDACTED,
+ cmd_output['auth.password']
+ )
diff --git a/openstackclient/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py
index bbc52197..211c52b1 100644
--- a/openstackclient/tests/functional/common/test_help.py
+++ b/openstackclient/tests/functional/common/test_help.py
@@ -64,3 +64,10 @@ class HelpTests(base.TestCase):
raw_output = self.openstack('help server')
for command in [row[0] for row in self.SERVER_COMMANDS]:
self.assertIn(command, raw_output)
+
+ def test_networking_commands_help(self):
+ """Check networking related commands in help message."""
+ raw_output = self.openstack('help network list')
+ self.assertIn('List networks', raw_output)
+ raw_output = self.openstack('network create --help')
+ self.assertIn('Create new network', raw_output)
diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py
new file mode 100644
index 00000000..e9e4ee3a
--- /dev/null
+++ b/openstackclient/tests/functional/common/test_module.py
@@ -0,0 +1,79 @@
+# Copyright 2017 Huawei, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import json
+
+from openstackclient.tests.functional import base
+
+
+class ModuleTest(base.TestCase):
+ """Functional tests for openstackclient module list output."""
+
+ CLIENTS = ['openstackclient',
+ 'keystoneclient',
+ 'novaclient',
+ 'openstack']
+
+ LIBS = ['osc_lib',
+ 'os_client_config',
+ 'keystoneauth1']
+
+ def test_module_list(self):
+ # Test module list
+ cmd_output = json.loads(self.openstack('module list -f json'))
+ for one_module in self.CLIENTS:
+ self.assertIn(one_module, cmd_output.keys())
+ for one_module in self.LIBS:
+ self.assertNotIn(one_module, cmd_output.keys())
+
+ # Test module list --all
+ cmd_output = json.loads(self.openstack('module list --all -f json'))
+ for one_module in self.CLIENTS + self.LIBS:
+ self.assertIn(one_module, cmd_output.keys())
+
+
+class CommandTest(base.TestCase):
+ """Functional tests for openstackclient command list."""
+ GROUPS = [
+ 'openstack.volume.v2',
+ 'openstack.network.v2',
+ 'openstack.image.v2',
+ 'openstack.identity.v3',
+ 'openstack.compute.v2',
+ 'openstack.common',
+ 'openstack.cli',
+ ]
+
+ def test_command_list_no_option(self):
+ cmd_output = json.loads(self.openstack('command list -f json'))
+ group_names = [each.get('Command Group') for each in cmd_output]
+ for one_group in self.GROUPS:
+ self.assertIn(one_group, group_names)
+
+ def test_command_list_with_group(self):
+ input_groups = [
+ 'volume',
+ 'network',
+ 'image',
+ 'identity',
+ 'compute.v2'
+ ]
+ for each_input in input_groups:
+ cmd_output = json.loads(self.openstack(
+ 'command list --group %s -f json' % each_input
+ ))
+ group_names = [each.get('Command Group') for each in cmd_output]
+ for each_name in group_names:
+ self.assertIn(each_input, each_name)
diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py
index c1de9aa9..8092b3ce 100644
--- a/openstackclient/tests/functional/common/test_quota.py
+++ b/openstackclient/tests/functional/common/test_quota.py
@@ -25,6 +25,27 @@ class QuotaTests(base.TestCase):
cls.PROJECT_NAME =\
cls.get_openstack_configuration_value('auth.project_name')
+ def test_quota_list_network_option(self):
+ self.openstack('quota set --networks 40 ' +
+ self.PROJECT_NAME)
+ raw_output = self.openstack('quota list --network')
+ self.assertIsNotNone(raw_output)
+ self.assertIn("40", raw_output)
+
+ def test_quota_list_compute_option(self):
+ self.openstack('quota set --instances 40 ' +
+ self.PROJECT_NAME)
+ raw_output = self.openstack('quota list --compute')
+ self.assertIsNotNone(raw_output)
+ self.assertIn("40", raw_output)
+
+ def test_quota_list_volume_option(self):
+ self.openstack('quota set --backups 40 ' +
+ self.PROJECT_NAME)
+ raw_output = self.openstack('quota list --volume')
+ self.assertIsNotNone(raw_output)
+ self.assertIn("40", raw_output)
+
def test_quota_set(self):
self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' +
self.PROJECT_NAME)
diff --git a/openstackclient/tests/functional/compute/v2/common.py b/openstackclient/tests/functional/compute/v2/common.py
new file mode 100644
index 00000000..99d87bb4
--- /dev/null
+++ b/openstackclient/tests/functional/compute/v2/common.py
@@ -0,0 +1,145 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import json
+import time
+import uuid
+
+from tempest.lib import exceptions
+
+from openstackclient.tests.functional import base
+
+
+class ComputeTestCase(base.TestCase):
+ """Common functional test bits for Compute commands"""
+
+ flavor_name = None
+ image_name = None
+ network_arg = None
+
+ def setUp(self):
+ """Select common resources"""
+ super(ComputeTestCase, self).setUp()
+ self.flavor_name = self.get_flavor()
+ self.image_name = self.get_image()
+ self.network_arg = self.get_network()
+
+ @classmethod
+ def get_flavor(cls):
+ # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional
+ # tests may create other flavors.
+ flavors = json.loads(cls.openstack(
+ "flavor list -f json "
+ ))
+ server_flavor = None
+ for flavor in flavors:
+ if flavor['Name'] in ['m1.tiny', 'cirros256']:
+ server_flavor = flavor['Name']
+ break
+ return server_flavor
+
+ @classmethod
+ def get_image(cls):
+ # NOTE(rtheis): Get first Cirros image since functional tests may
+ # create other images. Image may be named '-uec' or
+ # '-disk'.
+ images = json.loads(cls.openstack(
+ "image list -f json "
+ ))
+ server_image = None
+ for image in images:
+ if (image['Name'].startswith('cirros-') and
+ (image['Name'].endswith('-uec') or
+ image['Name'].endswith('-disk'))):
+ server_image = image['Name']
+ break
+ return server_image
+
+ @classmethod
+ def get_network(cls):
+ try:
+ # NOTE(rtheis): Get private network since functional tests may
+ # create other networks.
+ cmd_output = json.loads(cls.openstack(
+ 'network show private -f json'
+ ))
+ except exceptions.CommandFailed:
+ return ''
+ return '--nic net-id=' + cmd_output['id']
+
+ def server_create(self, name=None, cleanup=True):
+ """Create server, with cleanup"""
+ if not self.flavor_name:
+ self.flavor_name = self.get_flavor()
+ if not self.image_name:
+ self.image_name = self.get_image()
+ if not self.network_arg:
+ self.network_arg = self.get_network()
+ name = name or uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'server create -f json ' +
+ '--flavor ' + self.flavor_name + ' ' +
+ '--image ' + self.image_name + ' ' +
+ self.network_arg + ' ' +
+ '--wait ' +
+ name
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+ if cleanup:
+ self.addCleanup(self.server_delete, name)
+ return cmd_output
+
+ def server_delete(self, name):
+ """Delete server by name"""
+ raw_output = self.openstack('server delete ' + name)
+ self.assertOutput('', raw_output)
+
+ def wait_for_status(
+ self,
+ name,
+ expected_status='ACTIVE',
+ wait=900,
+ interval=10,
+ ):
+ """Wait until server reaches expected status"""
+ # TODO(thowe): Add a server wait command to osc
+ failures = ['ERROR']
+ total_sleep = 0
+ while total_sleep < wait:
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ status = cmd_output['status']
+ print('Waiting for {}, current status: {}'.format(
+ expected_status,
+ status,
+ ))
+ if status == expected_status:
+ break
+ self.assertNotIn(status, failures)
+ time.sleep(interval)
+ total_sleep += interval
+
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ status = cmd_output['status']
+ self.assertEqual(status, expected_status)
+ # give it a little bit more time
+ time.sleep(5)
diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py
index 7115db1f..1a112e82 100644
--- a/openstackclient/tests/functional/compute/v2/test_agent.py
+++ b/openstackclient/tests/functional/compute/v2/test_agent.py
@@ -11,6 +11,7 @@
# under the License.
import hashlib
+import json
from openstackclient.tests.functional import base
@@ -18,60 +19,178 @@ from openstackclient.tests.functional import base
class ComputeAgentTests(base.TestCase):
"""Functional tests for compute agent."""
- ID = None
- MD5HASH = hashlib.md5().hexdigest()
- URL = "http://localhost"
- VER = "v1"
- OS = "TEST_OS"
- ARCH = "x86_64"
- HYPER = "kvm"
-
- HEADERS = ['agent_id', 'md5hash']
- FIELDS = ['agent_id', 'md5hash']
-
- @classmethod
- def setUpClass(cls):
- opts = cls.get_opts(cls.HEADERS)
- raw_output = cls.openstack('compute agent create ' +
- cls.OS + ' ' + cls.ARCH + ' ' +
- cls.VER + ' ' + cls.URL + ' ' +
- cls.MD5HASH + ' ' + cls.HYPER + ' ' +
- opts)
-
- # Get agent id because agent can only be deleted by ID
- output_list = raw_output.split('\n', 1)
- cls.ID = output_list[0]
-
- cls.assertOutput(cls.MD5HASH + '\n', output_list[1])
-
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('compute agent delete ' + cls.ID)
- cls.assertOutput('', raw_output)
-
- def test_agent_list(self):
- raw_output = self.openstack('compute agent list')
- self.assertIn(self.ID, raw_output)
- self.assertIn(self.OS, raw_output)
- self.assertIn(self.ARCH, raw_output)
- self.assertIn(self.VER, raw_output)
- self.assertIn(self.URL, raw_output)
- self.assertIn(self.MD5HASH, raw_output)
- self.assertIn(self.HYPER, raw_output)
-
- def test_agent_set(self):
- ver = 'v2'
- url = "http://openstack"
- md5hash = hashlib.md5().hexdigest()
-
- self.openstack('compute agent set '
- + self.ID
- + ' --agent-version ' + ver
- + ' --url ' + url
- + ' --md5hash ' + md5hash)
-
- raw_output = self.openstack('compute agent list')
- self.assertIn(self.ID, raw_output)
- self.assertIn(ver, raw_output)
- self.assertIn(url, raw_output)
- self.assertIn(md5hash, raw_output)
+ # Generate two different md5hash
+ MD5HASH1 = hashlib.md5()
+ MD5HASH1.update('agent_1')
+ MD5HASH1 = MD5HASH1.hexdigest()
+ MD5HASH2 = hashlib.md5()
+ MD5HASH2.update('agent_2')
+ MD5HASH2 = MD5HASH2.hexdigest()
+
+ def test_compute_agent_delete(self):
+ """Test compute agent create, delete multiple"""
+ os1 = "os_1"
+ arch1 = "x86_64"
+ ver1 = "v1"
+ url1 = "http://localhost"
+ md5hash1 = self.MD5HASH1
+ hyper1 = "kvm"
+ cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1))
+
+ cmd_output = json.loads(self.openstack(
+ 'compute agent create -f json ' +
+ cmd1
+ ))
+ agent_id1 = str(cmd_output["agent_id"])
+
+ os2 = "os_2"
+ arch2 = "x86"
+ ver2 = "v2"
+ url2 = "http://openstack"
+ md5hash2 = self.MD5HASH2
+ hyper2 = "xen"
+ cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2))
+
+ cmd_output = json.loads(self.openstack(
+ 'compute agent create -f json ' +
+ cmd2
+ ))
+ agent_id2 = str(cmd_output["agent_id"])
+
+ # Test compute agent delete
+ del_output = self.openstack(
+ 'compute agent delete ' +
+ agent_id1 + ' ' + agent_id2
+ )
+ self.assertOutput('', del_output)
+
+ def test_compute_agent_list(self):
+ """Test compute agent create and list"""
+ os1 = "os_1"
+ arch1 = "x86_64"
+ ver1 = "v1"
+ url1 = "http://localhost"
+ md5hash1 = self.MD5HASH1
+ hyper1 = "kvm"
+ cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1))
+
+ cmd_output = json.loads(self.openstack(
+ 'compute agent create -f json ' +
+ cmd1
+ ))
+ agent_id1 = str(cmd_output["agent_id"])
+ self.addCleanup(self.openstack, 'compute agent delete ' + agent_id1)
+
+ os2 = "os_2"
+ arch2 = "x86"
+ ver2 = "v2"
+ url2 = "http://openstack"
+ md5hash2 = self.MD5HASH2
+ hyper2 = "xen"
+ cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2))
+
+ cmd_output = json.loads(self.openstack(
+ 'compute agent create -f json ' +
+ cmd2
+ ))
+ agent_id2 = str(cmd_output["agent_id"])
+ self.addCleanup(self.openstack, 'compute agent delete ' + agent_id2)
+
+ # Test compute agent list
+ cmd_output = json.loads(self.openstack(
+ 'compute agent list -f json'
+ ))
+
+ hypervisors = [x["Hypervisor"] for x in cmd_output]
+ self.assertIn(hyper1, hypervisors)
+ self.assertIn(hyper2, hypervisors)
+
+ os = [x['OS'] for x in cmd_output]
+ self.assertIn(os1, os)
+ self.assertIn(os2, os)
+
+ archs = [x['Architecture'] for x in cmd_output]
+ self.assertIn(arch1, archs)
+ self.assertIn(arch2, archs)
+
+ versions = [x['Version'] for x in cmd_output]
+ self.assertIn(ver1, versions)
+ self.assertIn(ver2, versions)
+
+ md5hashes = [x['Md5Hash'] for x in cmd_output]
+ self.assertIn(md5hash1, md5hashes)
+ self.assertIn(md5hash2, md5hashes)
+
+ urls = [x['URL'] for x in cmd_output]
+ self.assertIn(url1, urls)
+ self.assertIn(url2, urls)
+
+ # Test compute agent list --hypervisor
+ cmd_output = json.loads(self.openstack(
+ 'compute agent list -f json ' +
+ '--hypervisor kvm'
+ ))
+
+ hypervisors = [x["Hypervisor"] for x in cmd_output]
+ self.assertIn(hyper1, hypervisors)
+ self.assertNotIn(hyper2, hypervisors)
+
+ os = [x['OS'] for x in cmd_output]
+ self.assertIn(os1, os)
+ self.assertNotIn(os2, os)
+
+ archs = [x['Architecture'] for x in cmd_output]
+ self.assertIn(arch1, archs)
+ self.assertNotIn(arch2, archs)
+
+ versions = [x['Version'] for x in cmd_output]
+ self.assertIn(ver1, versions)
+ self.assertNotIn(ver2, versions)
+
+ md5hashes = [x['Md5Hash'] for x in cmd_output]
+ self.assertIn(md5hash1, md5hashes)
+ self.assertNotIn(md5hash2, md5hashes)
+
+ urls = [x['URL'] for x in cmd_output]
+ self.assertIn(url1, urls)
+ self.assertNotIn(url2, urls)
+
+ def test_compute_agent_set(self):
+ """Test compute agent set"""
+ os1 = "os_1"
+ arch1 = "x86_64"
+ ver1 = "v1"
+ ver2 = "v2"
+ url1 = "http://localhost"
+ url2 = "http://openstack"
+ md5hash1 = self.MD5HASH1
+ md5hash2 = self.MD5HASH2
+ hyper1 = "kvm"
+ cmd = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1))
+
+ cmd_output = json.loads(self.openstack(
+ 'compute agent create -f json ' +
+ cmd
+ ))
+ agent_id = str(cmd_output["agent_id"])
+ self.assertEqual(ver1, cmd_output["version"])
+ self.assertEqual(url1, cmd_output["url"])
+ self.assertEqual(md5hash1, cmd_output["md5hash"])
+
+ self.addCleanup(self.openstack, 'compute agent delete ' + agent_id)
+
+ raw_output = self.openstack(
+ 'compute agent set ' +
+ agent_id + ' ' +
+ '--agent-version ' + ver2 + ' ' +
+ '--url ' + url2 + ' ' +
+ '--md5hash ' + md5hash2
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'compute agent list -f json'
+ ))
+ self.assertEqual(ver2, cmd_output[0]["Version"])
+ self.assertEqual(url2, cmd_output[0]["URL"])
+ self.assertEqual(md5hash2, cmd_output[0]["Md5Hash"])
diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py
index 2bc88e7b..95068fc2 100644
--- a/openstackclient/tests/functional/compute/v2/test_aggregate.py
+++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -18,50 +19,164 @@ from openstackclient.tests.functional import base
class AggregateTests(base.TestCase):
"""Functional tests for aggregate."""
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
+ def test_aggregate_create_and_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'aggregate create -f json ' +
+ '--zone nova ' +
+ name1))
+ self.assertEqual(
+ name1,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'nova',
+ cmd_output['availability_zone']
+ )
- @classmethod
- def setUpClass(cls):
- opts = cls.get_opts(cls.FIELDS)
- # Use the default 'nova' availability zone for the aggregate.
- raw_output = cls.openstack(
- 'aggregate create --zone nova ' + cls.NAME + opts
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'aggregate create -f json ' +
+ '--zone nova ' +
+ name2))
+ self.assertEqual(
+ name2,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'nova',
+ cmd_output['availability_zone']
)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('aggregate delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
+ del_output = self.openstack(
+ 'aggregate delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
def test_aggregate_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('aggregate list' + opts)
- self.assertIn(self.NAME, raw_output)
+ """Test aggregate list"""
+ name1 = uuid.uuid4().hex
+ self.openstack(
+ 'aggregate create ' +
+ '--zone nova ' +
+ '--property a=b ' +
+ name1)
+ self.addCleanup(self.openstack, 'aggregate delete ' + name1)
+
+ name2 = uuid.uuid4().hex
+ self.openstack(
+ 'aggregate create ' +
+ '--zone internal ' +
+ '--property c=d ' +
+ name2)
+ self.addCleanup(self.openstack, 'aggregate delete ' + name2)
- def test_aggregate_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('aggregate show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'aggregate list -f json'))
+ names = [x['Name'] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+ zones = [x['Availability Zone'] for x in cmd_output]
+ self.assertIn('nova', zones)
+ self.assertIn('internal', zones)
- def test_aggregate_properties(self):
- opts = self.get_opts(['properties'])
+ # Test aggregate list --long
+ cmd_output = json.loads(self.openstack(
+ 'aggregate list --long -f json'))
+ names = [x['Name'] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+ zones = [x['Availability Zone'] for x in cmd_output]
+ self.assertIn('nova', zones)
+ self.assertIn('internal', zones)
+ properties = [x['Properties'] for x in cmd_output]
+ self.assertIn({'a': 'b'}, properties)
+ self.assertIn({'c': 'd'}, properties)
+
+ def test_aggregate_set_and_unset(self):
+ """Test aggregate set, show and unset"""
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+ self.openstack(
+ 'aggregate create ' +
+ '--zone nova ' +
+ '--property a=b ' +
+ name1)
+ self.addCleanup(self.openstack, 'aggregate delete ' + name2)
raw_output = self.openstack(
- 'aggregate set --property a=b --property c=d ' + self.NAME
+ 'aggregate set --name ' +
+ name2 +
+ ' --zone internal ' +
+ '--no-property ' +
+ '--property c=d ' +
+ name1
)
- self.assertEqual('', raw_output)
+ self.assertOutput('', raw_output)
- raw_output = self.openstack('aggregate show ' + self.NAME + opts)
- self.assertIn("a='b', c='d'\n", raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'aggregate show -f json ' + name2))
+ self.assertEqual(
+ name2,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'internal',
+ cmd_output['availability_zone']
+ )
+ self.assertIn(
+ "c='d'",
+ cmd_output['properties']
+ )
+ self.assertNotIn(
+ "a='b'",
+ cmd_output['properties']
+ )
+ # Test unset
raw_output = self.openstack(
- 'aggregate unset --property a ' + self.NAME
+ 'aggregate unset --property c ' +
+ name2
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'aggregate show -f json ' + name2))
+ self.assertNotIn(
+ "c='d'",
+ cmd_output['properties']
+ )
+
+ def test_aggregate_add_and_remove_host(self):
+ """Test aggregate add and remove host"""
+ name = uuid.uuid4().hex
+ self.openstack(
+ 'aggregate create ' + name)
+ self.addCleanup(self.openstack, 'aggregate delete ' + name)
+
+ # Get a host
+ cmd_output = json.loads(self.openstack(
+ 'host list -f json'))
+ host_name = cmd_output[0]['Host Name']
+
+ # Test add host
+ cmd_output = json.loads(self.openstack(
+ 'aggregate add host -f json ' +
+ name + ' ' +
+ host_name
+ ))
+ self.assertIn(
+ host_name,
+ cmd_output['hosts']
)
- self.assertEqual('', raw_output)
- raw_output = self.openstack('aggregate show ' + self.NAME + opts)
- self.assertIn("c='d'\n", raw_output)
+ # Test remove host
+ cmd_output = json.loads(self.openstack(
+ 'aggregate remove host -f json ' +
+ name + ' ' +
+ host_name
+ ))
+ self.assertNotIn(
+ host_name,
+ cmd_output['hosts']
+ )
diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py
index 794a6cc3..0b01da51 100644
--- a/openstackclient/tests/functional/compute/v2/test_flavor.py
+++ b/openstackclient/tests/functional/compute/v2/test_flavor.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -18,52 +19,224 @@ from openstackclient.tests.functional import base
class FlavorTests(base.TestCase):
"""Functional tests for flavor."""
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
+ PROJECT_NAME = uuid.uuid4().hex
@classmethod
def setUpClass(cls):
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack(
- 'flavor create --property a=b --property c=d ' + cls.NAME + opts)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
+ # Make a project
+ cmd_output = json.loads(cls.openstack(
+ "project create -f json --enable " + cls.PROJECT_NAME
+ ))
+ cls.project_id = cmd_output["id"]
@classmethod
def tearDownClass(cls):
- raw_output = cls.openstack('flavor delete ' + cls.NAME)
+ raw_output = cls.openstack("project delete " + cls.PROJECT_NAME)
cls.assertOutput('', raw_output)
+ def test_flavor_delete(self):
+ """Test create w/project, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ "flavor create -f json " +
+ "--project " + self.PROJECT_NAME + " " +
+ "--private " +
+ name1
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ "flavor create -f json " +
+ "--id qaz " +
+ "--project " + self.PROJECT_NAME + " " +
+ "--private " +
+ name2
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ "qaz",
+ cmd_output["id"],
+ )
+
+ raw_output = self.openstack(
+ "flavor delete " + name1 + " " + name2,
+ )
+ self.assertOutput('', raw_output)
+
def test_flavor_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('flavor list' + opts)
- self.assertIn("small", raw_output)
- self.assertIn(self.NAME, raw_output)
+ """Test create defaults, list filters, delete"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ "flavor create -f json " +
+ "--property a=b " +
+ "--property c=d " +
+ name1
+ ))
+ self.addCleanup(self.openstack, "flavor delete " + name1)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ "flavor create -f json " +
+ "--id qaz " +
+ "--ram 123 " +
+ "--private " +
+ "--property a=b2 " +
+ "--property b=d2 " +
+ name2
+ ))
+ self.addCleanup(self.openstack, "flavor delete " + name2)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ "qaz",
+ cmd_output["id"],
+ )
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 123,
+ cmd_output["ram"],
+ )
+ self.assertEqual(
+ 0,
+ cmd_output["disk"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["os-flavor-access:is_public"],
+ )
+ self.assertEqual(
+ "a='b2', b='d2'",
+ cmd_output["properties"],
+ )
- def test_flavor_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('flavor show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ "flavor list -f json"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertNotIn(name2, col_name)
+
+ # Test list --long
+ cmd_output = json.loads(self.openstack(
+ "flavor list -f json " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ col_properties = [x['Properties'] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertIn("a='b', c='d'", col_properties)
+ self.assertNotIn(name2, col_name)
+ self.assertNotIn("b2', b='d2'", col_properties)
+
+ # Test list --public
+ cmd_output = json.loads(self.openstack(
+ "flavor list -f json " +
+ "--public"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertNotIn(name2, col_name)
+
+ # Test list --private
+ cmd_output = json.loads(self.openstack(
+ "flavor list -f json " +
+ "--private"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, col_name)
+ self.assertIn(name2, col_name)
+
+ # Test list --all
+ cmd_output = json.loads(self.openstack(
+ "flavor list -f json " +
+ "--all"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertIn(name2, col_name)
def test_flavor_properties(self):
- opts = self.get_opts(['properties'])
- # check the properties we added in create command.
- raw_output = self.openstack('flavor show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d'\n", raw_output)
+ """Test create defaults, list filters, delete"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ "flavor create -f json " +
+ "--id qaz " +
+ "--ram 123 " +
+ "--disk 20 " +
+ "--private " +
+ "--property a=first " +
+ "--property b=second " +
+ name1
+ ))
+ self.addCleanup(self.openstack, "flavor delete " + name1)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ "qaz",
+ cmd_output["id"],
+ )
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 123,
+ cmd_output["ram"],
+ )
+ self.assertEqual(
+ 20,
+ cmd_output["disk"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["os-flavor-access:is_public"],
+ )
+ self.assertEqual(
+ "a='first', b='second'",
+ cmd_output["properties"],
+ )
raw_output = self.openstack(
- 'flavor set --property e=f --property g=h ' + self.NAME
+ "flavor set " +
+ "--property a='third and 10' " +
+ "--property g=fourth " +
+ name1
)
self.assertEqual('', raw_output)
- raw_output = self.openstack('flavor show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d', e='f', g='h'\n", raw_output)
+ cmd_output = json.loads(self.openstack(
+ "flavor show -f json " +
+ name1
+ ))
+ self.assertEqual(
+ "qaz",
+ cmd_output["id"],
+ )
+ self.assertEqual(
+ "a='third and 10', b='second', g='fourth'",
+ cmd_output['properties'],
+ )
raw_output = self.openstack(
- 'flavor unset --property a --property c ' + self.NAME
+ "flavor unset " +
+ "--property b " +
+ name1
)
self.assertEqual('', raw_output)
- raw_output = self.openstack('flavor show ' + self.NAME + opts)
- self.assertEqual("e='f', g='h'\n", raw_output)
+ cmd_output = json.loads(self.openstack(
+ "flavor show -f json " +
+ name1
+ ))
+ self.assertEqual(
+ "a='third and 10', g='fourth'",
+ cmd_output["properties"],
+ )
diff --git a/openstackclient/tests/functional/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py
index 01078c61..1e1a03d6 100644
--- a/openstackclient/tests/functional/compute/v2/test_keypair.py
+++ b/openstackclient/tests/functional/compute/v2/test_keypair.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import tempfile
from openstackclient.tests.functional import base
@@ -100,6 +101,26 @@ class KeypairTests(KeypairBase):
)
self.assertIn('tmpkey', raw_output)
+ def test_keypair_create_private_key(self):
+ """Test for create keypair with --private-key option.
+
+ Test steps:
+ 1) Create keypair with private key file
+ 2) Delete keypair
+ """
+ with tempfile.NamedTemporaryFile() as f:
+ cmd_output = json.loads(self.openstack(
+ 'keypair create -f json --private-key %s tmpkey' % f.name,
+ ))
+ self.addCleanup(self.openstack, 'keypair delete tmpkey')
+ self.assertEqual('tmpkey', cmd_output.get('name'))
+ self.assertIsNotNone(cmd_output.get('user_id'))
+ self.assertIsNotNone(cmd_output.get('fingerprint'))
+ pk_content = f.read()
+ self.assertInOutput('-----BEGIN RSA PRIVATE KEY-----', pk_content)
+ self.assertRegex(pk_content, "[0-9A-Za-z+/]+[=]{0,3}\n")
+ self.assertInOutput('-----END RSA PRIVATE KEY-----', pk_content)
+
def test_keypair_create(self):
"""Test keypair create command.
diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py
index 6eedf408..f152de80 100644
--- a/openstackclient/tests/functional/compute/v2/test_server.py
+++ b/openstackclient/tests/functional/compute/v2/test_server.py
@@ -10,299 +10,398 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
+import json
+import uuid
-from tempest.lib.common.utils import data_utils
-
-from openstackclient.tests.functional import base
from tempest.lib import exceptions
+from openstackclient.tests.functional.compute.v2 import common
+from openstackclient.tests.functional.volume.v2 import test_volume
-class ServerTests(base.TestCase):
- """Functional tests for openstack server commands."""
-
- @classmethod
- def get_flavor(cls):
- # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional
- # tests may create other flavors.
- flavors = cls.openstack('flavor list -c Name -f value').split('\n')
- server_flavor = None
- for flavor in flavors:
- if flavor in ['m1.tiny', 'cirros256']:
- server_flavor = flavor
- break
- return server_flavor
-
- @classmethod
- def get_image(cls):
- # NOTE(rtheis): Get cirros image since functional tests may
- # create other images.
- images = cls.openstack('image list -c Name -f value').split('\n')
- server_image = None
- for image in images:
- if image.startswith('cirros-') and image.endswith('-uec'):
- server_image = image
- break
- return server_image
-
- @classmethod
- def get_network(cls):
- try:
- # NOTE(rtheis): Get private network since functional tests may
- # create other networks.
- raw_output = cls.openstack('network show private -c id -f value')
- except exceptions.CommandFailed:
- return ''
- return ' --nic net-id=' + raw_output.strip('\n')
-
- def server_create(self, name=None):
- """Create server. Add cleanup."""
- name = name or data_utils.rand_uuid()
- opts = self.get_opts(self.FIELDS)
- flavor = self.get_flavor()
- image = self.get_image()
- network = self.get_network()
- raw_output = self.openstack('--debug server create --flavor ' +
- flavor +
- ' --image ' + image + network + ' ' +
- name + opts)
- if not raw_output:
- self.fail('Server has not been created!')
- self.addCleanup(self.server_delete, name)
-
- def server_list(self, params=[]):
- """List servers."""
- opts = self.get_opts(params)
- return self.openstack('server list' + opts)
-
- def server_delete(self, name):
- """Delete server by name."""
- self.openstack('server delete ' + name)
-
- def setUp(self):
- """Set necessary variables and create server."""
- super(ServerTests, self).setUp()
- self.NAME = data_utils.rand_name('TestServer')
- self.OTHER_NAME = data_utils.rand_name('TestServer')
- self.HEADERS = ['"Name"']
- self.FIELDS = ['name']
- self.IP_POOL = 'public'
- self.server_create(self.NAME)
-
- def test_server_rename(self):
- """Test server rename command.
-
- Test steps:
- 1) Boot server in setUp
- 2) Rename server
- 3) Check output
- 4) Rename server back to original name
- """
- raw_output = self.openstack('server set --name ' + self.OTHER_NAME +
- ' ' + self.NAME)
- self.assertOutput("", raw_output)
- self.assertNotIn(self.NAME, self.server_list(['Name']))
- self.assertIn(self.OTHER_NAME, self.server_list(['Name']))
- self.openstack('server set --name ' + self.NAME + ' ' +
- self.OTHER_NAME)
+
+class ServerTests(common.ComputeTestCase):
+ """Functional tests for openstack server commands"""
def test_server_list(self):
- """Test server list command.
+ """Test server list, set"""
+ cmd_output = self.server_create()
+ name1 = cmd_output['name']
+ cmd_output = self.server_create()
+ name2 = cmd_output['name']
+ self.wait_for_status(name1, "ACTIVE")
+ self.wait_for_status(name2, "ACTIVE")
+
+ cmd_output = json.loads(self.openstack(
+ 'server list -f json'
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertIn(name2, col_name)
+
+ # Test list --status PAUSED
+ raw_output = self.openstack('server pause ' + name2)
+ self.assertEqual("", raw_output)
+ self.wait_for_status(name2, "PAUSED")
+ cmd_output = json.loads(self.openstack(
+ 'server list -f json ' +
+ '--status ACTIVE'
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertNotIn(name2, col_name)
+ cmd_output = json.loads(self.openstack(
+ 'server list -f json ' +
+ '--status PAUSED'
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, col_name)
+ self.assertIn(name2, col_name)
+
+ def test_server_set(self):
+ """Test server create, delete, set, show"""
+ cmd_output = self.server_create()
+ name = cmd_output['name']
+ # self.wait_for_status(name, "ACTIVE")
+
+ # Have a look at some other fields
+ flavor = json.loads(self.openstack(
+ 'flavor show -f json ' +
+ self.flavor_name
+ ))
+ self.assertEqual(
+ self.flavor_name,
+ flavor['name'],
+ )
+ self.assertEqual(
+ '%s (%s)' % (flavor['name'], flavor['id']),
+ cmd_output["flavor"],
+ )
+ image = json.loads(self.openstack(
+ 'image show -f json ' +
+ self.image_name
+ ))
+ self.assertEqual(
+ self.image_name,
+ image['name'],
+ )
+ self.assertEqual(
+ '%s (%s)' % (image['name'], image['id']),
+ cmd_output["image"],
+ )
+
+ # Test properties set
+ raw_output = self.openstack(
+ 'server set ' +
+ '--property a=b --property c=d ' +
+ name
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ # Really, shouldn't this be a list?
+ self.assertEqual(
+ "a='b', c='d'",
+ cmd_output['properties'],
+ )
- Test steps:
- 1) Boot server in setUp
- 2) List servers
- 3) Check output
- """
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('server list' + opts)
- self.assertIn(self.NAME, raw_output)
+ raw_output = self.openstack(
+ 'server unset ' +
+ '--property a ' +
+ name
+ )
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ self.assertEqual(
+ "c='d'",
+ cmd_output['properties'],
+ )
+
+ # Test set --name
+ new_name = uuid.uuid4().hex
+ raw_output = self.openstack(
+ 'server set ' +
+ '--name ' + new_name + ' ' +
+ name
+ )
+ self.assertOutput("", raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["name"],
+ )
+ # Put it back so we clean up properly
+ raw_output = self.openstack(
+ 'server set ' +
+ '--name ' + name + ' ' +
+ new_name
+ )
+ self.assertOutput("", raw_output)
- def test_server_show(self):
- """Test server show command.
+ def test_server_actions(self):
+ """Test server action pairs
- Test steps:
- 1) Boot server in setUp
- 2) Show server
- 3) Check output
- """
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('server show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
-
- def test_server_metadata(self):
- """Test command to set server metadata.
-
- Test steps:
- 1) Boot server in setUp
- 2) Set properties for server
- 3) Check server properties in server show output
- 4) Unset properties for server
- 5) Check server properties in server show output
+ suspend/resume
+ pause/unpause
+ rescue/unrescue
+ lock/unlock
"""
- self.wait_for_status("ACTIVE")
- # metadata
- raw_output = self.openstack(
- 'server set --property a=b --property c=d ' + self.NAME)
- opts = self.get_opts(["name", "properties"])
- raw_output = self.openstack('server show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
+ cmd_output = self.server_create()
+ name = cmd_output['name']
- raw_output = self.openstack(
- 'server unset --property a ' + self.NAME)
- opts = self.get_opts(["name", "properties"])
- raw_output = self.openstack('server show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\nc='d'\n", raw_output)
-
- def test_server_suspend_resume(self):
- """Test server suspend and resume commands.
-
- Test steps:
- 1) Boot server in setUp
- 2) Suspend server
- 3) Check for SUSPENDED server status
- 4) Resume server
- 5) Check for ACTIVE server status
- """
- self.wait_for_status("ACTIVE")
# suspend
- raw_output = self.openstack('server suspend ' + self.NAME)
- self.assertEqual("", raw_output)
- self.wait_for_status("SUSPENDED")
- # resume
- raw_output = self.openstack('server resume ' + self.NAME)
+ raw_output = self.openstack('server suspend ' + name)
self.assertEqual("", raw_output)
- self.wait_for_status("ACTIVE")
-
- def test_server_lock_unlock(self):
- """Test server lock and unlock commands.
+ self.wait_for_status(name, "SUSPENDED")
- Test steps:
- 1) Boot server in setUp
- 2) Lock server
- 3) Check output
- 4) Unlock server
- 5) Check output
- """
- self.wait_for_status("ACTIVE")
- # lock
- raw_output = self.openstack('server lock ' + self.NAME)
- self.assertEqual("", raw_output)
- # unlock
- raw_output = self.openstack('server unlock ' + self.NAME)
+ # resume
+ raw_output = self.openstack('server resume ' + name)
self.assertEqual("", raw_output)
+ self.wait_for_status(name, "ACTIVE")
- def test_server_pause_unpause(self):
- """Test server pause and unpause commands.
-
- Test steps:
- 1) Boot server in setUp
- 2) Pause server
- 3) Check for PAUSED server status
- 4) Unpause server
- 5) Check for ACTIVE server status
- """
- self.wait_for_status("ACTIVE")
# pause
- raw_output = self.openstack('server pause ' + self.NAME)
+ raw_output = self.openstack('server pause ' + name)
self.assertEqual("", raw_output)
- self.wait_for_status("PAUSED")
+ self.wait_for_status(name, "PAUSED")
+
# unpause
- raw_output = self.openstack('server unpause ' + self.NAME)
+ raw_output = self.openstack('server unpause ' + name)
self.assertEqual("", raw_output)
- self.wait_for_status("ACTIVE")
-
- def test_server_rescue_unrescue(self):
- """Test server rescue and unrescue commands.
+ self.wait_for_status(name, "ACTIVE")
- Test steps:
- 1) Boot server in setUp
- 2) Rescue server
- 3) Check for RESCUE server status
- 4) Unrescue server
- 5) Check for ACTIVE server status
- """
- self.wait_for_status("ACTIVE")
# rescue
- opts = self.get_opts(["adminPass"])
- raw_output = self.openstack('server rescue ' + self.NAME + opts)
+ raw_output = self.openstack('server rescue ' + name)
self.assertNotEqual("", raw_output)
- self.wait_for_status("RESCUE")
+ self.wait_for_status(name, "RESCUE")
+
# unrescue
- raw_output = self.openstack('server unrescue ' + self.NAME)
+ raw_output = self.openstack('server unrescue ' + name)
+ self.assertEqual("", raw_output)
+ self.wait_for_status(name, "ACTIVE")
+
+ # lock
+ raw_output = self.openstack('server lock ' + name)
self.assertEqual("", raw_output)
- self.wait_for_status("ACTIVE")
+ # NOTE(dtroyer): No way to verify this status???
+
+ # unlock
+ raw_output = self.openstack('server unlock ' + name)
+ self.assertEqual("", raw_output)
+ # NOTE(dtroyer): No way to verify this status???
def test_server_attach_detach_floating_ip(self):
- """Test commands to attach and detach floating IP for server.
-
- Test steps:
- 1) Boot server in setUp
- 2) Create floating IP
- 3) Add floating IP to server
- 4) Check for floating IP in server show output
- 5) Remove floating IP from server
- 6) Check that floating IP is not in server show output
- 7) Delete floating IP
- 8) Check output
- """
- self.wait_for_status("ACTIVE")
+ """Test floating ip create/delete; server add/remove floating ip"""
+ cmd_output = self.server_create()
+ name = cmd_output['name']
+ self.wait_for_status(name, "ACTIVE")
+
# attach ip
- opts = self.get_opts(["id", "floating_ip_address"])
- raw_output = self.openstack('floating ip create ' +
- self.IP_POOL +
- opts)
- ip, ipid, rol = tuple(raw_output.split('\n'))
- self.assertNotEqual("", ipid)
- self.assertNotEqual("", ip)
- raw_output = self.openstack('server add floating ip ' + self.NAME +
- ' ' + ip)
+ cmd_output = json.loads(self.openstack(
+ 'floating ip create -f json ' +
+ 'public'
+ ))
+ floating_ip = cmd_output['floating_ip_address']
+ self.assertNotEqual('', cmd_output['id'])
+ self.assertNotEqual('', floating_ip)
+ self.addCleanup(
+ self.openstack,
+ 'floating ip delete ' + cmd_output['id']
+ )
+
+ raw_output = self.openstack(
+ 'server add floating ip ' +
+ name + ' ' +
+ floating_ip
+ )
self.assertEqual("", raw_output)
- raw_output = self.openstack('server show ' + self.NAME)
- self.assertIn(ip, raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ self.assertIn(
+ floating_ip,
+ cmd_output['addresses'],
+ )
# detach ip
- raw_output = self.openstack('server remove floating ip ' + self.NAME +
- ' ' + ip)
- self.assertEqual("", raw_output)
- raw_output = self.openstack('server show ' + self.NAME)
- self.assertNotIn(ip, raw_output)
- raw_output = self.openstack('floating ip delete ' + ipid)
+ raw_output = self.openstack(
+ 'server remove floating ip ' +
+ name + ' ' +
+ floating_ip
+ )
self.assertEqual("", raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ self.assertNotIn(
+ floating_ip,
+ cmd_output['addresses'],
+ )
+
def test_server_reboot(self):
- """Test server reboot command.
+ """Test server reboot"""
+ cmd_output = self.server_create()
+ name = cmd_output['name']
- Test steps:
- 1) Boot server in setUp
- 2) Reboot server
- 3) Check for ACTIVE server status
- """
- self.wait_for_status("ACTIVE")
# reboot
- raw_output = self.openstack('server reboot ' + self.NAME)
+ raw_output = self.openstack('server reboot ' + name)
self.assertEqual("", raw_output)
- self.wait_for_status("ACTIVE")
-
- def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30):
- """Wait until server reaches expected status."""
- # TODO(thowe): Add a server wait command to osc
- failures = ['ERROR']
- total_sleep = 0
- opts = self.get_opts(['status'])
- while total_sleep < wait:
- status = self.openstack('server show ' + self.NAME + opts)
- status = status.rstrip()
- print('Waiting for {} current status: {}'.format(expected_status,
- status))
- if status == expected_status:
- break
- self.assertNotIn(status, failures)
- time.sleep(interval)
- total_sleep += interval
-
- status = self.openstack('server show ' + self.NAME + opts)
- status = status.rstrip()
- self.assertEqual(status, expected_status)
- # give it a little bit more time
- time.sleep(5)
+ self.wait_for_status(name, "ACTIVE")
+
+ def test_server_boot_from_volume(self):
+ """Test server create from volume, server delete"""
+ # get volume status wait function
+ volume_wait_for = test_volume.VolumeTests(
+ methodName='wait_for',
+ ).wait_for
+
+ # get image size
+ cmd_output = json.loads(self.openstack(
+ 'image show -f json ' +
+ self.image_name
+ ))
+ try:
+ image_size = cmd_output['min_disk']
+ if image_size < 1:
+ image_size = 1
+ except ValueError:
+ image_size = 1
+
+ # create volume from image
+ volume_name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--image ' + self.image_name + ' ' +
+ '--size ' + str(image_size) + ' ' +
+ volume_name
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+ self.addCleanup(self.openstack, 'volume delete ' + volume_name)
+ self.assertEqual(
+ volume_name,
+ cmd_output['name'],
+ )
+ volume_wait_for("volume", volume_name, "available")
+
+ # create empty volume
+ empty_volume_name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size ' + str(image_size) + ' ' +
+ empty_volume_name
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+ self.addCleanup(self.openstack, 'volume delete ' + empty_volume_name)
+ self.assertEqual(
+ empty_volume_name,
+ cmd_output['name'],
+ )
+ volume_wait_for("volume", empty_volume_name, "available")
+
+ # create server
+ server_name = uuid.uuid4().hex
+ server = json.loads(self.openstack(
+ 'server create -f json ' +
+ '--flavor ' + self.flavor_name + ' ' +
+ '--volume ' + volume_name + ' ' +
+ '--block-device-mapping vdb=' + empty_volume_name + ' ' +
+ self.network_arg + ' ' +
+ '--wait ' +
+ server_name
+ ))
+ self.assertIsNotNone(server["id"])
+ self.addCleanup(self.openstack, 'server delete --wait ' + server_name)
+ self.assertEqual(
+ server_name,
+ server['name'],
+ )
+
+ # check volumes
+ cmd_output = json.loads(self.openstack(
+ 'volume show -f json ' +
+ volume_name
+ ))
+ attachments = cmd_output['attachments']
+ self.assertEqual(
+ 1,
+ len(attachments),
+ )
+ self.assertEqual(
+ server['id'],
+ attachments[0]['server_id'],
+ )
+ self.assertEqual(
+ "in-use",
+ cmd_output['status'],
+ )
+
+ # NOTE(dtroyer): Prior to https://review.openstack.org/#/c/407111
+ # --block-device-mapping was ignored if --volume
+ # present on the command line. Now we should see the
+ # attachment.
+ cmd_output = json.loads(self.openstack(
+ 'volume show -f json ' +
+ empty_volume_name
+ ))
+ attachments = cmd_output['attachments']
+ self.assertEqual(
+ 1,
+ len(attachments),
+ )
+ self.assertEqual(
+ server['id'],
+ attachments[0]['server_id'],
+ )
+ self.assertEqual(
+ "in-use",
+ cmd_output['status'],
+ )
+
+ def test_server_create_with_none_network(self):
+ """Test server create with none network option."""
+ server_name = uuid.uuid4().hex
+ server = json.loads(self.openstack(
+ # auto/none enable in nova micro version (v2.37+)
+ '--os-compute-api-version 2.latest ' +
+ 'server create -f json ' +
+ '--flavor ' + self.flavor_name + ' ' +
+ '--image ' + self.image_name + ' ' +
+ '--nic none ' +
+ server_name
+ ))
+ self.assertIsNotNone(server["id"])
+ self.addCleanup(self.openstack, 'server delete --wait ' + server_name)
+ self.assertEqual(server_name, server['name'])
+ self.wait_for_status(server_name, "ACTIVE")
+ server = json.loads(self.openstack(
+ 'server show -f json ' + server_name
+ ))
+ self.assertIsNotNone(server['addresses'])
+ self.assertEqual('', server['addresses'])
+
+ def test_server_create_with_empty_network_option_latest(self):
+ """Test server create with empty network option in nova 2.latest."""
+ server_name = uuid.uuid4().hex
+ try:
+ self.openstack(
+ # auto/none enable in nova micro version (v2.37+)
+ '--os-compute-api-version 2.latest ' +
+ 'server create -f json ' +
+ '--flavor ' + self.flavor_name + ' ' +
+ '--image ' + self.image_name + ' ' +
+ server_name
+ )
+ except exceptions.CommandFailed as e:
+ self.assertIn('nics are required after microversion 2.36',
+ e.stderr)
+ else:
+ self.fail('CommandFailed should be raised.')
diff --git a/openstackclient/tests/functional/compute/v2/test_server_event.py b/openstackclient/tests/functional/compute/v2/test_server_event.py
new file mode 100644
index 00000000..953ade43
--- /dev/null
+++ b/openstackclient/tests/functional/compute/v2/test_server_event.py
@@ -0,0 +1,84 @@
+# Copyright 2017 Huawei, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import json
+
+from openstackclient.tests.functional.compute.v2 import common
+
+
+class ServerEventTests(common.ComputeTestCase):
+ """Functional tests for server event"""
+
+ def setUp(self):
+ super(ServerEventTests, self).setUp()
+
+ # NOTE(dtroyer): As long as these tests are read-only we can get away
+ # with using the same server instance for all of them.
+ cmd_output = self.server_create()
+ self.server_id = cmd_output.get('id')
+ self.server_name = cmd_output['name']
+
+ def test_server_event_list_and_show(self):
+ """Test list, show server event"""
+ # Test 'server event list' for creating
+ cmd_output = json.loads(self.openstack(
+ 'server event list -f json ' + self.server_name
+ ))
+ request_id = None
+ for each_event in cmd_output:
+ self.assertNotIn('Message', each_event)
+ self.assertNotIn('Project ID', each_event)
+ self.assertNotIn('User ID', each_event)
+ if each_event.get('Action') == 'create':
+ self.assertEqual(self.server_id, each_event.get('Server ID'))
+ request_id = each_event.get('Request ID')
+ break
+ self.assertIsNotNone(request_id)
+ # Test 'server event show' for creating
+ cmd_output = json.loads(self.openstack(
+ 'server event show -f json ' + self.server_name + ' ' + request_id
+ ))
+ self.assertEqual(self.server_id, cmd_output.get('instance_uuid'))
+ self.assertEqual(request_id, cmd_output.get('request_id'))
+ self.assertEqual('create', cmd_output.get('action'))
+ self.assertIsNotNone(cmd_output.get('events'))
+ self.assertIsInstance(cmd_output.get('events'), list)
+
+ # Reboot server, trigger reboot event
+ self.openstack('server reboot --wait ' + self.server_name)
+ # Test 'server event list --long' for rebooting
+ cmd_output = json.loads(self.openstack(
+ 'server event list --long -f json ' + self.server_name
+ ))
+ request_id = None
+ for each_event in cmd_output:
+ self.assertIn('Message', each_event)
+ self.assertIn('Project ID', each_event)
+ self.assertIn('User ID', each_event)
+ if each_event.get('Action') == 'reboot':
+ request_id = each_event.get('Request ID')
+ self.assertEqual(self.server_id, each_event.get('Server ID'))
+ break
+ self.assertIsNotNone(request_id)
+ # Test 'server event show' for rebooting
+ cmd_output = json.loads(self.openstack(
+ 'server event show -f json ' + self.server_name + ' ' + request_id
+ ))
+
+ self.assertEqual(self.server_id, cmd_output.get('instance_uuid'))
+ self.assertEqual(request_id, cmd_output.get('request_id'))
+ self.assertEqual('reboot', cmd_output.get('action'))
+ self.assertIsNotNone(cmd_output.get('events'))
+ self.assertIsInstance(cmd_output.get('events'), list)
diff --git a/openstackclient/tests/functional/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py
index 3f0a24e5..44ecda1d 100644
--- a/openstackclient/tests/functional/compute/v2/test_server_group.py
+++ b/openstackclient/tests/functional/compute/v2/test_server_group.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -18,29 +19,88 @@ from openstackclient.tests.functional import base
class ServerGroupTests(base.TestCase):
"""Functional tests for servergroup."""
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
-
- @classmethod
- def setUpClass(cls):
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('server group create --policy affinity ' +
- cls.NAME + opts)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
-
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('server group delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
-
- def test_server_group_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('server group list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_server_group_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('server group show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ def test_server_group_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'server group create -f json ' +
+ '--policy affinity ' +
+ name1
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'affinity',
+ cmd_output['policies']
+ )
+
+ cmd_output = json.loads(self.openstack(
+ 'server group create -f json ' +
+ '--policy anti-affinity ' +
+ name2
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'anti-affinity',
+ cmd_output['policies']
+ )
+
+ del_output = self.openstack(
+ 'server group delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
+
+ def test_server_group_show_and_list(self):
+ """Test server group create, show, and list"""
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+
+ # test server gorup show
+ cmd_output = json.loads(self.openstack(
+ 'server group create -f json ' +
+ '--policy affinity ' +
+ name1
+ ))
+ self.addCleanup(self.openstack, 'server group delete ' + name1)
+ cmd_output = json.loads(self.openstack(
+ 'server group show -f json ' + name1))
+ self.assertEqual(
+ name1,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'affinity',
+ cmd_output['policies']
+ )
+
+ cmd_output = json.loads(self.openstack(
+ 'server group create -f json ' +
+ '--policy anti-affinity ' +
+ name2
+ ))
+ self.addCleanup(self.openstack, 'server group delete ' + name2)
+ cmd_output = json.loads(self.openstack(
+ 'server group show -f json ' + name2))
+ self.assertEqual(
+ name2,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ 'anti-affinity',
+ cmd_output['policies']
+ )
+
+ # test server group list
+ cmd_output = json.loads(self.openstack(
+ 'server group list -f json'))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+ policies = [x["Policies"] for x in cmd_output]
+ self.assertIn('affinity', policies)
+ self.assertIn('anti-affinity', policies)
diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py
index b390c5bc..4f3e1801 100644
--- a/openstackclient/tests/functional/identity/v2/common.py
+++ b/openstackclient/tests/functional/identity/v2/common.py
@@ -22,14 +22,13 @@ BASIC_LIST_HEADERS = ['ID', 'Name']
class IdentityTests(base.TestCase):
"""Functional tests for Identity commands. """
- USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id',
- 'username', 'domain_id', 'default_project_id']
- PROJECT_FIELDS = ['enabled', 'id', 'name', 'description', 'domain_id']
+ USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', 'username']
+ PROJECT_FIELDS = ['enabled', 'id', 'name', 'description']
TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id']
- ROLE_FIELDS = ['id', 'name', 'links', 'domain_id']
+ ROLE_FIELDS = ['id', 'name', 'domain_id']
SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description']
ENDPOINT_FIELDS = ['id', 'region', 'service_id', 'service_name',
- 'service_type', 'enabled', 'publicurl',
+ 'service_type', 'publicurl',
'adminurl', 'internalurl']
EC2_CREDENTIALS_FIELDS = ['access', 'project_id', 'secret',
@@ -44,8 +43,8 @@ class IdentityTests(base.TestCase):
# prepare v2 env
os.environ['OS_IDENTITY_API_VERSION'] = '2.0'
auth_url = os.environ.get('OS_AUTH_URL')
- auth_url = auth_url.replace('v3', 'v2.0')
- os.environ['OS_AUTH_URL'] = auth_url
+ if auth_url:
+ os.environ['OS_AUTH_URL'] = auth_url.replace('v3', 'v2.0')
# create dummy project
cls.project_name = data_utils.rand_name('TestProject')
diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py
index 5dd42e70..1ec3ac92 100644
--- a/openstackclient/tests/functional/identity/v3/common.py
+++ b/openstackclient/tests/functional/identity/v3/common.py
@@ -23,15 +23,15 @@ BASIC_LIST_HEADERS = ['ID', 'Name']
class IdentityTests(base.TestCase):
"""Functional tests for Identity commands. """
- DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links']
- GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links']
+ DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name']
+ GROUP_FIELDS = ['description', 'domain_id', 'id', 'name']
TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id']
USER_FIELDS = ['email', 'enabled', 'id', 'name', 'name',
'domain_id', 'default_project_id', 'description',
'password_expires_at']
PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain',
- 'enabled', 'name', 'parent_id', 'links']
- ROLE_FIELDS = ['id', 'name', 'links', 'domain_id']
+ 'enabled', 'name', 'parent_id']
+ ROLE_FIELDS = ['id', 'name', 'domain_id']
SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description']
REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region']
ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id',
@@ -42,7 +42,8 @@ class IdentityTests(base.TestCase):
ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type',
'Enabled', 'Interface', 'URL']
- IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids']
+ IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids',
+ 'domain_id']
IDENTITY_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description']
SERVICE_PROVIDER_FIELDS = ['auth_url', 'description', 'enabled',
@@ -55,8 +56,8 @@ class IdentityTests(base.TestCase):
# prepare v3 env
os.environ['OS_IDENTITY_API_VERSION'] = '3'
auth_url = os.environ.get('OS_AUTH_URL')
- auth_url = auth_url.replace('v2.0', 'v3')
- os.environ['OS_AUTH_URL'] = auth_url
+ if auth_url:
+ os.environ['OS_AUTH_URL'] = auth_url.replace('v2.0', 'v3')
# create dummy domain
cls.domain_name = data_utils.rand_name('TestDomain')
diff --git a/openstackclient/tests/functional/identity/v3/test_group.py b/openstackclient/tests/functional/identity/v3/test_group.py
index 70491183..917d5df0 100644
--- a/openstackclient/tests/functional/identity/v3/test_group.py
+++ b/openstackclient/tests/functional/identity/v3/test_group.py
@@ -102,11 +102,7 @@ class GroupTests(common.IdentityTests):
'user_domain': self.domain_name,
'group': group_name,
'user': username})
- self.assertEqual(
- '%(user)s added to group %(group)s\n' % {'user': username,
- 'group': group_name},
- raw_output
- )
+ self.assertOutput('', raw_output)
def test_group_contains_user(self):
group_name = self._create_dummy_group()
@@ -128,11 +124,7 @@ class GroupTests(common.IdentityTests):
'user_domain': self.domain_name,
'group': group_name,
'user': username})
- self.assertEqual(
- '%(user)s added to group %(group)s\n' % {'user': username,
- 'group': group_name},
- raw_output
- )
+ self.assertOutput('', raw_output)
raw_output = self.openstack(
'group contains user '
'--group-domain %(group_domain)s '
@@ -165,14 +157,5 @@ class GroupTests(common.IdentityTests):
'user_domain': self.domain_name,
'group': group_name,
'user': username})
- self.assertEqual(
- '%(user)s added to group %(group)s\n' % {'user': username,
- 'group': group_name},
- add_raw_output
- )
- self.assertEqual(
- '%(user)s removed from '
- 'group %(group)s\n' % {'user': username,
- 'group': group_name},
- remove_raw_output
- )
+ self.assertOutput('', add_raw_output)
+ self.assertOutput('', remove_raw_output)
diff --git a/openstackclient/tests/functional/identity/v3/test_idp.py b/openstackclient/tests/functional/identity/v3/test_idp.py
index f9d8cb80..5db3610a 100644
--- a/openstackclient/tests/functional/identity/v3/test_idp.py
+++ b/openstackclient/tests/functional/identity/v3/test_idp.py
@@ -10,9 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from openstackclient.tests.functional.identity.v3 import common
from tempest.lib.common.utils import data_utils
+from openstackclient.tests.functional.identity.v3 import common
+
class IdentityProviderTests(common.IdentityTests):
# Introduce functional test case for command 'Identity Provider'
diff --git a/openstackclient/tests/functional/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py
index 5639dc16..96d41c3a 100644
--- a/openstackclient/tests/functional/identity/v3/test_project.py
+++ b/openstackclient/tests/functional/identity/v3/test_project.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
+
from tempest.lib.common.utils import data_utils
from openstackclient.tests.functional.identity.v3 import common
@@ -111,3 +113,14 @@ class ProjectTests(common.IdentityTests):
'name': self.project_name})
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.PROJECT_FIELDS)
+
+ def test_project_show_with_parents_children(self):
+ json_output = json.loads(self.openstack(
+ 'project show '
+ '--parents --children -f json '
+ '--domain %(domain)s '
+ '%(name)s' % {'domain': self.domain_name,
+ 'name': self.project_name}))
+ for attr_name in (self.PROJECT_FIELDS + ['parents', 'subtree']):
+ self.assertIn(attr_name, json_output)
+ self.assertEqual(self.project_name, json_output.get('name'))
diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py
index 3f432b02..b6baf570 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.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_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py
index ef4b5756..eaf88969 100644
--- a/openstackclient/tests/functional/network/v2/test_address_scope.py
+++ b/openstackclient/tests/functional/network/v2/test_address_scope.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -17,33 +18,157 @@ from openstackclient.tests.functional import base
class AddressScopeTests(base.TestCase):
"""Functional tests for address scope. """
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
- @classmethod
- def setUpClass(cls):
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('address scope create ' + cls.NAME + opts)
- cls.assertOutput(cls.NAME + "\n", raw_output)
+ # NOTE(dtroyer): Do not normalize the setup and teardown of the resource
+ # creation and deletion. Little is gained when each test
+ # has its own needs and there are collisions when running
+ # tests in parallel.
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('address scope delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
+ def test_address_scope_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'address scope create -f json ' +
+ name1
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output['name'],
+ )
+ # Check the default values
+ self.assertEqual(
+ False,
+ cmd_output['shared'],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'address scope create -f json ' +
+ name2
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output['name'],
+ )
+
+ raw_output = self.openstack(
+ 'address scope delete ' + name1 + ' ' + name2,
+ )
+ self.assertOutput('', raw_output)
def test_address_scope_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('address scope list' + opts)
- self.assertIn(self.NAME, raw_output)
+ """Test create defaults, list filters, delete"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'address scope create -f json ' +
+ '--ip-version 4 ' +
+ '--share ' +
+ name1
+ ))
+ self.addCleanup(self.openstack, 'address scope delete ' + name1)
+ self.assertEqual(
+ name1,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ 4,
+ cmd_output['ip_version'],
+ )
+ self.assertEqual(
+ True,
+ cmd_output['shared'],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'address scope create -f json ' +
+ '--ip-version 6 ' +
+ '--no-share ' +
+ name2
+ ))
+ self.addCleanup(self.openstack, 'address scope delete ' + name2)
+ self.assertEqual(
+ name2,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ 6,
+ cmd_output['ip_version'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['shared'],
+ )
+
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ 'address scope list -f json ',
+ ))
+ col_data = [x["IP Version"] for x in cmd_output]
+ self.assertIn(4, col_data)
+ self.assertIn(6, col_data)
- def test_address_scope_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('address scope show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ # Test list --share
+ cmd_output = json.loads(self.openstack(
+ 'address scope list -f json --share',
+ ))
+ col_data = [x["Shared"] for x in cmd_output]
+ self.assertIn(True, col_data)
+ self.assertNotIn(False, col_data)
+
+ # Test list --no-share
+ cmd_output = json.loads(self.openstack(
+ 'address scope list -f json --no-share',
+ ))
+ col_data = [x["Shared"] for x in cmd_output]
+ self.assertIn(False, col_data)
+ self.assertNotIn(True, col_data)
def test_address_scope_set(self):
- self.openstack('address scope set --share ' + self.NAME)
- opts = self.get_opts(['shared'])
- raw_output = self.openstack('address scope show ' + self.NAME + opts)
- self.assertEqual("True\n", raw_output)
+ """Tests create options, set, show, delete"""
+ name = uuid.uuid4().hex
+ newname = name + "_"
+ cmd_output = json.loads(self.openstack(
+ 'address scope create -f json ' +
+ '--ip-version 4 ' +
+ '--no-share ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'address scope delete ' + newname)
+ self.assertEqual(
+ name,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ 4,
+ cmd_output['ip_version'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['shared'],
+ )
+
+ raw_output = self.openstack(
+ 'address scope set ' +
+ '--name ' + newname +
+ ' --share ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'address scope show -f json ' +
+ newname,
+ ))
+ self.assertEqual(
+ newname,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ 4,
+ cmd_output['ip_version'],
+ )
+ self.assertEqual(
+ True,
+ cmd_output['shared'],
+ )
diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py
index f3a1971f..8fbec3d5 100644
--- a/openstackclient/tests/functional/network/v2/test_floating_ip.py
+++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py
@@ -10,49 +10,161 @@
# License for the specific language governing permissions and limitations
# under the License.
+import random
+import re
import uuid
from openstackclient.tests.functional import base
class FloatingIpTests(base.TestCase):
- """Functional tests for floating ip. """
+ """Functional tests for floating ip"""
SUBNET_NAME = uuid.uuid4().hex
NETWORK_NAME = uuid.uuid4().hex
- ID = None
- HEADERS = ['ID']
- FIELDS = ['id']
@classmethod
def setUpClass(cls):
- # Create a network for the floating ip.
- cls.openstack('network create --external ' + cls.NETWORK_NAME)
- # Create a subnet for the network.
- cls.openstack(
- 'subnet create --network ' + cls.NETWORK_NAME +
- ' --subnet-range 10.10.10.0/24 ' +
- cls.SUBNET_NAME
- )
- opts = cls.get_opts(cls.FIELDS)
+ # Set up some regex for matching below
+ cls.re_id = re.compile("id\s+\|\s+(\S+)")
+ cls.re_floating_ip = re.compile("floating_ip_address\s+\|\s+(\S+)")
+ cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)")
+ cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|")
+ cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)")
+
+ # Create a network for the floating ip
raw_output = cls.openstack(
- 'floating ip create ' + cls.NETWORK_NAME + opts)
- cls.ID = raw_output.strip('\n')
+ 'network create --external ' + cls.NETWORK_NAME
+ )
+ cls.network_id = re.search(cls.re_id, raw_output).group(1)
+
+ # Try random subnet range for subnet creating
+ # Because we can not determine ahead of time what subnets are already
+ # in use, possibly by another test running in parallel, try 4 times
+ for i in range(4):
+ # Make a random subnet
+ cls.subnet = ".".join(map(
+ str,
+ (random.randint(0, 223) for _ in range(3))
+ )) + ".0/26"
+ try:
+ # Create a subnet for the network
+ raw_output = cls.openstack(
+ 'subnet create ' +
+ '--network ' + cls.NETWORK_NAME + ' ' +
+ '--subnet-range ' + cls.subnet + ' ' +
+ cls.SUBNET_NAME
+ )
+ except Exception:
+ if (i == 3):
+ # raise the exception at the last time
+ raise
+ pass
+ else:
+ # break and no longer retry if create sucessfully
+ break
+
+ cls.subnet_id = re.search(cls.re_id, raw_output).group(1)
@classmethod
def tearDownClass(cls):
- raw_output = cls.openstack('floating ip delete ' + cls.ID)
- cls.assertOutput('', raw_output)
raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME)
cls.assertOutput('', raw_output)
raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME)
cls.assertOutput('', raw_output)
+ def test_floating_ip_delete(self):
+ """Test create, delete multiple"""
+ raw_output = self.openstack(
+ 'floating ip create ' +
+ '--description aaaa ' +
+ self.NETWORK_NAME
+ )
+ re_ip = re.search(self.re_floating_ip, raw_output)
+ self.assertIsNotNone(re_ip)
+ ip1 = re_ip.group(1)
+ self.assertEqual(
+ 'aaaa',
+ re.search(self.re_description, raw_output).group(1),
+ )
+
+ raw_output = self.openstack(
+ 'floating ip create ' +
+ '--description bbbb ' +
+ self.NETWORK_NAME
+ )
+ ip2 = re.search(self.re_floating_ip, raw_output).group(1)
+ self.assertEqual(
+ 'bbbb',
+ re.search(self.re_description, raw_output).group(1),
+ )
+
+ # Clean up after ourselves
+ raw_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2)
+ self.assertOutput('', raw_output)
+
def test_floating_ip_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('floating ip list' + opts)
- self.assertIn(self.ID, raw_output)
+ """Test create defaults, list filters, delete"""
+ raw_output = self.openstack(
+ 'floating ip create ' +
+ '--description aaaa ' +
+ self.NETWORK_NAME
+ )
+ re_ip = re.search(self.re_floating_ip, raw_output)
+ self.assertIsNotNone(re_ip)
+ ip1 = re_ip.group(1)
+ self.addCleanup(self.openstack, 'floating ip delete ' + ip1)
+ self.assertEqual(
+ 'aaaa',
+ re.search(self.re_description, raw_output).group(1),
+ )
+ self.assertIsNotNone(re.search(self.re_network_id, raw_output))
+
+ raw_output = self.openstack(
+ 'floating ip create ' +
+ '--description bbbb ' +
+ self.NETWORK_NAME
+ )
+ ip2 = re.search(self.re_floating_ip, raw_output).group(1)
+ self.addCleanup(self.openstack, 'floating ip delete ' + ip2)
+ self.assertEqual(
+ 'bbbb',
+ re.search(self.re_description, raw_output).group(1),
+ )
+
+ # Test list
+ raw_output = self.openstack('floating ip list')
+ self.assertIsNotNone(re.search("\|\s+" + ip1 + "\s+\|", raw_output))
+ self.assertIsNotNone(re.search("\|\s+" + ip2 + "\s+\|", raw_output))
+
+ # Test list --long
+ raw_output = self.openstack('floating ip list --long')
+ self.assertIsNotNone(re.search("\|\s+" + ip1 + "\s+\|", raw_output))
+ self.assertIsNotNone(re.search("\|\s+" + ip2 + "\s+\|", raw_output))
+
+ # TODO(dtroyer): add more filter tests
def test_floating_ip_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('floating ip show ' + self.ID + opts)
- self.assertEqual(self.ID + "\n", raw_output)
+ """Test show"""
+ raw_output = self.openstack(
+ 'floating ip create ' +
+ '--description shosho ' +
+ # '--fixed-ip-address 1.2.3.4 ' +
+ self.NETWORK_NAME
+ )
+ re_ip = re.search(self.re_floating_ip, raw_output)
+ self.assertIsNotNone(re_ip)
+ ip = re_ip.group(1)
+
+ raw_output = self.openstack('floating ip show ' + ip)
+ self.addCleanup(self.openstack, 'floating ip delete ' + ip)
+
+ self.assertEqual(
+ 'shosho',
+ re.search(self.re_description, raw_output).group(1),
+ )
+ # TODO(dtroyer): not working???
+ # self.assertEqual(
+ # '1.2.3.4',
+ # re.search(self.re_floating_ip, raw_output).group(1),
+ # )
+ self.assertIsNotNone(re.search(self.re_network_id, raw_output))
diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py
index b5c908f4..7440f250 100644
--- a/openstackclient/tests/functional/network/v2/test_ip_availability.py
+++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -17,22 +18,19 @@ from openstackclient.tests.functional import base
class IPAvailabilityTests(base.TestCase):
"""Functional tests for IP availability. """
- NAME = uuid.uuid4().hex
- NETWORK_NAME = uuid.uuid4().hex
- FIELDS = ['network_name']
@classmethod
def setUpClass(cls):
# Create a network for the subnet.
+ cls.NAME = uuid.uuid4().hex
+ cls.NETWORK_NAME = uuid.uuid4().hex
cls.openstack('network create ' + cls.NETWORK_NAME)
- opts = cls.get_opts(['name'])
- raw_output = cls.openstack(
- 'subnet create --network ' + cls.NETWORK_NAME +
+ cmd_output = json.loads(cls.openstack(
+ 'subnet create -f json --network ' + cls.NETWORK_NAME +
' --subnet-range 10.10.10.0/24 ' +
- cls.NAME + opts
- )
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
+ cls.NAME
+ ))
+ cls.assertOutput(cls.NAME, cmd_output['name'])
@classmethod
def tearDownClass(cls):
@@ -42,12 +40,17 @@ class IPAvailabilityTests(base.TestCase):
cls.assertOutput('', raw_network)
def test_ip_availability_list(self):
- opts = ' -f csv -c "Network Name"'
- raw_output = self.openstack('ip availability list' + opts)
- self.assertIn(self.NETWORK_NAME, raw_output)
+ """Test ip availability list"""
+ cmd_output = json.loads(self.openstack(
+ 'ip availability list -f json'))
+ names = [x['Network Name'] for x in cmd_output]
+ self.assertIn(self.NETWORK_NAME, names)
def test_ip_availability_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack(
- 'ip availability show ' + self.NETWORK_NAME + opts)
- self.assertEqual(self.NETWORK_NAME + "\n", raw_output)
+ """Test ip availability show"""
+ cmd_output = json.loads(self.openstack(
+ 'ip availability show -f json ' + self.NETWORK_NAME))
+ self.assertEqual(
+ self.NETWORK_NAME,
+ cmd_output['network_name'],
+ )
diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py
index c77ff642..0e10bfce 100644
--- a/openstackclient/tests/functional/network/v2/test_network.py
+++ b/openstackclient/tests/functional/network/v2/test_network.py
@@ -10,41 +10,354 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
class NetworkTests(base.TestCase):
- """Functional tests for network. """
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
-
- @classmethod
- def setUpClass(cls):
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('network create ' + cls.NAME + opts)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
-
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('network delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
+ """Functional tests for network"""
+
+ def test_network_create(self):
+ """Test create options, delete"""
+ # Get project IDs
+ cmd_output = json.loads(self.openstack('token issue -f json '))
+ auth_project_id = cmd_output['project_id']
+
+ cmd_output = json.loads(self.openstack('project list -f json '))
+ admin_project_id = None
+ demo_project_id = None
+ for p in cmd_output:
+ if p['Name'] == 'admin':
+ admin_project_id = p['ID']
+ if p['Name'] == 'demo':
+ demo_project_id = p['ID']
+
+ # Verify assumptions:
+ # * admin and demo projects are present
+ # * demo and admin are distinct projects
+ # * tests run as admin
+ self.assertIsNotNone(admin_project_id)
+ self.assertIsNotNone(demo_project_id)
+ self.assertNotEqual(admin_project_id, demo_project_id)
+ self.assertEqual(admin_project_id, auth_project_id)
+
+ # network create with no options
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ name1
+ ))
+ self.addCleanup(self.openstack, 'network delete ' + name1)
+ self.assertIsNotNone(cmd_output["id"])
+
+ # Check the default values
+ self.assertEqual(
+ admin_project_id,
+ cmd_output["project_id"],
+ )
+ self.assertEqual(
+ '',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ 'UP',
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["shared"],
+ )
+ self.assertEqual(
+ 'Internal',
+ cmd_output["router:external"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--project demo ' +
+ name2
+ ))
+ self.addCleanup(self.openstack, 'network delete ' + name2)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ demo_project_id,
+ cmd_output["project_id"],
+ )
+ self.assertEqual(
+ '',
+ cmd_output["description"],
+ )
+
+ def test_network_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description aaaa ' +
+ name1
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description bbbb ' +
+ name2
+ ))
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["description"],
+ )
+
+ del_output = self.openstack('network delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
def test_network_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('network list' + opts)
- self.assertIn(self.NAME, raw_output)
+ """Test create defaults, list filters, delete"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description aaaa ' +
+ '--no-default ' +
+ name1
+ ))
+ self.addCleanup(self.openstack, 'network delete ' + name1)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+ # Check the default values
+ self.assertEqual(
+ 'UP',
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["shared"],
+ )
+ self.assertEqual(
+ 'Internal',
+ cmd_output["router:external"],
+ )
+
+ self.assertEqual(
+ False,
+ cmd_output["is_default"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["port_security_enabled"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description bbbb ' +
+ '--disable ' +
+ '--share ' +
+ name2
+ ))
+ self.addCleanup(self.openstack, 'network delete ' + name2)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ 'DOWN',
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["shared"],
+ )
+ self.assertEqual(
+ None,
+ cmd_output["is_default"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["port_security_enabled"],
+ )
+
+ # Test list --long
+ cmd_output = json.loads(self.openstack(
+ "network list -f json " +
+ "--long"
+ ))
+ col_name = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, col_name)
+ self.assertIn(name2, col_name)
+
+ # Test list --long --enable
+ 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
+ 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
+ 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
+ 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)
+
+ def test_network_dhcp_agent(self):
+ name1 = uuid.uuid4().hex
+ cmd_output1 = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description aaaa ' +
+ name1
+ ))
+
+ self.addCleanup(self.openstack, 'network delete ' + name1)
+
+ # Get network ID
+ network_id = cmd_output1['id']
+
+ # Get DHCP Agent ID
+ cmd_output2 = json.loads(self.openstack(
+ 'network agent list -f json --agent-type dhcp'
+ ))
+ agent_id = cmd_output2[0]['ID']
+
+ # Add Agent to Network
+ self.openstack(
+ 'network agent add network --dhcp '
+ + agent_id + ' ' + network_id
+ )
+
+ # Test network list --agent
+ cmd_output3 = json.loads(self.openstack(
+ 'network list -f json --agent ' + agent_id
+ ))
+
+ # Cleanup
+ # Remove Agent from Network
+ self.openstack(
+ 'network agent remove network --dhcp '
+ + agent_id + ' ' + network_id
+ )
+
+ # Assert
+ col_name = [x["ID"] for x in cmd_output3]
+ self.assertIn(
+ network_id, col_name
+ )
def test_network_set(self):
- raw_output = self.openstack('network set --disable ' + self.NAME)
- opts = self.get_opts(['name', 'admin_state_up'])
- raw_output = self.openstack('network show ' + self.NAME + opts)
- self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output)
-
- def test_network_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('network show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ """Tests create options, set, show, delete"""
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description aaaa ' +
+ '--enable ' +
+ '--no-share ' +
+ '--internal ' +
+ '--no-default ' +
+ '--enable-port-security ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'network delete ' + name)
+ self.assertIsNotNone(cmd_output["id"])
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ 'UP',
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["shared"],
+ )
+ self.assertEqual(
+ 'Internal',
+ cmd_output["router:external"],
+ )
+
+ self.assertEqual(
+ False,
+ cmd_output["is_default"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["port_security_enabled"],
+ )
+
+ raw_output = self.openstack(
+ 'network set ' +
+ '--description cccc ' +
+ '--disable ' +
+ '--share ' +
+ '--external ' +
+ '--disable-port-security ' +
+ name
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'network show -f json ' + name
+ ))
+
+ self.assertEqual(
+ 'cccc',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ 'DOWN',
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["shared"],
+ )
+ self.assertEqual(
+ 'External',
+ cmd_output["router:external"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["is_default"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["port_security_enabled"],
+ )
diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py
index dd6112e7..6da721d1 100644
--- a/openstackclient/tests/functional/network/v2/test_network_agent.py
+++ b/openstackclient/tests/functional/network/v2/test_network_agent.py
@@ -10,6 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
+import uuid
+
from openstackclient.tests.functional import base
@@ -39,3 +42,52 @@ class NetworkAgentTests(base.TestCase):
self.openstack('network agent set --enable ' + self.IDs[0])
raw_output = self.openstack('network agent show ' + self.IDs[0] + opts)
self.assertEqual("UP\n", raw_output)
+
+
+class NetworkAgentListTests(base.TestCase):
+ """Functional test for network agent list --network. """
+
+ def test_network_dhcp_agent_list(self):
+ """Test network agent list"""
+
+ name1 = uuid.uuid4().hex
+ cmd_output1 = json.loads(self.openstack(
+ 'network create -f json ' +
+ '--description aaaa ' +
+ name1
+ ))
+
+ self.addCleanup(self.openstack, 'network delete ' + name1)
+
+ # Get network ID
+ network_id = cmd_output1['id']
+
+ # Get DHCP Agent ID
+ cmd_output2 = json.loads(self.openstack(
+ 'network agent list -f json --agent-type dhcp'
+ ))
+ agent_id = cmd_output2[0]['ID']
+
+ # Add Agent to Network
+ self.openstack(
+ 'network agent add network --dhcp '
+ + agent_id + ' ' + network_id
+ )
+
+ # Test network agent list --network
+ cmd_output3 = json.loads(self.openstack(
+ 'network agent list -f json --network ' + network_id
+ ))
+
+ # Cleanup
+ # Remove Agent from Network
+ self.openstack(
+ 'network agent remove network --dhcp '
+ + agent_id + ' ' + network_id
+ )
+
+ # Assert
+ col_name = [x["ID"] for x in cmd_output3]
+ self.assertIn(
+ agent_id, col_name
+ )
diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py
new file mode 100644
index 00000000..b2fc2eae
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py
@@ -0,0 +1,176 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import json
+import uuid
+
+from openstackclient.tests.functional import base
+
+
+class NetworkFlavorTests(base.TestCase):
+ """Functional tests for network flavor."""
+
+ def test_network_flavor_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network flavor create -f json --description testdescription '
+ '--enable --service-type L3_ROUTER_NAT ' + name1,
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ True,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription',
+ cmd_output['description'],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network flavor create -f json --description testdescription1 '
+ '--disable --service-type L3_ROUTER_NAT ' + name2,
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription1',
+ cmd_output['description'],
+ )
+ raw_output = self.openstack(
+ 'network flavor delete ' + name1 + " " + name2)
+ self.assertOutput('', raw_output)
+
+ def test_network_flavor_list(self):
+ """Test create defaults, list filters, delete"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network flavor create -f json --description testdescription '
+ '--enable --service-type L3_ROUTER_NAT ' + name1,
+ ))
+ self.addCleanup(self.openstack, "network flavor delete " + name1)
+ self.assertEqual(
+ name1,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ True,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription',
+ cmd_output['description'],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network flavor create -f json --description testdescription1 '
+ '--disable --service-type L3_ROUTER_NAT ' + name2,
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription1',
+ cmd_output['description'],
+ )
+ self.addCleanup(self.openstack, "network flavor delete " + name2)
+
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ 'network flavor list -f json ',))
+ self.assertIsNotNone(cmd_output)
+
+ name_list = [item.get('Name') for item in cmd_output]
+ self.assertIn(name1, name_list)
+ self.assertIn(name2, name_list)
+
+ def test_network_flavor_set(self):
+ """Tests create options, set, show, delete"""
+ name = uuid.uuid4().hex
+ newname = name + "_"
+ cmd_output = json.loads(self.openstack(
+ 'network flavor create -f json --description testdescription '
+ '--disable --service-type L3_ROUTER_NAT ' + name,
+ ))
+ self.addCleanup(self.openstack, "network flavor delete " + newname)
+ self.assertEqual(
+ name,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription',
+ cmd_output['description'],
+ )
+
+ raw_output = self.openstack(
+ 'network flavor set --name ' + newname + ' --disable ' + name
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'network flavor show -f json ' + newname,))
+ self.assertEqual(
+ newname,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription',
+ cmd_output['description'],
+ )
+
+ def test_network_flavor_show(self):
+ """Test show network flavor"""
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'network flavor create -f json --description testdescription '
+ '--disable --service-type L3_ROUTER_NAT ' + name,
+ ))
+ self.addCleanup(self.openstack, "network flavor delete " + name)
+ cmd_output = json.loads(self.openstack(
+ 'network flavor show -f json ' + name,))
+ self.assertEqual(
+ name,
+ cmd_output['name'],
+ )
+ self.assertEqual(
+ False,
+ cmd_output['enabled'],
+ )
+ self.assertEqual(
+ 'testdescription',
+ cmd_output['description'],
+ )
diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py
new file mode 100644
index 00000000..1a82c82b
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py
@@ -0,0 +1,151 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from openstackclient.tests.functional import base
+
+
+class NetworkFlavorProfileTests(base.TestCase):
+ """Functional tests for network flavor-profile."""
+
+ DESCRIPTION = 'fakedescription'
+ METAINFO = 'Extrainfo'
+
+ def test_network_flavor_profile_create(self):
+ json_output = json.loads(self.openstack(
+ ' network flavor profile create -f json --description '
+ + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO))
+ ID = json_output.get('id')
+ self.assertIsNotNone(ID)
+ self.assertEqual(
+ True,
+ json_output.get('enabled'))
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description'))
+ self.assertEqual(
+ 'Extrainfo',
+ json_output.get('meta_info')
+ )
+
+ # Clean up
+ raw_output = self.openstack('network flavor profile delete ' + ID)
+ self.assertOutput('', raw_output)
+
+ def test_network_flavor_profile_list(self):
+ json_output = json.loads(self.openstack(
+ ' network flavor profile create -f json --description '
+ + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO))
+ ID1 = json_output.get('id')
+ self.assertIsNotNone(ID1)
+ self.assertEqual(
+ True,
+ json_output.get('enabled'))
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description'))
+ self.assertEqual(
+ 'Extrainfo',
+ json_output.get('meta_info')
+ )
+
+ json_output = json.loads(self.openstack(
+ ' network flavor profile create -f json --description '
+ + self.DESCRIPTION + ' --disable --metainfo ' + self.METAINFO))
+ ID2 = json_output.get('id')
+ self.assertIsNotNone(ID2)
+ self.assertEqual(
+ False,
+ json_output.get('enabled'))
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description'))
+ self.assertEqual(
+ 'Extrainfo',
+ json_output.get('meta_info')
+ )
+
+ # Test list
+ json_output = json.loads(self.openstack(
+ 'network flavor profile list -f json'))
+ self.assertIsNotNone(json_output)
+
+ id_list = [item.get('ID') for item in json_output]
+ self.assertIn(ID1, id_list)
+ self.assertIn(ID2, id_list)
+
+ # Clean up
+ raw_output = self.openstack(
+ 'network flavor profile delete ' + ID1 + " " + ID2)
+ self.assertOutput('', raw_output)
+
+ def test_network_flavor_profile_set(self):
+ json_output_1 = json.loads(self.openstack(
+ ' network flavor profile create -f json --description '
+ + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO))
+ ID = json_output_1.get('id')
+ self.assertIsNotNone(ID)
+ self.assertEqual(
+ True,
+ json_output_1.get('enabled'))
+ self.assertEqual(
+ 'fakedescription',
+ json_output_1.get('description'))
+ self.assertEqual(
+ 'Extrainfo',
+ json_output_1.get('meta_info')
+ )
+
+ self.openstack('network flavor profile set --disable ' + ID)
+
+ json_output = json.loads(self.openstack('network flavor profile show '
+ '-f json ' + ID))
+ self.assertEqual(
+ False,
+ json_output.get('enabled'))
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description'))
+ self.assertEqual(
+ 'Extrainfo',
+ json_output.get('meta_info')
+ )
+
+ # Clean up
+ raw_output = self.openstack('network flavor profile delete ' + ID)
+ self.assertOutput('', raw_output)
+
+ def test_network_flavor_profile_show(self):
+ json_output_1 = json.loads(self.openstack(
+ ' network flavor profile create -f json --description '
+ + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO))
+ ID = json_output_1.get('id')
+ self.assertIsNotNone(ID)
+ json_output = json.loads(self.openstack('network flavor profile show '
+ '-f json ' + ID))
+ self.assertEqual(
+ ID,
+ json_output["id"])
+ self.assertEqual(
+ True,
+ json_output["enabled"])
+ self.assertEqual(
+ 'fakedescription',
+ json_output["description"])
+ self.assertEqual(
+ 'Extrainfo',
+ json_output["meta_info"])
+
+ # Clean up
+ raw_output = self.openstack('network flavor profile delete ' + ID)
+ self.assertOutput('', raw_output)
diff --git a/openstackclient/tests/functional/network/v2/test_network_meter.py b/openstackclient/tests/functional/network/v2/test_network_meter.py
new file mode 100644
index 00000000..f73f4812
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_meter.py
@@ -0,0 +1,158 @@
+# 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 json
+import uuid
+
+from openstackclient.tests.functional import base
+
+
+class TestMeter(base.TestCase):
+ """Functional tests for network meter."""
+
+ # NOTE(dtroyer): Do not normalize the setup and teardown of the resource
+ # creation and deletion. Little is gained when each test
+ # has its own needs and there are collisions when running
+ # tests in parallel.
+
+ def test_meter_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+ description = 'fakedescription'
+ json_output = json.loads(self.openstack(
+ 'network meter create -f json ' + name1 + ' --description '
+ + description)
+ )
+ self.assertEqual(
+ name1,
+ json_output.get('name'),
+ )
+ # Check if default shared values
+ self.assertEqual(
+ False,
+ json_output.get('shared')
+ )
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description')
+ )
+
+ json_output_2 = json.loads(self.openstack(
+ 'network meter create -f json ' + name2 + ' --description '
+ + description)
+ )
+ self.assertEqual(
+ name2,
+ json_output_2.get('name'),
+ )
+ # Check if default shared values
+ self.assertEqual(
+ False,
+ json_output_2.get('shared')
+ )
+ self.assertEqual(
+ 'fakedescription',
+ json_output_2.get('description')
+ )
+
+ raw_output = self.openstack(
+ 'network meter delete ' + name1 + ' ' + name2,
+ )
+ self.assertOutput('', raw_output)
+
+ def test_meter_list(self):
+ """Test create, list filters, delete"""
+ name1 = uuid.uuid4().hex
+ json_output = json.loads(self.openstack(
+ 'network meter create -f json --description Test1 --share '
+ + name1)
+ )
+ self.addCleanup(self.openstack, 'network meter delete ' + name1)
+
+ self.assertEqual(
+ 'Test1',
+ json_output.get('description'),
+ )
+ self.assertEqual(
+ True,
+ json_output.get('shared'),
+ )
+
+ name2 = uuid.uuid4().hex
+ json_output_2 = json.loads(self.openstack(
+ 'network meter create -f json --description Test2 --no-share '
+ + name2)
+ )
+ self.addCleanup(self.openstack, 'network meter delete ' + name2)
+
+ self.assertEqual(
+ 'Test2',
+ json_output_2.get('description')
+ )
+ self.assertEqual(
+ False,
+ json_output_2.get('shared')
+ )
+
+ raw_output = json.loads(self.openstack('network meter list -f json'))
+ name_list = [item.get('Name') for item in raw_output]
+ self.assertIn(name1, name_list)
+ self.assertIn(name2, name_list)
+
+ def test_meter_show(self):
+ """Test create, show, delete"""
+ name1 = uuid.uuid4().hex
+ description = 'fakedescription'
+ json_output = json.loads(self.openstack(
+ 'network meter create -f json ' + name1 + ' --description '
+ + description)
+ )
+ meter_id = json_output.get('id')
+ self.addCleanup(self.openstack, 'network meter delete ' + name1)
+
+ # Test show with ID
+ json_output = json.loads(self.openstack(
+ 'network meter show -f json ' + meter_id)
+ )
+ self.assertEqual(
+ False,
+ json_output.get('shared')
+ )
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description')
+ )
+ self.assertEqual(
+ name1,
+ json_output.get('name')
+ )
+
+ # Test show with name
+ json_output = json.loads(self.openstack(
+ 'network meter show -f json ' + name1)
+ )
+ self.assertEqual(
+ meter_id,
+ json_output.get('id')
+ )
+ self.assertEqual(
+ False,
+ json_output.get('shared')
+ )
+ self.assertEqual(
+ 'fakedescription',
+ json_output.get('description')
+ )
diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py
new file mode 100644
index 00000000..d15cdf77
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py
@@ -0,0 +1,123 @@
+# 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 json
+import uuid
+
+from openstackclient.tests.functional import base
+
+
+class TestMeterRule(base.TestCase):
+ """Functional tests for meter rule"""
+ METER_NAME = uuid.uuid4().hex
+ METER_ID = None
+ METER_RULE_ID = None
+
+ @classmethod
+ def setUpClass(cls):
+ json_output = json.loads(cls.openstack(
+ 'network meter create -f json ' + cls.METER_NAME
+ ))
+
+ cls.METER_ID = json_output.get('id')
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output = cls.openstack('network meter delete ' + cls.METER_ID)
+ cls.assertOutput('', raw_output)
+
+ def test_meter_rule_delete(self):
+ """test create, delete"""
+
+ json_output = json.loads(self.openstack(
+ 'network meter rule create -f json ' +
+ '--remote-ip-prefix 10.0.0.0/8 ' +
+ self.METER_ID
+ ))
+ rule_id = json_output.get('id')
+ re_ip = json_output.get('remote_ip_prefix')
+
+ self.addCleanup(self.openstack,
+ 'network meter rule delete ' + rule_id)
+ self.assertIsNotNone(re_ip)
+ self.assertIsNotNone(rule_id)
+ self.assertEqual(
+ '10.0.0.0/8', re_ip
+ )
+
+ def test_meter_rule_list(self):
+ """Test create, list, delete"""
+ json_output = json.loads(self.openstack(
+ 'network meter rule create -f json ' +
+ '--remote-ip-prefix 10.0.0.0/8 ' +
+ self.METER_ID
+ ))
+ rule_id_1 = json_output.get('id')
+ self.addCleanup(self.openstack,
+ 'network meter rule delete ' + rule_id_1)
+ self.assertEqual(
+ '10.0.0.0/8',
+ json_output.get('remote_ip_prefix')
+ )
+
+ json_output_1 = json.loads(self.openstack(
+ 'network meter rule create -f json ' +
+ '--remote-ip-prefix 11.0.0.0/8 ' +
+ self.METER_ID
+ ))
+ rule_id_2 = json_output_1.get('id')
+ self.addCleanup(self.openstack,
+ 'network meter rule delete ' + rule_id_2)
+ self.assertEqual(
+ '11.0.0.0/8',
+ json_output_1.get('remote_ip_prefix')
+ )
+
+ json_output = json.loads(self.openstack('network meter rule list -f '
+ 'json'))
+ rule_id_list = [item.get('ID') for item in json_output]
+ ip_prefix_list = [item.get('Remote IP Prefix') for item in json_output]
+ self.assertIn(rule_id_1, rule_id_list)
+ self.assertIn(rule_id_2, rule_id_list)
+ self.assertIn('10.0.0.0/8', ip_prefix_list)
+ self.assertIn('11.0.0.0/8', ip_prefix_list)
+
+ def test_meter_rule_show(self):
+
+ """Test create, show, delete"""
+ json_output = json.loads(self.openstack(
+ 'network meter rule create -f json ' +
+ '--remote-ip-prefix 10.0.0.0/8 ' +
+ '--egress ' +
+ self.METER_ID
+ ))
+ rule_id = json_output.get('id')
+
+ self.assertEqual(
+ 'egress',
+ json_output.get('direction')
+ )
+
+ json_output = json.loads(self.openstack('network meter rule show'
+ ' -f json ' + rule_id))
+
+ self.assertEqual(
+ '10.0.0.0/8',
+ json_output.get('remote_ip_prefix')
+ )
+ self.assertIsNotNone(rule_id)
+
+ self.addCleanup(self.openstack,
+ 'network meter rule delete ' + rule_id)
diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py
new file mode 100644
index 00000000..07dea31b
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from openstackclient.tests.functional import base
+
+
+class QosPolicyTests(base.TestCase):
+ """Functional tests for QoS policy. """
+ NAME = uuid.uuid4().hex
+ HEADERS = ['Name']
+ FIELDS = ['name']
+
+ @classmethod
+ def setUpClass(cls):
+ opts = cls.get_opts(cls.FIELDS)
+ raw_output = cls.openstack('network qos policy create ' + cls.NAME +
+ opts)
+ cls.assertOutput(cls.NAME + "\n", raw_output)
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output = cls.openstack('network qos policy delete ' + cls.NAME)
+ cls.assertOutput('', raw_output)
+
+ def test_qos_policy_list(self):
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('network qos policy list' + opts)
+ self.assertIn(self.NAME, raw_output)
+
+ def test_qos_policy_show(self):
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('network qos policy show ' + self.NAME +
+ opts)
+ self.assertEqual(self.NAME + "\n", raw_output)
+
+ def test_qos_policy_set(self):
+ self.openstack('network qos policy set --share ' + self.NAME)
+ opts = self.get_opts(['shared'])
+ raw_output = self.openstack('network qos policy show ' + self.NAME +
+ opts)
+ self.assertEqual("True\n", raw_output)
diff --git a/openstackclient/tests/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
new file mode 100644
index 00000000..7dff0cbd
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional import base
+
+
+class NetworkQosRuleTypeTests(base.TestCase):
+ """Functional tests for Network QoS rule type. """
+
+ AVAILABLE_RULE_TYPES = ['dscp_marking',
+ 'bandwidth_limit']
+
+ def test_qos_rule_type_list(self):
+ raw_output = self.openstack('network qos rule type list')
+ for rule_type in self.AVAILABLE_RULE_TYPES:
+ self.assertIn(rule_type, raw_output)
diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py
new file mode 100644
index 00000000..6fbff6c8
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional import base
+
+
+class TestNetworkServiceProvider(base.TestCase):
+ """Functional tests for network service provider"""
+
+ SERVICE_TYPE = 'L3_ROUTER_NAT'
+
+ def test_network_service_provider_list(self):
+ raw_output = self.openstack('network service provider list')
+ self.assertIn(self.SERVICE_TYPE, raw_output)
diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py
index decd9553..bd5eefa5 100644
--- a/openstackclient/tests/functional/network/v2/test_port.py
+++ b/openstackclient/tests/functional/network/v2/test_port.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -19,40 +20,149 @@ class PortTests(base.TestCase):
"""Functional tests for port. """
NAME = uuid.uuid4().hex
NETWORK_NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
+ SG_NAME = uuid.uuid4().hex
@classmethod
def setUpClass(cls):
- # Create a network for the subnet.
+ # Create a network for the port
cls.openstack('network create ' + cls.NETWORK_NAME)
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack(
- 'port create --network ' + cls.NETWORK_NAME + ' ' +
- cls.NAME + opts
- )
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
@classmethod
def tearDownClass(cls):
- raw_output = cls.openstack('port delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME)
cls.assertOutput('', raw_output)
+ def test_port_delete(self):
+ """Test create, delete multiple"""
+ json_output = json.loads(self.openstack(
+ 'port create -f json --network ' +
+ self.NETWORK_NAME + ' ' + self.NAME
+ ))
+ id1 = json_output.get('id')
+ self.assertIsNotNone(id1)
+ self.assertIsNotNone(json_output.get('mac_address'))
+ self.assertEqual(self.NAME, json_output.get('name'))
+
+ json_output = json.loads(self.openstack(
+ 'port create -f json --network ' + self.NETWORK_NAME + ' ' +
+ self.NAME + 'x'
+ ))
+ id2 = json_output.get('id')
+ self.assertIsNotNone(id2)
+ self.assertIsNotNone(json_output.get('mac_address'))
+ self.assertEqual(self.NAME + 'x', json_output.get('name'))
+
+ # Clean up after ourselves
+ raw_output = self.openstack('port delete ' + id1 + ' ' + id2)
+ self.assertOutput('', raw_output)
+
def test_port_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('port list' + opts)
- self.assertIn(self.NAME, raw_output)
+ """Test create defaults, list, delete"""
+ json_output = json.loads(self.openstack(
+ 'port create -f json --network ' + self.NETWORK_NAME + ' ' +
+ self.NAME
+ ))
+ id1 = json_output.get('id')
+ self.assertIsNotNone(id1)
+ mac1 = json_output.get('mac_address')
+ self.assertIsNotNone(mac1)
+ self.addCleanup(self.openstack, 'port delete ' + id1)
+ self.assertEqual(self.NAME, json_output.get('name'))
+
+ json_output = json.loads(self.openstack(
+ 'port create -f json --network ' + self.NETWORK_NAME + ' ' +
+ self.NAME + 'x'
+ ))
+ id2 = json_output.get('id')
+ self.assertIsNotNone(id2)
+ mac2 = json_output.get('mac_address')
+ self.assertIsNotNone(mac2)
+ self.addCleanup(self.openstack, 'port delete ' + id2)
+ self.assertEqual(self.NAME + 'x', json_output.get('name'))
+
+ # Test list
+ json_output = json.loads(self.openstack(
+ 'port list -f json'
+ ))
+ item_map = {item.get('ID'): item.get('MAC Address') for item in
+ json_output}
+ self.assertIn(id1, item_map.keys())
+ self.assertIn(id2, item_map.keys())
+ self.assertIn(mac1, item_map.values())
+ self.assertIn(mac2, item_map.values())
+
+ # Test list --long
+ json_output = json.loads(self.openstack(
+ 'port list --long -f json'
+ ))
+ id_list = [item.get('ID') for item in json_output]
+ self.assertIn(id1, id_list)
+ self.assertIn(id2, id_list)
+
+ # Test list --mac-address
+ json_output = json.loads(self.openstack(
+ 'port list -f json --mac-address ' + mac2
+ ))
+ item_map = {item.get('ID'): item.get('MAC Address') for item in
+ json_output}
+ self.assertNotIn(id1, item_map.keys())
+ self.assertIn(id2, item_map.keys())
+ self.assertNotIn(mac1, item_map.values())
+ self.assertIn(mac2, item_map.values())
def test_port_set(self):
- self.openstack('port set --disable ' + self.NAME)
- opts = self.get_opts(['name', 'admin_state_up'])
- raw_output = self.openstack('port show ' + self.NAME + opts)
- self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output)
-
- def test_port_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('port show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ """Test create, set, show, delete"""
+ json_output = json.loads(self.openstack(
+ 'port create -f json ' +
+ '--network ' + self.NETWORK_NAME + ' ' +
+ '--description xyzpdq '
+ '--disable ' +
+ self.NAME
+ ))
+ id1 = json_output.get('id')
+ self.addCleanup(self.openstack, 'port delete ' + id1)
+ self.assertEqual(self.NAME, json_output.get('name'))
+ self.assertEqual('xyzpdq', json_output.get('description'))
+ self.assertEqual('DOWN', json_output.get('admin_state_up'))
+
+ raw_output = self.openstack(
+ 'port set ' + '--enable ' + self.NAME)
+ self.assertOutput('', raw_output)
+
+ json_output = json.loads(self.openstack(
+ 'port show -f json ' + self.NAME
+ ))
+ sg_id = json_output.get('security_group_ids')
+
+ self.assertEqual(self.NAME, json_output.get('name'))
+ self.assertEqual('xyzpdq', json_output.get('description'))
+ self.assertEqual('UP', json_output.get('admin_state_up'))
+ self.assertIsNotNone(json_output.get('mac_address'))
+
+ raw_output = self.openstack(
+ 'port unset --security-group ' + sg_id + ' ' + id1)
+ self.assertOutput('', raw_output)
+
+ json_output = json.loads(self.openstack(
+ 'port show -f json ' + self.NAME
+ ))
+ self.assertEqual('', json_output.get('security_group_ids'))
+
+ def test_port_admin_set(self):
+ """Test create, set (as admin), show, delete"""
+ json_output = json.loads(self.openstack(
+ 'port create -f json ' +
+ '--network ' + self.NETWORK_NAME + ' ' + self.NAME
+ ))
+ id_ = json_output.get('id')
+ self.addCleanup(self.openstack, 'port delete ' + id_)
+
+ raw_output = self.openstack(
+ '--os-username admin '
+ + 'port set --mac-address 11:22:33:44:55:66 '
+ + self.NAME)
+ self.assertOutput('', raw_output)
+ json_output = json.loads(self.openstack(
+ 'port show -f json ' + self.NAME
+ ))
+ self.assertEqual(json_output.get('mac_address'), '11:22:33:44:55:66')
diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py
index 789c3825..aa708e0a 100644
--- a/openstackclient/tests/functional/network/v2/test_router.py
+++ b/openstackclient/tests/functional/network/v2/test_router.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional import base
@@ -17,34 +18,207 @@ from openstackclient.tests.functional import base
class RouterTests(base.TestCase):
"""Functional tests for router. """
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
-
- @classmethod
- def setUpClass(cls):
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('router create ' + cls.NAME + opts)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
-
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('router delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
+
+ def test_router_create_and_delete(self):
+ """Test create options, delete multiple"""
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'router create -f json ' +
+ name1
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+ cmd_output = json.loads(self.openstack(
+ 'router create -f json ' +
+ name2
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+
+ del_output = self.openstack(
+ 'router delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
def test_router_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('router list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_router_set(self):
- self.openstack('router set --disable ' + self.NAME)
- opts = self.get_opts(['name', 'admin_state_up'])
- raw_output = self.openstack('router show ' + self.NAME + opts)
- self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output)
-
- def test_router_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('router show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ """Test create, list filter"""
+ # Get project IDs
+ cmd_output = json.loads(self.openstack('token issue -f json '))
+ auth_project_id = cmd_output['project_id']
+
+ cmd_output = json.loads(self.openstack('project list -f json '))
+ admin_project_id = None
+ demo_project_id = None
+ for p in cmd_output:
+ if p['Name'] == 'admin':
+ admin_project_id = p['ID']
+ if p['Name'] == 'demo':
+ demo_project_id = p['ID']
+
+ # Verify assumptions:
+ # * admin and demo projects are present
+ # * demo and admin are distinct projects
+ # * tests run as admin
+ self.assertIsNotNone(admin_project_id)
+ self.assertIsNotNone(demo_project_id)
+ self.assertNotEqual(admin_project_id, demo_project_id)
+ self.assertEqual(admin_project_id, auth_project_id)
+
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'router create -f json ' +
+ '--disable ' +
+ name1
+ ))
+
+ self.addCleanup(self.openstack, 'router delete ' + name1)
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ "DOWN",
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ admin_project_id,
+ cmd_output["project_id"],
+ )
+ cmd_output = json.loads(self.openstack(
+ 'router create -f json ' +
+ '--project ' + demo_project_id +
+ ' ' + name2
+ ))
+
+ self.addCleanup(self.openstack, 'router delete ' + name2)
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ "UP",
+ cmd_output["admin_state_up"],
+ )
+ self.assertEqual(
+ demo_project_id,
+ cmd_output["project_id"],
+ )
+
+ # Test list --project
+ cmd_output = json.loads(self.openstack(
+ 'router list -f json ' +
+ '--project ' + demo_project_id
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --disable
+ cmd_output = json.loads(self.openstack(
+ 'router list -f json ' +
+ '--disable '
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertNotIn(name2, names)
+
+ # Test list --name
+ cmd_output = json.loads(self.openstack(
+ 'router list -f json ' +
+ '--name ' + name1
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertNotIn(name2, names)
+
+ # Test list --long
+ cmd_output = json.loads(self.openstack(
+ 'router list -f json ' +
+ '--long '
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ def test_router_set_show_unset(self):
+ """Tests create router, set, unset, show"""
+
+ name = uuid.uuid4().hex
+ new_name = name + "_"
+ cmd_output = json.loads(self.openstack(
+ 'router create -f json ' +
+ '--description aaaa ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'router delete ' + new_name)
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+
+ # Test set --disable
+ cmd_output = self.openstack(
+ 'router set ' +
+ '--name ' + new_name +
+ ' --description bbbb ' +
+ '--disable ' +
+ name
+ )
+ self.assertOutput('', cmd_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'router show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ 'DOWN',
+ cmd_output["admin_state_up"],
+ )
+
+ # Test set --ha --distributed
+ cmd_output = self.openstack(
+ 'router set ' +
+ '--distributed ' +
+ '--external-gateway public ' +
+ new_name
+ )
+ self.assertOutput('', cmd_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'router show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ True,
+ cmd_output["distributed"],
+ )
+ self.assertIsNotNone(cmd_output["external_gateway_info"])
+
+ # Test unset
+ cmd_output = self.openstack(
+ 'router unset ' +
+ '--external-gateway ' +
+ new_name
+ )
+ cmd_output = json.loads(self.openstack(
+ 'router show -f json ' +
+ new_name
+ ))
+ self.assertIsNone(cmd_output["external_gateway_info"])
diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py
index 231671f3..61cffcde 100644
--- a/openstackclient/tests/functional/network/v2/test_subnet.py
+++ b/openstackclient/tests/functional/network/v2/test_subnet.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
+import random
import uuid
from openstackclient.tests.functional import base
@@ -17,50 +19,253 @@ from openstackclient.tests.functional import base
class SubnetTests(base.TestCase):
"""Functional tests for subnet. """
- NAME = uuid.uuid4().hex
- NETWORK_NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['name']
@classmethod
def setUpClass(cls):
- # Create a network for the subnet.
- cls.openstack('network create ' + cls.NETWORK_NAME)
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack(
- 'subnet create --network ' + cls.NETWORK_NAME +
- ' --subnet-range 10.10.10.0/24 ' +
- cls.NAME + opts
- )
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
+ # Create a network for the all subnet tests.
+ cls.NETWORK_NAME = uuid.uuid4().hex
+ cmd_output = json.loads(cls.openstack(
+ 'network create -f json ' +
+ cls.NETWORK_NAME
+ ))
+ # Get network_id for assertEqual
+ cls.NETWORK_ID = cmd_output["id"]
@classmethod
def tearDownClass(cls):
- raw_output = cls.openstack('subnet delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME)
cls.assertOutput('', raw_output)
+ def test_subnet_create_and_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd = ('subnet create -f json --network ' +
+ self.NETWORK_NAME +
+ ' --subnet-range')
+ cmd_output = self._subnet_create(cmd, name1)
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ self.NETWORK_ID,
+ cmd_output["network_id"],
+ )
+ name2 = uuid.uuid4().hex
+ cmd = ('subnet create -f json --network ' +
+ self.NETWORK_NAME +
+ ' --subnet-range')
+ cmd_output = self._subnet_create(cmd, name2)
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ self.NETWORK_ID,
+ cmd_output["network_id"],
+ )
+
+ del_output = self.openstack(
+ 'subnet delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
+
def test_subnet_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('subnet list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_subnet_set(self):
- self.openstack('subnet set --no-dhcp ' + self.NAME)
- opts = self.get_opts(['name', 'enable_dhcp'])
- raw_output = self.openstack('subnet show ' + self.NAME + opts)
- self.assertEqual("False\n" + self.NAME + "\n", raw_output)
-
- def test_subnet_set_service_type(self):
- TYPE = 'network:floatingip_agent_gateway'
- self.openstack('subnet set --service-type ' + TYPE + ' ' + self.NAME)
- opts = self.get_opts(['name', 'service_types'])
- raw_output = self.openstack('subnet show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n" + TYPE + "\n", raw_output)
-
- def test_subnet_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('subnet show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ """Test create, list filter"""
+ name1 = uuid.uuid4().hex
+ name2 = uuid.uuid4().hex
+ cmd = ('subnet create -f json ' +
+ '--network ' + self.NETWORK_NAME +
+ ' --dhcp --subnet-range')
+ cmd_output = self._subnet_create(cmd, name1)
+
+ self.addCleanup(self.openstack, 'subnet delete ' + name1)
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ True,
+ cmd_output["enable_dhcp"],
+ )
+ self.assertEqual(
+ self.NETWORK_ID,
+ cmd_output["network_id"],
+ )
+ self.assertEqual(
+ 4,
+ cmd_output["ip_version"],
+ )
+
+ cmd = ('subnet create -f json ' +
+ '--network ' + self.NETWORK_NAME +
+ ' --ip-version 6 --no-dhcp ' +
+ '--subnet-range')
+ cmd_output = self._subnet_create(cmd, name2, is_type_ipv4=False)
+
+ self.addCleanup(self.openstack, 'subnet delete ' + name2)
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["enable_dhcp"],
+ )
+ self.assertEqual(
+ self.NETWORK_ID,
+ cmd_output["network_id"],
+ )
+ self.assertEqual(
+ 6,
+ cmd_output["ip_version"],
+ )
+
+ # Test list --long
+ cmd_output = json.loads(self.openstack(
+ 'subnet list -f json ' +
+ '--long '
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --name
+ cmd_output = json.loads(self.openstack(
+ 'subnet list -f json ' +
+ '--name ' + name1
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertNotIn(name2, names)
+
+ # Test list --ip-version
+ cmd_output = json.loads(self.openstack(
+ 'subnet list -f json ' +
+ '--ip-version 6'
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --network
+ cmd_output = json.loads(self.openstack(
+ 'subnet list -f json ' +
+ '--network ' + self.NETWORK_ID
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --no-dhcp
+ cmd_output = json.loads(self.openstack(
+ 'subnet list -f json ' +
+ '--no-dhcp '
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, names)
+ self.assertIn(name2, names)
+
+ def test_subnet_set_show_unset(self):
+ """Test create subnet, set, unset, show"""
+
+ name = uuid.uuid4().hex
+ new_name = name + "_"
+ cmd = ('subnet create -f json ' +
+ '--network ' + self.NETWORK_NAME +
+ ' --description aaaa --subnet-range')
+ cmd_output = self._subnet_create(cmd, name)
+
+ self.addCleanup(self.openstack, 'subnet delete ' + new_name)
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+
+ # Test set --no-dhcp --name --gateway --description
+ cmd_output = self.openstack(
+ 'subnet set ' +
+ '--name ' + new_name +
+ ' --description bbbb ' +
+ '--no-dhcp ' +
+ '--gateway 10.10.11.1 ' +
+ '--service-type network:floatingip_agent_gateway ' +
+ name
+ )
+ self.assertOutput('', cmd_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'subnet show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ False,
+ cmd_output["enable_dhcp"],
+ )
+ self.assertEqual(
+ '10.10.11.1',
+ cmd_output["gateway_ip"],
+ )
+ self.assertEqual(
+ 'network:floatingip_agent_gateway',
+ cmd_output["service_types"],
+ )
+
+ # Test unset
+ cmd_output = self.openstack(
+ 'subnet unset ' +
+ '--service-type network:floatingip_agent_gateway ' +
+ new_name
+ )
+ self.assertOutput('', cmd_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'subnet show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ '',
+ cmd_output["service_types"],
+ )
+
+ def _subnet_create(self, cmd, name, is_type_ipv4=True):
+ # Try random subnet range for subnet creating
+ # Because we can not determine ahead of time what subnets are already
+ # in use, possibly by another test running in parallel, try 4 times
+ for i in range(4):
+ # Make a random subnet
+ if is_type_ipv4:
+ subnet = ".".join(map(
+ str,
+ (random.randint(0, 223) for _ in range(3))
+ )) + ".0/26"
+ else:
+ subnet = ":".join(map(
+ str,
+ (hex(random.randint(0, 65535))[2:] for _ in range(7))
+ )) + ":0/112"
+ try:
+ cmd_output = json.loads(self.openstack(
+ cmd + ' ' + subnet + ' ' +
+ name
+ ))
+ except Exception:
+ if (i == 3):
+ # raise the exception at the last time
+ raise
+ pass
+ else:
+ # break and no longer retry if create sucessfully
+ break
+ return cmd_output
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/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py
index 770d5acb..434840f6 100644
--- a/openstackclient/tests/functional/volume/v1/test_qos.py
+++ b/openstackclient/tests/functional/volume/v1/test_qos.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional.volume.v1 import common
@@ -18,38 +19,103 @@ from openstackclient.tests.functional.volume.v1 import common
class QosTests(common.BaseVolumeTests):
"""Functional tests for volume qos. """
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['id', 'name']
- ID = None
-
- @classmethod
- def setUpClass(cls):
- super(QosTests, cls).setUpClass()
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('volume qos create ' + cls.NAME + opts)
- cls.ID, name, rol = raw_output.split('\n')
- cls.assertOutput(cls.NAME, name)
-
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('volume qos delete ' + cls.ID)
- cls.assertOutput('', raw_output)
-
- def test_volume_qos_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('volume qos list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_volume_qos_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('volume qos show ' + self.ID + opts)
- self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output)
-
- def test_volume_qos_metadata(self):
+ def test_volume_qos_create_list(self):
+ """Test create, list, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume qos create -f json ' +
+ name1
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output['name']
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume qos create -f json ' +
+ name2
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output['name']
+ )
+
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ 'volume qos list -f json'
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test delete multiple
+ del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
+
+ def test_volume_qos_set_show_unset(self):
+ """Tests create volume qos, set, unset, show, delete"""
+
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume qos create -f json ' +
+ '--consumer front-end '
+ '--property Alpha=a ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'volume qos delete ' + name)
+ self.assertEqual(
+ name,
+ cmd_output['name']
+ )
+
+ self.assertEqual(
+ "front-end",
+ cmd_output['consumer']
+ )
+
+ # Test volume qos set
+ raw_output = self.openstack(
+ 'volume qos set ' +
+ '--property Alpha=c ' +
+ '--property Beta=b ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ # Test volume qos show
+ cmd_output = json.loads(self.openstack(
+ 'volume qos show -f json ' +
+ name
+ ))
+ self.assertEqual(
+ name,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ "Alpha='c', Beta='b'",
+ cmd_output['properties']
+ )
+
+ # Test volume qos unset
raw_output = self.openstack(
- 'volume qos set --property a=b --property c=d ' + self.ID)
- self.assertEqual("", raw_output)
- opts = self.get_opts(['name', 'specs'])
- raw_output = self.openstack('volume qos show ' + self.ID + opts)
- self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
+ 'volume qos unset ' +
+ '--property Alpha ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume qos show -f json ' +
+ name
+ ))
+ self.assertEqual(
+ name,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ "Beta='b'",
+ cmd_output['properties']
+ )
+
+ # TODO(qiangjiahui): Add tests for associate and disassociate volume type
diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py
index c6d65ccc..89a98661 100644
--- a/openstackclient/tests/functional/volume/v1/test_snapshot.py
+++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py
@@ -10,19 +10,17 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import time
import uuid
from openstackclient.tests.functional.volume.v1 import common
-class SnapshotTests(common.BaseVolumeTests):
- """Functional tests for snapshot. """
+class VolumeSnapshotTests(common.BaseVolumeTests):
+ """Functional tests for volume snapshot. """
VOLLY = uuid.uuid4().hex
- NAME = uuid.uuid4().hex
- OTHER_NAME = uuid.uuid4().hex
- HEADERS = ['"Name"']
@classmethod
def wait_for_status(cls, command, status, tries):
@@ -30,57 +28,223 @@ class SnapshotTests(common.BaseVolumeTests):
for attempt in range(tries):
time.sleep(1)
raw_output = cls.openstack(command + opts)
- if (raw_output == status):
+ if (raw_output.rstrip() == status):
return
cls.assertOutput(status, raw_output)
@classmethod
def setUpClass(cls):
- super(SnapshotTests, cls).setUpClass()
- cls.openstack('volume create --size 1 ' + cls.VOLLY)
- cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3)
- opts = cls.get_opts(['status'])
- raw_output = cls.openstack('snapshot create --name ' + cls.NAME +
- ' ' + cls.VOLLY + opts)
- cls.assertOutput('creating\n', raw_output)
- cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3)
+ super(VolumeSnapshotTests, cls).setUpClass()
+ # create a volume for all tests to create snapshot
+ cmd_output = json.loads(cls.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ cls.VOLLY
+ ))
+ cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6)
+ cls.VOLUME_ID = cmd_output['id']
@classmethod
def tearDownClass(cls):
- # Rename test
- raw_output = cls.openstack(
- 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME)
+ cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6)
+ raw_output = cls.openstack('volume delete --force ' + cls.VOLLY)
cls.assertOutput('', raw_output)
- # Delete test
- raw_output_snapshot = cls.openstack(
- 'snapshot delete ' + cls.OTHER_NAME)
- cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6)
- raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY)
- cls.assertOutput('', raw_output_snapshot)
- cls.assertOutput('', raw_output_volume)
-
- def test_snapshot_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('snapshot list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_snapshot_set_unset_properties(self):
+
+ def test_volume_snapshot__delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name1 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output["display_name"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name2 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output["display_name"],
+ )
+
+ self.wait_for_status(
+ 'volume snapshot show ' + name1, 'available', 6)
+ self.wait_for_status(
+ 'volume snapshot show ' + name2, 'available', 6)
+
+ del_output = self.openstack(
+ 'volume snapshot delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
+
+ def test_volume_snapshot_list(self):
+ """Test create, list filter"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name1 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.addCleanup(self.openstack, 'volume snapshot delete ' + name1)
+ self.assertEqual(
+ name1,
+ cmd_output["display_name"],
+ )
+ self.assertEqual(
+ self.VOLUME_ID,
+ cmd_output["volume_id"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.wait_for_status(
+ 'volume snapshot show ' + name1, 'available', 6)
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name2 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.addCleanup(self.openstack, 'volume snapshot delete ' + name2)
+ self.assertEqual(
+ name2,
+ cmd_output["display_name"],
+ )
+ self.assertEqual(
+ self.VOLUME_ID,
+ cmd_output["volume_id"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.wait_for_status(
+ 'volume snapshot show ' + name2, 'available', 6)
+
+ # Test list --long, --status
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot list -f json ' +
+ '--long ' +
+ '--status error'
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, names)
+ self.assertNotIn(name2, names)
+
+ # Test list --volume
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot list -f json ' +
+ '--volume ' + self.VOLLY
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --name
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot list -f json ' +
+ '--name ' + name1
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertNotIn(name2, names)
+
+ def test_snapshot_set(self):
+ """Test create, set, unset, show, delete volume snapshot"""
+ name = uuid.uuid4().hex
+ new_name = name + "_"
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ '--volume ' + self.VOLLY +
+ ' --description aaaa ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name)
+ self.assertEqual(
+ name,
+ cmd_output["display_name"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["display_description"],
+ )
+ self.wait_for_status(
+ 'volume snapshot show ' + name, 'available', 6)
+
+ # Test volume snapshot set
+ raw_output = self.openstack(
+ 'volume snapshot set ' +
+ '--name ' + new_name +
+ ' --description bbbb ' +
+ '--property Alpha=a ' +
+ '--property Beta=b ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ # Show snapshot set result
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["display_name"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["display_description"],
+ )
+ self.assertEqual(
+ "Alpha='a', Beta='b'",
+ cmd_output["properties"],
+ )
+
+ # Test volume unset
raw_output = self.openstack(
- 'snapshot set --property a=b --property c=d ' + self.NAME)
- self.assertEqual("", raw_output)
- opts = self.get_opts(["properties"])
- raw_output = self.openstack('snapshot show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d'\n", raw_output)
-
- raw_output = self.openstack('snapshot unset --property a ' + self.NAME)
- self.assertEqual("", raw_output)
- raw_output = self.openstack('snapshot show ' + self.NAME + opts)
- self.assertEqual("c='d'\n", raw_output)
-
- def test_snapshot_set_description(self):
+ 'volume snapshot unset ' +
+ '--property Alpha ' +
+ new_name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ "Beta='b'",
+ cmd_output["properties"],
+ )
+
+ # Test volume snapshot set --no-property
raw_output = self.openstack(
- 'snapshot set --description backup ' + self.NAME)
- self.assertEqual("", raw_output)
- opts = self.get_opts(["display_description", "display_name"])
- raw_output = self.openstack('snapshot show ' + self.NAME + opts)
- self.assertEqual("backup\n" + self.NAME + "\n", raw_output)
+ 'volume snapshot set ' +
+ '--no-property ' +
+ new_name,
+ )
+ self.assertOutput('', raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot show -f json ' +
+ new_name
+ ))
+ self.assertNotIn(
+ "Beta='b'",
+ cmd_output["properties"],
+ )
diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py
index 5e4bcbea..3f04e071 100644
--- a/openstackclient/tests/functional/volume/v1/test_volume.py
+++ b/openstackclient/tests/functional/volume/v1/test_volume.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
+import time
import uuid
from openstackclient.tests.functional.volume.v1 import common
@@ -18,71 +20,190 @@ from openstackclient.tests.functional.volume.v1 import common
class VolumeTests(common.BaseVolumeTests):
"""Functional tests for volume. """
- NAME = uuid.uuid4().hex
- OTHER_NAME = uuid.uuid4().hex
- HEADERS = ['"Display Name"']
- FIELDS = ['display_name']
-
- @classmethod
- def setUpClass(cls):
- super(VolumeTests, cls).setUpClass()
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
-
- @classmethod
- def tearDownClass(cls):
- # Rename test
- raw_output = cls.openstack(
- 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME)
- cls.assertOutput('', raw_output)
- # Delete test
- raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME)
- cls.assertOutput('', raw_output)
+ def test_volume_create_and_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ name1
+ ))
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 2 ' +
+ name2
+ ))
+ self.assertEqual(
+ 2,
+ cmd_output["size"],
+ )
+
+ self.wait_for("volume", name1, "available")
+ self.wait_for("volume", name2, "available")
+ del_output = self.openstack('volume delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
def test_volume_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('volume list' + opts)
- self.assertIn(self.NAME, raw_output)
+ """Test create, list filter"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ name1
+ ))
+ self.addCleanup(self.openstack, 'volume delete ' + name1)
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.wait_for("volume", name1, "available")
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 2 ' +
+ name2
+ ))
+ self.addCleanup(self.openstack, 'volume delete ' + name2)
+ self.assertEqual(
+ 2,
+ cmd_output["size"],
+ )
+ self.wait_for("volume", name2, "available")
+
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ 'volume list -f json '
+ ))
+ names = [x["Display Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --long
+ cmd_output = json.loads(self.openstack(
+ 'volume list -f json --long'
+ ))
+ bootable = [x["Bootable"] for x in cmd_output]
+ self.assertIn('false', bootable)
+
+ # Test list --name
+ cmd_output = json.loads(self.openstack(
+ 'volume list -f json ' +
+ '--name ' + name1
+ ))
+ names = [x["Display Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertNotIn(name2, names)
- def test_volume_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ def test_volume_set_and_unset(self):
+ """Tests create volume, set, unset, show, delete"""
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ '--description aaaa ' +
+ '--property Alpha=a ' +
+ name
+ ))
+ self.assertEqual(
+ name,
+ cmd_output["display_name"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["display_description"],
+ )
+ self.assertEqual(
+ "Alpha='a'",
+ cmd_output["properties"],
+ )
+ self.assertEqual(
+ 'false',
+ cmd_output["bootable"],
+ )
+ self.wait_for("volume", name, "available")
- def test_volume_properties(self):
+ # Test volume set
+ new_name = uuid.uuid4().hex
+ self.addCleanup(self.openstack, 'volume delete ' + new_name)
raw_output = self.openstack(
- 'volume set --property a=b --property c=d ' + self.NAME)
- self.assertEqual("", raw_output)
- opts = self.get_opts(["properties"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d'\n", raw_output)
-
- raw_output = self.openstack('volume unset --property a ' + self.NAME)
- self.assertEqual("", raw_output)
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("c='d'\n", raw_output)
-
- def test_volume_set(self):
- self.openstack('volume set --description RAMAC ' + self.NAME)
- opts = self.get_opts(["display_description", "display_name"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output)
-
- def test_volume_set_size(self):
- self.openstack('volume set --size 2 ' + self.NAME)
- opts = self.get_opts(["display_name", "size"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n2\n", raw_output)
-
- def test_volume_set_bootable(self):
- self.openstack('volume set --bootable ' + self.NAME)
- opts = self.get_opts(["bootable"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("true\n", raw_output)
-
- self.openstack('volume set --non-bootable ' + self.NAME)
- opts = self.get_opts(["bootable"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("false\n", raw_output)
+ 'volume set ' +
+ '--name ' + new_name +
+ ' --size 2 ' +
+ '--description bbbb ' +
+ '--no-property ' +
+ '--property Beta=b ' +
+ '--property Gamma=c ' +
+ '--bootable ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["display_name"],
+ )
+ self.assertEqual(
+ 2,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["display_description"],
+ )
+ self.assertEqual(
+ "Beta='b', Gamma='c'",
+ cmd_output["properties"],
+ )
+ self.assertEqual(
+ 'true',
+ cmd_output["bootable"],
+ )
+
+ # Test volume unset
+ raw_output = self.openstack(
+ 'volume unset ' +
+ '--property Beta ' +
+ new_name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ "Gamma='c'",
+ cmd_output["properties"],
+ )
+
+ def wait_for(self, check_type, check_name, desired_status, wait=120,
+ interval=5, failures=['ERROR']):
+ status = "notset"
+ total_sleep = 0
+ opts = self.get_opts(['status'])
+ while total_sleep < wait:
+ status = self.openstack(check_type + ' show ' + check_name + opts)
+ status = status.rstrip()
+ print('Checking {} {} Waiting for {} current status: {}'
+ .format(check_type, check_name, desired_status, status))
+ if status == desired_status:
+ break
+ self.assertNotIn(status, failures)
+ time.sleep(interval)
+ total_sleep += interval
+ self.assertEqual(desired_status, status)
diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py
index 955759b6..d1842795 100644
--- a/openstackclient/tests/functional/volume/v1/test_volume_type.py
+++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py
@@ -87,3 +87,74 @@ class VolumeTypeTests(common.BaseVolumeTests):
time.sleep(5)
raw_output = self.openstack(cmd)
self.assertOutput('', raw_output)
+
+ # NOTE: Add some basic funtional tests with the old format to
+ # make sure the command works properly, need to change
+ # these to new test format when beef up all tests for
+ # volume tye commands.
+ def test_encryption_type(self):
+ encryption_type = uuid.uuid4().hex
+ # test create new encryption type
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type create '
+ '--encryption-provider LuksEncryptor '
+ '--encryption-cipher aes-xts-plain64 '
+ '--encryption-key-size 128 '
+ '--encryption-control-location front-end ' +
+ encryption_type + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test show encryption type
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + encryption_type + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test list encryption type
+ opts = self.get_opts(['Encryption'])
+ raw_output = self.openstack(
+ 'volume type list --encryption-type ' + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test set new encryption type
+ raw_output = self.openstack(
+ 'volume type set '
+ '--encryption-provider LuksEncryptor '
+ '--encryption-cipher aes-xts-plain64 '
+ '--encryption-key-size 128 '
+ '--encryption-control-location front-end ' +
+ self.NAME)
+ self.assertEqual('', raw_output)
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + self.NAME + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test unset encryption type
+ raw_output = self.openstack(
+ 'volume type unset --encryption-type ' + self.NAME)
+ self.assertEqual('', raw_output)
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + self.NAME + opts)
+ self.assertEqual('\n', raw_output)
+ # test delete encryption type
+ raw_output = self.openstack('volume type delete ' + encryption_type)
+ self.assertEqual('', raw_output)
diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py
index a54acbfd..aee10dca 100644
--- a/openstackclient/tests/functional/volume/v2/test_qos.py
+++ b/openstackclient/tests/functional/volume/v2/test_qos.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import uuid
from openstackclient.tests.functional.volume.v2 import common
@@ -18,45 +19,107 @@ from openstackclient.tests.functional.volume.v2 import common
class QosTests(common.BaseVolumeTests):
"""Functional tests for volume qos. """
- NAME = uuid.uuid4().hex
- HEADERS = ['Name']
- FIELDS = ['id', 'name']
- ID = None
-
- @classmethod
- def setUpClass(cls):
- super(QosTests, cls).setUpClass()
- opts = cls.get_opts(cls.FIELDS)
- raw_output = cls.openstack('volume qos create ' + cls.NAME + opts)
- cls.ID, name, rol = raw_output.split('\n')
- cls.assertOutput(cls.NAME, name)
-
- @classmethod
- def tearDownClass(cls):
- raw_output = cls.openstack('volume qos delete ' + cls.ID)
- cls.assertOutput('', raw_output)
-
- def test_volume_qos_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('volume qos list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_volume_qos_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('volume qos show ' + self.ID + opts)
- self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output)
-
- def test_volume_qos_metadata(self):
+ def test_volume_qos_create_delete_list(self):
+ """Test create, list, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume qos create -f json ' +
+ name1
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output['name']
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume qos create -f json ' +
+ name2
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output['name']
+ )
+
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ 'volume qos list -f json'
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test delete multiple
+ del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
+
+ def test_volume_qos_set_show_unset(self):
+ """Tests create volume qos, set, unset, show, delete"""
+
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume qos create -f json ' +
+ '--consumer front-end '
+ '--property Alpha=a ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'volume qos delete ' + name)
+ self.assertEqual(
+ name,
+ cmd_output['name']
+ )
+
+ self.assertEqual(
+ "front-end",
+ cmd_output['consumer']
+ )
+ self.assertEqual(
+ "Alpha='a'",
+ cmd_output['properties']
+ )
+
+ # Test volume qos set
raw_output = self.openstack(
- 'volume qos set --property a=b --property c=d ' + self.ID)
- self.assertEqual("", raw_output)
- opts = self.get_opts(['name', 'specs'])
- raw_output = self.openstack('volume qos show ' + self.ID + opts)
- self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
+ 'volume qos set ' +
+ '--property Alpha=c ' +
+ '--property Beta=b ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ # Test volume qos show
+ cmd_output = json.loads(self.openstack(
+ 'volume qos show -f json ' +
+ name
+ ))
+ self.assertEqual(
+ name,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ "Alpha='c', Beta='b'",
+ cmd_output['properties']
+ )
+ # Test volume qos unset
raw_output = self.openstack(
- 'volume qos unset --property a ' + self.ID)
- self.assertEqual("", raw_output)
- opts = self.get_opts(['name', 'specs'])
- raw_output = self.openstack('volume qos show ' + self.ID + opts)
- self.assertEqual(self.NAME + "\nc='d'\n", raw_output)
+ 'volume qos unset ' +
+ '--property Alpha ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume qos show -f json ' +
+ name
+ ))
+ self.assertEqual(
+ name,
+ cmd_output['name']
+ )
+ self.assertEqual(
+ "Beta='b'",
+ cmd_output['properties']
+ )
+
+ # TODO(qiangjiahui): Add tests for associate and disassociate volume type
diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py
index fcbc31cb..422e5b7c 100644
--- a/openstackclient/tests/functional/volume/v2/test_snapshot.py
+++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py
@@ -10,19 +10,17 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import time
import uuid
from openstackclient.tests.functional.volume.v2 import common
-class SnapshotTests(common.BaseVolumeTests):
- """Functional tests for snapshot. """
+class VolumeSnapshotTests(common.BaseVolumeTests):
+ """Functional tests for volume snapshot. """
VOLLY = uuid.uuid4().hex
- NAME = uuid.uuid4().hex
- OTHER_NAME = uuid.uuid4().hex
- HEADERS = ['"Name"']
@classmethod
def wait_for_status(cls, command, status, tries):
@@ -30,57 +28,234 @@ class SnapshotTests(common.BaseVolumeTests):
for attempt in range(tries):
time.sleep(1)
raw_output = cls.openstack(command + opts)
- if (raw_output == status):
+ if (raw_output.rstrip() == status):
return
cls.assertOutput(status, raw_output)
@classmethod
def setUpClass(cls):
- super(SnapshotTests, cls).setUpClass()
- cls.openstack('volume create --size 1 ' + cls.VOLLY)
- cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3)
- opts = cls.get_opts(['status'])
- raw_output = cls.openstack('snapshot create --name ' + cls.NAME +
- ' ' + cls.VOLLY + opts)
- cls.assertOutput('creating\n', raw_output)
- cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3)
+ super(VolumeSnapshotTests, cls).setUpClass()
+ # create a volume for all tests to create snapshot
+ cmd_output = json.loads(cls.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ cls.VOLLY
+ ))
+ cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6)
+ cls.VOLUME_ID = cmd_output['id']
@classmethod
def tearDownClass(cls):
- # Rename test
- raw_output = cls.openstack(
- 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME)
+ cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6)
+ raw_output = cls.openstack('volume delete --force ' + cls.VOLLY)
cls.assertOutput('', raw_output)
- # Delete test
- raw_output_snapshot = cls.openstack(
- 'snapshot delete ' + cls.OTHER_NAME)
- cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6)
- raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY)
- cls.assertOutput('', raw_output_snapshot)
- cls.assertOutput('', raw_output_volume)
-
- def test_snapshot_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('snapshot list' + opts)
- self.assertIn(self.NAME, raw_output)
-
- def test_snapshot_properties(self):
+
+ def test_volume_snapshot__delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name1 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name2 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+
+ self.wait_for_status(
+ 'volume snapshot show ' + name1, 'available', 6)
+ self.wait_for_status(
+ 'volume snapshot show ' + name2, 'available', 6)
+
+ del_output = self.openstack(
+ 'volume snapshot delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
+
+ def test_volume_snapshot_list(self):
+ """Test create, list filter"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name1 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.addCleanup(self.openstack, 'volume snapshot delete ' + name1)
+ self.assertEqual(
+ name1,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ self.VOLUME_ID,
+ cmd_output["volume_id"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.wait_for_status(
+ 'volume snapshot show ' + name1, 'available', 6)
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ name2 +
+ ' --volume ' + self.VOLLY
+ ))
+ self.addCleanup(self.openstack, 'volume snapshot delete ' + name2)
+ self.assertEqual(
+ name2,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ self.VOLUME_ID,
+ cmd_output["volume_id"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.wait_for_status(
+ 'volume snapshot show ' + name2, 'available', 6)
+ raw_output = self.openstack(
+ 'volume snapshot set ' +
+ '--state error ' +
+ name2
+ )
+ self.assertOutput('', raw_output)
+
+ # Test list --long, --status
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot list -f json ' +
+ '--long ' +
+ '--status error'
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertNotIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --volume
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot list -f json ' +
+ '--volume ' + self.VOLLY
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --name
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot list -f json ' +
+ '--name ' + name1
+ ))
+ names = [x["Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertNotIn(name2, names)
+
+ def test_volume_snapshot_set(self):
+ """Test create, set, unset, show, delete volume snapshot"""
+ name = uuid.uuid4().hex
+ new_name = name + "_"
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ '--volume ' + self.VOLLY +
+ ' --description aaaa ' +
+ '--property Alpha=a ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name)
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ "Alpha='a'",
+ cmd_output["properties"],
+ )
+ self.wait_for_status(
+ 'volume snapshot show ' + name, 'available', 6)
+
+ # Test volume snapshot set
raw_output = self.openstack(
- 'snapshot set --property a=b --property c=d ' + self.NAME)
- self.assertEqual("", raw_output)
- opts = self.get_opts(["properties"])
- raw_output = self.openstack('snapshot show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d'\n", raw_output)
-
- raw_output = self.openstack('snapshot unset --property a ' + self.NAME)
- self.assertEqual("", raw_output)
- raw_output = self.openstack('snapshot show ' + self.NAME + opts)
- self.assertEqual("c='d'\n", raw_output)
-
- def test_snapshot_set(self):
+ 'volume snapshot set ' +
+ '--name ' + new_name +
+ ' --description bbbb ' +
+ '--property Alpha=c ' +
+ '--property Beta=b ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ # Show snapshot set result
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ "Alpha='c', Beta='b'",
+ cmd_output["properties"],
+ )
+
+ # Test volume snapshot unset
+ raw_output = self.openstack(
+ 'volume snapshot unset ' +
+ '--property Alpha ' +
+ new_name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ "Beta='b'",
+ cmd_output["properties"],
+ )
+
+ # Test volume snapshot set --no-property
raw_output = self.openstack(
- 'snapshot set --description backup ' + self.NAME)
- self.assertEqual("", raw_output)
- opts = self.get_opts(["description", "name"])
- raw_output = self.openstack('snapshot show ' + self.NAME + opts)
- self.assertEqual("backup\n" + self.NAME + "\n", raw_output)
+ 'volume snapshot set ' +
+ '--no-property ' +
+ new_name,
+ )
+ self.assertOutput('', raw_output)
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot show -f json ' +
+ new_name
+ ))
+ self.assertNotIn(
+ "Beta='b'",
+ cmd_output["properties"],
+ )
diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py
index fb880578..ce98236f 100644
--- a/openstackclient/tests/functional/volume/v2/test_volume.py
+++ b/openstackclient/tests/functional/volume/v2/test_volume.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import time
import uuid
@@ -19,116 +20,232 @@ from openstackclient.tests.functional.volume.v2 import common
class VolumeTests(common.BaseVolumeTests):
"""Functional tests for volume. """
- NAME = uuid.uuid4().hex
- SNAPSHOT_NAME = uuid.uuid4().hex
- VOLUME_FROM_SNAPSHOT_NAME = uuid.uuid4().hex
- OTHER_NAME = uuid.uuid4().hex
- HEADERS = ['"Display Name"']
- FIELDS = ['name']
-
- @classmethod
- def setUpClass(cls):
- super(VolumeTests, cls).setUpClass()
- opts = cls.get_opts(cls.FIELDS)
-
- # Create test volume
- raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts)
- expected = cls.NAME + '\n'
- cls.assertOutput(expected, raw_output)
-
- @classmethod
- def tearDownClass(cls):
- # Rename test volume
- raw_output = cls.openstack(
- 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME)
- cls.assertOutput('', raw_output)
-
- # Set volume state
- cls.openstack('volume set --state error ' + cls.OTHER_NAME)
- opts = cls.get_opts(["status"])
- raw_output_status = cls.openstack(
- 'volume show ' + cls.OTHER_NAME + opts)
-
- # Delete test volume
- raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME)
- cls.assertOutput('', raw_output)
- cls.assertOutput('error\n', raw_output_status)
+ def test_volume_delete(self):
+ """Test create, delete multiple"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ name1
+ ))
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 2 ' +
+ name2
+ ))
+ self.assertEqual(
+ 2,
+ cmd_output["size"],
+ )
+
+ self.wait_for("volume", name1, "available")
+ self.wait_for("volume", name2, "available")
+ del_output = self.openstack('volume delete ' + name1 + ' ' + name2)
+ self.assertOutput('', del_output)
def test_volume_list(self):
- opts = self.get_opts(self.HEADERS)
- raw_output = self.openstack('volume list' + opts)
- self.assertIn(self.NAME, raw_output)
+ """Test create, list filter"""
+ name1 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ name1
+ ))
+ self.addCleanup(self.openstack, 'volume delete ' + name1)
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.wait_for("volume", name1, "available")
+
+ name2 = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 2 ' +
+ name2
+ ))
+ self.addCleanup(self.openstack, 'volume delete ' + name2)
+ self.assertEqual(
+ 2,
+ cmd_output["size"],
+ )
+ self.wait_for("volume", name2, "available")
+ raw_output = self.openstack(
+ 'volume set ' +
+ '--state error ' +
+ name2
+ )
+ self.assertOutput('', raw_output)
+
+ # Test list --long
+ cmd_output = json.loads(self.openstack(
+ 'volume list -f json ' +
+ '--long'
+ ))
+ names = [x["Display Name"] for x in cmd_output]
+ self.assertIn(name1, names)
+ self.assertIn(name2, names)
+
+ # Test list --status
+ cmd_output = json.loads(self.openstack(
+ 'volume list -f json ' +
+ '--status error'
+ ))
+ names = [x["Display Name"] for x in cmd_output]
+ self.assertNotIn(name1, names)
+ self.assertIn(name2, names)
+
+ # TODO(qiangjiahui): Add project option to filter tests when we can
+ # specify volume with project
+
+ def test_volume_set_and_unset(self):
+ """Tests create volume, set, unset, show, delete"""
+ name = uuid.uuid4().hex
+ new_name = name + "_"
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ '--description aaaa ' +
+ '--property Alpha=a ' +
+ name
+ ))
+ self.addCleanup(self.openstack, 'volume delete ' + new_name)
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 1,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'aaaa',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ "Alpha='a'",
+ cmd_output["properties"],
+ )
+ self.assertEqual(
+ 'false',
+ cmd_output["bootable"],
+ )
+ self.wait_for("volume", name, "available")
- def test_volume_show(self):
- opts = self.get_opts(self.FIELDS)
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n", raw_output)
+ # Test volume set
+ raw_output = self.openstack(
+ 'volume set ' +
+ '--name ' + new_name +
+ ' --size 2 ' +
+ '--description bbbb ' +
+ '--no-property ' +
+ '--property Beta=b ' +
+ '--property Gamma=c ' +
+ '--image-property a=b ' +
+ '--image-property c=d ' +
+ '--bootable ' +
+ name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ new_name,
+ cmd_output["name"],
+ )
+ self.assertEqual(
+ 2,
+ cmd_output["size"],
+ )
+ self.assertEqual(
+ 'bbbb',
+ cmd_output["description"],
+ )
+ self.assertEqual(
+ "Beta='b', Gamma='c'",
+ cmd_output["properties"],
+ )
+ self.assertEqual(
+ {'a': 'b', 'c': 'd'},
+ cmd_output["volume_image_metadata"],
+ )
+ self.assertEqual(
+ 'true',
+ cmd_output["bootable"],
+ )
- def test_volume_properties(self):
+ # Test volume unset
raw_output = self.openstack(
- 'volume set --property a=b --property c=d ' + self.NAME)
- self.assertEqual("", raw_output)
- opts = self.get_opts(["properties"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d'\n", raw_output)
-
- raw_output = self.openstack('volume unset --property a ' + self.NAME)
- self.assertEqual("", raw_output)
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("c='d'\n", raw_output)
-
- def test_volume_set(self):
- discription = uuid.uuid4().hex
- self.openstack('volume set --description ' + discription + ' ' +
- self.NAME)
- opts = self.get_opts(["description", "name"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual(discription + "\n" + self.NAME + "\n", raw_output)
-
- def test_volume_set_size(self):
- self.openstack('volume set --size 2 ' + self.NAME)
- opts = self.get_opts(["name", "size"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual(self.NAME + "\n2\n", raw_output)
-
- def test_volume_set_bootable(self):
- self.openstack('volume set --bootable ' + self.NAME)
- opts = self.get_opts(["bootable"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("true\n", raw_output)
-
- self.openstack('volume set --non-bootable ' + self.NAME)
- opts = self.get_opts(["bootable"])
- raw_output = self.openstack('volume show ' + self.NAME + opts)
- self.assertEqual("false\n", raw_output)
+ 'volume unset ' +
+ '--property Beta ' +
+ '--image-property a ' +
+ new_name,
+ )
+ self.assertOutput('', raw_output)
+
+ cmd_output = json.loads(self.openstack(
+ 'volume show -f json ' +
+ new_name
+ ))
+ self.assertEqual(
+ "Gamma='c'",
+ cmd_output["properties"],
+ )
+ self.assertEqual(
+ {'c': 'd'},
+ cmd_output["volume_image_metadata"],
+ )
def test_volume_snapshot(self):
- opts = self.get_opts(self.FIELDS)
-
- # Create snapshot from test volume
- raw_output = self.openstack('snapshot create ' + self.NAME +
- ' --name ' + self.SNAPSHOT_NAME + opts)
- expected = self.SNAPSHOT_NAME + '\n'
- self.assertOutput(expected, raw_output)
- self.wait_for("snapshot", self.SNAPSHOT_NAME, "available")
-
- # Create volume from snapshot
- raw_output = self.openstack('volume create --size 2 --snapshot ' +
- self.SNAPSHOT_NAME + ' ' +
- self.VOLUME_FROM_SNAPSHOT_NAME + opts)
- expected = self.VOLUME_FROM_SNAPSHOT_NAME + '\n'
- self.assertOutput(expected, raw_output)
- self.wait_for("volume", self.VOLUME_FROM_SNAPSHOT_NAME, "available")
-
- # Delete volume that create from snapshot
- raw_output = self.openstack('volume delete ' +
- self.VOLUME_FROM_SNAPSHOT_NAME)
- self.assertOutput('', raw_output)
+ """Tests volume create from snapshot"""
+
+ volume_name = uuid.uuid4().hex
+ snapshot_name = uuid.uuid4().hex
+ # Make a snapshot
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--size 1 ' +
+ volume_name
+ ))
+ self.wait_for("volume", volume_name, "available")
+ self.assertEqual(
+ volume_name,
+ cmd_output["name"],
+ )
+ cmd_output = json.loads(self.openstack(
+ 'volume snapshot create -f json ' +
+ snapshot_name +
+ ' --volume ' + volume_name
+ ))
+ self.wait_for("volume snapshot", snapshot_name, "available")
- # Delete test snapshot
- raw_output = self.openstack('snapshot delete ' + self.SNAPSHOT_NAME)
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume create -f json ' +
+ '--snapshot ' + snapshot_name +
+ ' ' + name
+ ))
+ self.addCleanup(self.openstack, 'volume delete ' + name)
+ self.addCleanup(self.openstack, 'volume delete ' + volume_name)
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+ self.wait_for("volume", name, "available")
+
+ # Delete snapshot
+ raw_output = self.openstack(
+ 'volume snapshot delete ' + snapshot_name)
self.assertOutput('', raw_output)
- self.wait_for("volume", self.NAME, "available")
def wait_for(self, check_type, check_name, desired_status, wait=120,
interval=5, failures=['ERROR']):
diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py
index d8bd3a96..a5d0a767 100644
--- a/openstackclient/tests/functional/volume/v2/test_volume_type.py
+++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py
@@ -42,6 +42,11 @@ class VolumeTypeTests(common.BaseVolumeTests):
raw_output = self.openstack('volume type list' + opts)
self.assertIn(self.NAME, raw_output)
+ def test_volume_type_list_default(self):
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('volume type list --default' + opts)
+ self.assertEqual("lvmdriver-1\n", raw_output)
+
def test_volume_type_show(self):
opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume type show ' + self.NAME + opts)
@@ -97,3 +102,90 @@ class VolumeTypeTests(common.BaseVolumeTests):
time.sleep(5)
raw_output = self.openstack(cmd)
self.assertOutput('', raw_output)
+
+ # NOTE: Add some basic funtional tests with the old format to
+ # make sure the command works properly, need to change
+ # these to new test format when beef up all tests for
+ # volume tye commands.
+ def test_encryption_type(self):
+ encryption_type = uuid.uuid4().hex
+ # test create new encryption type
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type create '
+ '--encryption-provider LuksEncryptor '
+ '--encryption-cipher aes-xts-plain64 '
+ '--encryption-key-size 128 '
+ '--encryption-control-location front-end ' +
+ encryption_type + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test show encryption type
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + encryption_type + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test list encryption type
+ opts = self.get_opts(['Encryption'])
+ raw_output = self.openstack(
+ 'volume type list --encryption-type ' + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test set existing encryption type
+ raw_output = self.openstack(
+ 'volume type set '
+ '--encryption-key-size 256 '
+ '--encryption-control-location back-end ' +
+ encryption_type)
+ self.assertEqual('', raw_output)
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + encryption_type + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='256'",
+ "control_location='back-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test set new encryption type
+ raw_output = self.openstack(
+ 'volume type set '
+ '--encryption-provider LuksEncryptor '
+ '--encryption-cipher aes-xts-plain64 '
+ '--encryption-key-size 128 '
+ '--encryption-control-location front-end ' +
+ self.NAME)
+ self.assertEqual('', raw_output)
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + self.NAME + opts)
+ expected = ["provider='LuksEncryptor'",
+ "cipher='aes-xts-plain64'",
+ "key_size='128'",
+ "control_location='front-end'"]
+ for attr in expected:
+ self.assertIn(attr, raw_output)
+ # test unset encryption type
+ raw_output = self.openstack(
+ 'volume type unset --encryption-type ' + self.NAME)
+ self.assertEqual('', raw_output)
+ opts = self.get_opts(['encryption'])
+ raw_output = self.openstack(
+ 'volume type show --encryption-type ' + self.NAME + opts)
+ self.assertEqual('\n', raw_output)
+ # test delete encryption type
+ raw_output = self.openstack('volume type delete ' + encryption_type)
+ self.assertEqual('', raw_output)
diff --git a/openstackclient/tests/functional/volume/v3/__init__.py b/openstackclient/tests/functional/volume/v3/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/__init__.py
diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py
new file mode 100644
index 00000000..57a62df6
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/common.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+from openstackclient.tests.functional import base
+
+
+class BaseVolumeTests(base.TestCase):
+ """Base class for Volume functional tests. """
+
+ @classmethod
+ def setUpClass(cls):
+ os.environ['OS_VOLUME_API_VERSION'] = '3'
diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py
new file mode 100644
index 00000000..46965ced
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/test_qos.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional.volume.v2 import test_qos as v2
+import os
+
+
+class QosTests(v2.QosTests):
+ """Functional tests for volume qos. """
+
+ @classmethod
+ def setUpClass(cls):
+ super(QosTests, cls).setUpClass()
+ os.environ['OS_VOLUME_API_VERSION'] = '3'
diff --git a/openstackclient/tests/functional/volume/v3/test_snapshot.py b/openstackclient/tests/functional/volume/v3/test_snapshot.py
new file mode 100644
index 00000000..bf05b9de
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/test_snapshot.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional.volume.v2 import test_snapshot as v2
+import os
+
+
+class VolumeSnapshotTests(v2.VolumeSnapshotTests):
+ """Functional tests for volume snapshot. """
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumeSnapshotTests, cls).setUpClass()
+ os.environ['OS_VOLUME_API_VERSION'] = '3'
diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py
new file mode 100644
index 00000000..7b54dd20
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py
@@ -0,0 +1,24 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional.volume.v2 import test_transfer_request \
+ as v2
+import os
+
+
+class TransferRequestTests(v2.TransferRequestTests):
+ """Functional tests for transfer request. """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TransferRequestTests, cls).setUpClass()
+ os.environ['OS_VOLUME_API_VERSION'] = '3'
diff --git a/openstackclient/tests/functional/volume/v3/test_volume.py b/openstackclient/tests/functional/volume/v3/test_volume.py
new file mode 100644
index 00000000..333826d8
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/test_volume.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional.volume.v2 import test_volume as v2
+import os
+
+
+class VolumeTests(v2.VolumeTests):
+ """Functional tests for volume. """
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumeTests, cls).setUpClass()
+ os.environ['OS_VOLUME_API_VERSION'] = '3'
diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py
new file mode 100644
index 00000000..f10e64b4
--- /dev/null
+++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.tests.functional.volume.v2 import test_volume_type as v2
+import os
+
+
+class VolumeTypeTests(v2.VolumeTypeTests):
+ """Functional tests for volume type. """
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumeTypeTests, cls).setUpClass()
+ os.environ['OS_VOLUME_API_VERSION'] = '3'
diff --git a/openstackclient/tests/unit/api/test_image_v1.py b/openstackclient/tests/unit/api/test_image_v1.py
index e02ef381..6ce3ddea 100644
--- a/openstackclient/tests/unit/api/test_image_v1.py
+++ b/openstackclient/tests/unit/api/test_image_v1.py
@@ -21,7 +21,7 @@ from openstackclient.tests.unit import utils
FAKE_PROJECT = 'xyzpdq'
-FAKE_URL = 'http://gopher.com'
+FAKE_URL = 'http://gopher.dev10.com'
class TestImageAPIv1(utils.TestCase):
diff --git a/openstackclient/tests/unit/api/test_image_v2.py b/openstackclient/tests/unit/api/test_image_v2.py
index 5dbb51e0..22490e46 100644
--- a/openstackclient/tests/unit/api/test_image_v2.py
+++ b/openstackclient/tests/unit/api/test_image_v2.py
@@ -21,7 +21,7 @@ from openstackclient.tests.unit import utils
FAKE_PROJECT = 'xyzpdq'
-FAKE_URL = 'http://gopher.com'
+FAKE_URL = 'http://gopher.dev20.com'
class TestImageAPIv2(utils.TestCase):
diff --git a/openstackclient/tests/unit/common/test_module.py b/openstackclient/tests/unit/common/test_module.py
index eb54dbe0..2491d639 100644
--- a/openstackclient/tests/unit/common/test_module.py
+++ b/openstackclient/tests/unit/common/test_module.py
@@ -26,19 +26,28 @@ from openstackclient.tests.unit import utils
# currently == '*client*'
module_name_1 = 'fakeclient'
module_version_1 = '0.1.2'
-MODULE_1 = {
- '__version__': module_version_1,
-}
module_name_2 = 'zlib'
module_version_2 = '1.1'
-MODULE_2 = {
- '__version__': module_version_2,
-}
+
+# module_3 match openstacksdk
+module_name_3 = 'openstack'
+module_version_3 = '0.9.13'
+
+# module_4 match sub module of fakeclient
+module_name_4 = 'fakeclient.submodule'
+module_version_4 = '0.2.2'
+
+# module_5 match private module
+module_name_5 = '_private_module.lib'
+module_version_5 = '0.0.1'
MODULES = {
module_name_1: fakes.FakeModule(module_name_1, module_version_1),
module_name_2: fakes.FakeModule(module_name_2, module_version_2),
+ module_name_3: fakes.FakeModule(module_name_3, module_version_3),
+ module_name_4: fakes.FakeModule(module_name_4, module_version_4),
+ module_name_5: fakes.FakeModule(module_name_5, module_version_5),
}
@@ -79,6 +88,41 @@ class TestCommandList(utils.TestCommand):
self.assertEqual(datalist, tuple(data))
+ def test_command_list_with_group_not_found(self):
+ arglist = [
+ '--group', 'not_exist',
+ ]
+ verifylist = [
+ ('group', 'not_exist'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = ('Command Group', 'Commands')
+ self.assertEqual(collist, columns)
+ self.assertEqual([], data)
+
+ def test_command_list_with_group(self):
+ arglist = [
+ '--group', 'common',
+ ]
+ verifylist = [
+ ('group', 'common'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = ('Command Group', 'Commands')
+ self.assertEqual(collist, columns)
+ datalist = ((
+ 'openstack.common',
+ 'limits show\nextension list'
+ ),)
+
+ self.assertEqual(datalist, tuple(data))
+
@mock.patch.dict(
'openstackclient.common.module.sys.modules',
@@ -105,9 +149,18 @@ class TestModuleList(utils.TestCommand):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- # Additional modules may be present, just check our additions
+ # Output xxxclient and openstacksdk, but not regular module, like: zlib
self.assertIn(module_name_1, columns)
self.assertIn(module_version_1, data)
+ self.assertNotIn(module_name_2, columns)
+ self.assertNotIn(module_version_2, data)
+ self.assertIn(module_name_3, columns)
+ self.assertIn(module_version_3, data)
+ # Filter sub and private modules
+ self.assertNotIn(module_name_4, columns)
+ self.assertNotIn(module_version_4, data)
+ self.assertNotIn(module_name_5, columns)
+ self.assertNotIn(module_version_5, data)
def test_module_list_all(self):
arglist = [
@@ -123,8 +176,15 @@ class TestModuleList(utils.TestCommand):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- # Additional modules may be present, just check our additions
+ # Output xxxclient, openstacksdk and regular module, like: zlib
self.assertIn(module_name_1, columns)
- self.assertIn(module_name_2, columns)
self.assertIn(module_version_1, data)
+ self.assertIn(module_name_2, columns)
self.assertIn(module_version_2, data)
+ self.assertIn(module_name_3, columns)
+ self.assertIn(module_version_3, data)
+ # Filter sub and private modules
+ self.assertNotIn(module_name_4, columns)
+ self.assertNotIn(module_version_4, data)
+ self.assertNotIn(module_name_5, columns)
+ self.assertNotIn(module_version_5, data)
diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py
index 7dd23373..63f6435f 100644
--- a/openstackclient/tests/unit/common/test_quota.py
+++ b/openstackclient/tests/unit/common/test_quota.py
@@ -17,6 +17,7 @@ from openstackclient.common import quota
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
from openstackclient.tests.unit import fakes
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
@@ -518,3 +519,161 @@ class TestQuotaShow(TestQuota):
self.network.get_quota.assert_called_once_with(
identity_fakes.project_id)
self.assertNotCalled(self.network.get_quota_default)
+
+
+class TestQuotaList(TestQuota):
+ """Test cases for quota list command"""
+
+ project = identity_fakes_v3.FakeProject.create_one_project()
+
+ quota_list = network_fakes.FakeQuota.create_one_net_quota()
+ quota_list1 = compute_fakes.FakeQuota.create_one_comp_quota()
+ quota_list2 = volume_fakes.FakeQuota.create_one_vol_quota()
+
+ default_quota = network_fakes.FakeQuota.create_one_default_net_quota()
+ default_quota1 = compute_fakes.FakeQuota.create_one_default_comp_quota()
+ default_quota2 = volume_fakes.FakeQuota.create_one_default_vol_quota()
+
+ reference_data = (project.id,
+ quota_list.floating_ips,
+ quota_list.networks,
+ quota_list.ports,
+ quota_list.rbac_policies,
+ quota_list.routers,
+ quota_list.security_groups,
+ quota_list.security_group_rules,
+ quota_list.subnets,
+ quota_list.subnet_pools)
+
+ comp_reference_data = (project.id,
+ quota_list1.cores,
+ quota_list1.fixed_ips,
+ quota_list1.injected_files,
+ quota_list1.injected_file_content_bytes,
+ quota_list1.injected_file_path_bytes,
+ quota_list1.instances,
+ quota_list1.key_pairs,
+ quota_list1.metadata_items,
+ quota_list1.ram,
+ quota_list1.server_groups,
+ quota_list1.server_group_members)
+
+ vol_reference_data = (project.id,
+ quota_list2.backups,
+ quota_list2.backup_gigabytes,
+ quota_list2.gigabytes,
+ quota_list2.per_volume_gigabytes,
+ quota_list2.snapshots,
+ quota_list2.volumes)
+
+ net_column_header = (
+ 'Project ID',
+ 'Floating IPs',
+ 'Networks',
+ 'Ports',
+ 'RBAC Policies',
+ 'Routers',
+ 'Security Groups',
+ 'Security Group Rules',
+ 'Subnets',
+ 'Subnet Pools'
+ )
+
+ comp_column_header = (
+ 'Project ID',
+ 'Cores',
+ 'Fixed IPs',
+ 'Injected Files',
+ 'Injected File Content Bytes',
+ 'Injected File Path Bytes',
+ 'Instances',
+ 'Key Pairs',
+ 'Metadata Items',
+ 'Ram',
+ 'Server Groups',
+ 'Server Group Members',
+ )
+
+ vol_column_header = (
+ 'Project ID',
+ 'Backups',
+ 'Backup Gigabytes',
+ 'Gigabytes',
+ 'Per Volume Gigabytes',
+ 'Snapshots',
+ 'Volumes',
+ )
+
+ def setUp(self):
+ super(TestQuotaList, self).setUp()
+
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+
+ self.identity = self.app.client_manager.identity
+ self.identity.tenants.list = mock.Mock(return_value=[self.project])
+
+ self.network = self.app.client_manager.network
+ self.compute = self.app.client_manager.compute
+ self.volume = self.app.client_manager.volume
+
+ self.network.get_quota = mock.Mock(return_value=self.quota_list)
+ self.compute.quotas.get = mock.Mock(return_value=self.quota_list1)
+ self.volume.quotas.get = mock.Mock(return_value=self.quota_list2)
+
+ self.network.get_quota_default = mock.Mock(
+ return_value=self.default_quota)
+ self.compute.quotas.defaults = mock.Mock(
+ return_value=self.default_quota1)
+ self.volume.quotas.defaults = mock.Mock(
+ return_value=self.default_quota2)
+
+ self.cmd = quota.ListQuota(self.app, None)
+
+ def test_quota_list_network(self):
+ arglist = [
+ '--network'
+ ]
+ verifylist = [
+ ('network', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.net_column_header, columns)
+
+ self.assertEqual(self.reference_data, list(data)[0])
+
+ def test_quota_list_compute(self):
+ arglist = [
+ '--compute'
+ ]
+ verifylist = [
+ ('compute', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.comp_column_header, columns)
+
+ self.assertEqual(self.comp_reference_data, list(data)[0])
+
+ def test_quota_list_volume(self):
+ arglist = [
+ '--volume'
+ ]
+ verifylist = [
+ ('volume', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.vol_column_header, columns)
+
+ self.assertEqual(self.vol_reference_data, list(data)[0])
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index 985ce5e2..4a194859 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -168,6 +168,9 @@ class FakeComputev2Client(object):
self.quota_classes = mock.Mock()
self.quota_classes.resource_class = fakes.FakeResource(None, {})
+ self.usage = mock.Mock()
+ self.usage.resource_class = fakes.FakeResource(None, {})
+
self.volumes = mock.Mock()
self.volumes.resource_class = fakes.FakeResource(None, {})
@@ -201,6 +204,9 @@ class FakeComputev2Client(object):
self.server_groups = mock.Mock()
self.server_groups.resource_class = fakes.FakeResource(None, {})
+ self.instance_action = mock.Mock()
+ self.instance_action.resource_class = fakes.FakeResource(None, {})
+
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
@@ -653,6 +659,47 @@ class FakeServer(object):
return mock.Mock(side_effect=servers)
+class FakeServerEvent(object):
+ """Fake one or more server event."""
+
+ @staticmethod
+ def create_one_server_event(attrs=None):
+ """Create a fake server event.
+
+ :param attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id and other attributes
+ """
+ attrs = attrs or {}
+
+ # Set default attributes
+ server_event_info = {
+ "instance_uuid": "server-event-" + uuid.uuid4().hex,
+ "user_id": "user-id-" + uuid.uuid4().hex,
+ "start_time": "2017-02-27T07:47:13.000000",
+ "request_id": "req-" + uuid.uuid4().hex,
+ "action": "create",
+ "message": None,
+ "project_id": "project-id-" + uuid.uuid4().hex,
+ "events": [{
+ "finish_time": "2017-02-27T07:47:25.000000",
+ "start_time": "2017-02-27T07:47:15.000000",
+ "traceback": None,
+ "event": "compute__do_build_and_run_instance",
+ "result": "Success"
+ }]
+ }
+ # Overwrite default attributes
+ server_event_info.update(attrs)
+
+ server_event = fakes.FakeResource(
+ info=copy.deepcopy(server_event_info),
+ loaded=True,
+ )
+ return server_event
+
+
class FakeService(object):
"""Fake one or more services."""
@@ -1248,3 +1295,129 @@ class FakeServerGroup(object):
info=copy.deepcopy(server_group_info),
loaded=True)
return server_group
+
+
+class FakeUsage(object):
+ """Fake one or more usage."""
+
+ @staticmethod
+ def create_one_usage(attrs=None):
+ """Create a fake usage.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with tenant_id and other attributes
+ """
+ if attrs is None:
+ attrs = {}
+
+ # Set default attributes.
+ usage_info = {
+ 'tenant_id': 'usage-tenant-id-' + uuid.uuid4().hex,
+ 'total_memory_mb_usage': 512.0,
+ 'total_vcpus_usage': 1.0,
+ 'total_local_gb_usage': 1.0,
+ 'server_usages': [
+ {
+ 'ended_at': None,
+ 'flavor': 'usage-flavor-' + uuid.uuid4().hex,
+ 'hours': 1.0,
+ 'local_gb': 1,
+ 'memory_mb': 512,
+ 'name': 'usage-name-' + uuid.uuid4().hex,
+ 'state': 'active',
+ 'uptime': 3600,
+ 'vcpus': 1
+ }
+ ]
+ }
+
+ # Overwrite default attributes.
+ usage_info.update(attrs)
+
+ usage = fakes.FakeResource(info=copy.deepcopy(usage_info),
+ loaded=True)
+
+ return usage
+
+ @staticmethod
+ def create_usages(attrs=None, count=2):
+ """Create multiple fake services.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of services to fake
+ :return:
+ A list of FakeResource objects faking the services
+ """
+ usages = []
+ for i in range(0, count):
+ usages.append(FakeUsage.create_one_usage(attrs))
+
+ return usages
+
+
+class FakeQuota(object):
+ """Fake quota"""
+
+ @staticmethod
+ def create_one_comp_quota(attrs=None):
+ """Create one quota"""
+
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'cores': 20,
+ 'fixed_ips': 30,
+ 'injected_files': 100,
+ 'injected_file_content_bytes': 10240,
+ 'injected_file_path_bytes': 255,
+ 'instances': 50,
+ 'key_pairs': 20,
+ 'metadata_items': 10,
+ 'ram': 51200,
+ 'server_groups': 10,
+ 'server_group_members': 10
+ }
+
+ quota_attrs.update(attrs)
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+
+ quota.project_id = quota_attrs['id']
+
+ return quota
+
+ @staticmethod
+ def create_one_default_comp_quota(attrs=None):
+ """Crate one quota"""
+
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'cores': 10,
+ 'fixed_ips': 10,
+ 'injected_files': 100,
+ 'injected_file_content_bytes': 10240,
+ 'injected_file_path_bytes': 255,
+ 'instances': 20,
+ 'key_pairs': 20,
+ 'metadata_items': 10,
+ 'ram': 51200,
+ 'server_groups': 10,
+ 'server_group_members': 10
+ }
+
+ quota_attrs.update(attrs)
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+
+ quota.project_id = quota_attrs['id']
+
+ return quota
diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py
index d53d241e..3c708aae 100644
--- a/openstackclient/tests/unit/compute/v2/test_console.py
+++ b/openstackclient/tests/unit/compute/v2/test_console.py
@@ -35,11 +35,7 @@ class TestConsoleUrlShow(TestConsole):
'protocol': 'fake_protocol',
'type': 'fake_type'}}
methods = {
- 'get_vnc_console': fake_console_data,
- 'get_spice_console': fake_console_data,
- 'get_serial_console': fake_console_data,
- 'get_rdp_console': fake_console_data,
- 'get_mks_console': fake_console_data,
+ 'get_console_url': fake_console_data
}
self.fake_server = compute_fakes.FakeServer.create_one_server(
methods=methods)
@@ -68,7 +64,7 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.fake_server.get_console_url.assert_called_once_with('novnc')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -83,7 +79,7 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.fake_server.get_console_url.assert_called_once_with('novnc')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -98,7 +94,7 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc')
+ self.fake_server.get_console_url.assert_called_once_with('xvpvnc')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -113,14 +109,14 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_spice_console.assert_called_once_with(
+ self.fake_server.get_console_url.assert_called_once_with(
'spice-html5')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
def test_console_url_show_compatible(self):
methods = {
- 'get_vnc_console': {'console': {'url': 'http://localhost',
+ 'get_console_url': {'console': {'url': 'http://localhost',
'type': 'fake_type'}},
}
old_fake_server = compute_fakes.FakeServer.create_one_server(
@@ -130,8 +126,8 @@ class TestConsoleUrlShow(TestConsole):
'url',
)
old_data = (
- methods['get_vnc_console']['console']['type'],
- methods['get_vnc_console']['console']['url']
+ methods['get_console_url']['console']['type'],
+ methods['get_console_url']['console']['url']
)
arglist = [
'foo_vm',
@@ -144,7 +140,7 @@ class TestConsoleUrlShow(TestConsole):
with mock.patch.object(self.servers_mock, 'get',
return_value=old_fake_server):
columns, data = self.cmd.take_action(parsed_args)
- old_fake_server.get_vnc_console.assert_called_once_with('novnc')
+ old_fake_server.get_console_url.assert_called_once_with('novnc')
self.assertEqual(old_columns, columns)
self.assertEqual(old_data, data)
@@ -159,7 +155,7 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_rdp_console.assert_called_once_with(
+ self.fake_server.get_console_url.assert_called_once_with(
'rdp-html5')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -175,7 +171,7 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_serial_console.assert_called_once_with(
+ self.fake_server.get_console_url.assert_called_once_with(
'serial')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -191,6 +187,6 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_mks_console.assert_called_once_with()
+ self.fake_server.get_console_url.assert_called_once_with('webmks')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py
index 93ad9d14..4cdbb25b 100644
--- a/openstackclient/tests/unit/compute/v2/test_flavor.py
+++ b/openstackclient/tests/unit/compute/v2/test_flavor.py
@@ -160,7 +160,7 @@ class TestFlavorCreate(TestFlavor):
self.flavor.is_public = False
arglist = [
- '--id', self.flavor.id,
+ '--id', 'auto',
'--ram', str(self.flavor.ram),
'--disk', str(self.flavor.disk),
'--ephemeral', str(self.flavor.ephemeral),
@@ -174,7 +174,6 @@ class TestFlavorCreate(TestFlavor):
self.flavor.name,
]
verifylist = [
- ('id', self.flavor.id),
('ram', self.flavor.ram),
('disk', self.flavor.disk),
('ephemeral', self.flavor.ephemeral),
@@ -193,7 +192,7 @@ class TestFlavorCreate(TestFlavor):
self.flavor.ram,
self.flavor.vcpus,
self.flavor.disk,
- self.flavor.id,
+ 'auto',
self.flavor.ephemeral,
self.flavor.swap,
self.flavor.rxtx_factor,
@@ -529,6 +528,23 @@ class TestFlavorSet(TestFlavor):
self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'})
self.assertIsNone(result)
+ def test_flavor_set_no_property(self):
+ arglist = [
+ '--no-property',
+ 'baremetal'
+ ]
+ verifylist = [
+ ('no_property', True),
+ ('flavor', 'baremetal')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
+ is_public=None)
+ self.flavor.unset_keys.assert_called_with(['property'])
+ self.assertIsNone(result)
+
def test_flavor_set_project(self):
arglist = [
'--project', self.project.id,
diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py
index e39570af..7200d04e 100644
--- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py
+++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py
@@ -48,19 +48,63 @@ class TestHypervisorList(TestHypervisor):
self.columns = (
"ID",
- "Hypervisor Hostname"
+ "Hypervisor Hostname",
+ "Hypervisor Type",
+ "Host IP",
+ "State"
+ )
+ self.columns_long = (
+ "ID",
+ "Hypervisor Hostname",
+ "Hypervisor Type",
+ "Host IP",
+ "State",
+ "vCPUs Used",
+ "vCPUs",
+ "Memory MB Used",
+ "Memory MB"
)
self.data = (
(
self.hypervisors[0].id,
self.hypervisors[0].hypervisor_hostname,
+ self.hypervisors[0].hypervisor_type,
+ self.hypervisors[0].host_ip,
+ self.hypervisors[0].state
),
(
self.hypervisors[1].id,
self.hypervisors[1].hypervisor_hostname,
+ self.hypervisors[1].hypervisor_type,
+ self.hypervisors[1].host_ip,
+ self.hypervisors[1].state
),
)
+ self.data_long = (
+ (
+ self.hypervisors[0].id,
+ self.hypervisors[0].hypervisor_hostname,
+ self.hypervisors[0].hypervisor_type,
+ self.hypervisors[0].host_ip,
+ self.hypervisors[0].state,
+ self.hypervisors[0].vcpus_used,
+ self.hypervisors[0].vcpus,
+ self.hypervisors[0].memory_mb_used,
+ self.hypervisors[0].memory_mb
+ ),
+ (
+ self.hypervisors[1].id,
+ self.hypervisors[1].hypervisor_hostname,
+ self.hypervisors[1].hypervisor_type,
+ self.hypervisors[1].host_ip,
+ self.hypervisors[1].state,
+ self.hypervisors[1].vcpus_used,
+ self.hypervisors[1].vcpus,
+ self.hypervisors[1].memory_mb_used,
+ self.hypervisors[1].memory_mb
+ ),
+ )
# Get the command object to test
self.cmd = hypervisor.ListHypervisor(self.app, None)
@@ -93,6 +137,9 @@ class TestHypervisorList(TestHypervisor):
(
self.hypervisors[0].id,
self.hypervisors[0].hypervisor_hostname,
+ self.hypervisors[1].hypervisor_type,
+ self.hypervisors[1].host_ip,
+ self.hypervisors[1].state,
),
)
@@ -123,6 +170,24 @@ class TestHypervisorList(TestHypervisor):
self.cmd.take_action,
parsed_args)
+ def test_hypervisor_list_long_option(self):
+ arglist = [
+ '--long',
+ ]
+ verifylist = [
+ ('long', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.hypervisors_mock.list.assert_called_with()
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, tuple(data))
+
class TestHypervisorShow(TestHypervisor):
diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py
index cb008545..d6f5ecf4 100644
--- a/openstackclient/tests/unit/compute/v2/test_keypair.py
+++ b/openstackclient/tests/unit/compute/v2/test_keypair.py
@@ -15,6 +15,7 @@
import mock
from mock import call
+import uuid
from osc_lib import exceptions
from osc_lib import utils
@@ -115,6 +116,36 @@ class TestKeypairCreate(TestKeypair):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_keypair_create_private_key(self):
+ tmp_pk_file = '/tmp/kp-file-' + uuid.uuid4().hex
+ arglist = [
+ '--private-key', tmp_pk_file,
+ self.keypair.name,
+ ]
+ verifylist = [
+ ('private_key', tmp_pk_file),
+ ('name', self.keypair.name)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch('io.open') as mock_open:
+ mock_open.return_value = mock.MagicMock()
+ m_file = mock_open.return_value.__enter__.return_value
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.keypairs_mock.create.assert_called_with(
+ self.keypair.name,
+ public_key=None
+ )
+
+ mock_open.assert_called_once_with(tmp_pk_file, 'w+')
+ m_file.write.assert_called_once_with(self.keypair.private_key)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestKeypairDelete(TestKeypair):
@@ -179,8 +210,7 @@ class TestKeypairDelete(TestKeypair):
self.cmd.take_action(parsed_args)
self.fail('CommandError should be raised.')
except exceptions.CommandError as e:
- self.assertEqual('1 of 2 public keys failed to delete.',
- str(e))
+ self.assertEqual('1 of 2 keys failed to delete.', str(e))
find_mock.assert_any_call(
self.keypairs_mock, self.keypairs[0].name)
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 1081b9a3..7691ef59 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -19,6 +19,7 @@ from mock import call
from osc_lib import exceptions
from osc_lib import utils as common_utils
+from oslo_utils import timeutils
from openstackclient.compute.v2 import server
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -36,10 +37,6 @@ class TestServer(compute_fakes.TestComputev2):
self.servers_mock = self.app.client_manager.compute.servers
self.servers_mock.reset_mock()
- # Get a shortcut to the compute client ImageManager Mock
- self.cimages_mock = self.app.client_manager.compute.images
- self.cimages_mock.reset_mock()
-
# Get a shortcut to the compute client FlavorManager Mock
self.flavors_mock = self.app.client_manager.compute.flavors
self.flavors_mock.reset_mock()
@@ -149,24 +146,33 @@ class TestServerAddFloatingIP(TestServer):
'add_floating_ip': None,
}
- def test_server_add_floating_ip(self):
+ def _test_server_add_floating_ip(self, extralist, fixed_ip_address):
servers = self.setup_servers_mock(count=1)
arglist = [
servers[0].id,
'1.2.3.4',
- ]
+ ] + extralist
verifylist = [
('server', servers[0].id),
('ip_address', '1.2.3.4'),
+ ('fixed_ip_address', fixed_ip_address),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- servers[0].add_floating_ip.assert_called_once_with('1.2.3.4')
+ servers[0].add_floating_ip.assert_called_once_with('1.2.3.4',
+ fixed_ip_address)
self.assertIsNone(result)
+ def test_server_add_floating_ip(self):
+ self._test_server_add_floating_ip([], None)
+
+ def test_server_add_floating_ip_to_fixed_ip(self):
+ extralist = ['--fixed-ip-address', '5.6.7.8']
+ self._test_server_add_floating_ip(extralist, '5.6.7.8')
+
class TestServerAddSecurityGroup(TestServer):
@@ -259,7 +265,7 @@ class TestServerCreate(TestServer):
self.servers_mock.create.return_value = self.new_server
self.image = image_fakes.FakeImage.create_one_image()
- self.cimages_mock.get.return_value = self.image
+ self.images_mock.get.return_value = self.image
self.flavor = compute_fakes.FakeFlavor.create_one_flavor()
self.flavors_mock.get.return_value = self.flavor
@@ -312,7 +318,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
- block_device_mapping={},
+ block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
config_drive=None,
@@ -328,6 +334,61 @@ class TestServerCreate(TestServer):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
+ def test_server_create_with_options(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--key-name', 'keyname',
+ '--property', 'Beta=b',
+ '--security-group', 'securitygroup',
+ '--hint', 'a=b',
+ '--hint', 'a=c',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('key_name', 'keyname'),
+ ('property', {'Beta': 'b'}),
+ ('security_group', ['securitygroup']),
+ ('hint', ['a=b', 'a=c']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = dict(
+ meta={'Beta': 'b'},
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=['securitygroup'],
+ userdata=None,
+ key_name='keyname',
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics=[],
+ scheduler_hints={'a': ['b', 'c']},
+ config_drive=None,
+ )
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
def test_server_create_with_network(self):
arglist = [
'--image', 'image1',
@@ -392,7 +453,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
- block_device_mapping={},
+ block_device_mapping_v2=[],
nics=[{'net-id': 'net1_uuid',
'v4-fixed-ip': '',
'v6-fixed-ip': '',
@@ -415,6 +476,248 @@ class TestServerCreate(TestServer):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
+ def test_server_create_with_auto_network(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--nic', 'auto',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('nic', ['auto']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = dict(
+ meta=None,
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics='auto',
+ scheduler_hints={},
+ config_drive=None,
+ )
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
+ def test_server_create_with_none_network(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('nic', ['none']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = dict(
+ meta=None,
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics='none',
+ scheduler_hints={},
+ config_drive=None,
+ )
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
+ def test_server_create_with_conflict_network_options(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ '--nic', 'auto',
+ '--nic', 'port-id=port1',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('nic', ['none', 'auto', 'port-id=port1']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ get_endpoints = mock.Mock()
+ get_endpoints.return_value = {'network': []}
+ self.app.client_manager.auth_ref = mock.Mock()
+ self.app.client_manager.auth_ref.service_catalog = mock.Mock()
+ self.app.client_manager.auth_ref.service_catalog.get_endpoints = (
+ get_endpoints)
+
+ find_port = mock.Mock()
+ network_client = self.app.client_manager.network
+ network_client.find_port = find_port
+ port_resource = mock.Mock()
+ port_resource.id = 'port1_uuid'
+ find_port.return_value = port_resource
+
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+ self.assertNotCalled(self.servers_mock.create)
+
+ def test_server_create_with_invalid_network_options(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--nic', 'abcdefgh',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('nic', ['abcdefgh']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+ self.assertNotCalled(self.servers_mock.create)
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
+ def test_server_create_with_wait_ok(self, mock_wait_for_status):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--wait',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('config_drive', False),
+ ('wait', True),
+ ('server_name', self.new_server.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ mock_wait_for_status.assert_called_once_with(
+ self.servers_mock.get,
+ self.new_server.id,
+ callback=server._show_progress,
+ )
+
+ kwargs = dict(
+ meta=None,
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics=[],
+ scheduler_hints={},
+ config_drive=None,
+ )
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=False)
+ def test_server_create_with_wait_fails(self, mock_wait_for_status):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--wait',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('config_drive', False),
+ ('wait', True),
+ ('server_name', self.new_server.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(SystemExit, self.cmd.take_action, parsed_args)
+
+ mock_wait_for_status.assert_called_once_with(
+ self.servers_mock.get,
+ self.new_server.id,
+ callback=server._show_progress,
+ )
+
+ kwargs = dict(
+ meta=None,
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics=[],
+ scheduler_hints={},
+ config_drive=None,
+ )
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+
@mock.patch('openstackclient.compute.v2.server.io.open')
def test_server_create_userdata(self, mock_open):
mock_file = mock.Mock(name='File')
@@ -458,7 +761,7 @@ class TestServerCreate(TestServer):
userdata=mock_file,
key_name=None,
availability_zone=None,
- block_device_mapping={},
+ block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
config_drive=None,
@@ -509,9 +812,14 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
- block_device_mapping={
- 'vda': real_volume_mapping
- },
+ block_device_mapping_v2=[{
+ 'device_name': 'vda',
+ 'uuid': real_volume_mapping.split(':', 1)[0],
+ 'destination_type': 'volume',
+ 'source_type': 'volume',
+ 'delete_on_termination': '0',
+ 'volume_size': ''
+ }],
nics=[],
scheduler_hints={},
config_drive=None,
@@ -658,6 +966,8 @@ class TestServerList(TestServer):
'Networks',
'Image Name',
'Image ID',
+ 'Flavor Name',
+ 'Flavor ID',
'Availability Zone',
'Host',
'Properties',
@@ -679,6 +989,8 @@ class TestServerList(TestServer):
'tenant_id': None,
'all_tenants': False,
'user_id': None,
+ 'deleted': False,
+ 'changes_since': None,
}
# Default params of the core function of the command in the case of no
@@ -708,7 +1020,7 @@ class TestServerList(TestServer):
self.servers_mock.list.return_value = self.servers
self.image = image_fakes.FakeImage.create_one_image()
- self.cimages_mock.get.return_value = self.image
+ self.images_mock.get.return_value = self.image
self.flavor = compute_fakes.FakeFlavor.create_one_flavor()
self.flavors_mock.get.return_value = self.flavor
@@ -726,6 +1038,12 @@ class TestServerList(TestServer):
for s in self.servers
]
+ Flavor = collections.namedtuple('Flavor', 'id name')
+ self.flavors_mock.list.return_value = [
+ Flavor(id=s.flavor['id'], name=self.flavor.name)
+ for s in self.servers
+ ]
+
for s in self.servers:
self.data.append((
s.id,
@@ -745,6 +1063,8 @@ class TestServerList(TestServer):
server._format_servers_list_networks(s.networks),
self.image.name,
s.image['id'],
+ self.flavor.name,
+ s.flavor['id'],
getattr(s, 'OS-EXT-AZ:availability_zone'),
getattr(s, 'OS-EXT-SRV-ATTR:host'),
s.Metadata,
@@ -755,6 +1075,7 @@ class TestServerList(TestServer):
verifylist = [
('all_projects', False),
('long', False),
+ ('deleted', False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -792,7 +1113,7 @@ class TestServerList(TestServer):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.cimages_mock.get.assert_any_call(self.image.id)
+ self.images_mock.get.assert_any_call(self.image.id)
self.search_opts['image'] = self.image.id
self.servers_mock.list.assert_called_with(**self.kwargs)
@@ -820,6 +1141,48 @@ class TestServerList(TestServer):
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
+ def test_server_list_with_changes_since(self):
+
+ arglist = [
+ '--changes-since', '2016-03-04T06:27:59Z',
+ '--deleted'
+ ]
+ verifylist = [
+ ('changes_since', '2016-03-04T06:27:59Z'),
+ ('deleted', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.search_opts['changes_since'] = '2016-03-04T06:27:59Z'
+ self.search_opts['deleted'] = True
+ self.servers_mock.list.assert_called_with(**self.kwargs)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError)
+ def test_server_list_with_invalid_changes_since(self, mock_parse_isotime):
+
+ arglist = [
+ '--changes-since', 'Invalid time value',
+ ]
+ verifylist = [
+ ('changes_since', 'Invalid time value'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('Invalid changes-since value: Invalid time '
+ 'value', str(e))
+ mock_parse_isotime.assert_called_once_with(
+ 'Invalid time value'
+ )
+
class TestServerLock(TestServer):
@@ -841,6 +1204,209 @@ class TestServerLock(TestServer):
self.run_method_with_servers('lock', 3)
+class TestServerMigrate(TestServer):
+
+ def setUp(self):
+ super(TestServerMigrate, self).setUp()
+
+ methods = {
+ 'migrate': None,
+ 'live_migrate': None,
+ }
+ self.server = compute_fakes.FakeServer.create_one_server(
+ methods=methods)
+
+ # This is the return value for utils.find_resource()
+ self.servers_mock.get.return_value = self.server
+
+ self.servers_mock.migrate.return_value = None
+ self.servers_mock.live_migrate.return_value = None
+
+ # Get the command object to test
+ self.cmd = server.MigrateServer(self.app, None)
+
+ def test_server_migrate_no_options(self):
+ arglist = [
+ self.server.id,
+ ]
+ verifylist = [
+ ('live', None),
+ ('block_migration', False),
+ ('disk_overcommit', False),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.migrate.assert_called_with()
+ self.assertNotCalled(self.servers_mock.live_migrate)
+ self.assertIsNone(result)
+
+ def test_server_migrate_with_block_migration(self):
+ arglist = [
+ '--block-migration', self.server.id,
+ ]
+ verifylist = [
+ ('live', None),
+ ('block_migration', True),
+ ('disk_overcommit', False),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.assertNotCalled(self.servers_mock.live_migrate)
+ self.assertNotCalled(self.servers_mock.migrate)
+
+ def test_server_migrate_with_disk_overcommit(self):
+ arglist = [
+ '--disk-overcommit', self.server.id,
+ ]
+ verifylist = [
+ ('live', None),
+ ('block_migration', False),
+ ('disk_overcommit', True),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.assertNotCalled(self.servers_mock.live_migrate)
+ self.assertNotCalled(self.servers_mock.migrate)
+
+ def test_server_live_migrate(self):
+ arglist = [
+ '--live', 'fakehost', self.server.id,
+ ]
+ verifylist = [
+ ('live', 'fakehost'),
+ ('block_migration', False),
+ ('disk_overcommit', False),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.live_migrate.assert_called_with(block_migration=False,
+ disk_over_commit=False,
+ host='fakehost')
+ self.assertNotCalled(self.servers_mock.migrate)
+ self.assertIsNone(result)
+
+ def test_server_block_live_migrate(self):
+ arglist = [
+ '--live', 'fakehost', '--block-migration', self.server.id,
+ ]
+ verifylist = [
+ ('live', 'fakehost'),
+ ('block_migration', True),
+ ('disk_overcommit', False),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.live_migrate.assert_called_with(block_migration=True,
+ disk_over_commit=False,
+ host='fakehost')
+ self.assertNotCalled(self.servers_mock.migrate)
+ self.assertIsNone(result)
+
+ def test_server_live_migrate_with_disk_overcommit(self):
+ arglist = [
+ '--live', 'fakehost', '--disk-overcommit', self.server.id,
+ ]
+ verifylist = [
+ ('live', 'fakehost'),
+ ('block_migration', False),
+ ('disk_overcommit', True),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.live_migrate.assert_called_with(block_migration=False,
+ disk_over_commit=True,
+ host='fakehost')
+ self.assertNotCalled(self.servers_mock.migrate)
+ self.assertIsNone(result)
+
+ def test_server_live_migrate_with_false_value_options(self):
+ arglist = [
+ '--live', 'fakehost', '--no-disk-overcommit',
+ '--shared-migration', self.server.id,
+ ]
+ verifylist = [
+ ('live', 'fakehost'),
+ ('block_migration', False),
+ ('disk_overcommit', False),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.live_migrate.assert_called_with(block_migration=False,
+ disk_over_commit=False,
+ host='fakehost')
+ self.assertNotCalled(self.servers_mock.migrate)
+ self.assertIsNone(result)
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
+ def test_server_migrate_with_wait(self, mock_wait_for_status):
+ arglist = [
+ '--wait', self.server.id,
+ ]
+ verifylist = [
+ ('live', None),
+ ('block_migration', False),
+ ('disk_overcommit', False),
+ ('wait', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.migrate.assert_called_with()
+ self.assertNotCalled(self.servers_mock.live_migrate)
+ self.assertIsNone(result)
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=False)
+ def test_server_migrate_with_wait_fails(self, mock_wait_for_status):
+ arglist = [
+ '--wait', self.server.id,
+ ]
+ verifylist = [
+ ('live', None),
+ ('block_migration', False),
+ ('disk_overcommit', False),
+ ('wait', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(SystemExit, self.cmd.take_action, parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.migrate.assert_called_with()
+ self.assertNotCalled(self.servers_mock.live_migrate)
+
+
class TestServerPause(TestServer):
def setUp(self):
@@ -868,7 +1434,7 @@ class TestServerRebuild(TestServer):
# Return value for utils.find_resource for image
self.image = image_fakes.FakeImage.create_one_image()
- self.cimages_mock.get.return_value = self.image
+ self.images_mock.get.return_value = self.image
# Fake the rebuilt new server.
new_server = compute_fakes.FakeServer.create_one_server()
@@ -908,7 +1474,7 @@ class TestServerRebuild(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(self.server.id)
- self.cimages_mock.get.assert_called_with(self.image.id)
+ self.images_mock.get.assert_called_with(self.image.id)
self.server.rebuild.assert_called_with(self.image, None)
def test_rebuild_with_current_image_and_password(self):
@@ -927,7 +1493,7 @@ class TestServerRebuild(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(self.server.id)
- self.cimages_mock.get.assert_called_with(self.image.id)
+ self.images_mock.get.assert_called_with(self.image.id)
self.server.rebuild.assert_called_with(self.image, password)
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
@@ -955,7 +1521,7 @@ class TestServerRebuild(TestServer):
)
self.servers_mock.get.assert_called_with(self.server.id)
- self.cimages_mock.get.assert_called_with(self.image.id)
+ self.images_mock.get.assert_called_with(self.image.id)
self.server.rebuild.assert_called_with(self.image, None)
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
@@ -979,7 +1545,7 @@ class TestServerRebuild(TestServer):
)
self.servers_mock.get.assert_called_with(self.server.id)
- self.cimages_mock.get.assert_called_with(self.image.id)
+ self.images_mock.get.assert_called_with(self.image.id)
self.server.rebuild.assert_called_with(self.image, None)
@@ -1477,7 +2043,7 @@ class TestServerShow(TestServer):
# This is the return value for utils.find_resource()
self.servers_mock.get.return_value = self.server
- self.cimages_mock.get.return_value = self.image
+ self.images_mock.get.return_value = self.image
self.flavors_mock.get.return_value = self.flavor
# Get the command object to test
@@ -1835,6 +2401,7 @@ class TestServerGeneral(TestServer):
# Call _prep_server_detail().
server_detail = server._prep_server_detail(
self.app.client_manager.compute,
+ self.app.client_manager.image,
_server
)
# 'networks' is used to create _server. Remove it.
diff --git a/openstackclient/tests/unit/compute/v2/test_server_event.py b/openstackclient/tests/unit/compute/v2/test_server_event.py
new file mode 100644
index 00000000..5c94891a
--- /dev/null
+++ b/openstackclient/tests/unit/compute/v2/test_server_event.py
@@ -0,0 +1,167 @@
+# Copyright 2017 Huawei, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from openstackclient.compute.v2 import server_event
+from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+
+
+class TestServerEvent(compute_fakes.TestComputev2):
+
+ fake_server = compute_fakes.FakeServer.create_one_server()
+
+ def setUp(self):
+ super(TestServerEvent, self).setUp()
+
+ self.servers_mock = self.app.client_manager.compute.servers
+ self.servers_mock.reset_mock()
+ self.events_mock = self.app.client_manager.compute.instance_action
+ self.events_mock.reset_mock()
+
+ self.servers_mock.get.return_value = self.fake_server
+
+
+class TestListServerEvent(TestServerEvent):
+
+ fake_event = compute_fakes.FakeServerEvent.create_one_server_event()
+
+ columns = (
+ 'Request ID',
+ 'Server ID',
+ 'Action',
+ 'Start Time',
+ )
+ data = ((
+ fake_event.request_id,
+ fake_event.instance_uuid,
+ fake_event.action,
+ fake_event.start_time,
+ ), )
+
+ long_columns = (
+ 'Request ID',
+ 'Server ID',
+ 'Action',
+ 'Start Time',
+ 'Message',
+ 'Project ID',
+ 'User ID',
+ )
+ long_data = ((
+ fake_event.request_id,
+ fake_event.instance_uuid,
+ fake_event.action,
+ fake_event.start_time,
+ fake_event.message,
+ fake_event.project_id,
+ fake_event.user_id,
+ ), )
+
+ def setUp(self):
+ super(TestListServerEvent, self).setUp()
+
+ self.events_mock.list.return_value = [self.fake_event, ]
+ self.cmd = server_event.ListServerEvent(self.app, None)
+
+ def test_server_event_list(self):
+ arglist = [
+ self.fake_server.name,
+ ]
+ verifylist = [
+ ('server', self.fake_server.name),
+ ('long', False),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_once_with(self.fake_server.name)
+ self.events_mock.list.assert_called_once_with(self.fake_server.id)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, tuple(data))
+
+ def test_server_event_list_long(self):
+ arglist = [
+ '--long',
+ self.fake_server.name,
+ ]
+ verifylist = [
+ ('server', self.fake_server.name),
+ ('long', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_once_with(self.fake_server.name)
+ self.events_mock.list.assert_called_once_with(self.fake_server.id)
+
+ self.assertEqual(self.long_columns, columns)
+ self.assertEqual(self.long_data, tuple(data))
+
+
+class TestShowServerEvent(TestServerEvent):
+
+ fake_event = compute_fakes.FakeServerEvent.create_one_server_event()
+
+ columns = (
+ 'action',
+ 'events',
+ 'instance_uuid',
+ 'message',
+ 'project_id',
+ 'request_id',
+ 'start_time',
+ 'user_id',
+ )
+ data = (
+ fake_event.action,
+ fake_event.events,
+ fake_event.instance_uuid,
+ fake_event.message,
+ fake_event.project_id,
+ fake_event.request_id,
+ fake_event.start_time,
+ fake_event.user_id,
+ )
+
+ def setUp(self):
+ super(TestShowServerEvent, self).setUp()
+
+ self.events_mock.get.return_value = self.fake_event
+ self.cmd = server_event.ShowServerEvent(self.app, None)
+
+ def test_server_event_show(self):
+ arglist = [
+ self.fake_server.name,
+ self.fake_event.request_id,
+ ]
+ verifylist = [
+ ('server', self.fake_server.name),
+ ('request_id', self.fake_event.request_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_once_with(self.fake_server.name)
+ self.events_mock.get.assert_called_once_with(
+ self.fake_server.id, self.fake_event.request_id)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py
index d474f41d..088497da 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_group.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_group.py
@@ -63,54 +63,23 @@ class TestServerGroupCreate(TestServerGroup):
def test_server_group_create(self):
arglist = [
- '--policy', 'affinity',
+ '--policy', 'anti-affinity',
'affinity_group',
]
verifylist = [
- ('policy', ['affinity']),
+ ('policy', 'anti-affinity'),
('name', 'affinity_group'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.server_groups_mock.create.assert_called_once_with(
name=parsed_args.name,
- policies=parsed_args.policy,
+ policies=[parsed_args.policy],
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
- def test_server_group_create_with_multiple_policies(self):
- arglist = [
- '--policy', 'affinity',
- '--policy', 'soft-affinity',
- 'affinity_group',
- ]
- verifylist = [
- ('policy', ['affinity', 'soft-affinity']),
- ('name', 'affinity_group'),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
- self.server_groups_mock.create.assert_called_once_with(
- name=parsed_args.name,
- policies=parsed_args.policy,
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.data, data)
-
- def test_server_group_create_no_policy(self):
- arglist = [
- 'affinity_group',
- ]
- verifylist = None
- self.assertRaises(tests_utils.ParserException,
- self.check_parser,
- self.cmd,
- arglist,
- verifylist)
-
class TestServerGroupDelete(TestServerGroup):
diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py
new file mode 100644
index 00000000..a383e903
--- /dev/null
+++ b/openstackclient/tests/unit/compute/v2/test_usage.py
@@ -0,0 +1,179 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import datetime
+import mock
+
+from openstackclient.compute.v2 import usage
+from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
+
+
+class TestUsage(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestUsage, self).setUp()
+
+ self.usage_mock = self.app.client_manager.compute.usage
+ self.usage_mock.reset_mock()
+
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+
+
+class TestUsageList(TestUsage):
+
+ project = identity_fakes.FakeProject.create_one_project()
+ # Return value of self.usage_mock.list().
+ usages = compute_fakes.FakeUsage.create_usages(
+ attrs={'tenant_id': project.name}, count=1)
+
+ columns = (
+ "Project",
+ "Servers",
+ "RAM MB-Hours",
+ "CPU Hours",
+ "Disk GB-Hours"
+ )
+
+ data = [(
+ usages[0].tenant_id,
+ len(usages[0].server_usages),
+ float("%.2f" % usages[0].total_memory_mb_usage),
+ float("%.2f" % usages[0].total_vcpus_usage),
+ float("%.2f" % usages[0].total_local_gb_usage),
+ )]
+
+ def setUp(self):
+ super(TestUsageList, self).setUp()
+
+ self.usage_mock.list.return_value = self.usages
+
+ self.projects_mock.list.return_value = [self.project]
+ # Get the command object to test
+ self.cmd = usage.ListUsage(self.app, None)
+
+ def test_usage_list_no_options(self):
+
+ arglist = []
+ verifylist = [
+ ('start', None),
+ ('end', None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.projects_mock.list.assert_called_with()
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ def test_usage_list_with_options(self):
+ arglist = [
+ '--start', '2016-11-11',
+ '--end', '2016-12-20',
+ ]
+ verifylist = [
+ ('start', '2016-11-11'),
+ ('end', '2016-12-20'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.projects_mock.list.assert_called_with()
+ self.usage_mock.list.assert_called_with(
+ datetime.datetime(2016, 11, 11, 0, 0),
+ datetime.datetime(2016, 12, 20, 0, 0),
+ detailed=True)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+
+class TestUsageShow(TestUsage):
+
+ project = identity_fakes.FakeProject.create_one_project()
+ # Return value of self.usage_mock.list().
+ usage = compute_fakes.FakeUsage.create_one_usage(
+ attrs={'tenant_id': project.name})
+
+ columns = (
+ 'CPU Hours',
+ 'Disk GB-Hours',
+ 'RAM MB-Hours',
+ 'Servers',
+ )
+
+ data = (
+ float("%.2f" % usage.total_vcpus_usage),
+ float("%.2f" % usage.total_local_gb_usage),
+ float("%.2f" % usage.total_memory_mb_usage),
+ len(usage.server_usages),
+ )
+
+ def setUp(self):
+ super(TestUsageShow, self).setUp()
+
+ self.usage_mock.get.return_value = self.usage
+
+ self.projects_mock.get.return_value = self.project
+ # Get the command object to test
+ self.cmd = usage.ShowUsage(self.app, None)
+
+ def test_usage_show_no_options(self):
+
+ self.app.client_manager.auth_ref = mock.Mock()
+ self.app.client_manager.auth_ref.project_id = self.project.id
+
+ arglist = []
+ verifylist = [
+ ('project', None),
+ ('start', None),
+ ('end', None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_usage_show_with_options(self):
+
+ arglist = [
+ '--project', self.project.id,
+ '--start', '2016-11-11',
+ '--end', '2016-12-20',
+ ]
+ verifylist = [
+ ('project', self.project.id),
+ ('start', '2016-11-11'),
+ ('end', '2016-12-20'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.usage_mock.get.assert_called_with(
+ self.project.id,
+ datetime.datetime(2016, 11, 11, 0, 0),
+ datetime.datetime(2016, 12, 20, 0, 0))
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py
index f7cb5676..f28f9103 100644
--- a/openstackclient/tests/unit/fakes.py
+++ b/openstackclient/tests/unit/fakes.py
@@ -161,6 +161,9 @@ class FakeModule(object):
def __init__(self, name, version):
self.name = name
self.__version__ = version
+ # Workaround for openstacksdk case
+ self.version = mock.Mock()
+ self.version.__version__ = version
class FakeResource(object):
@@ -212,10 +215,19 @@ class FakeResource(object):
def keys(self):
return self._info.keys()
+ def to_dict(self):
+ return self._info
+
@property
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..c726f2a6 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
@@ -23,6 +26,7 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
class TestProject(identity_fakes.TestIdentityv2):
fake_project = identity_fakes.FakeProject.create_one_project()
+ fake_projects = identity_fakes.FakeProject.create_projects()
columns = (
'description',
@@ -36,6 +40,12 @@ class TestProject(identity_fakes.TestIdentityv2):
fake_project.id,
fake_project.name,
)
+ datalists = (
+ (fake_projects[0].description, True,
+ fake_projects[0].id, fake_projects[0].name,),
+ (fake_projects[1].description, True,
+ fake_projects[1].id, fake_projects[1].name,),
+ )
def setUp(self):
super(TestProject, self).setUp()
@@ -302,6 +312,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):
@@ -357,6 +393,35 @@ class TestProjectList(TestProject):
), )
self.assertEqual(datalist, tuple(data))
+ def test_project_list_sort(self):
+ self.projects_mock.list.return_value = self.fake_projects
+
+ arglist = ['--sort', 'name:asc', ]
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ (columns, data) = self.cmd.take_action(parsed_args)
+ self.projects_mock.list.assert_called_with()
+
+ collist = ('ID', 'Name')
+ self.assertEqual(collist, columns)
+
+ if self.fake_projects[0].name > self.fake_projects[1].name:
+ datalists = (
+ (self.fake_projects[1].id, self.fake_projects[1].name),
+ (self.fake_projects[0].id, self.fake_projects[0].name),
+ )
+ else:
+ datalists = (
+ (self.fake_projects[0].id, self.fake_projects[0].name),
+ (self.fake_projects[1].id, self.fake_projects[1].name),
+ )
+
+ self.assertEqual(datalists, tuple(data))
+
class TestProjectSet(TestProject):
diff --git a/openstackclient/tests/unit/identity/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/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py
index 75065e65..139d90d5 100644
--- a/openstackclient/tests/unit/identity/v3/fakes.py
+++ b/openstackclient/tests/unit/identity/v3/fakes.py
@@ -622,6 +622,23 @@ class FakeProject(object):
loaded=True)
return project
+ @staticmethod
+ def create_projects(attrs=None, count=2):
+ """Create multiple fake projects.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of projects to fake
+ :return:
+ A list of FakeResource objects faking the projects
+ """
+
+ projects = []
+ for i in range(0, count):
+ projects.append(FakeProject.create_one_project(attrs))
+ return projects
+
class FakeDomain(object):
"""Fake one or more domain."""
@@ -753,6 +770,44 @@ class FakeUser(object):
loaded=True)
return user
+ @staticmethod
+ def create_users(attrs=None, count=2):
+ """Create multiple fake users.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of users to fake
+ :return:
+ A list of FakeResource objects faking the users
+ """
+ users = []
+ for i in range(0, count):
+ user = FakeUser.create_one_user(attrs)
+ users.append(user)
+
+ return users
+
+ @staticmethod
+ def get_users(users=None, count=2):
+ """Get an iterable MagicMock object with a list of faked users.
+
+ If users list is provided, then initialize the Mock object with
+ the list. Otherwise create one.
+
+ :param List users:
+ A list of FakeResource objects faking users
+ :param Integer count:
+ The number of users to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ users
+ """
+ if users is None:
+ users = FakeUser.create_users(count)
+
+ return mock.Mock(side_effect=users)
+
class FakeGroup(object):
"""Fake one or more group."""
diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py
index eb50adb5..81722631 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
@@ -41,34 +42,79 @@ class TestGroup(identity_fakes.TestIdentityv3):
class TestGroupAddUser(TestGroup):
- group = identity_fakes.FakeGroup.create_one_group()
- user = identity_fakes.FakeUser.create_one_user()
+ _group = identity_fakes.FakeGroup.create_one_group()
+ users = identity_fakes.FakeUser.create_users(count=2)
def setUp(self):
super(TestGroupAddUser, self).setUp()
- self.groups_mock.get.return_value = self.group
- self.users_mock.get.return_value = self.user
+ self.groups_mock.get.return_value = self._group
+ self.users_mock.get = (
+ identity_fakes.FakeUser.get_users(self.users))
self.users_mock.add_to_group.return_value = None
self.cmd = group.AddUserToGroup(self.app, None)
def test_group_add_user(self):
arglist = [
- self.group.name,
- self.user.name,
+ self._group.name,
+ self.users[0].name,
]
verifylist = [
- ('group', self.group.name),
- ('user', self.user.name),
+ ('group', self._group.name),
+ ('user', [self.users[0].name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.users_mock.add_to_group.assert_called_once_with(
- self.user.id, self.group.id)
+ self.users[0].id, self._group.id)
+ self.assertIsNone(result)
+
+ def test_group_add_multi_users(self):
+ arglist = [
+ self._group.name,
+ self.users[0].name,
+ self.users[1].name,
+ ]
+ verifylist = [
+ ('group', self._group.name),
+ ('user', [self.users[0].name, self.users[1].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ calls = [call(self.users[0].id, self._group.id),
+ call(self.users[1].id, self._group.id)]
+ self.users_mock.add_to_group.assert_has_calls(calls)
self.assertIsNone(result)
+ @mock.patch.object(group.LOG, 'error')
+ def test_group_add_user_with_error(self, mock_error):
+ self.users_mock.add_to_group.side_effect = [
+ exceptions.CommandError(), None]
+ arglist = [
+ self._group.name,
+ self.users[0].name,
+ self.users[1].name,
+ ]
+ verifylist = [
+ ('group', self._group.name),
+ ('user', [self.users[0].name, self.users[1].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ msg = "1 of 2 users not added to group %s." % self._group.name
+ self.assertEqual(msg, str(e))
+ msg = ("%(user)s not added to group %(group)s: ") % {
+ 'user': self.users[0].name,
+ 'group': self._group.name,
+ }
+ mock_error.assert_called_once_with(msg)
+
class TestGroupCheckUser(TestGroup):
@@ -100,6 +146,23 @@ class TestGroupCheckUser(TestGroup):
self.user.id, self.group.id)
self.assertIsNone(result)
+ def test_group_check_user_server_error(self):
+ def server_error(*args):
+ raise ks_exc.http.InternalServerError
+ self.users_mock.check_in_group.side_effect = server_error
+ arglist = [
+ self.group.name,
+ self.user.name,
+ ]
+ verifylist = [
+ ('group', self.group.name),
+ ('user', self.user.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(ks_exc.http.InternalServerError,
+ self.cmd.take_action, parsed_args)
+
class TestGroupCreate(TestGroup):
@@ -257,6 +320,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):
@@ -405,34 +494,79 @@ class TestGroupList(TestGroup):
class TestGroupRemoveUser(TestGroup):
- group = identity_fakes.FakeGroup.create_one_group()
- user = identity_fakes.FakeUser.create_one_user()
+ _group = identity_fakes.FakeGroup.create_one_group()
+ users = identity_fakes.FakeUser.create_users(count=2)
def setUp(self):
super(TestGroupRemoveUser, self).setUp()
- self.groups_mock.get.return_value = self.group
- self.users_mock.get.return_value = self.user
+ self.groups_mock.get.return_value = self._group
+ self.users_mock.get = (
+ identity_fakes.FakeUser.get_users(self.users))
self.users_mock.remove_from_group.return_value = None
self.cmd = group.RemoveUserFromGroup(self.app, None)
def test_group_remove_user(self):
arglist = [
- self.group.id,
- self.user.id,
+ self._group.id,
+ self.users[0].id,
]
verifylist = [
- ('group', self.group.id),
- ('user', self.user.id),
+ ('group', self._group.id),
+ ('user', [self.users[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.users_mock.remove_from_group.assert_called_once_with(
- self.user.id, self.group.id)
+ self.users[0].id, self._group.id)
self.assertIsNone(result)
+ def test_group_remove_multi_users(self):
+ arglist = [
+ self._group.name,
+ self.users[0].name,
+ self.users[1].name,
+ ]
+ verifylist = [
+ ('group', self._group.name),
+ ('user', [self.users[0].name, self.users[1].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ calls = [call(self.users[0].id, self._group.id),
+ call(self.users[1].id, self._group.id)]
+ self.users_mock.remove_from_group.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ @mock.patch.object(group.LOG, 'error')
+ def test_group_remove_user_with_error(self, mock_error):
+ self.users_mock.remove_from_group.side_effect = [
+ exceptions.CommandError(), None]
+ arglist = [
+ self._group.id,
+ self.users[0].id,
+ self.users[1].id,
+ ]
+ verifylist = [
+ ('group', self._group.id),
+ ('user', [self.users[0].id, self.users[1].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ msg = "1 of 2 users not removed from group %s." % self._group.id
+ self.assertEqual(msg, str(e))
+ msg = ("%(user)s not removed from group %(group)s: ") % {
+ 'user': self.users[0].id,
+ 'group': self._group.id,
+ }
+ mock_error.assert_called_once_with(msg)
+
class TestGroupSet(TestGroup):
diff --git a/openstackclient/tests/unit/identity/v3/test_mappings.py b/openstackclient/tests/unit/identity/v3/test_mappings.py
index 5086724c..93fe1196 100644
--- a/openstackclient/tests/unit/identity/v3/test_mappings.py
+++ b/openstackclient/tests/unit/identity/v3/test_mappings.py
@@ -181,16 +181,12 @@ class TestMappingSet(TestMapping):
mocker.return_value = identity_fakes.MAPPING_RULES_2
with mock.patch("openstackclient.identity.v3.mapping."
"SetMapping._read_rules", mocker):
- columns, data = self.cmd.take_action(parsed_args)
+ result = self.cmd.take_action(parsed_args)
self.mapping_mock.update.assert_called_with(
mapping=identity_fakes.mapping_id,
rules=identity_fakes.MAPPING_RULES_2)
- collist = ('id', 'rules')
- self.assertEqual(collist, columns)
- datalist = (identity_fakes.mapping_id,
- identity_fakes.MAPPING_RULES_2)
- self.assertEqual(datalist, data)
+ self.assertIsNone(result)
def test_set_rules_wrong_file_path(self):
arglist = [
diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py
index 702d9209..7be81153 100644
--- a/openstackclient/tests/unit/identity/v3/test_project.py
+++ b/openstackclient/tests/unit/identity/v3/test_project.py
@@ -14,8 +14,10 @@
#
import mock
+from mock import call
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,12 +447,39 @@ 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):
domain = identity_fakes.FakeDomain.create_one_domain()
project = identity_fakes.FakeProject.create_one_project(
attrs={'domain_id': domain.id})
+ projects = identity_fakes.FakeProject.create_projects()
columns = (
'ID',
@@ -462,6 +491,12 @@ class TestProjectList(TestProject):
project.name,
),
)
+ datalists = (
+ (projects[0].description, True,
+ projects[0].id, projects[0].name,),
+ (projects[1].description, True,
+ projects[1].id, projects[1].name,),
+ )
def setUp(self):
super(TestProjectList, self).setUp()
@@ -552,6 +587,66 @@ class TestProjectList(TestProject):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
+ def test_project_list_sort(self):
+ self.projects_mock.list.return_value = self.projects
+
+ arglist = ['--sort', 'name:asc', ]
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ (columns, data) = self.cmd.take_action(parsed_args)
+ self.projects_mock.list.assert_called_with()
+
+ collist = ('ID', 'Name')
+ self.assertEqual(collist, columns)
+
+ if self.projects[0].name > self.projects[1].name:
+ datalists = (
+ (self.projects[1].id, self.projects[1].name),
+ (self.projects[0].id, self.projects[0].name),
+ )
+ else:
+ datalists = (
+ (self.projects[0].id, self.projects[0].name),
+ (self.projects[1].id, self.projects[1].name),
+ )
+
+ self.assertEqual(datalists, tuple(data))
+
+ def test_project_list_my_projects(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN_WITH_PROJECT_ID,
+ )
+ ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = ar_mock
+
+ arglist = [
+ '--my-projects',
+ ]
+ verifylist = [
+ ('my_projects', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+ self.projects_mock.list.assert_called_with(
+ user=self.app.client_manager.auth_ref.user_id)
+
+ collist = ('ID', 'Name')
+ self.assertEqual(collist, columns)
+ datalist = ((
+ self.project.id,
+ self.project.name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
class TestProjectSet(TestProject):
@@ -736,8 +831,6 @@ class TestProjectShow(TestProject):
def test_project_show(self):
- self.projects_mock.get.side_effect = [Exception("Not found"),
- self.project]
self.projects_mock.get.return_value = self.project
arglist = [
@@ -763,11 +856,7 @@ class TestProjectShow(TestProject):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.projects_mock.get.assert_called_with(
- self.project.id,
- parents_as_list=False,
- subtree_as_list=False,
- )
+ self.projects_mock.get.assert_called_once_with(self.project.id)
collist = (
'description',
@@ -797,8 +886,6 @@ class TestProjectShow(TestProject):
'parents': [{'project': {'id': self.project.parent_id}}]
}
)
- self.projects_mock.get.side_effect = [Exception("Not found"),
- self.project]
self.projects_mock.get.return_value = self.project
arglist = [
@@ -822,11 +909,12 @@ class TestProjectShow(TestProject):
}
columns, data = self.cmd.take_action(parsed_args)
- self.projects_mock.get.assert_called_with(
- self.project.id,
- parents_as_list=True,
- subtree_as_list=False,
- )
+
+ self.projects_mock.get.assert_has_calls([call(self.project.id),
+ call(self.project.id,
+ parents_as_list=True,
+ subtree_as_list=False,
+ )])
collist = (
'description',
@@ -858,8 +946,6 @@ class TestProjectShow(TestProject):
'subtree': [{'project': {'id': 'children-id'}}]
}
)
- self.projects_mock.get.side_effect = [Exception("Not found"),
- self.project]
self.projects_mock.get.return_value = self.project
arglist = [
@@ -883,11 +969,11 @@ class TestProjectShow(TestProject):
}
columns, data = self.cmd.take_action(parsed_args)
- self.projects_mock.get.assert_called_with(
- self.project.id,
- parents_as_list=False,
- subtree_as_list=True,
- )
+ self.projects_mock.get.assert_has_calls([call(self.project.id),
+ call(self.project.id,
+ parents_as_list=False,
+ subtree_as_list=True,
+ )])
collist = (
'description',
@@ -920,8 +1006,6 @@ class TestProjectShow(TestProject):
'subtree': [{'project': {'id': 'children-id'}}]
}
)
- self.projects_mock.get.side_effect = [Exception("Not found"),
- self.project]
self.projects_mock.get.return_value = self.project
arglist = [
@@ -946,11 +1030,11 @@ class TestProjectShow(TestProject):
}
columns, data = self.cmd.take_action(parsed_args)
- self.projects_mock.get.assert_called_with(
- self.project.id,
- parents_as_list=True,
- subtree_as_list=True,
- )
+ self.projects_mock.get.assert_has_calls([call(self.project.id),
+ call(self.project.id,
+ parents_as_list=True,
+ subtree_as_list=True,
+ )])
collist = (
'description',
diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py
index 448e18d3..39dbd244 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
@@ -269,6 +273,22 @@ class TestRoleAdd(TestRole):
)
self.assertIsNone(result)
+ def test_role_add_with_error(self):
+ arglist = [
+ identity_fakes.role_name,
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
class TestRoleAddInherited(TestRoleAdd, TestRoleInherited):
pass
@@ -428,6 +448,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):
@@ -737,6 +787,17 @@ class TestRoleList(TestRole):
), )
self.assertEqual(datalist, tuple(data))
+ def test_role_list_group_with_error(self):
+ arglist = [
+ '--group', identity_fakes.group_id,
+ ]
+ verifylist = [
+ ('group', identity_fakes.group_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
class TestRoleRemove(TestRole):
@@ -948,6 +1009,22 @@ class TestRoleRemove(TestRole):
)
self.assertIsNone(result)
+ def test_role_remove_with_error(self):
+ arglist = [
+ identity_fakes.role_name,
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
class TestRoleSet(TestRole):
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_unscoped_saml.py b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py
index 9e4e1876..34655263 100644
--- a/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py
+++ b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py
@@ -12,8 +12,6 @@
import copy
-from osc_lib import exceptions
-
from openstackclient.identity.v3 import unscoped_saml
from openstackclient.tests.unit import fakes
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@@ -48,7 +46,6 @@ class TestDomainList(TestUnscopedSAML):
self.cmd = unscoped_saml.ListAccessibleDomains(self.app, None)
def test_accessible_domains_list(self):
- self.app.client_manager.auth_plugin_name = 'v3unscopedsaml'
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -70,17 +67,6 @@ class TestDomainList(TestUnscopedSAML):
), )
self.assertEqual(datalist, tuple(data))
- def test_accessible_domains_list_wrong_auth(self):
- auth = identity_fakes.FakeAuth("wrong auth")
- self.app.client_manager.identity.session.auth = auth
- arglist = []
- verifylist = []
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- self.assertRaises(exceptions.CommandError,
- self.cmd.take_action,
- parsed_args)
-
class TestProjectList(TestUnscopedSAML):
@@ -99,7 +85,6 @@ class TestProjectList(TestUnscopedSAML):
self.cmd = unscoped_saml.ListAccessibleProjects(self.app, None)
def test_accessible_projects_list(self):
- self.app.client_manager.auth_plugin_name = 'v3unscopedsaml'
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -120,14 +105,3 @@ class TestProjectList(TestUnscopedSAML):
identity_fakes.project_name,
), )
self.assertEqual(datalist, tuple(data))
-
- def test_accessible_projects_list_wrong_auth(self):
- auth = identity_fakes.FakeAuth("wrong auth")
- self.app.client_manager.identity.session.auth = auth
- arglist = []
- verifylist = []
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- self.assertRaises(exceptions.CommandError,
- self.cmd.take_action,
- parsed_args)
diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py
index 6150a5f3..2ce66e94 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):
@@ -655,9 +684,14 @@ class TestUserList(TestUser):
class TestUserSet(TestUser):
project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
user = identity_fakes.FakeUser.create_one_user(
attrs={'default_project_id': project.id}
)
+ user2 = identity_fakes.FakeUser.create_one_user(
+ attrs={'default_project_id': project.id,
+ 'domain_id': domain.id}
+ )
def setUp(self):
super(TestUserSet, self).setUp()
@@ -719,6 +753,37 @@ class TestUserSet(TestUser):
)
self.assertIsNone(result)
+ def test_user_set_specify_domain(self):
+ arglist = [
+ '--name', 'qwerty',
+ '--domain', self.domain.id,
+ self.user2.name
+ ]
+ verifylist = [
+ ('name', 'qwerty'),
+ ('password', None),
+ ('domain', self.domain.id),
+ ('email', None),
+ ('project', None),
+ ('enable', False),
+ ('disable', False),
+ ('user', self.user2.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'enabled': True,
+ 'name': 'qwerty'
+ }
+
+ self.users_mock.update.assert_called_with(
+ self.user.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_user_set_password(self):
arglist = [
'--password', 'secret',
diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py
index a8e52fa3..080356ee 100644
--- a/openstackclient/tests/unit/image/v1/fakes.py
+++ b/openstackclient/tests/unit/image/v1/fakes.py
@@ -13,7 +13,9 @@
# under the License.
#
+import copy
import mock
+import uuid
from openstackclient.tests.unit import fakes
from openstackclient.tests.unit import utils
@@ -74,3 +76,45 @@ class TestImagev1(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+
+
+class FakeImage(object):
+ """Fake one or more images."""
+
+ @staticmethod
+ def create_one_image(attrs=None):
+ """Create a fake image.
+
+ :param Dictionary attrs:
+ A dictionary with all attrbutes of image
+ :return:
+ A FakeResource object with id, name, owner, protected,
+ visibility and tags attrs
+ """
+ attrs = attrs or {}
+
+ # Set default attribute
+ image_info = {
+ 'id': str(uuid.uuid4()),
+ 'name': 'image-name' + uuid.uuid4().hex,
+ 'owner': 'image-owner' + uuid.uuid4().hex,
+ 'container_format': '',
+ 'disk_format': '',
+ 'min_disk': 0,
+ 'min_ram': 0,
+ 'is_public': True,
+ 'protected': False,
+ 'properties': {
+ 'Alpha': 'a',
+ 'Beta': 'b',
+ 'Gamma': 'g'},
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ image_info.update(attrs)
+
+ image = fakes.FakeResource(
+ info=copy.deepcopy(image_info),
+ loaded=True)
+
+ return image
diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py
index a6bc80a0..036c8336 100644
--- a/openstackclient/tests/unit/image/v1/test_image.py
+++ b/openstackclient/tests/unit/image/v1/test_image.py
@@ -17,6 +17,7 @@ import copy
import mock
from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.image.v1 import image
from openstackclient.tests.unit import fakes
@@ -35,25 +36,39 @@ class TestImage(image_fakes.TestImagev1):
class TestImageCreate(TestImage):
+ new_image = image_fakes.FakeImage.create_one_image()
+ columns = (
+ 'container_format',
+ 'disk_format',
+ 'id',
+ 'is_public',
+ 'min_disk',
+ 'min_ram',
+ 'name',
+ 'owner',
+ 'properties',
+ 'protected',
+ )
+ data = (
+ new_image.container_format,
+ new_image.disk_format,
+ new_image.id,
+ new_image.is_public,
+ new_image.min_disk,
+ new_image.min_ram,
+ new_image.name,
+ new_image.owner,
+ utils.format_dict(new_image.properties),
+ new_image.protected,
+ )
+
def setUp(self):
super(TestImageCreate, self).setUp()
- self.images_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
+ self.images_mock.create.return_value = self.new_image
# This is the return value for utils.find_resource()
- self.images_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
- self.images_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
+ self.images_mock.get.return_value = self.new_image
+ self.images_mock.update.return_value = self.new_image
# Get the command object to test
self.cmd = image.CreateImage(self.app, None)
@@ -65,12 +80,12 @@ class TestImageCreate(TestImage):
}
self.images_mock.configure_mock(**mock_exception)
arglist = [
- image_fakes.image_name,
+ self.new_image.name,
]
verifylist = [
('container_format', image.DEFAULT_CONTAINER_FORMAT),
('disk_format', image.DEFAULT_DISK_FORMAT),
- ('name', image_fakes.image_name),
+ ('name', self.new_image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -81,7 +96,7 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.images_mock.create.assert_called_with(
- name=image_fakes.image_name,
+ name=self.new_image.name,
container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT,
data=mock.ANY,
@@ -90,8 +105,8 @@ class TestImageCreate(TestImage):
# Verify update() was not called, if it was show the args
self.assertEqual(self.images_mock.update.call_args_list, [])
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
def test_image_reserve_options(self):
mock_exception = {
@@ -101,17 +116,17 @@ class TestImageCreate(TestImage):
self.images_mock.configure_mock(**mock_exception)
arglist = [
'--container-format', 'ovf',
- '--disk-format', 'fs',
+ '--disk-format', 'ami',
'--min-disk', '10',
'--min-ram', '4',
'--protected',
'--private',
'--project', 'q',
- image_fakes.image_name,
+ self.new_image.name,
]
verifylist = [
('container_format', 'ovf'),
- ('disk_format', 'fs'),
+ ('disk_format', 'ami'),
('min_disk', 10),
('min_ram', 4),
('protected', True),
@@ -119,7 +134,7 @@ class TestImageCreate(TestImage):
('public', False),
('private', True),
('project', 'q'),
- ('name', image_fakes.image_name),
+ ('name', self.new_image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -130,9 +145,9 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.images_mock.create.assert_called_with(
- name=image_fakes.image_name,
+ name=self.new_image.name,
container_format='ovf',
- disk_format='fs',
+ disk_format='ami',
min_disk=10,
min_ram=4,
protected=True,
@@ -144,14 +159,14 @@ class TestImageCreate(TestImage):
# Verify update() was not called, if it was show the args
self.assertEqual(self.images_mock.update.call_args_list, [])
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
@mock.patch('openstackclient.image.v1.image.io.open', name='Open')
def test_image_create_file(self, mock_open):
mock_file = mock.Mock(name='File')
mock_open.return_value = mock_file
- mock_open.read.return_value = image_fakes.image_data
+ mock_open.read.return_value = self.data
mock_exception = {
'find.side_effect': exceptions.CommandError('x'),
'get.side_effect': exceptions.CommandError('x'),
@@ -164,7 +179,7 @@ class TestImageCreate(TestImage):
'--public',
'--property', 'Alpha=1',
'--property', 'Beta=2',
- image_fakes.image_name,
+ self.new_image.name,
]
verifylist = [
('file', 'filer'),
@@ -173,7 +188,7 @@ class TestImageCreate(TestImage):
('public', True),
('private', False),
('properties', {'Alpha': '1', 'Beta': '2'}),
- ('name', image_fakes.image_name),
+ ('name', self.new_image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -193,7 +208,7 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.images_mock.create.assert_called_with(
- name=image_fakes.image_name,
+ name=self.new_image.name,
container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT,
protected=False,
@@ -208,21 +223,19 @@ class TestImageCreate(TestImage):
# Verify update() was not called, if it was show the args
self.assertEqual(self.images_mock.update.call_args_list, [])
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
class TestImageDelete(TestImage):
+ _image = image_fakes.FakeImage.create_one_image()
+
def setUp(self):
super(TestImageDelete, self).setUp()
# This is the return value for utils.find_resource()
- self.images_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
+ self.images_mock.get.return_value = self._image
self.images_mock.delete.return_value = None
# Get the command object to test
@@ -230,21 +243,23 @@ class TestImageDelete(TestImage):
def test_image_delete_no_options(self):
arglist = [
- image_fakes.image_id,
+ self._image.id,
]
verifylist = [
- ('images', [image_fakes.image_id]),
+ ('images', [self._image.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.images_mock.delete.assert_called_with(image_fakes.image_id)
+ self.images_mock.delete.assert_called_with(self._image.id)
self.assertIsNone(result)
class TestImageList(TestImage):
+ _image = image_fakes.FakeImage.create_one_image()
+
columns = (
'ID',
'Name',
@@ -252,18 +267,33 @@ class TestImageList(TestImage):
)
datalist = (
(
- image_fakes.image_id,
- image_fakes.image_name,
+ _image.id,
+ _image.name,
'',
),
)
+ # create a image_info as the side_effect of the fake image_list()
+ info = {
+ 'id': _image.id,
+ 'name': _image.name,
+ 'owner': _image.owner,
+ 'container_format': _image.container_format,
+ 'disk_format': _image.disk_format,
+ 'min_disk': _image.min_disk,
+ 'min_ram': _image.min_ram,
+ 'is_public': _image.is_public,
+ 'protected': _image.protected,
+ 'properties': _image.properties,
+ }
+ image_info = copy.deepcopy(info)
+
def setUp(self):
super(TestImageList, self).setUp()
self.api_mock = mock.Mock()
self.api_mock.image_list.side_effect = [
- [copy.deepcopy(image_fakes.IMAGE)], [],
+ [self.image_info], [],
]
self.app.client_manager.image.api = self.api_mock
@@ -285,7 +315,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=True,
- marker=image_fakes.image_id,
+ marker=self._image.id,
)
self.assertEqual(self.columns, columns)
@@ -309,7 +339,7 @@ class TestImageList(TestImage):
self.api_mock.image_list.assert_called_with(
detailed=True,
public=True,
- marker=image_fakes.image_id,
+ marker=self._image.id,
)
self.assertEqual(self.columns, columns)
@@ -333,7 +363,7 @@ class TestImageList(TestImage):
self.api_mock.image_list.assert_called_with(
detailed=True,
private=True,
- marker=image_fakes.image_id,
+ marker=self._image.id,
)
self.assertEqual(self.columns, columns)
@@ -354,7 +384,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=True,
- marker=image_fakes.image_id,
+ marker=self._image.id,
)
collist = (
@@ -373,8 +403,8 @@ class TestImageList(TestImage):
self.assertEqual(collist, columns)
datalist = ((
- image_fakes.image_id,
- image_fakes.image_name,
+ self._image.id,
+ self._image.name,
'',
'',
'',
@@ -382,7 +412,7 @@ class TestImageList(TestImage):
'',
'public',
False,
- image_fakes.image_owner,
+ self._image.owner,
"Alpha='a', Beta='b', Gamma='g'",
), )
self.assertEqual(datalist, tuple(data))
@@ -390,7 +420,7 @@ class TestImageList(TestImage):
@mock.patch('openstackclient.api.utils.simple_filter')
def test_image_list_property_option(self, sf_mock):
sf_mock.side_effect = [
- [copy.deepcopy(image_fakes.IMAGE)], [],
+ [self.image_info], [],
]
arglist = [
@@ -407,10 +437,10 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=True,
- marker=image_fakes.image_id,
+ marker=self._image.id,
)
sf_mock.assert_called_with(
- [image_fakes.IMAGE],
+ [self.image_info],
attr='a',
value='1',
property_field='properties',
@@ -422,7 +452,7 @@ class TestImageList(TestImage):
@mock.patch('osc_lib.utils.sort_items')
def test_image_list_sort_option(self, si_mock):
si_mock.side_effect = [
- [copy.deepcopy(image_fakes.IMAGE)], [],
+ [self.image_info], [],
]
arglist = ['--sort', 'name:asc']
@@ -435,10 +465,10 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=True,
- marker=image_fakes.image_id,
+ marker=self._image.id,
)
si_mock.assert_called_with(
- [image_fakes.IMAGE],
+ [self.image_info],
'name:asc'
)
@@ -448,36 +478,30 @@ class TestImageList(TestImage):
class TestImageSet(TestImage):
+ _image = image_fakes.FakeImage.create_one_image()
+
def setUp(self):
super(TestImageSet, self).setUp()
# This is the return value for utils.find_resource()
- self.images_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
- self.images_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
+ self.images_mock.get.return_value = self._image
+ self.images_mock.update.return_value = self._image
# Get the command object to test
self.cmd = image.SetImage(self.app, None)
def test_image_set_no_options(self):
arglist = [
- image_fakes.image_name,
+ self._image.name,
]
verifylist = [
- ('image', image_fakes.image_name),
+ ('image', self._image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.images_mock.update.assert_called_with(image_fakes.image_id,
+ self.images_mock.update.assert_called_with(self._image.id,
**{})
self.assertIsNone(result)
@@ -490,7 +514,7 @@ class TestImageSet(TestImage):
'--disk-format', 'vmdk',
'--size', '35165824',
'--project', 'new-owner',
- image_fakes.image_name,
+ self._image.name,
]
verifylist = [
('name', 'new-name'),
@@ -500,7 +524,7 @@ class TestImageSet(TestImage):
('disk_format', 'vmdk'),
('size', 35165824),
('project', 'new-owner'),
- ('image', image_fakes.image_name),
+ ('image', self._image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -517,7 +541,7 @@ class TestImageSet(TestImage):
}
# ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
**kwargs
)
self.assertIsNone(result)
@@ -526,14 +550,14 @@ class TestImageSet(TestImage):
arglist = [
'--protected',
'--private',
- image_fakes.image_name,
+ self._image.name,
]
verifylist = [
('protected', True),
('unprotected', False),
('public', False),
('private', True),
- ('image', image_fakes.image_name),
+ ('image', self._image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -545,7 +569,7 @@ class TestImageSet(TestImage):
}
# ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
**kwargs
)
self.assertIsNone(result)
@@ -554,14 +578,14 @@ class TestImageSet(TestImage):
arglist = [
'--unprotected',
'--public',
- image_fakes.image_name,
+ self._image.name,
]
verifylist = [
('protected', False),
('unprotected', True),
('public', True),
('private', False),
- ('image', image_fakes.image_name),
+ ('image', self._image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -573,7 +597,7 @@ class TestImageSet(TestImage):
}
# ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
**kwargs
)
self.assertIsNone(result)
@@ -582,11 +606,11 @@ class TestImageSet(TestImage):
arglist = [
'--property', 'Alpha=1',
'--property', 'Beta=2',
- image_fakes.image_name,
+ self._image.name,
]
verifylist = [
('properties', {'Alpha': '1', 'Beta': '2'}),
- ('image', image_fakes.image_name),
+ ('image', self._image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -601,7 +625,7 @@ class TestImageSet(TestImage):
}
# ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
**kwargs
)
self.assertIsNone(result)
@@ -624,7 +648,7 @@ class TestImageSet(TestImage):
"volume_type": 'volume_type',
"container_format": image.DEFAULT_CONTAINER_FORMAT,
"disk_format": image.DEFAULT_DISK_FORMAT,
- "image": image_fakes.image_name,
+ "image": self._image.name,
}
full_response = {"os-volume_upload_image": response}
volumes_mock.upload_to_image.return_value = (201, full_response)
@@ -632,7 +656,7 @@ class TestImageSet(TestImage):
arglist = [
'--volume', 'volly',
'--name', 'updated_image',
- image_fakes.image_name,
+ self._image.name,
]
verifylist = [
('private', False),
@@ -642,7 +666,7 @@ class TestImageSet(TestImage):
('volume', 'volly'),
('force', False),
('name', 'updated_image'),
- ('image', image_fakes.image_name),
+ ('image', self._image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -653,13 +677,13 @@ class TestImageSet(TestImage):
volumes_mock.upload_to_image.assert_called_with(
'vol1',
False,
- image_fakes.image_name,
+ self._image.name,
'',
'',
)
# ImageManager.update(image_id, remove_props=, **)
self.images_mock.update.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
name='updated_image',
volume='volly',
)
@@ -668,24 +692,46 @@ class TestImageSet(TestImage):
class TestImageShow(TestImage):
+ _image = image_fakes.FakeImage.create_one_image()
+ columns = (
+ 'container_format',
+ 'disk_format',
+ 'id',
+ 'is_public',
+ 'min_disk',
+ 'min_ram',
+ 'name',
+ 'owner',
+ 'properties',
+ 'protected',
+ )
+ data = (
+ _image.container_format,
+ _image.disk_format,
+ _image.id,
+ _image.is_public,
+ _image.min_disk,
+ _image.min_ram,
+ _image.name,
+ _image.owner,
+ utils.format_dict(_image.properties),
+ _image.protected,
+ )
+
def setUp(self):
super(TestImageShow, self).setUp()
- self.images_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
+ self.images_mock.get.return_value = self._image
# Get the command object to test
self.cmd = image.ShowImage(self.app, None)
def test_image_show(self):
arglist = [
- image_fakes.image_id,
+ self._image.id,
]
verifylist = [
- ('image', image_fakes.image_id),
+ ('image', self._image.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -694,8 +740,8 @@ class TestImageShow(TestImage):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
self.images_mock.get.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
)
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py
index ebc9c3a7..164185df 100644
--- a/openstackclient/tests/unit/image/v2/test_image.py
+++ b/openstackclient/tests/unit/image/v2/test_image.py
@@ -130,7 +130,7 @@ class TestImageCreate(TestImage):
self.images_mock.configure_mock(**mock_exception)
arglist = [
'--container-format', 'ovf',
- '--disk-format', 'fs',
+ '--disk-format', 'ami',
'--min-disk', '10',
'--min-ram', '4',
('--protected'
@@ -143,7 +143,7 @@ class TestImageCreate(TestImage):
]
verifylist = [
('container_format', 'ovf'),
- ('disk_format', 'fs'),
+ ('disk_format', 'ami'),
('min_disk', 10),
('min_ram', 4),
('protected', self.new_image.protected),
@@ -165,7 +165,7 @@ class TestImageCreate(TestImage):
self.images_mock.create.assert_called_with(
name=self.new_image.name,
container_format='ovf',
- disk_format='fs',
+ disk_format='ami',
min_disk=10,
min_ram=4,
owner=self.project.id,
@@ -193,7 +193,7 @@ class TestImageCreate(TestImage):
arglist = [
'--container-format', 'ovf',
- '--disk-format', 'fs',
+ '--disk-format', 'ami',
'--min-disk', '10',
'--min-ram', '4',
'--owner', 'unexist_owner',
@@ -203,7 +203,7 @@ class TestImageCreate(TestImage):
]
verifylist = [
('container_format', 'ovf'),
- ('disk_format', 'fs'),
+ ('disk_format', 'ami'),
('min_disk', 10),
('min_ram', 4),
('owner', 'unexist_owner'),
@@ -227,7 +227,7 @@ class TestImageCreate(TestImage):
arglist = [
'--container-format', 'ovf',
- '--disk-format', 'fs',
+ '--disk-format', 'ami',
'--min-disk', '10',
'--min-ram', '4',
'--protected',
@@ -237,7 +237,7 @@ class TestImageCreate(TestImage):
]
verifylist = [
('container_format', 'ovf'),
- ('disk_format', 'fs'),
+ ('disk_format', 'ami'),
('min_disk', 10),
('min_ram', 4),
('protected', True),
@@ -535,7 +535,9 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=self._image.id,
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
@@ -558,6 +560,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
public=True,
+ marker=self._image.id,
)
self.assertEqual(self.columns, columns)
@@ -581,6 +584,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
private=True,
+ marker=self._image.id,
)
self.assertEqual(self.columns, columns)
@@ -604,6 +608,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
shared=True,
+ marker=self._image.id,
)
self.assertEqual(self.columns, columns)
@@ -622,7 +627,9 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=self._image.id,
+ )
collist = (
'ID',
@@ -670,7 +677,9 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=self._image.id,
+ )
sf_mock.assert_called_with(
[self._image],
attr='a',
@@ -693,7 +702,9 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=self._image.id,
+ )
si_mock.assert_called_with(
[self._image],
'name:asc'
@@ -712,7 +723,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
- limit=1,
+ limit=1, marker=self._image.id
)
self.assertEqual(self.columns, columns)
@@ -818,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)
@@ -834,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/test_common.py b/openstackclient/tests/unit/network/test_common.py
index 325aad2a..4b9a754b 100644
--- a/openstackclient/tests/unit/network/test_common.py
+++ b/openstackclient/tests/unit/network/test_common.py
@@ -14,6 +14,8 @@
import argparse
import mock
+import openstack
+from openstackclient.common import exceptions
from openstackclient.network import common
from openstackclient.tests.unit import utils
@@ -172,3 +174,15 @@ class TestNetworkAndComputeShowOne(TestNetworkAndCompute):
def setUp(self):
super(TestNetworkAndComputeShowOne, self).setUp()
self.cmd = FakeNetworkAndComputeShowOne(self.app, self.namespace)
+
+ def test_take_action_with_http_exception(self):
+ with mock.patch.object(self.cmd, 'take_action_network') as m_action:
+ m_action.side_effect = openstack.exceptions.HttpException("bar")
+ self.assertRaisesRegex(exceptions.CommandError, "bar",
+ self.cmd.take_action, mock.Mock())
+
+ self.app.client_manager.network_endpoint_enabled = False
+ with mock.patch.object(self.cmd, 'take_action_compute') as m_action:
+ m_action.side_effect = openstack.exceptions.HttpException("bar")
+ self.assertRaisesRegex(exceptions.CommandError, "bar",
+ self.cmd.take_action, mock.Mock())
diff --git a/openstackclient/tests/unit/network/test_sdk_utils.py b/openstackclient/tests/unit/network/test_sdk_utils.py
new file mode 100644
index 00000000..d1efa7e4
--- /dev/null
+++ b/openstackclient/tests/unit/network/test_sdk_utils.py
@@ -0,0 +1,59 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from openstackclient.network import sdk_utils
+from openstackclient.tests.unit import utils as tests_utils
+
+
+class TestSDKUtils(tests_utils.TestCase):
+
+ def setUp(self):
+ super(TestSDKUtils, self).setUp()
+
+ def _test_get_osc_show_columns_for_sdk_resource(
+ self, sdk_resource, column_map,
+ expected_display_columns, expected_attr_columns):
+ display_columns, attr_columns = \
+ sdk_utils.get_osc_show_columns_for_sdk_resource(
+ sdk_resource, column_map)
+ self.assertEqual(expected_display_columns, display_columns)
+ self.assertEqual(expected_attr_columns, attr_columns)
+
+ def test_get_osc_show_columns_for_sdk_resource_empty(self):
+ self._test_get_osc_show_columns_for_sdk_resource(
+ {}, {}, tuple(), tuple())
+
+ def test_get_osc_show_columns_for_sdk_resource_empty_map(self):
+ self._test_get_osc_show_columns_for_sdk_resource(
+ {'foo': 'foo1'}, {},
+ ('foo',), ('foo',))
+
+ def test_get_osc_show_columns_for_sdk_resource_empty_data(self):
+ self._test_get_osc_show_columns_for_sdk_resource(
+ {}, {'foo': 'foo_map'},
+ ('foo_map',), ('foo_map',))
+
+ def test_get_osc_show_columns_for_sdk_resource_map(self):
+ self._test_get_osc_show_columns_for_sdk_resource(
+ {'foo': 'foo1'}, {'foo': 'foo_map'},
+ ('foo_map',), ('foo',))
+
+ def test_get_osc_show_columns_for_sdk_resource_map_dup(self):
+ self._test_get_osc_show_columns_for_sdk_resource(
+ {'foo': 'foo1', 'foo_map': 'foo1'}, {'foo': 'foo_map'},
+ ('foo_map',), ('foo',))
+
+ def test_get_osc_show_columns_for_sdk_resource_map_full(self):
+ self._test_get_osc_show_columns_for_sdk_resource(
+ {'foo': 'foo1', 'bar': 'bar1'},
+ {'foo': 'foo_map', 'new': 'bar'},
+ ('bar', 'foo_map'), ('bar', 'foo'))
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index cea00282..d3685409 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, {})
@@ -59,6 +71,10 @@ class TestNetworkV2(utils.TestCommand):
token=fakes.AUTH_TOKEN,
)
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.network = \
+ self.app.client_manager.network
+
self.app.client_manager.identity = (
identity_fakes_v3.FakeIdentityv3Client(
endpoint=fakes.AUTH_URL,
@@ -98,6 +114,7 @@ class FakeAddressScope(object):
loaded=True)
# Set attributes with special mapping in OpenStack SDK.
+ address_scope.is_shared = address_scope_attrs['shared']
address_scope.project_id = address_scope_attrs['tenant_id']
return address_scope
@@ -140,6 +157,31 @@ class FakeAddressScope(object):
return mock.Mock(side_effect=address_scopes)
+class FakeAutoAllocatedTopology(object):
+ """Fake Auto Allocated Topology"""
+
+ @staticmethod
+ def create_one_topology(attrs=None):
+ attrs = attrs or {}
+
+ auto_allocated_topology_attrs = {
+ 'id': 'network-id-' + uuid.uuid4().hex,
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ }
+
+ auto_allocated_topology_attrs.update(attrs)
+
+ auto_allocated_topology = fakes.FakeResource(
+ info=copy.deepcopy(auto_allocated_topology_attrs),
+ loaded=True)
+
+ auto_allocated_topology.project_id = auto_allocated_topology_attrs[
+ 'tenant_id'
+ ]
+
+ return auto_allocated_topology
+
+
class FakeAvailabilityZone(object):
"""Fake one or more network availability zones (AZs)."""
@@ -193,15 +235,18 @@ class FakeIPAvailability(object):
"""Fake one or more network ip availabilities."""
@staticmethod
- def create_one_ip_availability():
+ def create_one_ip_availability(attrs=None):
"""Create a fake list with ip availability stats of a network.
+ :param Dictionary attrs:
+ A dictionary with all attributes
:return:
A FakeResource object with network_name, network_id, etc.
"""
+ attrs = attrs or {}
# Set default attributes.
- network_ip_availability = {
+ network_ip_attrs = {
'network_id': 'network-id-' + uuid.uuid4().hex,
'network_name': 'network-name-' + uuid.uuid4().hex,
'tenant_id': '',
@@ -209,10 +254,13 @@ class FakeIPAvailability(object):
'total_ips': 254,
'used_ips': 6,
}
+ network_ip_attrs.update(attrs)
network_ip_availability = fakes.FakeResource(
- info=copy.deepcopy(network_ip_availability),
+ info=copy.deepcopy(network_ip_attrs),
loaded=True)
+ network_ip_availability.project_id = network_ip_attrs['tenant_id']
+
return network_ip_availability
@staticmethod
@@ -290,12 +338,17 @@ class FakeNetwork(object):
'admin_state_up': True,
'shared': False,
'subnets': ['a', 'b'],
- 'provider_network_type': 'vlan',
+ 'provider:network_type': 'vlan',
+ 'provider:physical_network': 'physnet1',
+ 'provider:segmentation_id': "400",
'router:external': True,
'availability_zones': [],
'availability_zone_hints': [],
'is_default': False,
'port_security_enabled': True,
+ 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
+ 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex,
+ 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex,
}
# Overwrite default attributes.
@@ -307,8 +360,21 @@ class FakeNetwork(object):
# Set attributes with special mapping in OpenStack SDK.
network.project_id = network_attrs['tenant_id']
network.is_router_external = network_attrs['router:external']
+ network.is_admin_state_up = network_attrs['admin_state_up']
network.is_port_security_enabled = \
network_attrs['port_security_enabled']
+ network.subnet_ids = network_attrs['subnets']
+ network.is_shared = network_attrs['shared']
+ network.provider_network_type = \
+ network_attrs['provider:network_type']
+ network.provider_physical_network = \
+ network_attrs['provider:physical_network']
+ network.provider_segmentation_id = \
+ network_attrs['provider:segmentation_id']
+ network.ipv4_address_scope_id = \
+ network_attrs['ipv4_address_scope']
+ network.ipv6_address_scope_id = \
+ network_attrs['ipv6_address_scope']
return network
@@ -349,6 +415,69 @@ class FakeNetwork(object):
return mock.Mock(side_effect=networks)
+class FakeNetworkFlavor(object):
+ """Fake Network Flavor."""
+
+ @staticmethod
+ def create_one_network_flavor(attrs=None):
+ """Create a fake network flavor.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object faking the network flavor
+ """
+ attrs = attrs or {}
+
+ fake_uuid = uuid.uuid4().hex
+ network_flavor_attrs = {
+ 'description': 'network-flavor-description-' + fake_uuid,
+ 'enabled': True,
+ 'id': 'network-flavor-id-' + fake_uuid,
+ 'name': 'network-flavor-name-' + fake_uuid,
+ 'service_type': 'vpn',
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ network_flavor_attrs.update(attrs)
+
+ network_flavor = fakes.FakeResource(
+ info=copy.deepcopy(network_flavor_attrs),
+ loaded=True
+ )
+
+ network_flavor.project_id = network_flavor_attrs['tenant_id']
+ network_flavor.is_enabled = network_flavor_attrs['enabled']
+
+ return network_flavor
+
+ @staticmethod
+ def create_flavor(attrs=None, count=2):
+ """Create multiple fake network flavors.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of network flavors to fake
+ :return:
+ A list of FakeResource objects faking the network falvors
+ """
+ network_flavors = []
+ for i in range(0, count):
+ network_flavors.append(
+ FakeNetworkFlavor.create_one_network_flavor(attrs)
+ )
+ return network_flavors
+
+ @staticmethod
+ def get_flavor(network_flavors=None, count=2):
+ """Get a list of flavors."""
+ if network_flavors is None:
+ network_flavors = (FakeNetworkFlavor.create_flavor(count))
+ return mock.Mock(side_effect=network_flavors)
+
+
class FakeNetworkSegment(object):
"""Fake one or more network segments."""
@@ -427,18 +556,20 @@ class FakePort(object):
'binding:vif_details': {},
'binding:vif_type': 'ovs',
'binding:vnic_type': 'normal',
+ 'description': 'description-' + uuid.uuid4().hex,
'device_id': 'device-id-' + uuid.uuid4().hex,
'device_owner': 'compute:nova',
'dns_assignment': [{}],
'dns_name': 'dns-name-' + uuid.uuid4().hex,
'extra_dhcp_opts': [{}],
- 'fixed_ips': [{}],
+ 'fixed_ips': [{'ip_address': '10.0.0.3',
+ 'subnet_id': 'subnet-id-' + uuid.uuid4().hex}],
'id': 'port-id-' + uuid.uuid4().hex,
'mac_address': 'fa:16:3e:a9:4e:72',
'name': 'port-name-' + uuid.uuid4().hex,
'network_id': 'network-id-' + uuid.uuid4().hex,
'port_security_enabled': True,
- 'security_groups': [],
+ 'security_group_ids': [],
'status': 'ACTIVE',
'tenant_id': 'project-id-' + uuid.uuid4().hex,
}
@@ -450,12 +581,15 @@ class FakePort(object):
loaded=True)
# Set attributes with special mappings in OpenStack SDK.
- port.project_id = port_attrs['tenant_id']
port.binding_host_id = port_attrs['binding:host_id']
port.binding_profile = port_attrs['binding:profile']
port.binding_vif_details = port_attrs['binding:vif_details']
port.binding_vif_type = port_attrs['binding:vif_type']
port.binding_vnic_type = port_attrs['binding:vnic_type']
+ port.is_admin_state_up = port_attrs['admin_state_up']
+ port.is_port_security_enabled = port_attrs['port_security_enabled']
+ port.project_id = port_attrs['tenant_id']
+ port.security_group_ids = port_attrs['security_group_ids']
return port
@@ -524,6 +658,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
@@ -633,6 +769,254 @@ class FakeNetworkRBAC(object):
return mock.Mock(side_effect=rbac_policies)
+class FakeNetworkFlavorProfile(object):
+ """Fake network flavor profile."""
+
+ @staticmethod
+ def create_one_service_profile(attrs=None):
+ """Create flavor profile."""
+ attrs = attrs or {}
+
+ flavor_profile_attrs = {
+ 'id': 'flavor-profile-id' + uuid.uuid4().hex,
+ 'description': 'flavor-profile-description-' + uuid.uuid4().hex,
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ 'driver': 'driver-' + uuid.uuid4().hex,
+ 'metainfo': 'metainfo-' + uuid.uuid4().hex,
+ 'enabled': True
+ }
+
+ flavor_profile_attrs.update(attrs)
+
+ flavor_profile = fakes.FakeResource(
+ info=copy.deepcopy(flavor_profile_attrs),
+ loaded=True)
+
+ flavor_profile.project_id = flavor_profile_attrs['tenant_id']
+ flavor_profile.is_enabled = flavor_profile_attrs['enabled']
+
+ return flavor_profile
+
+ @staticmethod
+ def create_service_profile(attrs=None, count=2):
+ """Create multiple flavor profiles."""
+
+ flavor_profiles = []
+ for i in range(0, count):
+ flavor_profiles.append(FakeNetworkFlavorProfile.
+ create_one_service_profile(attrs))
+ return flavor_profiles
+
+ @staticmethod
+ def get_service_profile(flavor_profile=None, count=2):
+ """Get a list of flavor profiles."""
+ if flavor_profile is None:
+ flavor_profile = (FakeNetworkFlavorProfile.
+ create_service_profile(count))
+ return mock.Mock(side_effect=flavor_profile)
+
+
+class FakeNetworkQosPolicy(object):
+ """Fake one or more QoS policies."""
+
+ @staticmethod
+ def create_one_qos_policy(attrs=None):
+ """Create a fake QoS policy.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, id, etc.
+ """
+ attrs = attrs or {}
+ qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex
+ rule_attrs = {'qos_policy_id': qos_id}
+ rules = [FakeNetworkQosRule.create_one_qos_rule(rule_attrs)]
+
+ # Set default attributes.
+ qos_policy_attrs = {
+ 'name': 'qos-policy-name-' + uuid.uuid4().hex,
+ 'id': qos_id,
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ 'shared': False,
+ 'description': 'qos-policy-description-' + uuid.uuid4().hex,
+ 'rules': rules,
+ }
+
+ # Overwrite default attributes.
+ qos_policy_attrs.update(attrs)
+
+ qos_policy = fakes.FakeResource(
+ info=copy.deepcopy(qos_policy_attrs),
+ loaded=True)
+
+ # Set attributes with special mapping in OpenStack SDK.
+ qos_policy.is_shared = qos_policy_attrs['shared']
+ qos_policy.project_id = qos_policy_attrs['tenant_id']
+
+ return qos_policy
+
+ @staticmethod
+ def create_qos_policies(attrs=None, count=2):
+ """Create multiple fake QoS policies.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of QoS policies to fake
+ :return:
+ A list of FakeResource objects faking the QoS policies
+ """
+ qos_policies = []
+ for i in range(0, count):
+ qos_policies.append(
+ FakeNetworkQosPolicy.create_one_qos_policy(attrs))
+
+ return qos_policies
+
+ @staticmethod
+ def get_qos_policies(qos_policies=None, count=2):
+ """Get an iterable MagicMock object with a list of faked QoS policies.
+
+ If qos policies list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List address scopes:
+ A list of FakeResource objects faking qos policies
+ :param int count:
+ The number of QoS policies to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ QoS policies
+ """
+ if qos_policies is None:
+ qos_policies = FakeNetworkQosPolicy.create_qos_policies(count)
+ return mock.Mock(side_effect=qos_policies)
+
+
+class FakeNetworkQosRule(object):
+ """Fake one or more Network QoS rules."""
+
+ @staticmethod
+ def create_one_qos_rule(attrs=None):
+ """Create a fake Network QoS rule.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, id, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ 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,
+ '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_rule_attrs.update(attrs)
+
+ qos_rule = fakes.FakeResource(info=copy.deepcopy(qos_rule_attrs),
+ loaded=True)
+
+ # Set attributes with special mapping in OpenStack SDK.
+ qos_rule.project_id = qos_rule['tenant_id']
+
+ return qos_rule
+
+ @staticmethod
+ 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 Network QoS rule to fake
+ :return:
+ A list of FakeResource objects faking the Network QoS rules
+ """
+ qos_rules = []
+ for i in range(0, count):
+ qos_rules.append(FakeNetworkQosRule.create_one_qos_rule(attrs))
+ return qos_rules
+
+ @staticmethod
+ def get_qos_rules(qos_rules=None, count=2):
+ """Get a list of faked Network QoS rules.
+
+ 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 Network QoS rules
+ :param int count:
+ The number of QoS minimum bandwidth rules to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ qos minimum bandwidth rules
+ """
+ if qos_rules is None:
+ qos_rules = (FakeNetworkQosRule.create_qos_rules(count))
+ return mock.Mock(side_effect=qos_rules)
+
+
+class FakeNetworkQosRuleType(object):
+ """Fake one or more Network QoS rule types."""
+
+ @staticmethod
+ def create_one_qos_rule_type(attrs=None):
+ """Create a fake Network QoS rule type.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, id, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ qos_rule_type_attrs = {
+ 'type': 'rule-type-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ qos_rule_type_attrs.update(attrs)
+
+ return fakes.FakeResource(
+ info=copy.deepcopy(qos_rule_type_attrs),
+ loaded=True)
+
+ @staticmethod
+ def create_qos_rule_types(attrs=None, count=2):
+ """Create multiple fake Network QoS rule types.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of QoS rule types to fake
+ :return:
+ A list of FakeResource objects faking the QoS rule types
+ """
+ qos_rule_types = []
+ for i in range(0, count):
+ qos_rule_types.append(
+ FakeNetworkQosRuleType.create_one_qos_rule_type(attrs))
+
+ return qos_rule_types
+
+
class FakeRouter(object):
"""Fake one or more routers."""
@@ -672,6 +1056,9 @@ class FakeRouter(object):
# Set attributes with special mapping in OpenStack SDK.
router.project_id = router_attrs['tenant_id']
+ router.is_admin_state_up = router_attrs['admin_state_up']
+ router.is_distributed = router_attrs['distributed']
+ router.is_ha = router_attrs['ha']
return router
@@ -731,7 +1118,7 @@ class FakeSecurityGroup(object):
'id': 'security-group-id-' + uuid.uuid4().hex,
'name': 'security-group-name-' + uuid.uuid4().hex,
'description': 'security-group-description-' + uuid.uuid4().hex,
- 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ 'project_id': 'project-id-' + uuid.uuid4().hex,
'security_group_rules': [],
}
@@ -743,7 +1130,7 @@ class FakeSecurityGroup(object):
loaded=True)
# Set attributes with special mapping in OpenStack SDK.
- security_group.project_id = security_group_attrs['tenant_id']
+ security_group.project_id = security_group_attrs['project_id']
return security_group
@@ -801,8 +1188,10 @@ class FakeSecurityGroupRule(object):
# Set default attributes.
security_group_rule_attrs = {
+ 'description': 'security-group-rule-description-' +
+ uuid.uuid4().hex,
'direction': 'ingress',
- 'ethertype': 'IPv4',
+ 'ether_type': 'IPv4',
'id': 'security-group-rule-id-' + uuid.uuid4().hex,
'port_range_max': None,
'port_range_min': None,
@@ -907,6 +1296,8 @@ class FakeSubnet(object):
loaded=True)
# Set attributes with special mappings in OpenStack SDK.
+ subnet.is_dhcp_enabled = subnet_attrs['enable_dhcp']
+ subnet.subnet_pool_id = subnet_attrs['subnetpool_id']
subnet.project_id = subnet_attrs['tenant_id']
return subnet
@@ -1026,6 +1417,97 @@ class FakeFloatingIP(object):
return mock.Mock(side_effect=floating_ips)
+class FakeNetworkMeter(object):
+ """Fake network meter"""
+
+ @staticmethod
+ def create_one_meter(attrs=None):
+ """Create metering pool"""
+ attrs = attrs or {}
+
+ meter_attrs = {
+ 'id': 'meter-id-' + uuid.uuid4().hex,
+ 'name': 'meter-name-' + uuid.uuid4().hex,
+ 'description': 'meter-description-' + uuid.uuid4().hex,
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ 'shared': False
+ }
+
+ meter_attrs.update(attrs)
+
+ meter = fakes.FakeResource(
+ info=copy.deepcopy(meter_attrs),
+ loaded=True)
+
+ meter.project_id = meter_attrs['tenant_id']
+
+ return meter
+
+ @staticmethod
+ def create_meter(attrs=None, count=2):
+ """Create multiple meters"""
+
+ meters = []
+ for i in range(0, count):
+ meters.append(FakeNetworkMeter.
+ create_one_meter(attrs))
+ return meters
+
+ @staticmethod
+ def get_meter(meter=None, count=2):
+ """Get a list of meters"""
+ if meter is None:
+ meter = (FakeNetworkMeter.
+ create_meter(count))
+ return mock.Mock(side_effect=meter)
+
+
+class FakeNetworkMeterRule(object):
+ """Fake metering rule"""
+
+ @staticmethod
+ def create_one_rule(attrs=None):
+ """Create one meter rule"""
+ attrs = attrs or {}
+
+ meter_rule_attrs = {
+ 'id': 'meter-label-rule-id-' + uuid.uuid4().hex,
+ 'direction': 'ingress',
+ 'excluded': False,
+ 'metering_label_id': 'meter-label-id-' + uuid.uuid4().hex,
+ 'remote_ip_prefix': '10.0.0.0/24',
+ 'tenant_id': 'project-id-' + uuid.uuid4().hex,
+ }
+
+ meter_rule_attrs.update(attrs)
+
+ meter_rule = fakes.FakeResource(
+ info=copy.deepcopy(meter_rule_attrs),
+ loaded=True)
+
+ meter_rule.project_id = meter_rule_attrs['tenant_id']
+
+ return meter_rule
+
+ @staticmethod
+ def create_meter_rule(attrs=None, count=2):
+ """Create multiple meter rules"""
+
+ meter_rules = []
+ for i in range(0, count):
+ meter_rules.append(FakeNetworkMeterRule.
+ create_one_rule(attrs))
+ return meter_rules
+
+ @staticmethod
+ def get_meter_rule(meter_rule=None, count=2):
+ """Get a list of meter rules"""
+ if meter_rule is None:
+ meter_rule = (FakeNetworkMeterRule.
+ create_meter_rule(count))
+ return mock.Mock(side_effect=meter_rule)
+
+
class FakeSubnetPool(object):
"""Fake one or more subnet pools."""
@@ -1066,6 +1548,11 @@ class FakeSubnetPool(object):
)
# Set attributes with special mapping in OpenStack SDK.
+ subnet_pool.default_prefix_length = \
+ subnet_pool_attrs['default_prefixlen']
+ subnet_pool.is_shared = subnet_pool_attrs['shared']
+ subnet_pool.maximum_prefix_length = subnet_pool_attrs['max_prefixlen']
+ subnet_pool.minimum_prefix_length = subnet_pool_attrs['min_prefixlen']
subnet_pool.project_id = subnet_pool_attrs['tenant_id']
return subnet_pool
@@ -1107,3 +1594,88 @@ class FakeSubnetPool(object):
if subnet_pools is None:
subnet_pools = FakeSubnetPool.create_subnet_pools(count)
return mock.Mock(side_effect=subnet_pools)
+
+
+class FakeNetworkServiceProvider(object):
+ """Fake Network Service Providers"""
+
+ @staticmethod
+ def create_one_network_service_provider(attrs=None):
+ """Create service provider"""
+ attrs = attrs or {}
+
+ service_provider = {
+ 'name': 'provider-name-' + uuid.uuid4().hex,
+ 'service_type': 'service-type-' + uuid.uuid4().hex,
+ 'default': False,
+ }
+
+ service_provider.update(attrs)
+
+ provider = fakes.FakeResource(
+ info=copy.deepcopy(service_provider),
+ loaded=True)
+ provider.is_default = service_provider['default']
+
+ return provider
+
+ @staticmethod
+ def create_network_service_providers(attrs=None, count=2):
+ """Create multiple service providers"""
+
+ service_providers = []
+ for i in range(0, count):
+ service_providers.append(FakeNetworkServiceProvider.
+ create_one_network_service_provider(
+ attrs))
+ return service_providers
+
+
+class FakeQuota(object):
+ """Fake quota"""
+
+ @staticmethod
+ def create_one_net_quota(attrs=None):
+ """Create one quota"""
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'floating_ips': 20,
+ 'networks': 25,
+ 'ports': 11,
+ 'rbac_policies': 15,
+ 'routers': 40,
+ 'security_groups': 10,
+ 'security_group_rules': 100,
+ 'subnets': 20,
+ 'subnet_pools': 30}
+
+ quota_attrs.update(attrs)
+
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+ return quota
+
+ @staticmethod
+ def create_one_default_net_quota(attrs=None):
+ """Create one quota"""
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'floatingip': 30,
+ 'network': 20,
+ 'port': 10,
+ 'rbac_policy': 25,
+ 'router': 30,
+ 'security_group': 30,
+ 'security_group_rule': 200,
+ 'subnet': 10,
+ 'subnetpool': 20}
+
+ quota_attrs.update(attrs)
+
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+ return quota
diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py
index 12c3f1d6..40067188 100644
--- a/openstackclient/tests/unit/network/v2/test_address_scope.py
+++ b/openstackclient/tests/unit/network/v2/test_address_scope.py
@@ -275,6 +275,104 @@ class TestListAddressScope(TestAddressScope):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_address_scope_list_name(self):
+ arglist = [
+ '--name', self.address_scopes[0].name,
+ ]
+ verifylist = [
+ ('name', self.address_scopes[0].name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.address_scopes.assert_called_once_with(
+ **{'name': self.address_scopes[0].name})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_address_scope_list_ip_version(self):
+ arglist = [
+ '--ip-version', str(4),
+ ]
+ verifylist = [
+ ('ip_version', 4),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.address_scopes.assert_called_once_with(
+ **{'ip_version': 4})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_address_scope_list_project(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.address_scopes.assert_called_once_with(
+ **{'tenant_id': project.id, 'project_id': project.id})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_address_scope_project_domain(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ '--project-domain', project.domain_id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.address_scopes.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_address_scope_list_share(self):
+ arglist = [
+ '--share',
+ ]
+ verifylist = [
+ ('share', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.address_scopes.assert_called_once_with(
+ **{'is_shared': True}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_address_scope_list_no_share(self):
+ arglist = [
+ '--no-share',
+ ]
+ verifylist = [
+ ('no_share', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.address_scopes.assert_called_once_with(
+ **{'is_shared': False}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestSetAddressScope(TestAddressScope):
diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py
index 1f30f2e9..e395300d 100644
--- a/openstackclient/tests/unit/network/v2/test_floating_ip.py
+++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py
@@ -18,6 +18,7 @@ from osc_lib import exceptions
from openstackclient.network.v2 import floating_ip
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit import utils as tests_utils
@@ -31,6 +32,10 @@ class TestFloatingIPNetwork(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
@@ -145,6 +150,54 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_floating_ip_create_project(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ self.floating_ip.floating_network_id,
+ ]
+ verifylist = [
+ ('network', self.floating_ip.floating_network_id),
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_ip.assert_called_once_with(**{
+ 'floating_network_id': self.floating_ip.floating_network_id,
+ 'tenant_id': project.id,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_floating_ip_create_project_domain(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ "--project", project.name,
+ "--project-domain", domain.name,
+ self.floating_ip.floating_network_id,
+ ]
+ verifylist = [
+ ('network', self.floating_ip.floating_network_id),
+ ('project', project.name),
+ ('project_domain', domain.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_ip.assert_called_once_with(**{
+ 'floating_network_id': self.floating_ip.floating_network_id,
+ 'tenant_id': project.id,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
@@ -155,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,
]
@@ -172,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 = []
@@ -190,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',
@@ -207,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]
)
@@ -231,27 +329,60 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork):
# The floating ips to list up
floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3)
+ fake_network = network_fakes.FakeNetwork.create_one_network({
+ 'id': 'fake_network_id',
+ })
+ fake_port = network_fakes.FakePort.create_one_port({
+ 'id': 'fake_port_id',
+ })
+ fake_router = network_fakes.FakeRouter.create_one_router({
+ 'id': 'fake_router_id',
+ })
columns = (
'ID',
'Floating IP Address',
'Fixed IP Address',
'Port',
+ 'Floating Network',
+ 'Project',
+ )
+ columns_long = columns + (
+ 'Router',
+ 'Status',
+ 'Description',
)
data = []
+ data_long = []
for ip in floating_ips:
data.append((
ip.id,
ip.floating_ip_address,
ip.fixed_ip_address,
ip.port_id,
+ ip.floating_network_id,
+ ip.tenant_id,
+ ))
+ data_long.append((
+ ip.id,
+ ip.floating_ip_address,
+ ip.fixed_ip_address,
+ ip.port_id,
+ ip.floating_network_id,
+ ip.tenant_id,
+ ip.router_id,
+ ip.status,
+ ip.description,
))
def setUp(self):
super(TestListFloatingIPNetwork, self).setUp()
self.network.ips = mock.Mock(return_value=self.floating_ips)
+ self.network.find_network = mock.Mock(return_value=self.fake_network)
+ self.network.find_port = mock.Mock(return_value=self.fake_port)
+ self.network.find_router = mock.Mock(return_value=self.fake_router)
# Get the command object to test
self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace)
@@ -263,10 +394,149 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork):
columns, data = self.cmd.take_action(parsed_args)
- self.network.ips.assert_called_once_with(**{})
+ self.network.ips.assert_called_once_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_floating_ip_list_network(self):
+ arglist = [
+ '--network', 'fake_network_id',
+ ]
+ verifylist = [
+ ('network', 'fake_network_id'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with(**{
+ 'floating_network_id': 'fake_network_id',
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_floating_ip_list_port(self):
+ arglist = [
+ '--port', 'fake_port_id',
+ ]
+ verifylist = [
+ ('port', 'fake_port_id'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with(**{
+ 'port_id': 'fake_port_id',
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_floating_ip_list_fixed_ip_address(self):
+ arglist = [
+ '--fixed-ip-address', self.floating_ips[0].fixed_ip_address,
+ ]
+ verifylist = [
+ ('fixed_ip_address', self.floating_ips[0].fixed_ip_address),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with(**{
+ 'fixed_ip_address': self.floating_ips[0].fixed_ip_address,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_floating_ip_list_long(self):
+ arglist = ['--long', ]
+ verifylist = [('long', True), ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with()
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
+
+ def test_floating_ip_list_status(self):
+ arglist = [
+ '--status', 'ACTIVE',
+ '--long',
+ ]
+ verifylist = [
+ ('status', 'ACTIVE'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with(**{
+ 'status': 'ACTIVE',
+ })
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
+
+ def test_floating_ip_list_project(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id,
+ 'project_id': project.id, }
+
+ self.network.ips.assert_called_once_with(**filters)
+
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_floating_ip_list_project_domain(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ '--project-domain', project.domain_id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id,
+ 'project_id': project.id, }
+
+ self.network.ips.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_floating_ip_list_router(self):
+ arglist = [
+ '--router', 'fake_router_id',
+ '--long',
+ ]
+ verifylist = [
+ ('router', 'fake_router_id'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with(**{
+ 'router_id': 'fake_router_id',
+ })
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
+
class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
@@ -296,7 +566,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
floating_ip.floating_network_id,
floating_ip.id,
floating_ip.port_id,
- floating_ip.tenant_id,
+ floating_ip.project_id,
floating_ip.router_id,
floating_ip.status,
)
@@ -309,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,
]
@@ -320,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_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py
index c929ab82..c7c5a9b4 100644
--- a/openstackclient/tests/unit/network/v2/test_ip_availability.py
+++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py
@@ -107,6 +107,7 @@ class TestListIPAvailability(TestIPAvailability):
columns, data = self.cmd.take_action(parsed_args)
filters = {'tenant_id': self.project.id,
+ 'project_id': self.project.id,
'ip_version': 4}
self.network.network_ip_availabilities.assert_called_once_with(
@@ -117,8 +118,10 @@ class TestListIPAvailability(TestIPAvailability):
class TestShowIPAvailability(TestIPAvailability):
+ _network = network_fakes.FakeNetwork.create_one_network()
_ip_availability = \
- network_fakes.FakeIPAvailability.create_one_ip_availability()
+ network_fakes.FakeIPAvailability.create_one_ip_availability(
+ attrs={'network_id': _network.id})
columns = (
'network_id',
@@ -143,6 +146,8 @@ class TestShowIPAvailability(TestIPAvailability):
self.network.find_network_ip_availability = mock.Mock(
return_value=self._ip_availability)
+ self.network.find_network = mock.Mock(
+ return_value=self._network)
# Get the command object to test
self.cmd = ip_availability.ShowIPAvailability(
@@ -165,8 +170,10 @@ class TestShowIPAvailability(TestIPAvailability):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.network.find_network_ip_availability.assert_called_once_with(
+ self._ip_availability.network_id,
+ ignore_missing=False)
+ self.network.find_network.assert_called_once_with(
self._ip_availability.network_name,
ignore_missing=False)
-
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py
index 50a60c2d..bc1279ec 100644
--- a/openstackclient/tests/unit/network/v2/test_network.py
+++ b/openstackclient/tests/unit/network/v2/test_network.py
@@ -53,6 +53,8 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'availability_zone_hints': ["nova"],
}
)
+ qos_policy = (network_fakes.FakeNetworkQosPolicy.
+ create_one_qos_policy(attrs={'id': _network.qos_policy_id}))
columns = (
'admin_state_up',
@@ -60,11 +62,16 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'availability_zones',
'description',
'id',
+ 'ipv4_address_scope',
+ 'ipv6_address_scope',
'is_default',
'name',
'port_security_enabled',
'project_id',
- 'provider_network_type',
+ 'provider:network_type',
+ 'provider:physical_network',
+ 'provider:segmentation_id',
+ 'qos_policy_id',
'router:external',
'shared',
'status',
@@ -77,11 +84,16 @@ class TestCreateNetworkIdentityV3(TestNetwork):
utils.format_list(_network.availability_zones),
_network.description,
_network.id,
+ _network.ipv4_address_scope_id,
+ _network.ipv6_address_scope_id,
_network.is_default,
_network.name,
_network.is_port_security_enabled,
_network.project_id,
_network.provider_network_type,
+ _network.provider_physical_network,
+ _network.provider_segmentation_id,
+ _network.qos_policy_id,
network._format_router_external(_network.is_router_external),
_network.shared,
_network.status,
@@ -98,6 +110,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
self.projects_mock.get.return_value = self.project
self.domains_mock.get.return_value = self.domain
+ self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy)
def test_create_no_options(self):
arglist = []
@@ -140,6 +153,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
"--provider-network-type", "vlan",
"--provider-physical-network", "physnet1",
"--provider-segment", "400",
+ "--qos-policy", self.qos_policy.id,
"--transparent-vlan",
"--enable-port-security",
self._network.name,
@@ -156,6 +170,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
('provider_network_type', 'vlan'),
('physical_network', 'physnet1'),
('segmentation_id', '400'),
+ ('qos_policy', self.qos_policy.id),
('transparent_vlan', True),
('enable_port_security', True),
('name', self._network.name),
@@ -170,12 +185,15 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'name': self._network.name,
'shared': True,
'description': self._network.description,
+ # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor
'tenant_id': self.project.id,
+ 'project_id': self.project.id,
'is_default': True,
'router:external': True,
'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1',
'provider:segmentation_id': '400',
+ 'qos_policy_id': self.qos_policy.id,
'vlan_transparent': True,
'port_security_enabled': True,
})
@@ -224,11 +242,16 @@ class TestCreateNetworkIdentityV2(TestNetwork):
'availability_zones',
'description',
'id',
+ 'ipv4_address_scope',
+ 'ipv6_address_scope',
'is_default',
'name',
'port_security_enabled',
'project_id',
- 'provider_network_type',
+ 'provider:network_type',
+ 'provider:physical_network',
+ 'provider:segmentation_id',
+ 'qos_policy_id',
'router:external',
'shared',
'status',
@@ -241,11 +264,16 @@ class TestCreateNetworkIdentityV2(TestNetwork):
utils.format_list(_network.availability_zones),
_network.description,
_network.id,
+ _network.ipv4_address_scope_id,
+ _network.ipv6_address_scope_id,
_network.is_default,
_network.name,
_network.is_port_security_enabled,
_network.project_id,
_network.provider_network_type,
+ _network.provider_physical_network,
+ _network.provider_segmentation_id,
+ _network.qos_policy_id,
network._format_router_external(_network.is_router_external),
_network.shared,
_network.status,
@@ -293,7 +321,9 @@ class TestCreateNetworkIdentityV2(TestNetwork):
self.network.create_network.assert_called_once_with(**{
'admin_state_up': True,
'name': self._network.name,
+ # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor
'tenant_id': self.project.id,
+ 'project_id': self.project.id,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -461,6 +491,13 @@ class TestListNetwork(TestNetwork):
self.network.networks = mock.Mock(return_value=self._network)
+ self._agent = \
+ network_fakes.FakeNetworkAgent.create_one_network_agent()
+ self.network.get_agent = mock.Mock(return_value=self._agent)
+
+ self.network.dhcp_agent_hosting_networks = mock.Mock(
+ return_value=self._network)
+
def test_network_list_no_options(self):
arglist = []
verifylist = [
@@ -494,7 +531,7 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'router:external': True}
+ **{'router:external': True, 'is_router_external': True}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -511,7 +548,7 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'router:external': False}
+ **{'router:external': False, 'is_router_external': False}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -567,7 +604,7 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'admin_state_up': True}
+ **{'admin_state_up': True, 'is_admin_state_up': True}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -585,7 +622,7 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'admin_state_up': False}
+ **{'admin_state_up': False, 'is_admin_state_up': False}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -603,13 +640,13 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'tenant_id': project.id}
+ **{'tenant_id': project.id, 'project_id': project.id}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
- def test_networ_list_project_domain(self):
+ def test_network_list_project_domain(self):
project = identity_fakes_v3.FakeProject.create_one_project()
self.projects_mock.get.return_value = project
arglist = [
@@ -622,9 +659,11 @@ class TestListNetwork(TestNetwork):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id}
+ filters = {'tenant_id': project.id, 'project_id': project.id}
self.network.networks.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
def test_network_list_share(self):
arglist = [
@@ -638,7 +677,7 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'shared': True}
+ **{'shared': True, 'is_shared': True}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -655,7 +694,7 @@ class TestListNetwork(TestNetwork):
columns, data = self.cmd.take_action(parsed_args)
self.network.networks.assert_called_once_with(
- **{'shared': False}
+ **{'shared': False, 'is_shared': False}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -679,11 +718,86 @@ class TestListNetwork(TestNetwork):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_network_list_provider_network_type(self):
+ network_type = self._network[0].provider_network_type
+ arglist = [
+ '--provider-network-type', network_type,
+ ]
+ verifylist = [
+ ('provider_network_type', network_type),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.networks.assert_called_once_with(
+ **{'provider:network_type': network_type,
+ 'provider_network_type': network_type}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_list_provider_physical_network(self):
+ physical_network = self._network[0].provider_physical_network
+ arglist = [
+ '--provider-physical-network', physical_network,
+ ]
+ verifylist = [
+ ('physical_network', physical_network),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.networks.assert_called_once_with(
+ **{'provider:physical_network': physical_network,
+ 'provider_physical_network': physical_network}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_list_provider_segment(self):
+ segmentation_id = self._network[0].provider_segmentation_id
+ arglist = [
+ '--provider-segment', segmentation_id,
+ ]
+ verifylist = [
+ ('segmentation_id', segmentation_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.networks.assert_called_once_with(
+ **{'provider:segmentation_id': segmentation_id,
+ 'provider_segmentation_id': segmentation_id}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_list_dhcp_agent(self):
+ arglist = [
+ '--agent', self._agent.id
+ ]
+ verifylist = [
+ ('agent_id', self._agent.id),
+ ]
+
+ attrs = {self._agent, }
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.dhcp_agent_hosting_networks.assert_called_once_with(
+ *attrs)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(list(data), list(self.data))
+
class TestSetNetwork(TestNetwork):
# The network to set.
_network = network_fakes.FakeNetwork.create_one_network()
+ qos_policy = (network_fakes.FakeNetworkQosPolicy.
+ create_one_qos_policy(attrs={'id': _network.qos_policy_id}))
def setUp(self):
super(TestSetNetwork, self).setUp()
@@ -691,6 +805,7 @@ class TestSetNetwork(TestNetwork):
self.network.update_network = mock.Mock(return_value=None)
self.network.find_network = mock.Mock(return_value=self._network)
+ self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy)
# Get the command object to test
self.cmd = network.SetNetwork(self.app, self.namespace)
@@ -709,6 +824,7 @@ class TestSetNetwork(TestNetwork):
'--provider-segment', '400',
'--no-transparent-vlan',
'--enable-port-security',
+ '--qos-policy', self.qos_policy.name,
]
verifylist = [
('network', self._network.name),
@@ -723,6 +839,7 @@ class TestSetNetwork(TestNetwork):
('segmentation_id', '400'),
('no_transparent_vlan', True),
('enable_port_security', True),
+ ('qos_policy', self.qos_policy.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -740,6 +857,7 @@ class TestSetNetwork(TestNetwork):
'provider:segmentation_id': '400',
'vlan_transparent': False,
'port_security_enabled': True,
+ 'qos_policy_id': self.qos_policy.id,
}
self.network.update_network.assert_called_once_with(
self._network, **attrs)
@@ -752,6 +870,7 @@ class TestSetNetwork(TestNetwork):
'--no-share',
'--internal',
'--disable-port-security',
+ '--no-qos-policy',
]
verifylist = [
('network', self._network.name),
@@ -759,6 +878,7 @@ class TestSetNetwork(TestNetwork):
('no_share', True),
('internal', True),
('disable_port_security', True),
+ ('no_qos_policy', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -769,6 +889,7 @@ class TestSetNetwork(TestNetwork):
'shared': False,
'router:external': False,
'port_security_enabled': False,
+ 'qos_policy_id': None,
}
self.network.update_network.assert_called_once_with(
self._network, **attrs)
@@ -798,11 +919,16 @@ class TestShowNetwork(TestNetwork):
'availability_zones',
'description',
'id',
+ 'ipv4_address_scope',
+ 'ipv6_address_scope',
'is_default',
'name',
'port_security_enabled',
'project_id',
- 'provider_network_type',
+ 'provider:network_type',
+ 'provider:physical_network',
+ 'provider:segmentation_id',
+ 'qos_policy_id',
'router:external',
'shared',
'status',
@@ -815,11 +941,16 @@ class TestShowNetwork(TestNetwork):
utils.format_list(_network.availability_zones),
_network.description,
_network.id,
+ _network.ipv4_address_scope_id,
+ _network.ipv6_address_scope_id,
_network.is_default,
_network.name,
_network.is_port_security_enabled,
_network.project_id,
_network.provider_network_type,
+ _network.provider_physical_network,
+ _network.provider_segmentation_id,
+ _network.qos_policy_id,
network._format_router_external(_network.is_router_external),
_network.shared,
_network.status,
@@ -1109,10 +1240,7 @@ class TestListNetworkCompute(TestNetworkCompute):
def test_network_list_no_options(self):
arglist = []
- verifylist = [
- ('external', False),
- ('long', False),
- ]
+ verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class Lister in cliff, abstract method take_action()
diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py
index 9f5b442a..0d741e06 100644
--- a/openstackclient/tests/unit/network/v2/test_network_agent.py
+++ b/openstackclient/tests/unit/network/v2/test_network_agent.py
@@ -31,6 +31,48 @@ class TestNetworkAgent(network_fakes.TestNetworkV2):
self.network = self.app.client_manager.network
+class TestAddNetworkToAgent(TestNetworkAgent):
+
+ net = network_fakes.FakeNetwork.create_one_network()
+ agent = network_fakes.FakeNetworkAgent.create_one_network_agent()
+
+ def setUp(self):
+ super(TestAddNetworkToAgent, self).setUp()
+
+ self.network.get_agent = mock.Mock(return_value=self.agent)
+ self.network.find_network = mock.Mock(return_value=self.net)
+ self.network.name = self.network.find_network.name
+ self.network.add_dhcp_agent_to_network = mock.Mock()
+ self.cmd = network_agent.AddNetworkToAgent(
+ self.app, self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_add_network_to_dhcp_agent(self):
+ arglist = [
+ '--dhcp',
+ self.agent.id,
+ self.net.id
+ ]
+ verifylist = [
+ ('dhcp', True),
+ ('agent_id', self.agent.id),
+ ('network', self.net.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ self.network.add_dhcp_agent_to_network.assert_called_once_with(
+ self.agent, self.net)
+
+
class TestDeleteNetworkAgent(TestNetworkAgent):
network_agents = (
@@ -66,7 +108,6 @@ class TestDeleteNetworkAgent(TestNetworkAgent):
def test_multi_network_agents_delete(self):
arglist = []
- verifylist = []
for n in self.network_agents:
arglist.append(n.id)
@@ -130,6 +171,7 @@ class TestListNetworkAgent(TestNetworkAgent):
)
data = []
for agent in network_agents:
+ agent.agent_type = 'DHCP agent'
data.append((
agent.id,
agent.agent_type,
@@ -140,11 +182,37 @@ class TestListNetworkAgent(TestNetworkAgent):
agent.binary,
))
+ network_agent_columns = (
+ 'ID',
+ 'Host',
+ 'Admin State Up',
+ 'Alive',
+ )
+
+ network_agent_data = []
+
+ for agent in network_agents:
+ network_agent_data.append((
+ agent.id,
+ agent.host,
+ network_agent._format_admin_state(agent.admin_state_up),
+ agent.alive,
+ ))
+
def setUp(self):
super(TestListNetworkAgent, self).setUp()
self.network.agents = mock.Mock(
return_value=self.network_agents)
+ _testagent = \
+ network_fakes.FakeNetworkAgent.create_one_network_agent()
+ self.network.get_agent = mock.Mock(return_value=_testagent)
+
+ self._testnetwork = network_fakes.FakeNetwork.create_one_network()
+ self.network.find_network = mock.Mock(return_value=self._testnetwork)
+ self.network.network_hosting_dhcp_agents = mock.Mock(
+ return_value=self.network_agents)
+
# Get the command object to test
self.cmd = network_agent.ListNetworkAgent(self.app, self.namespace)
@@ -159,7 +227,103 @@ class TestListNetworkAgent(TestNetworkAgent):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_network_agents_list_agent_type(self):
+ arglist = [
+ '--agent-type', 'dhcp',
+ ]
+ verifylist = [
+ ('agent_type', 'dhcp'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.agents.assert_called_once_with(**{
+ 'agent_type': self.network_agents[0].agent_type,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_agents_list_host(self):
+ arglist = [
+ '--host', self.network_agents[0].host,
+ ]
+ verifylist = [
+ ('host', self.network_agents[0].host),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.agents.assert_called_once_with(**{
+ 'host': self.network_agents[0].host,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_agents_list_networks(self):
+ arglist = [
+ '--network', self._testnetwork.id,
+ ]
+ verifylist = [
+ ('network', self._testnetwork.id),
+ ]
+
+ attrs = {self._testnetwork, }
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.network_hosting_dhcp_agents.assert_called_once_with(
+ *attrs)
+ self.assertEqual(self.network_agent_columns, columns)
+ self.assertEqual(list(self.network_agent_data), list(data))
+
+
+class TestRemoveNetworkFromAgent(TestNetworkAgent):
+
+ net = network_fakes.FakeNetwork.create_one_network()
+ agent = network_fakes.FakeNetworkAgent.create_one_network_agent()
+
+ def setUp(self):
+ super(TestRemoveNetworkFromAgent, self).setUp()
+
+ self.network.get_agent = mock.Mock(return_value=self.agent)
+ self.network.find_network = mock.Mock(return_value=self.net)
+ self.network.name = self.network.find_network.name
+ self.network.remove_dhcp_agent_from_network = mock.Mock()
+ self.cmd = network_agent.RemoveNetworkFromAgent(
+ self.app, self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_from_dhcp_agent(self):
+ arglist = [
+ '--dhcp',
+ self.agent.id,
+ self.net.id
+ ]
+ verifylist = [
+ ('dhcp', True),
+ ('agent_id', self.agent.id),
+ ('network', self.net.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ self.network.remove_dhcp_agent_from_network.assert_called_once_with(
+ self.agent, self.net)
+
+# TODO(huanxuan): Also update by the new attribute name
+# "is_admin_state_up" after sdk 0.9.12
class TestSetNetworkAgent(TestNetworkAgent):
_network_agent = (
@@ -289,6 +453,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_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py
new file mode 100644
index 00000000..1a231160
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py
@@ -0,0 +1,267 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from openstackclient.network.v2 import network_auto_allocated_topology
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+
+
+class TestAutoAllocatedTopology(network_fakes.TestNetworkV2):
+ def setUp(self):
+ super(TestAutoAllocatedTopology, self).setUp()
+ self.network = self.app.client_manager.network
+ self.projects_mock = self.app.client_manager.identity.projects
+
+
+class TestCreateAutoAllocatedTopology(TestAutoAllocatedTopology):
+ project = identity_fakes.FakeProject.create_one_project()
+ network_object = network_fakes.FakeNetwork.create_one_network()
+
+ topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology(
+ attrs={'id': network_object.id,
+ 'tenant_id': project.id}
+ )
+
+ columns = (
+ 'id',
+ 'project_id',
+ )
+
+ data = (
+ network_object.id,
+ project.id,
+ )
+
+ def setUp(self):
+ super(TestCreateAutoAllocatedTopology, self).setUp()
+
+ self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology(
+ self.app,
+ self.namespace)
+ self.network.get_auto_allocated_topology = mock.Mock(
+ return_value=self.topology)
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.network.get_auto_allocated_topology.assert_called_with(None)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_project_option(self):
+ arglist = [
+ '--project', self.project.id,
+ ]
+
+ verifylist = [
+ ('project', self.project.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.network.get_auto_allocated_topology.assert_called_with(
+ self.project.id
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_project_domain_option(self):
+ arglist = [
+ '--project', self.project.id,
+ '--project-domain', self.project.domain_id,
+ ]
+
+ verifylist = [
+ ('project', self.project.id),
+ ('project_domain', self.project.domain_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.network.get_auto_allocated_topology.assert_called_with(
+ self.project.id
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_or_show_option(self):
+ arglist = [
+ '--or-show',
+ ]
+
+ verifylist = [
+ ('or_show', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.network.get_auto_allocated_topology.assert_called_with(None)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestValidateAutoAllocatedTopology(TestAutoAllocatedTopology):
+ project = identity_fakes.FakeProject.create_one_project()
+ network_object = network_fakes.FakeNetwork.create_one_network()
+
+ topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology(
+ attrs={'id': network_object.id,
+ 'tenant_id': project.id}
+ )
+
+ columns = (
+ 'id',
+ 'project_id',
+ )
+
+ data = (
+ network_object.id,
+ project.id,
+ )
+
+ def setUp(self):
+ super(TestValidateAutoAllocatedTopology, self).setUp()
+
+ self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology(
+ self.app,
+ self.namespace)
+ self.network.validate_auto_allocated_topology = mock.Mock(
+ return_value=self.topology)
+
+ def test_show_dry_run_no_project(self):
+ arglist = [
+ '--check-resources',
+ ]
+ verifylist = [
+ ('check_resources', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.validate_auto_allocated_topology.assert_called_with(
+ None)
+
+ def test_show_dry_run_project_option(self):
+ arglist = [
+ '--check-resources',
+ '--project', self.project.id,
+ ]
+ verifylist = [
+ ('check_resources', True),
+ ('project', self.project.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.validate_auto_allocated_topology.assert_called_with(
+ self.project.id)
+
+ def test_show_dry_run_project_domain_option(self):
+ arglist = [
+ '--check-resources',
+ '--project', self.project.id,
+ '--project-domain', self.project.domain_id,
+ ]
+ verifylist = [
+ ('check_resources', True),
+ ('project', self.project.id),
+ ('project_domain', self.project.domain_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.validate_auto_allocated_topology.assert_called_with(
+ self.project.id)
+
+
+class TestDeleteAutoAllocatedTopology(TestAutoAllocatedTopology):
+ project = identity_fakes.FakeProject.create_one_project()
+ network_object = network_fakes.FakeNetwork.create_one_network()
+
+ topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology(
+ attrs={'id': network_object.id,
+ 'tenant_id': project.id}
+ )
+
+ def setUp(self):
+ super(TestDeleteAutoAllocatedTopology, self).setUp()
+
+ self.cmd = network_auto_allocated_topology.DeleteAutoAllocatedTopology(
+ self.app,
+ self.namespace)
+ self.network.delete_auto_allocated_topology = mock.Mock(
+ return_value=None)
+
+ def test_delete_no_project(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.delete_auto_allocated_topology.assert_called_once_with(
+ None)
+
+ self.assertIsNone(result)
+
+ def test_delete_project_arg(self):
+ arglist = [
+ '--project', self.project.id,
+ ]
+ verifylist = [
+ ('project', self.project.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.delete_auto_allocated_topology.assert_called_once_with(
+ self.project.id)
+
+ self.assertIsNone(result)
+
+ def test_delete_project_domain_arg(self):
+ arglist = [
+ '--project', self.project.id,
+ '--project-domain', self.project.domain_id,
+ ]
+ verifylist = [
+ ('project', self.project.id),
+ ('project_domain', self.project.domain_id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.delete_auto_allocated_topology.assert_called_once_with(
+ self.project.id)
+
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py
new file mode 100644
index 00000000..11e27841
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py
@@ -0,0 +1,407 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import mock
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_flavor
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+from openstackclient.tests.unit import utils as tests_utils
+
+
+class TestNetworkFlavor(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkFlavor, self).setUp()
+
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
+
+
+class TestCreateNetworkFlavor(TestNetworkFlavor):
+
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
+ # The new network flavor created.
+ new_network_flavor = (
+ network_fakes.FakeNetworkFlavor.create_one_network_flavor())
+ columns = (
+ 'description',
+ 'enabled',
+ 'id',
+ 'name',
+ 'project_id',
+ 'service_type'
+ )
+ data = (
+ new_network_flavor.description,
+ new_network_flavor.enabled,
+ new_network_flavor.id,
+ new_network_flavor.name,
+ new_network_flavor.project_id,
+ new_network_flavor.service_type,
+ )
+
+ def setUp(self):
+ super(TestCreateNetworkFlavor, self).setUp()
+ self.network.create_flavor = mock.Mock(
+ return_value=self.new_network_flavor)
+
+ # Get the command object to test
+ self.cmd = network_flavor.CreateNetworkFlavor(self.app, self.namespace)
+
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ '--service-type', self.new_network_flavor.service_type,
+ self.new_network_flavor.name,
+ ]
+ verifylist = [
+ ('service_type', self.new_network_flavor.service_type),
+ ('name', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_flavor.assert_called_once_with(**{
+ 'service_type': self.new_network_flavor.service_type,
+ 'name': self.new_network_flavor.name,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_all_options(self):
+ arglist = [
+ '--description', self.new_network_flavor.description,
+ '--enable',
+ '--project', self.new_network_flavor.project_id,
+ '--project-domain', self.domain.name,
+ '--service-type', self.new_network_flavor.service_type,
+ self.new_network_flavor.name,
+ ]
+ verifylist = [
+ ('description', self.new_network_flavor.description),
+ ('enable', True),
+ ('project', self.new_network_flavor.project_id),
+ ('project_domain', self.domain.name),
+ ('service_type', self.new_network_flavor.service_type),
+ ('name', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_flavor.assert_called_once_with(**{
+ 'description': self.new_network_flavor.description,
+ 'enabled': True,
+ 'tenant_id': self.project.id,
+ 'service_type': self.new_network_flavor.service_type,
+ 'name': self.new_network_flavor.name,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_disable(self):
+ arglist = [
+ '--disable',
+ '--service-type', self.new_network_flavor.service_type,
+ self.new_network_flavor.name,
+ ]
+ verifylist = [
+ ('disable', True),
+ ('service_type', self.new_network_flavor.service_type),
+ ('name', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_flavor.assert_called_once_with(**{
+ 'enabled': False,
+ 'service_type': self.new_network_flavor.service_type,
+ 'name': self.new_network_flavor.name,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestDeleteNetworkFlavor(TestNetworkFlavor):
+
+ # The network flavor to delete.
+ _network_flavors = (
+ network_fakes.FakeNetworkFlavor.create_flavor(count=2))
+
+ def setUp(self):
+ super(TestDeleteNetworkFlavor, self).setUp()
+ self.network.delete_flavor = mock.Mock(return_value=None)
+ self.network.find_flavor = (
+ network_fakes.FakeNetworkFlavor.get_flavor(
+ network_flavors=self._network_flavors)
+ )
+
+ # Get the command object to test
+ self.cmd = network_flavor.DeleteNetworkFlavor(self.app, self.namespace)
+
+ def test_network_flavor_delete(self):
+ arglist = [
+ self._network_flavors[0].name,
+ ]
+ verifylist = [
+ ('flavor', [self._network_flavors[0].name]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_flavor.assert_called_once_with(
+ self._network_flavors[0].name, ignore_missing=False)
+ self.network.delete_flavor.assert_called_once_with(
+ self._network_flavors[0])
+ self.assertIsNone(result)
+
+ def test_multi_network_flavors_delete(self):
+ arglist = []
+ verifylist = []
+
+ for a in self._network_flavors:
+ arglist.append(a.name)
+ verifylist = [
+ ('flavor', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for a in self._network_flavors:
+ calls.append(mock.call(a))
+ self.network.delete_flavor.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_network_flavors_delete_with_exception(self):
+ arglist = [
+ self._network_flavors[0].name,
+ 'unexist_network_flavor',
+ ]
+ verifylist = [
+ ('flavor',
+ [self._network_flavors[0].name, 'unexist_network_flavor']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._network_flavors[0], exceptions.CommandError]
+ self.network.find_flavor = (
+ mock.Mock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 flavors failed to delete.', str(e))
+
+ self.network.find_flavor.assert_any_call(
+ self._network_flavors[0].name, ignore_missing=False)
+ self.network.find_flavor.assert_any_call(
+ 'unexist_network_flavor', ignore_missing=False)
+ self.network.delete_flavor.assert_called_once_with(
+ self._network_flavors[0]
+ )
+
+
+class TestListNetworkFlavor(TestNetworkFlavor):
+
+ # The network flavors to list up.
+ _network_flavors = (
+ network_fakes.FakeNetworkFlavor.create_flavor(count=2))
+ columns = (
+ 'ID',
+ 'Name',
+ 'Enabled',
+ 'Service Type',
+ 'Description',
+ )
+ data = []
+ for flavor in _network_flavors:
+ data.append((
+ flavor.id,
+ flavor.name,
+ flavor.enabled,
+ flavor.service_type,
+ flavor.description,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkFlavor, self).setUp()
+ self.network.flavors = mock.Mock(
+ return_value=self._network_flavors)
+
+ # Get the command object to test
+ self.cmd = network_flavor.ListNetworkFlavor(self.app, self.namespace)
+
+ def test_network_flavor_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.flavors.assert_called_once_with(**{})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestShowNetworkFlavor(TestNetworkFlavor):
+
+ # The network flavor to show.
+ new_network_flavor = (
+ network_fakes.FakeNetworkFlavor.create_one_network_flavor())
+ columns = (
+ 'description',
+ 'enabled',
+ 'id',
+ 'name',
+ 'project_id',
+ 'service_type'
+ )
+ data = (
+ new_network_flavor.description,
+ new_network_flavor.enabled,
+ new_network_flavor.id,
+ new_network_flavor.name,
+ new_network_flavor.project_id,
+ new_network_flavor.service_type,
+ )
+
+ def setUp(self):
+ super(TestShowNetworkFlavor, self).setUp()
+ self.network.find_flavor = mock.Mock(
+ return_value=self.new_network_flavor)
+
+ # Get the command object to test
+ self.cmd = network_flavor.ShowNetworkFlavor(self.app, self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self.new_network_flavor.name,
+ ]
+ verifylist = [
+ ('flavor', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_flavor.assert_called_once_with(
+ self.new_network_flavor.name, ignore_missing=False)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestSetNetworkFlavor(TestNetworkFlavor):
+
+ # The network flavor to set.
+ new_network_flavor = (
+ network_fakes.FakeNetworkFlavor.create_one_network_flavor())
+
+ def setUp(self):
+ super(TestSetNetworkFlavor, self).setUp()
+ self.network.update_flavor = mock.Mock(return_value=None)
+ self.network.find_flavor = mock.Mock(
+ return_value=self.new_network_flavor)
+
+ # Get the command object to test
+ self.cmd = network_flavor.SetNetworkFlavor(self.app, self.namespace)
+
+ def test_set_nothing(self):
+ arglist = [self.new_network_flavor.name, ]
+ verifylist = [
+ ('flavor', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_flavor.assert_called_with(
+ self.new_network_flavor, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_name_and_enable(self):
+ arglist = [
+ '--name', 'new_network_flavor',
+ '--enable',
+ self.new_network_flavor.name,
+ ]
+ verifylist = [
+ ('name', 'new_network_flavor'),
+ ('enable', True),
+ ('flavor', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'name': "new_network_flavor",
+ 'enabled': True,
+ }
+ self.network.update_flavor.assert_called_with(
+ self.new_network_flavor, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_disable(self):
+ arglist = [
+ '--disable',
+ self.new_network_flavor.name,
+ ]
+ verifylist = [
+ ('disable', True),
+ ('flavor', self.new_network_flavor.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'enabled': False,
+ }
+ self.network.update_flavor.assert_called_with(
+ self.new_network_flavor, **attrs)
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py
new file mode 100644
index 00000000..91683241
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py
@@ -0,0 +1,448 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_flavor_profile
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+
+
+class TestFlavorProfile(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestFlavorProfile, self).setUp()
+ # Get the network client
+ self.network = self.app.client_manager.network
+ # Get the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
+
+
+class TestCreateFlavorProfile(TestFlavorProfile):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
+ new_flavor_profile = (
+ network_fakes.FakeNetworkFlavorProfile.
+ create_one_service_profile()
+ )
+ columns = (
+ 'description',
+ 'driver',
+ 'enabled',
+ 'id',
+ 'metainfo',
+ 'project_id',
+ )
+
+ data = (
+ new_flavor_profile.description,
+ new_flavor_profile.driver,
+ new_flavor_profile.enabled,
+ new_flavor_profile.id,
+ new_flavor_profile.metainfo,
+ new_flavor_profile.project_id,
+ )
+
+ def setUp(self):
+ super(TestCreateFlavorProfile, self).setUp()
+ self.network.create_service_profile = mock.Mock(
+ return_value=self.new_flavor_profile)
+ self.projects_mock.get.return_value = self.project
+ # Get the command object to test
+ self.cmd = (network_flavor_profile.CreateNetworkFlavorProfile(
+ self.app, self.namespace))
+
+ def test_create_all_options(self):
+ arglist = [
+ "--description", self.new_flavor_profile.description,
+ "--project", self.new_flavor_profile.project_id,
+ '--project-domain', self.domain.name,
+ "--enable",
+ "--driver", self.new_flavor_profile.driver,
+ "--metainfo", self.new_flavor_profile.metainfo,
+ ]
+
+ verifylist = [
+ ('description', self.new_flavor_profile.description),
+ ('project', self.new_flavor_profile.project_id),
+ ('project_domain', self.domain.name),
+ ('enable', True),
+ ('driver', self.new_flavor_profile.driver),
+ ('metainfo', self.new_flavor_profile.metainfo)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_service_profile.assert_called_once_with(
+ **{'description': self.new_flavor_profile.description,
+ 'tenant_id': self.project.id,
+ 'enabled': self.new_flavor_profile.enabled,
+ 'driver': self.new_flavor_profile.driver,
+ 'metainfo': self.new_flavor_profile.metainfo}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_with_metainfo(self):
+ arglist = [
+ "--description", self.new_flavor_profile.description,
+ "--project", self.new_flavor_profile.project_id,
+ '--project-domain', self.domain.name,
+ "--enable",
+ "--metainfo", self.new_flavor_profile.metainfo,
+ ]
+
+ verifylist = [
+ ('description', self.new_flavor_profile.description),
+ ('project', self.new_flavor_profile.project_id),
+ ('project_domain', self.domain.name),
+ ('enable', True),
+ ('metainfo', self.new_flavor_profile.metainfo)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_service_profile.assert_called_once_with(
+ **{'description': self.new_flavor_profile.description,
+ 'tenant_id': self.project.id,
+ 'enabled': self.new_flavor_profile.enabled,
+ 'metainfo': self.new_flavor_profile.metainfo}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_with_driver(self):
+ arglist = [
+ "--description", self.new_flavor_profile.description,
+ "--project", self.new_flavor_profile.project_id,
+ '--project-domain', self.domain.name,
+ "--enable",
+ "--driver", self.new_flavor_profile.driver,
+ ]
+
+ verifylist = [
+ ('description', self.new_flavor_profile.description),
+ ('project', self.new_flavor_profile.project_id),
+ ('project_domain', self.domain.name),
+ ('enable', True),
+ ('driver', self.new_flavor_profile.driver),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_service_profile.assert_called_once_with(
+ **{'description': self.new_flavor_profile.description,
+ 'tenant_id': self.project.id,
+ 'enabled': self.new_flavor_profile.enabled,
+ 'driver': self.new_flavor_profile.driver,
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_without_driver_and_metainfo(self):
+ arglist = [
+ "--description", self.new_flavor_profile.description,
+ "--project", self.new_flavor_profile.project_id,
+ '--project-domain', self.domain.name,
+ "--enable",
+ ]
+
+ verifylist = [
+ ('description', self.new_flavor_profile.description),
+ ('project', self.new_flavor_profile.project_id),
+ ('project_domain', self.domain.name),
+ ('enable', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+
+ def test_create_disable(self):
+ arglist = [
+ '--disable',
+ '--driver', self.new_flavor_profile.driver,
+ ]
+ verifylist = [
+ ('disable', True),
+ ('driver', self.new_flavor_profile.driver)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_service_profile.assert_called_once_with(**{
+ 'enabled': False,
+ 'driver': self.new_flavor_profile.driver,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestDeleteFlavorProfile(TestFlavorProfile):
+
+ # The network flavor_profiles to delete.
+ _network_flavor_profiles = (
+ network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2))
+
+ def setUp(self):
+ super(TestDeleteFlavorProfile, self).setUp()
+ self.network.delete_service_profile = mock.Mock(return_value=None)
+ self.network.find_service_profile = (
+ network_fakes.FakeNetworkFlavorProfile.get_service_profile(
+ flavor_profile=self._network_flavor_profiles)
+ )
+
+ # Get the command object to test
+ self.cmd = network_flavor_profile.DeleteNetworkFlavorProfile(
+ self.app, self.namespace)
+
+ def test_network_flavor_profile_delete(self):
+ arglist = [
+ self._network_flavor_profiles[0].id,
+ ]
+ verifylist = [
+ ('flavor_profile', [self._network_flavor_profiles[0].id]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_service_profile.assert_called_once_with(
+ self._network_flavor_profiles[0].id, ignore_missing=False)
+ self.network.delete_service_profile.assert_called_once_with(
+ self._network_flavor_profiles[0])
+ self.assertIsNone(result)
+
+ def test_multi_network_flavor_profiles_delete(self):
+ arglist = []
+
+ for a in self._network_flavor_profiles:
+ arglist.append(a.id)
+ verifylist = [
+ ('flavor_profile', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for a in self._network_flavor_profiles:
+ calls.append(mock.call(a))
+ self.network.delete_service_profile.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_network_flavor_profiles_delete_with_exception(self):
+ arglist = [
+ self._network_flavor_profiles[0].id,
+ 'unexist_network_flavor_profile',
+ ]
+ verifylist = [
+ ('flavor_profile',
+ [self._network_flavor_profiles[0].id,
+ 'unexist_network_flavor_profile']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._network_flavor_profiles[0],
+ exceptions.CommandError]
+ self.network.find_service_profile = (
+ mock.Mock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 flavor_profiles failed to delete.',
+ str(e))
+
+ self.network.find_service_profile.assert_any_call(
+ self._network_flavor_profiles[0].id, ignore_missing=False)
+ self.network.find_service_profile.assert_any_call(
+ 'unexist_network_flavor_profile', ignore_missing=False)
+ self.network.delete_service_profile.assert_called_once_with(
+ self._network_flavor_profiles[0]
+ )
+
+
+class TestListFlavorProfile(TestFlavorProfile):
+
+ # The network flavor profiles list
+ _network_flavor_profiles = (
+ network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2))
+
+ columns = (
+ 'ID',
+ 'Driver',
+ 'Enabled',
+ 'Metainfo',
+ 'Description',
+ )
+ data = []
+ for flavor_profile in _network_flavor_profiles:
+ data.append((
+ flavor_profile.id,
+ flavor_profile.driver,
+ flavor_profile.enabled,
+ flavor_profile.metainfo,
+ flavor_profile.description,
+ ))
+
+ def setUp(self):
+ super(TestListFlavorProfile, self).setUp()
+ self.network.service_profiles = mock.Mock(
+ return_value=self._network_flavor_profiles)
+
+ # Get the command object to test
+ self.cmd = network_flavor_profile.ListNetworkFlavorProfile(
+ self.app, self.namespace)
+
+ def test_network_flavor_profile_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.service_profiles.assert_called_once_with(**{})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestShowFlavorProfile(TestFlavorProfile):
+
+ # The network flavor profile to show.
+ network_flavor_profile = (
+ network_fakes.FakeNetworkFlavorProfile.create_one_service_profile())
+ columns = (
+ 'description',
+ 'driver',
+ 'enabled',
+ 'id',
+ 'metainfo',
+ 'project_id',
+ )
+ data = (
+ network_flavor_profile.description,
+ network_flavor_profile.driver,
+ network_flavor_profile.enabled,
+ network_flavor_profile.id,
+ network_flavor_profile.metainfo,
+ network_flavor_profile.project_id,
+ )
+
+ def setUp(self):
+ super(TestShowFlavorProfile, self).setUp()
+ self.network.find_service_profile = mock.Mock(
+ return_value=self.network_flavor_profile)
+
+ # Get the command object to test
+ self.cmd = network_flavor_profile.ShowNetworkFlavorProfile(
+ self.app, self.namespace)
+
+ def test_show_all_options(self):
+ arglist = [
+ self.network_flavor_profile.id,
+ ]
+ verifylist = [
+ ('flavor_profile', self.network_flavor_profile.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_service_profile.assert_called_once_with(
+ self.network_flavor_profile.id, ignore_missing=False)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestSetFlavorProfile(TestFlavorProfile):
+
+ # The network flavor profile to set.
+ network_flavor_profile = (
+ network_fakes.FakeNetworkFlavorProfile.create_one_service_profile())
+
+ def setUp(self):
+ super(TestSetFlavorProfile, self).setUp()
+ self.network.update_service_profile = mock.Mock(return_value=None)
+ self.network.find_service_profile = mock.Mock(
+ return_value=self.network_flavor_profile)
+
+ # Get the command object to test
+ self.cmd = network_flavor_profile.SetNetworkFlavorProfile(
+ self.app, self.namespace)
+
+ def test_set_nothing(self):
+ arglist = [self.network_flavor_profile.id]
+ verifylist = [
+ ('flavor_profile', self.network_flavor_profile.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_service_profile.assert_called_with(
+ self.network_flavor_profile, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_enable(self):
+ arglist = [
+ '--enable',
+ self.network_flavor_profile.id,
+ ]
+ verifylist = [
+ ('enable', True),
+ ('flavor_profile', self.network_flavor_profile.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'enabled': True,
+ }
+ self.network.update_service_profile.assert_called_with(
+ self.network_flavor_profile, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_disable(self):
+ arglist = [
+ '--disable',
+ self.network_flavor_profile.id,
+ ]
+ verifylist = [
+ ('disable', True),
+ ('flavor_profile', self.network_flavor_profile.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'enabled': False,
+ }
+ self.network.update_service_profile.assert_called_with(
+ self.network_flavor_profile, **attrs)
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/network/v2/test_network_meter.py b/openstackclient/tests/unit/network/v2/test_network_meter.py
new file mode 100644
index 00000000..2b96f7a6
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_meter.py
@@ -0,0 +1,304 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+from mock import call
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_meter
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+from openstackclient.tests.unit import utils as tests_utils
+
+
+class TestMeter(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestMeter, self).setUp()
+ self.network = self.app.client_manager.network
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.domains_mock = self.app.client_manager.identity.domains
+
+
+class TestCreateMeter(TestMeter):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
+
+ new_meter = (
+ network_fakes.FakeNetworkMeter.
+ create_one_meter()
+ )
+ columns = (
+ 'description',
+ 'id',
+ 'name',
+ 'project_id',
+ 'shared',
+ )
+
+ data = (
+ new_meter.description,
+ new_meter.id,
+ new_meter.name,
+ new_meter.project_id,
+ new_meter.shared,
+ )
+
+ def setUp(self):
+ super(TestCreateMeter, self).setUp()
+ self.network.create_metering_label = mock.Mock(
+ return_value=self.new_meter)
+ self.projects_mock.get.return_value = self.project
+ self.cmd = network_meter.CreateMeter(self.app, self.namespace)
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ self.new_meter.name,
+ ]
+
+ verifylist = [
+ ('name', self.new_meter.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_metering_label.assert_called_once_with(
+ **{'name': self.new_meter.name}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_all_options(self):
+ arglist = [
+ "--description", self.new_meter.description,
+ "--project", self.new_meter.project_id,
+ "--project-domain", self.domain.name,
+ "--share",
+ self.new_meter.name,
+ ]
+
+ verifylist = [
+ ('description', self.new_meter.description),
+ ('name', self.new_meter.name),
+ ('project', self.new_meter.project_id),
+ ('project_domain', self.domain.name),
+ ('share', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_metering_label.assert_called_once_with(
+ **{'description': self.new_meter.description,
+ 'name': self.new_meter.name,
+ 'tenant_id': self.project.id,
+ 'shared': True, }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestDeleteMeter(TestMeter):
+
+ def setUp(self):
+ super(TestDeleteMeter, self).setUp()
+
+ self.meter_list = \
+ network_fakes.FakeNetworkMeter.create_meter(count=2)
+
+ self.network.delete_metering_label = mock.Mock(return_value=None)
+
+ self.network.find_metering_label = network_fakes \
+ .FakeNetworkMeter.get_meter(
+ meter=self.meter_list
+ )
+
+ self.cmd = network_meter.DeleteMeter(self.app, self.namespace)
+
+ def test_delete_one_meter(self):
+ arglist = [
+ self.meter_list[0].name,
+ ]
+ verifylist = [
+ ('meter', [self.meter_list[0].name]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.delete_metering_label.assert_called_once_with(
+ self.meter_list[0]
+ )
+ self.assertIsNone(result)
+
+ def test_delete_multiple_meters(self):
+ arglist = []
+ for n in self.meter_list:
+ arglist.append(n.id)
+ verifylist = [
+ ('meter', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for n in self.meter_list:
+ calls.append(call(n))
+ self.network.delete_metering_label.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_meter_exception(self):
+ arglist = [
+ self.meter_list[0].id,
+ 'xxxx-yyyy-zzzz',
+ self.meter_list[1].id,
+ ]
+ verifylist = [
+ ('meter', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ return_find = [
+ self.meter_list[0],
+ exceptions.NotFound('404'),
+ self.meter_list[1],
+ ]
+ self.network.find_meter = mock.Mock(side_effect=return_find)
+
+ ret_delete = [
+ None,
+ exceptions.NotFound('404'),
+ ]
+ self.network.delete_metering_label = mock.Mock(side_effect=ret_delete)
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ calls = [
+ call(self.meter_list[0]),
+ call(self.meter_list[1]),
+ ]
+ self.network.delete_metering_label.assert_has_calls(calls)
+
+
+class TestListMeter(TestMeter):
+
+ meter_list = \
+ network_fakes.FakeNetworkMeter.create_meter(count=2)
+
+ columns = (
+ 'ID',
+ 'Name',
+ 'Description',
+ 'Shared',
+ )
+
+ data = []
+
+ for meters in meter_list:
+ data.append((
+ meters.id,
+ meters.name,
+ meters.description,
+ meters.shared,
+ ))
+
+ def setUp(self):
+ super(TestListMeter, self).setUp()
+
+ self.network.metering_labels = mock.Mock(
+ return_value=self.meter_list
+ )
+
+ self.cmd = network_meter.ListMeter(self.app, self.namespace)
+
+ def test_meter_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.metering_labels.assert_called_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestShowMeter(TestMeter):
+ new_meter = (
+ network_fakes.FakeNetworkMeter.
+ create_one_meter()
+ )
+ columns = (
+ 'description',
+ 'id',
+ 'name',
+ 'project_id',
+ 'shared',
+ )
+
+ data = (
+ new_meter.description,
+ new_meter.id,
+ new_meter.name,
+ new_meter.project_id,
+ new_meter.shared,
+ )
+
+ def setUp(self):
+ super(TestShowMeter, self).setUp()
+
+ self.cmd = network_meter.ShowMeter(self.app, self.namespace)
+
+ self.network.find_metering_label = \
+ mock.Mock(return_value=self.new_meter)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_meter_show_option(self):
+ arglist = [
+ self.new_meter.name,
+ ]
+ verifylist = [
+ ('meter', self.new_meter.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_metering_label.assert_called_with(
+ self.new_meter.name, ignore_missing=False
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py
new file mode 100644
index 00000000..af481793
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py
@@ -0,0 +1,321 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+from mock import call
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_meter_rule
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+from openstackclient.tests.unit import utils as tests_utils
+
+
+class TestMeterRule(network_fakes.TestNetworkV2):
+ def setUp(self):
+ super(TestMeterRule, self).setUp()
+ self.network = self.app.client_manager.network
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.domains_mock = self.app.client_manager.identity.domains
+
+
+class TestCreateMeterRule(TestMeterRule):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
+
+ new_rule = (
+ network_fakes.FakeNetworkMeterRule.
+ create_one_rule()
+ )
+
+ columns = (
+ 'direction',
+ 'excluded',
+ 'id',
+ 'metering_label_id',
+ 'project_id',
+ 'remote_ip_prefix',
+ )
+ data = (
+ new_rule.direction,
+ new_rule.excluded,
+ new_rule.id,
+ new_rule.metering_label_id,
+ new_rule.project_id,
+ new_rule.remote_ip_prefix,
+ )
+
+ def setUp(self):
+ super(TestCreateMeterRule, self).setUp()
+ fake_meter = network_fakes.FakeNetworkMeter.create_one_meter({
+ 'id': self.new_rule.metering_label_id})
+
+ self.network.create_metering_label_rule = mock.Mock(
+ return_value=self.new_rule)
+ self.projects_mock.get.return_value = self.project
+ self.cmd = network_meter_rule.CreateMeterRule(self.app,
+ self.namespace)
+ self.network.find_metering_label = mock.Mock(
+ return_value=fake_meter)
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ self.new_rule.metering_label_id,
+ "--remote-ip-prefix", self.new_rule.remote_ip_prefix,
+ ]
+ verifylist = [
+ ('meter', self.new_rule.metering_label_id),
+ ('remote_ip_prefix', self.new_rule.remote_ip_prefix),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_metering_label_rule.assert_called_once_with(
+ **{'direction': 'ingress',
+ 'metering_label_id': self.new_rule.metering_label_id,
+ 'remote_ip_prefix': self.new_rule.remote_ip_prefix, }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_all_options(self):
+ arglist = [
+ "--ingress",
+ "--include",
+ self.new_rule.metering_label_id,
+ "--remote-ip-prefix", self.new_rule.remote_ip_prefix,
+ ]
+ verifylist = [
+ ('ingress', True),
+ ('include', True),
+ ('meter', self.new_rule.metering_label_id),
+ ('remote_ip_prefix', self.new_rule.remote_ip_prefix),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_metering_label_rule.assert_called_once_with(
+ **{'direction': self.new_rule.direction,
+ 'excluded': self.new_rule.excluded,
+ 'metering_label_id': self.new_rule.metering_label_id,
+ 'remote_ip_prefix': self.new_rule.remote_ip_prefix, }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestDeleteMeterRule(TestMeterRule):
+ def setUp(self):
+ super(TestDeleteMeterRule, self).setUp()
+ self.rule_list = \
+ network_fakes.FakeNetworkMeterRule.create_meter_rule(
+ count=2
+ )
+ self.network.delete_metering_label_rule = mock.Mock(return_value=None)
+
+ self.network.find_metering_label_rule = network_fakes \
+ .FakeNetworkMeterRule.get_meter_rule(
+ meter_rule=self.rule_list
+ )
+
+ self.cmd = network_meter_rule.DeleteMeterRule(self.app,
+ self.namespace)
+
+ def test_delete_one_rule(self):
+ arglist = [
+ self.rule_list[0].id,
+ ]
+ verifylist = [
+ ('meter_rule_id', [self.rule_list[0].id]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.delete_metering_label_rule.assert_called_once_with(
+ self.rule_list[0]
+ )
+ self.assertIsNone(result)
+
+ def test_delete_multiple_rules(self):
+ arglist = []
+ for rule in self.rule_list:
+ arglist.append(rule.id)
+ verifylist = [
+ ('meter_rule_id', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for rule in self.rule_list:
+ calls.append(call(rule))
+ self.network.delete_metering_label_rule.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_rules_exception(self):
+ arglist = [
+ self.rule_list[0].id,
+ 'xxxx-yyyy-zzzz',
+ self.rule_list[1].id,
+ ]
+ verifylist = [
+ ('meter_rule_id', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ return_find = [
+ self.rule_list[0],
+ exceptions.NotFound('404'),
+ self.rule_list[1],
+ ]
+ self.network.find_metering_label_rule = mock.Mock(
+ side_effect=return_find
+ )
+
+ ret_delete = [
+ None,
+ exceptions.NotFound('404'),
+ ]
+ self.network.delete_metering_label_rule = mock.Mock(
+ side_effect=ret_delete
+ )
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ calls = [
+ call(self.rule_list[0]),
+ call(self.rule_list[1]),
+ ]
+ self.network.delete_metering_label_rule.assert_has_calls(calls)
+
+
+class TestListMeterRule(TestMeterRule):
+ rule_list = \
+ network_fakes.FakeNetworkMeterRule.create_meter_rule(
+ count=2
+ )
+
+ columns = (
+ 'ID',
+ 'Excluded',
+ 'Direction',
+ 'Remote IP Prefix',
+ )
+
+ data = []
+
+ for rule in rule_list:
+ data.append((
+ rule.id,
+ rule.excluded,
+ rule.direction,
+ rule.remote_ip_prefix,
+ ))
+
+ def setUp(self):
+ super(TestListMeterRule, self).setUp()
+
+ self.network.metering_label_rules = mock.Mock(
+ return_value=self.rule_list
+ )
+
+ self.cmd = network_meter_rule.ListMeterRule(self.app,
+ self.namespace)
+
+ def test_rule_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.metering_label_rules.assert_called_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestShowMeterRule(TestMeterRule):
+ new_rule = (
+ network_fakes.FakeNetworkMeterRule.
+ create_one_rule()
+ )
+
+ columns = (
+ 'direction',
+ 'excluded',
+ 'id',
+ 'metering_label_id',
+ 'project_id',
+ 'remote_ip_prefix',
+ )
+
+ data = (
+ new_rule.direction,
+ new_rule.excluded,
+ new_rule.id,
+ new_rule.metering_label_id,
+ new_rule.project_id,
+ new_rule.remote_ip_prefix,
+ )
+
+ def setUp(self):
+ super(TestShowMeterRule, self).setUp()
+
+ self.cmd = network_meter_rule.ShowMeterRule(self.app,
+ self.namespace)
+
+ self.network.find_metering_label_rule = \
+ mock.Mock(return_value=self.new_rule)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_label_rule_show_option(self):
+ arglist = [
+ self.new_rule.id,
+ ]
+ verifylist = [
+ ('meter_rule_id', self.new_rule.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_metering_label_rule.assert_called_with(
+ self.new_rule.id, ignore_missing=False
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py
new file mode 100644
index 00000000..667f5015
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py
@@ -0,0 +1,433 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+from mock import call
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_qos_policy
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+from openstackclient.tests.unit import utils as tests_utils
+
+
+class TestQosPolicy(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestQosPolicy, self).setUp()
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+
+
+class TestCreateNetworkQosPolicy(TestQosPolicy):
+
+ project = identity_fakes_v3.FakeProject.create_one_project()
+
+ # The new qos policy created.
+ new_qos_policy = (
+ network_fakes.FakeNetworkQosPolicy.create_one_qos_policy(
+ attrs={
+ 'tenant_id': project.id,
+ }
+ ))
+ columns = (
+ 'description',
+ 'id',
+ 'name',
+ 'project_id',
+ 'rules',
+ 'shared',
+ )
+
+ data = (
+ new_qos_policy.description,
+ new_qos_policy.id,
+ new_qos_policy.name,
+ new_qos_policy.project_id,
+ new_qos_policy.rules,
+ new_qos_policy.shared,
+ )
+
+ def setUp(self):
+ super(TestCreateNetworkQosPolicy, self).setUp()
+ self.network.create_qos_policy = mock.Mock(
+ return_value=self.new_qos_policy)
+
+ # Get the command object to test
+ self.cmd = network_qos_policy.CreateNetworkQosPolicy(
+ self.app, self.namespace)
+
+ self.projects_mock.get.return_value = self.project
+
+ def test_create_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_default_options(self):
+ arglist = [
+ self.new_qos_policy.name,
+ ]
+ verifylist = [
+ ('project', None),
+ ('name', self.new_qos_policy.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_qos_policy.assert_called_once_with(**{
+ 'name': self.new_qos_policy.name
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_all_options(self):
+ arglist = [
+ '--share',
+ '--project', self.project.name,
+ self.new_qos_policy.name,
+ '--description', 'QoS policy description',
+ ]
+ verifylist = [
+ ('share', True),
+ ('project', self.project.name),
+ ('name', self.new_qos_policy.name),
+ ('description', 'QoS policy description'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_qos_policy.assert_called_once_with(**{
+ 'shared': True,
+ 'tenant_id': self.project.id,
+ 'name': self.new_qos_policy.name,
+ 'description': 'QoS policy description',
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestDeleteNetworkQosPolicy(TestQosPolicy):
+
+ # The address scope to delete.
+ _qos_policies = (
+ network_fakes.FakeNetworkQosPolicy.create_qos_policies(count=2))
+
+ def setUp(self):
+ super(TestDeleteNetworkQosPolicy, self).setUp()
+ self.network.delete_qos_policy = mock.Mock(return_value=None)
+ self.network.find_qos_policy = (
+ network_fakes.FakeNetworkQosPolicy.get_qos_policies(
+ qos_policies=self._qos_policies)
+ )
+
+ # Get the command object to test
+ self.cmd = network_qos_policy.DeleteNetworkQosPolicy(
+ self.app, self.namespace)
+
+ def test_qos_policy_delete(self):
+ arglist = [
+ self._qos_policies[0].name,
+ ]
+ verifylist = [
+ ('policy', [self._qos_policies[0].name]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_qos_policy.assert_called_once_with(
+ self._qos_policies[0].name, ignore_missing=False)
+ self.network.delete_qos_policy.assert_called_once_with(
+ self._qos_policies[0])
+ self.assertIsNone(result)
+
+ def test_multi_qos_policies_delete(self):
+ arglist = []
+
+ for a in self._qos_policies:
+ arglist.append(a.name)
+ verifylist = [
+ ('policy', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for a in self._qos_policies:
+ calls.append(call(a))
+ self.network.delete_qos_policy.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_qos_policies_delete_with_exception(self):
+ arglist = [
+ self._qos_policies[0].name,
+ 'unexist_qos_policy',
+ ]
+ verifylist = [
+ ('policy',
+ [self._qos_policies[0].name, 'unexist_qos_policy']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._qos_policies[0], exceptions.CommandError]
+ self.network.find_qos_policy = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 QoS policies failed to delete.', str(e))
+
+ self.network.find_qos_policy.assert_any_call(
+ self._qos_policies[0].name, ignore_missing=False)
+ self.network.find_qos_policy.assert_any_call(
+ 'unexist_qos_policy', ignore_missing=False)
+ self.network.delete_qos_policy.assert_called_once_with(
+ self._qos_policies[0]
+ )
+
+
+class TestListNetworkQosPolicy(TestQosPolicy):
+
+ # The QoS policies to list up.
+ qos_policies = (
+ network_fakes.FakeNetworkQosPolicy.create_qos_policies(count=3))
+ columns = (
+ 'ID',
+ 'Name',
+ 'Shared',
+ 'Project',
+ )
+ data = []
+ for qos_policy in qos_policies:
+ data.append((
+ qos_policy.id,
+ qos_policy.name,
+ qos_policy.shared,
+ qos_policy.project_id,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkQosPolicy, self).setUp()
+ self.network.qos_policies = mock.Mock(return_value=self.qos_policies)
+
+ # Get the command object to test
+ self.cmd = network_qos_policy.ListNetworkQosPolicy(self.app,
+ self.namespace)
+
+ def test_qos_policy_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.qos_policies.assert_called_once_with(**{})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_qos_policy_list_share(self):
+ arglist = [
+ '--share',
+ ]
+ verifylist = [
+ ('share', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.qos_policies.assert_called_once_with(
+ **{'shared': True}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_qos_policy_list_no_share(self):
+ arglist = [
+ '--no-share',
+ ]
+ verifylist = [
+ ('no_share', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.qos_policies.assert_called_once_with(
+ **{'shared': False}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_qos_list_project(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ '--project-domain', project.domain_id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.network.qos_policies.assert_called_once_with(
+ **{'tenant_id': project.id}
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestSetNetworkQosPolicy(TestQosPolicy):
+
+ # The QoS policy to set.
+ _qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
+
+ def setUp(self):
+ super(TestSetNetworkQosPolicy, self).setUp()
+ self.network.update_qos_policy = mock.Mock(return_value=None)
+ self.network.find_qos_policy = mock.Mock(
+ return_value=self._qos_policy)
+
+ # Get the command object to test
+ self.cmd = network_qos_policy.SetNetworkQosPolicy(self.app,
+ self.namespace)
+
+ def test_set_nothing(self):
+ arglist = [self._qos_policy.name, ]
+ verifylist = [
+ ('policy', self._qos_policy.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_qos_policy.assert_called_with(
+ self._qos_policy, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_name_share_description(self):
+ arglist = [
+ '--name', 'new_qos_policy',
+ '--share',
+ '--description', 'QoS policy description',
+ self._qos_policy.name,
+ ]
+ verifylist = [
+ ('name', 'new_qos_policy'),
+ ('share', True),
+ ('description', 'QoS policy description'),
+ ('policy', self._qos_policy.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'name': 'new_qos_policy',
+ 'description': 'QoS policy description',
+ 'shared': True,
+ }
+ self.network.update_qos_policy.assert_called_with(
+ self._qos_policy, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_no_share(self):
+ arglist = [
+ '--no-share',
+ self._qos_policy.name,
+ ]
+ verifylist = [
+ ('no_share', True),
+ ('policy', self._qos_policy.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'shared': False
+ }
+ self.network.update_qos_policy.assert_called_with(
+ self._qos_policy, **attrs)
+ self.assertIsNone(result)
+
+
+class TestShowNetworkQosPolicy(TestQosPolicy):
+
+ # The QoS policy to show.
+ _qos_policy = (
+ network_fakes.FakeNetworkQosPolicy.create_one_qos_policy())
+ columns = (
+ 'description',
+ 'id',
+ 'name',
+ 'project_id',
+ 'rules',
+ 'shared',
+ )
+ data = (
+ _qos_policy.description,
+ _qos_policy.id,
+ _qos_policy.name,
+ _qos_policy.project_id,
+ _qos_policy.rules,
+ _qos_policy.shared,
+ )
+
+ def setUp(self):
+ super(TestShowNetworkQosPolicy, self).setUp()
+ self.network.find_qos_policy = mock.Mock(return_value=self._qos_policy)
+
+ # Get the command object to test
+ self.cmd = network_qos_policy.ShowNetworkQosPolicy(self.app,
+ self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self._qos_policy.name,
+ ]
+ verifylist = [
+ ('policy', self._qos_policy.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_qos_policy.assert_called_once_with(
+ self._qos_policy.name, ignore_missing=False)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(list(self.data), list(data))
diff --git a/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/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py
new file mode 100644
index 00000000..b93abe80
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from openstackclient.network.v2 import network_qos_rule_type as _qos_rule_type
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+
+
+class TestNetworkQosRuleType(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkQosRuleType, self).setUp()
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+
+
+class TestListNetworkQosRuleType(TestNetworkQosRuleType):
+
+ # The QoS policies to list up.
+ qos_rule_types = (
+ network_fakes.FakeNetworkQosRuleType.create_qos_rule_types(count=3))
+ columns = (
+ 'Type',
+ )
+ data = []
+ for qos_rule_type in qos_rule_types:
+ data.append((
+ qos_rule_type.type,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkQosRuleType, self).setUp()
+ self.network.qos_rule_types = mock.Mock(
+ return_value=self.qos_rule_types)
+
+ # Get the command object to test
+ self.cmd = _qos_rule_type.ListNetworkQosRuleType(self.app,
+ self.namespace)
+
+ def test_qos_rule_type_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.qos_rule_types.assert_called_once_with(**{})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py
index c526ae4e..935ce075 100644
--- a/openstackclient/tests/unit/network/v2/test_network_rbac.py
+++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py
@@ -36,6 +36,7 @@ class TestNetworkRBAC(network_fakes.TestNetworkV2):
class TestCreateNetworkRBAC(TestNetworkRBAC):
network_object = network_fakes.FakeNetwork.create_one_network()
+ qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
project = identity_fakes_v3.FakeProject.create_one_project()
rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac(
attrs={'tenant_id': project.id,
@@ -71,6 +72,8 @@ class TestCreateNetworkRBAC(TestNetworkRBAC):
return_value=self.rbac_policy)
self.network.find_network = mock.Mock(
return_value=self.network_object)
+ self.network.find_qos_policy = mock.Mock(
+ return_value=self.qos_object)
self.projects_mock.get.return_value = self.project
def test_network_rbac_create_no_type(self):
@@ -194,6 +197,43 @@ class TestCreateNetworkRBAC(TestNetworkRBAC):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_network_rbac_create_qos_object(self):
+ self.rbac_policy.object_type = 'qos_policy'
+ self.rbac_policy.object_id = self.qos_object.id
+ arglist = [
+ '--type', 'qos_policy',
+ '--action', self.rbac_policy.action,
+ '--target-project', self.rbac_policy.target_tenant,
+ self.qos_object.name,
+ ]
+ verifylist = [
+ ('type', 'qos_policy'),
+ ('action', self.rbac_policy.action),
+ ('target_project', self.rbac_policy.target_tenant),
+ ('rbac_object', self.qos_object.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_rbac_policy.assert_called_with(**{
+ 'object_id': self.qos_object.id,
+ 'object_type': 'qos_policy',
+ 'action': self.rbac_policy.action,
+ 'target_tenant': self.rbac_policy.target_tenant,
+ })
+ self.data = [
+ self.rbac_policy.action,
+ self.rbac_policy.id,
+ self.qos_object.id,
+ 'qos_policy',
+ self.rbac_policy.tenant_id,
+ self.rbac_policy.target_tenant,
+ ]
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestDeleteNetworkRBAC(TestNetworkRBAC):
@@ -287,7 +327,12 @@ class TestListNetworkRABC(TestNetworkRBAC):
'Object Type',
'Object ID',
)
-
+ columns_long = (
+ 'ID',
+ 'Object Type',
+ 'Object ID',
+ 'Action',
+ )
data = []
for r in rbac_policies:
data.append((
@@ -295,6 +340,14 @@ class TestListNetworkRABC(TestNetworkRBAC):
r.object_type,
r.object_id,
))
+ data_long = []
+ for r in rbac_policies:
+ data_long.append((
+ r.id,
+ r.object_type,
+ r.object_id,
+ r.action,
+ ))
def setUp(self):
super(TestListNetworkRABC, self).setUp()
@@ -316,6 +369,55 @@ class TestListNetworkRABC(TestNetworkRBAC):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_network_rbac_list_type_opt(self):
+ arglist = [
+ '--type', self.rbac_policies[0].object_type, ]
+ verifylist = [
+ ('type', self.rbac_policies[0].object_type)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.rbac_policies.assert_called_with(**{
+ 'object_type': self.rbac_policies[0].object_type
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_rbac_list_action_opt(self):
+ arglist = [
+ '--action', self.rbac_policies[0].action, ]
+ verifylist = [
+ ('action', self.rbac_policies[0].action)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.rbac_policies.assert_called_with(**{
+ 'action': self.rbac_policies[0].action
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_rbac_list_with_long(self):
+ arglist = [
+ '--long',
+ ]
+
+ verifylist = [
+ ('long', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.rbac_policies.assert_called_with()
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
+
class TestSetNetworkRBAC(TestNetworkRBAC):
diff --git a/openstackclient/tests/unit/network/v2/test_network_service_provider.py b/openstackclient/tests/unit/network/v2/test_network_service_provider.py
new file mode 100644
index 00000000..5ba85ddb
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_service_provider.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from openstackclient.network.v2 import network_service_provider \
+ as service_provider
+from openstackclient.tests.unit.network.v2 import fakes
+
+
+class TestNetworkServiceProvider(fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkServiceProvider, self).setUp()
+ self.network = self.app.client_manager.network
+
+
+class TestListNetworkServiceProvider(TestNetworkServiceProvider):
+ provider_list = \
+ fakes.FakeNetworkServiceProvider.create_network_service_providers(
+ count=2
+ )
+
+ columns = (
+ 'Service Type',
+ 'Name',
+ 'Default',
+ )
+
+ data = []
+
+ for provider in provider_list:
+ data.append((
+ provider.service_type,
+ provider.name,
+ provider.is_default,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkServiceProvider, self).setUp()
+ self.network.service_providers = mock.Mock(
+ return_value=self.provider_list
+ )
+
+ self.cmd = \
+ service_provider.ListNetworkServiceProvider(self.app,
+ self.namespace)
+
+ def test_network_service_provider_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.service_providers.assert_called_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py
index a2aceab1..d2df5841 100644
--- a/openstackclient/tests/unit/network/v2/test_port.py
+++ b/openstackclient/tests/unit/network/v2/test_port.py
@@ -20,6 +20,7 @@ from osc_lib import utils
from openstackclient.network.v2 import port
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit import utils as tests_utils
@@ -31,6 +32,8 @@ class TestPort(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
def _get_common_cols_data(self, fake_port):
columns = (
@@ -41,6 +44,7 @@ class TestPort(network_fakes.TestNetworkV2):
'binding_vif_details',
'binding_vif_type',
'binding_vnic_type',
+ 'description',
'device_id',
'device_owner',
'dns_assignment',
@@ -53,7 +57,7 @@ class TestPort(network_fakes.TestNetworkV2):
'network_id',
'port_security_enabled',
'project_id',
- 'security_groups',
+ 'security_group_ids',
'status',
)
@@ -65,6 +69,7 @@ class TestPort(network_fakes.TestNetworkV2):
utils.format_dict(fake_port.binding_vif_details),
fake_port.binding_vif_type,
fake_port.binding_vnic_type,
+ fake_port.description,
fake_port.device_id,
fake_port.device_owner,
utils.format_list_of_dicts(fake_port.dns_assignment),
@@ -77,7 +82,7 @@ class TestPort(network_fakes.TestNetworkV2):
fake_port.network_id,
fake_port.port_security_enabled,
fake_port.project_id,
- utils.format_list(fake_port.security_groups),
+ utils.format_list(fake_port.security_group_ids),
fake_port.status,
)
@@ -130,6 +135,7 @@ class TestCreatePort(TestPort):
'--mac-address', 'aa:aa:aa:aa:aa:aa',
'--fixed-ip', 'subnet=%s,ip-address=10.0.0.2'
% self.fake_subnet.id,
+ '--description', self._port.description,
'--device', 'deviceid',
'--device-owner', 'fakeowner',
'--disable',
@@ -137,6 +143,7 @@ class TestCreatePort(TestPort):
'--binding-profile', 'foo=bar',
'--binding-profile', 'foo2=bar2',
'--network', self._port.network_id,
+ '--dns-name', '8.8.8.8',
'test-port',
]
@@ -146,12 +153,14 @@ class TestCreatePort(TestPort):
'fixed_ip',
[{'subnet': self.fake_subnet.id, 'ip-address': '10.0.0.2'}]
),
+ ('description', self._port.description),
('device', 'deviceid'),
('device_owner', 'fakeowner'),
('disable', True),
('vnic_type', 'macvtap'),
('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}),
('network', self._port.network_id),
+ ('dns_name', '8.8.8.8'),
('name', 'test-port'),
]
@@ -163,12 +172,14 @@ class TestCreatePort(TestPort):
'mac_address': 'aa:aa:aa:aa:aa:aa',
'fixed_ips': [{'subnet_id': self.fake_subnet.id,
'ip_address': '10.0.0.2'}],
+ 'description': self._port.description,
'device_id': 'deviceid',
'device_owner': 'fakeowner',
'admin_state_up': False,
'binding:vnic_type': 'macvtap',
'binding:profile': {'foo': 'bar', 'foo2': 'bar2'},
'network_id': self._port.network_id,
+ 'dns_name': '8.8.8.8',
'name': 'test-port',
})
@@ -208,7 +219,7 @@ class TestCreatePort(TestPort):
'test-port',
]
verifylist = [
- ('network', self._port.network_id,),
+ ('network', self._port.network_id),
('enable', True),
('binding_profile', {'parent_name': 'fake_parent', 'tag': 42}),
('name', 'test-port'),
@@ -228,6 +239,237 @@ class TestCreatePort(TestPort):
self.assertEqual(ref_columns, columns)
self.assertEqual(ref_data, data)
+ def test_create_with_security_group(self):
+ secgroup = network_fakes.FakeSecurityGroup.create_one_security_group()
+ self.network.find_security_group = mock.Mock(return_value=secgroup)
+ arglist = [
+ '--network', self._port.network_id,
+ '--security-group', secgroup.id,
+ 'test-port',
+ ]
+
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('enable', True),
+ ('security_group', [secgroup.id]),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'security_group_ids': [secgroup.id],
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
+ def test_create_port_with_dns_name(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--dns-name', '8.8.8.8',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('enable', True),
+ ('dns_name', '8.8.8.8'),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'dns_name': '8.8.8.8',
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
+ def test_create_with_security_groups(self):
+ sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ self.network.find_security_group = mock.Mock(side_effect=[sg_1, sg_2])
+ arglist = [
+ '--network', self._port.network_id,
+ '--security-group', sg_1.id,
+ '--security-group', sg_2.id,
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('enable', True),
+ ('security_group', [sg_1.id, sg_2.id]),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'security_group_ids': [sg_1.id, sg_2.id],
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
+ def test_create_with_no_security_groups(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--no-security-group',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id),
+ ('enable', True),
+ ('no_security_group', True),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'security_group_ids': [],
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
+ def test_create_port_with_allowed_address_pair_ipaddr(self):
+ pairs = [{'ip_address': '192.168.1.123'},
+ {'ip_address': '192.168.1.45'}]
+ arglist = [
+ '--network', self._port.network_id,
+ '--allowed-address', 'ip-address=192.168.1.123',
+ '--allowed-address', 'ip-address=192.168.1.45',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id),
+ ('enable', True),
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.123'},
+ {'ip-address': '192.168.1.45'}]),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'allowed_address_pairs': pairs,
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
+ def test_create_port_with_allowed_address_pair(self):
+ pairs = [{'ip_address': '192.168.1.123',
+ 'mac_address': 'aa:aa:aa:aa:aa:aa'},
+ {'ip_address': '192.168.1.45',
+ 'mac_address': 'aa:aa:aa:aa:aa:b1'}]
+ arglist = [
+ '--network', self._port.network_id,
+ '--allowed-address',
+ 'ip-address=192.168.1.123,mac-address=aa:aa:aa:aa:aa:aa',
+ '--allowed-address',
+ 'ip-address=192.168.1.45,mac-address=aa:aa:aa:aa:aa:b1',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id),
+ ('enable', True),
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.123',
+ 'mac-address': 'aa:aa:aa:aa:aa:aa'},
+ {'ip-address': '192.168.1.45',
+ 'mac-address': 'aa:aa:aa:aa:aa:b1'}]),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'allowed_address_pairs': pairs,
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
+ def test_create_port_security_enabled(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--enable-port-security',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('enable', True),
+ ('enable_port_security', True),
+ ('name', 'test-port'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'port_security_enabled': True,
+ 'name': 'test-port',
+ })
+
+ def test_create_port_security_disabled(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--disable-port-security',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('enable', True),
+ ('disable_port_security', True),
+ ('name', 'test-port'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'port_security_enabled': False,
+ 'name': 'test-port',
+ })
+
class TestDeletePort(TestPort):
@@ -317,6 +559,17 @@ class TestListPort(TestPort):
'Name',
'MAC Address',
'Fixed IP Addresses',
+ 'Status',
+ )
+
+ columns_long = (
+ 'ID',
+ 'Name',
+ 'MAC Address',
+ 'Fixed IP Addresses',
+ 'Status',
+ 'Security Groups',
+ 'Device Owner',
)
data = []
@@ -326,6 +579,19 @@ class TestListPort(TestPort):
prt.name,
prt.mac_address,
utils.format_list_of_dicts(prt.fixed_ips),
+ prt.status,
+ ))
+
+ data_long = []
+ for prt in _ports:
+ data_long.append((
+ prt.id,
+ prt.name,
+ prt.mac_address,
+ utils.format_list_of_dicts(prt.fixed_ips),
+ prt.status,
+ utils.format_list(prt.security_group_ids),
+ prt.device_owner,
))
def setUp(self):
@@ -419,12 +685,14 @@ class TestListPort(TestPort):
'--device-owner', self._ports[0].device_owner,
'--router', 'fake-router-name',
'--network', 'fake-network-name',
+ '--mac-address', self._ports[0].mac_address,
]
verifylist = [
('device_owner', self._ports[0].device_owner),
('router', 'fake-router-name'),
- ('network', 'fake-network-name')
+ ('network', 'fake-network-name'),
+ ('mac_address', self._ports[0].mac_address)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -434,11 +702,172 @@ class TestListPort(TestPort):
self.network.ports.assert_called_once_with(**{
'device_owner': self._ports[0].device_owner,
'device_id': 'fake-router-id',
- 'network_id': 'fake-network-id'
+ 'network_id': 'fake-network-id',
+ 'mac_address': self._ports[0].mac_address
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_port_list_mac_address_opt(self):
+ arglist = [
+ '--mac-address', self._ports[0].mac_address,
+ ]
+
+ verifylist = [
+ ('mac_address', self._ports[0].mac_address)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'mac_address': self._ports[0].mac_address
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_port_list_fixed_ip_opt_ip_address(self):
+ ip_address = self._ports[0].fixed_ips[0]['ip_address']
+ arglist = [
+ '--fixed-ip', "ip-address=%s" % ip_address,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'ip-address': ip_address}])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'fixed_ips': ['ip_address=%s' % ip_address]})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_port_list_fixed_ip_opt_subnet_id(self):
+ subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
+ arglist = [
+ '--fixed-ip', "subnet=%s" % subnet_id,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'subnet': subnet_id}])
+ ]
+
+ self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'id': subnet_id})
+ self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'fixed_ips': ['subnet_id=%s' % subnet_id]})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_port_list_fixed_ip_opts(self):
+ subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
+ ip_address = self._ports[0].fixed_ips[0]['ip_address']
+ arglist = [
+ '--fixed-ip', "subnet=%s,ip-address=%s" % (subnet_id,
+ ip_address)
+ ]
+ verifylist = [
+ ('fixed_ip', [{'subnet': subnet_id,
+ 'ip-address': ip_address}])
+ ]
+
+ self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'id': subnet_id})
+ self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'fixed_ips': ['subnet_id=%s' % subnet_id,
+ 'ip_address=%s' % ip_address]})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_port_list_fixed_ips(self):
+ subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
+ ip_address = self._ports[0].fixed_ips[0]['ip_address']
+ arglist = [
+ '--fixed-ip', "subnet=%s" % subnet_id,
+ '--fixed-ip', "ip-address=%s" % ip_address,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'subnet': subnet_id},
+ {'ip-address': ip_address}])
+ ]
+
+ self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'id': subnet_id})
+ self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'fixed_ips': ['subnet_id=%s' % subnet_id,
+ 'ip_address=%s' % ip_address]})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_list_port_with_long(self):
+ arglist = [
+ '--long',
+ ]
+
+ verifylist = [
+ ('long', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with()
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
+
+ def test_port_list_project(self):
+ project = identity_fakes.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.ports.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_port_list_project_domain(self):
+ project = identity_fakes.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ '--project-domain', project.domain_id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ('project_domain', project.domain_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.ports.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestSetPort(TestPort):
@@ -458,6 +887,7 @@ class TestSetPort(TestPort):
arglist = [
'--fixed-ip', 'ip-address=10.0.0.11',
self._port.name,
+ '--no-fixed-ip',
]
verifylist = [
('fixed_ip', [{'ip-address': '10.0.0.11'}]),
@@ -473,6 +903,25 @@ class TestSetPort(TestPort):
self.network.update_port.assert_called_once_with(self._port, **attrs)
self.assertIsNone(result)
+ def test_set_dns_name(self):
+ arglist = [
+ '--dns-name', '8.8.8.8',
+ self._port.name,
+ ]
+ verifylist = [
+ ('dns_name', '8.8.8.8'),
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'dns_name': '8.8.8.8',
+ }
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
def test_append_fixed_ip(self):
_testport = network_fakes.FakePort.create_one_port(
{'fixed_ips': [{'ip_address': '0.0.0.1'}]})
@@ -538,6 +987,25 @@ class TestSetPort(TestPort):
self.network.update_port.assert_called_once_with(_testport, **attrs)
self.assertIsNone(result)
+ def test_overwrite_mac_address(self):
+ _testport = network_fakes.FakePort.create_one_port(
+ {'mac_address': '11:22:33:44:55:66'})
+ self.network.find_port = mock.Mock(return_value=_testport)
+ arglist = [
+ '--mac-address', '66:55:44:33:22:11',
+ _testport.name,
+ ]
+ verifylist = [
+ ('mac_address', '66:55:44:33:22:11'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'mac_address': '66:55:44:33:22:11',
+ }
+ self.network.update_port.assert_called_once_with(_testport, **attrs)
+ self.assertIsNone(result)
+
def test_set_this(self):
arglist = [
'--disable',
@@ -565,6 +1033,7 @@ class TestSetPort(TestPort):
def test_set_that(self):
arglist = [
+ '--description', 'newDescription',
'--enable',
'--vnic-type', 'macvtap',
'--binding-profile', 'foo=bar',
@@ -573,6 +1042,7 @@ class TestSetPort(TestPort):
self._port.name,
]
verifylist = [
+ ('description', 'newDescription'),
('enable', True),
('vnic_type', 'macvtap'),
('binding_profile', {'foo': 'bar'}),
@@ -589,6 +1059,7 @@ class TestSetPort(TestPort):
'binding:vnic_type': 'macvtap',
'binding:profile': {'foo': 'bar'},
'binding:host_id': 'binding-host-id-xxxx',
+ 'description': 'newDescription',
'name': 'newName',
}
self.network.update_port.assert_called_once_with(self._port, **attrs)
@@ -651,6 +1122,216 @@ class TestSetPort(TestPort):
self.network.update_port.assert_called_once_with(self._port, **attrs)
self.assertIsNone(result)
+ def test_set_security_group(self):
+ sg = network_fakes.FakeSecurityGroup.create_one_security_group()
+ self.network.find_security_group = mock.Mock(return_value=sg)
+ arglist = [
+ '--security-group', sg.id,
+ self._port.name,
+ ]
+ verifylist = [
+ ('security_group', [sg.id]),
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'security_group_ids': [sg.id],
+ }
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
+ def test_append_security_group(self):
+ sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ self.network.find_security_group = mock.Mock(side_effect=[sg_2, sg_3])
+ _testport = network_fakes.FakePort.create_one_port(
+ {'security_group_ids': [sg_1.id]})
+ self.network.find_port = mock.Mock(return_value=_testport)
+ arglist = [
+ '--security-group', sg_2.id,
+ '--security-group', sg_3.id,
+ _testport.name,
+ ]
+ verifylist = [
+ ('security_group', [sg_2.id, sg_3.id]),
+ ('port', _testport.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'security_group_ids': [sg_2.id, sg_3.id, sg_1.id],
+ }
+ self.network.update_port.assert_called_once_with(_testport, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_no_security_groups(self):
+ arglist = [
+ '--no-security-group',
+ self._port.name,
+ ]
+ verifylist = [
+ ('no_security_group', True),
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'security_group_ids': [],
+ }
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
+ def test_overwrite_security_group(self):
+ sg1 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ sg2 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ _testport = network_fakes.FakePort.create_one_port(
+ {'security_group_ids': [sg1.id]})
+ self.network.find_port = mock.Mock(return_value=_testport)
+ self.network.find_security_group = mock.Mock(return_value=sg2)
+ arglist = [
+ '--security-group', sg2.id,
+ '--no-security-group',
+ _testport.name,
+ ]
+ verifylist = [
+ ('security_group', [sg2.id]),
+ ('no_security_group', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'security_group_ids': [sg2.id],
+ }
+ self.network.update_port.assert_called_once_with(_testport, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_allowed_address_pair(self):
+ arglist = [
+ '--allowed-address', 'ip-address=192.168.1.123',
+ self._port.name,
+ ]
+ verifylist = [
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.123'}]),
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'allowed_address_pairs': [{'ip_address': '192.168.1.123'}],
+ }
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
+ def test_append_allowed_address_pair(self):
+ _testport = network_fakes.FakePort.create_one_port(
+ {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]})
+ self.network.find_port = mock.Mock(return_value=_testport)
+ arglist = [
+ '--allowed-address', 'ip-address=192.168.1.45',
+ _testport.name,
+ ]
+ verifylist = [
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.45'}]),
+ ('port', _testport.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'allowed_address_pairs': [{'ip_address': '192.168.1.123'},
+ {'ip_address': '192.168.1.45'}],
+ }
+ self.network.update_port.assert_called_once_with(_testport, **attrs)
+ self.assertIsNone(result)
+
+ def test_overwrite_allowed_address_pair(self):
+ _testport = network_fakes.FakePort.create_one_port(
+ {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]})
+ self.network.find_port = mock.Mock(return_value=_testport)
+ arglist = [
+ '--allowed-address', 'ip-address=192.168.1.45',
+ '--no-allowed-address',
+ _testport.name,
+ ]
+ verifylist = [
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.45'}]),
+ ('no_allowed_address_pair', True),
+ ('port', _testport.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'allowed_address_pairs': [{'ip_address': '192.168.1.45'}],
+ }
+ self.network.update_port.assert_called_once_with(_testport, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_no_allowed_address_pairs(self):
+ arglist = [
+ '--no-allowed-address',
+ self._port.name,
+ ]
+ verifylist = [
+ ('no_allowed_address_pair', True),
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'allowed_address_pairs': [],
+ }
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
+ def test_port_security_enabled(self):
+ arglist = [
+ '--enable-port-security',
+ self._port.id,
+ ]
+ verifylist = [
+ ('enable_port_security', True),
+ ('port', self._port.id,)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.network.update_port.assert_called_once_with(self._port, **{
+ 'port_security_enabled': True,
+ })
+
+ def test_port_security_disabled(self):
+ arglist = [
+ '--disable-port-security',
+ self._port.id,
+ ]
+ verifylist = [
+ ('disable_port_security', True),
+ ('port', self._port.id,)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.network.update_port.assert_called_once_with(self._port, **{
+ 'port_security_enabled': False,
+ })
+
class TestShowPort(TestPort):
@@ -767,3 +1448,86 @@ class TestUnsetPort(TestPort):
self.assertRaises(exceptions.CommandError,
self.cmd.take_action,
parsed_args)
+
+ def test_unset_security_group(self):
+ _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ _fake_port = network_fakes.FakePort.create_one_port(
+ {'security_group_ids': [_fake_sg1.id, _fake_sg2.id]})
+ self.network.find_port = mock.Mock(return_value=_fake_port)
+ self.network.find_security_group = mock.Mock(return_value=_fake_sg2)
+ arglist = [
+ '--security-group', _fake_sg2.id,
+ _fake_port.name,
+ ]
+ verifylist = [
+ ('security_group_ids', [_fake_sg2.id]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'security_group_ids': [_fake_sg1.id]
+ }
+ self.network.update_port.assert_called_once_with(
+ _fake_port, **attrs)
+ self.assertIsNone(result)
+
+ def test_unset_port_security_group_not_existent(self):
+ _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group()
+ _fake_port = network_fakes.FakePort.create_one_port(
+ {'security_group_ids': [_fake_sg1.id]})
+ self.network.find_security_group = mock.Mock(return_value=_fake_sg2)
+ arglist = [
+ '--security-group', _fake_sg2.id,
+ _fake_port.name,
+ ]
+ verifylist = [
+ ('security_group_ids', [_fake_sg2.id]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
+ def test_unset_port_allowed_address_pair(self):
+ _fake_port = network_fakes.FakePort.create_one_port(
+ {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]})
+ self.network.find_port = mock.Mock(return_value=_fake_port)
+ arglist = [
+ '--allowed-address', 'ip-address=192.168.1.123',
+ _fake_port.name,
+ ]
+ verifylist = [
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.123'}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'allowed_address_pairs': [],
+ }
+
+ self.network.update_port.assert_called_once_with(_fake_port, **attrs)
+ self.assertIsNone(result)
+
+ def test_unset_port_allowed_address_pair_not_existent(self):
+ _fake_port = network_fakes.FakePort.create_one_port(
+ {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]})
+ self.network.find_port = mock.Mock(return_value=_fake_port)
+ arglist = [
+ '--allowed-address', 'ip-address=192.168.1.45',
+ _fake_port.name,
+ ]
+ verifylist = [
+ ('allowed_address_pairs', [{'ip-address': '192.168.1.45'}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py
index 6a445862..b837afd1 100644
--- a/openstackclient/tests/unit/network/v2/test_router.py
+++ b/openstackclient/tests/unit/network/v2/test_router.py
@@ -18,6 +18,7 @@ from osc_lib import exceptions
from osc_lib import utils as osc_utils
from openstackclient.network.v2 import router
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit import utils as tests_utils
@@ -29,6 +30,7 @@ class TestRouter(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ self.projects_mock = self.app.client_manager.identity.projects
class TestAddPortToRouter(TestRouter):
@@ -427,6 +429,97 @@ class TestListRouter(TestRouter):
self.assertEqual(self.columns_long_no_az, columns)
self.assertEqual(self.data_long_no_az, list(data))
+ def test_list_name(self):
+ test_name = "fakename"
+ arglist = [
+ '--name', test_name,
+ ]
+ verifylist = [
+ ('long', False),
+ ('name', test_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.routers.assert_called_once_with(
+ **{'name': test_name}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_router_list_enable(self):
+ arglist = [
+ '--enable',
+ ]
+ verifylist = [
+ ('long', False),
+ ('enable', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.routers.assert_called_once_with(
+ **{'admin_state_up': True, 'is_admin_state_up': True}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_router_list_disable(self):
+ arglist = [
+ '--disable',
+ ]
+ verifylist = [
+ ('long', False),
+ ('disable', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.routers.assert_called_once_with(
+ **{'admin_state_up': False, 'is_admin_state_up': False}
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_router_list_project(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.routers.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_router_list_project_domain(self):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ '--project-domain', project.domain_id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ('project_domain', project.domain_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.routers.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestRemovePortFromRouter(TestRouter):
'''Remove port from a Router '''
@@ -509,17 +602,19 @@ class TestSetRouter(TestRouter):
# The router to set.
_default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'}
+ _network = network_fakes.FakeNetwork.create_one_network()
+ _subnet = network_fakes.FakeSubnet.create_one_subnet()
_router = network_fakes.FakeRouter.create_one_router(
attrs={'routes': [_default_route]}
)
def setUp(self):
super(TestSetRouter, self).setUp()
-
+ self.network.router_add_gateway = mock.Mock()
self.network.update_router = mock.Mock(return_value=None)
-
self.network.find_router = mock.Mock(return_value=self._router)
-
+ self.network.find_network = mock.Mock(return_value=self._network)
+ self.network.find_subnet = mock.Mock(return_value=self._subnet)
# Get the command object to test
self.cmd = router.SetRouter(self.app, self.namespace)
@@ -529,6 +624,7 @@ class TestSetRouter(TestRouter):
'--enable',
'--distributed',
'--name', 'noob',
+ '--no-ha',
'--description', 'router',
]
verifylist = [
@@ -537,6 +633,7 @@ class TestSetRouter(TestRouter):
('distributed', True),
('name', 'noob'),
('description', 'router'),
+ ('no_ha', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -546,6 +643,7 @@ class TestSetRouter(TestRouter):
'admin_state_up': True,
'distributed': True,
'name': 'noob',
+ 'ha': False,
'description': 'router',
}
self.network.update_router.assert_called_once_with(
@@ -557,11 +655,13 @@ class TestSetRouter(TestRouter):
self._router.name,
'--disable',
'--centralized',
+ '--ha',
]
verifylist = [
('router', self._router.name),
('disable', True),
('centralized', True),
+ ('ha', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -570,6 +670,7 @@ class TestSetRouter(TestRouter):
attrs = {
'admin_state_up': False,
'distributed': False,
+ 'ha': True,
}
self.network.update_router.assert_called_once_with(
self._router, **attrs)
@@ -603,10 +704,10 @@ class TestSetRouter(TestRouter):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
-
+ routes = [{'destination': '10.20.30.0/24',
+ 'nexthop': '10.20.30.1'}]
attrs = {
- 'routes': self._router.routes + [{'destination': '10.20.30.0/24',
- 'nexthop': '10.20.30.1'}],
+ 'routes': routes + self._router.routes
}
self.network.update_router.assert_called_once_with(
self._router, **attrs)
@@ -632,21 +733,31 @@ class TestSetRouter(TestRouter):
self._router, **attrs)
self.assertIsNone(result)
- def test_set_route_no_route(self):
+ def test_set_route_overwrite_route(self):
+ _testrouter = network_fakes.FakeRouter.create_one_router(
+ {'routes': [{"destination": "10.0.0.2",
+ "nexthop": "1.1.1.1"}]})
+ self.network.find_router = mock.Mock(return_value=_testrouter)
arglist = [
- self._router.name,
+ _testrouter.name,
'--route', 'destination=10.20.30.0/24,gateway=10.20.30.1',
'--no-route',
]
verifylist = [
- ('router', self._router.name),
+ ('router', _testrouter.name),
('routes', [{'destination': '10.20.30.0/24',
'gateway': '10.20.30.1'}]),
('no_route', True),
]
-
- self.assertRaises(tests_utils.ParserException, self.check_parser,
- self.cmd, arglist, verifylist)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'routes': [{'destination': '10.20.30.0/24',
+ 'nexthop': '10.20.30.1'}]
+ }
+ self.network.update_router.assert_called_once_with(
+ _testrouter, **attrs)
+ self.assertIsNone(result)
def test_set_clear_routes(self):
arglist = [
@@ -668,21 +779,31 @@ class TestSetRouter(TestRouter):
self._router, **attrs)
self.assertIsNone(result)
- def test_set_route_clear_routes(self):
+ def test_overwrite_route_clear_routes(self):
+ _testrouter = network_fakes.FakeRouter.create_one_router(
+ {'routes': [{"destination": "10.0.0.2",
+ "nexthop": "1.1.1.1"}]})
+ self.network.find_router = mock.Mock(return_value=_testrouter)
arglist = [
- self._router.name,
+ _testrouter.name,
'--route', 'destination=10.20.30.0/24,gateway=10.20.30.1',
'--clear-routes',
]
verifylist = [
- ('router', self._router.name),
+ ('router', _testrouter.name),
('routes', [{'destination': '10.20.30.0/24',
'gateway': '10.20.30.1'}]),
('clear_routes', True),
]
-
- self.assertRaises(tests_utils.ParserException, self.check_parser,
- self.cmd, arglist, verifylist)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'routes': [{'destination': '10.20.30.0/24',
+ 'nexthop': '10.20.30.1'}]
+ }
+ self.network.update_router.assert_called_once_with(
+ _testrouter, **attrs)
+ self.assertIsNone(result)
def test_set_nothing(self):
arglist = [
@@ -700,6 +821,110 @@ class TestSetRouter(TestRouter):
self._router, **attrs)
self.assertIsNone(result)
+ def test_wrong_gateway_params(self):
+ arglist = [
+ "--fixed-ip", "subnet='abc'",
+ self._router.id,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'subnet': "'abc'"}]),
+ ('router', self._router.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
+ def test_set_gateway_network_only(self):
+ arglist = [
+ "--external-gateway", self._network.id,
+ self._router.id,
+ ]
+ verifylist = [
+ ('external_gateway', self._network.id),
+ ('router', self._router.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.update_router.assert_called_with(
+ self._router, **{'external_gateway_info': {
+ 'network_id': self._network.id}})
+ self.assertIsNone(result)
+
+ def test_set_gateway_options_subnet_only(self):
+ arglist = [
+ "--external-gateway", self._network.id,
+ "--fixed-ip", "subnet='abc'",
+ self._router.id,
+ '--enable-snat',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('external_gateway', self._network.id),
+ ('fixed_ip', [{'subnet': "'abc'"}]),
+ ('enable_snat', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.update_router.assert_called_with(
+ self._router, **{'external_gateway_info': {
+ 'network_id': self._network.id,
+ 'external_fixed_ips': [{
+ 'subnet_id': self._subnet.id, }],
+ 'enable_snat': True, }})
+ self.assertIsNone(result)
+
+ def test_set_gateway_option_ipaddress_only(self):
+ arglist = [
+ "--external-gateway", self._network.id,
+ "--fixed-ip", "ip-address=10.0.1.1",
+ self._router.id,
+ '--enable-snat',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('external_gateway', self._network.id),
+ ('fixed_ip', [{'ip-address': "10.0.1.1"}]),
+ ('enable_snat', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.update_router.assert_called_with(
+ self._router, **{'external_gateway_info': {
+ 'network_id': self._network.id,
+ 'external_fixed_ips': [{
+ 'ip_address': "10.0.1.1", }],
+ 'enable_snat': True, }})
+ self.assertIsNone(result)
+
+ def test_set_gateway_options_subnet_ipaddress(self):
+ arglist = [
+ "--external-gateway", self._network.id,
+ "--fixed-ip", "subnet='abc',ip-address=10.0.1.1",
+ self._router.id,
+ '--enable-snat',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('external_gateway', self._network.id),
+ ('fixed_ip', [{'subnet': "'abc'",
+ 'ip-address': "10.0.1.1"}]),
+ ('enable_snat', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.update_router.assert_called_with(
+ self._router, **{'external_gateway_info': {
+ 'network_id': self._network.id,
+ 'external_fixed_ips': [{
+ 'subnet_id': self._subnet.id,
+ 'ip_address': "10.0.1.1", }],
+ 'enable_snat': True, }})
+ self.assertIsNone(result)
+
class TestShowRouter(TestRouter):
@@ -773,9 +998,9 @@ class TestUnsetRouter(TestRouter):
super(TestUnsetRouter, self).setUp()
self._testrouter = network_fakes.FakeRouter.create_one_router(
{'routes': [{"destination": "192.168.101.1/24",
- "gateway": "172.24.4.3"},
+ "nexthop": "172.24.4.3"},
{"destination": "192.168.101.2/24",
- "gateway": "172.24.4.3"}], })
+ "nexthop": "172.24.4.3"}], })
self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet()
self.network.find_router = mock.Mock(return_value=self._testrouter)
self.network.update_router = mock.Mock(return_value=None)
@@ -816,3 +1041,16 @@ class TestUnsetRouter(TestRouter):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError,
self.cmd.take_action, parsed_args)
+
+ def test_unset_router_external_gateway(self):
+ arglist = [
+ '--external-gateway',
+ self._testrouter.name,
+ ]
+ verifylist = [('external_gateway', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {'external_gateway_info': {}}
+ self.network.update_router.assert_called_once_with(
+ self._testrouter, **attrs)
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py
index 2615b77a..66d357f9 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group.py
@@ -404,7 +404,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
grp.id,
grp.name,
grp.description,
- grp.tenant_id,
+ grp.project_id,
))
def setUp(self):
@@ -444,6 +444,44 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_security_group_list_project(self):
+ project = identity_fakes.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.security_groups.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_security_group_list_project_domain(self):
+ project = identity_fakes.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = project
+ arglist = [
+ '--project', project.id,
+ '--project-domain', project.domain_id,
+ ]
+ verifylist = [
+ ('project', project.id),
+ ('project_domain', project.domain_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'tenant_id': project.id, 'project_id': project.id}
+
+ self.network.security_groups.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestListSecurityGroupCompute(TestSecurityGroupCompute):
diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py
index 96d58e5c..e3538d5f 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py
@@ -60,8 +60,9 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
network_fakes.FakeSecurityGroup.create_one_security_group()
expected_columns = (
+ 'description',
'direction',
- 'ethertype',
+ 'ether_type',
'id',
'port_range_max',
'port_range_min',
@@ -81,8 +82,9 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule = mock.Mock(
return_value=self._security_group_rule)
self.expected_data = (
+ self._security_group_rule.description,
self._security_group_rule.direction,
- self._security_group_rule.ethertype,
+ self._security_group_rule.ether_type,
self._security_group_rule.id,
self._security_group_rule.port_range_max,
self._security_group_rule.port_range_min,
@@ -119,6 +121,15 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, [])
+ def test_create_all_remote_options(self):
+ arglist = [
+ '--remote-ip', '10.10.0.0/24',
+ '--remote-group', self._security_group.id,
+ self._security_group.id,
+ ]
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
def test_create_bad_ethertype(self):
arglist = [
'--ethertype', 'foo',
@@ -173,7 +184,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'port_range_max': self._security_group_rule.port_range_max,
'port_range_min': self._security_group_rule.port_range_min,
'protocol': self._security_group_rule.protocol,
@@ -205,7 +216,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'protocol': self._security_group_rule.protocol,
'remote_ip_prefix': self._security_group_rule.remote_ip_prefix,
'security_group_id': self._security_group.id,
@@ -213,7 +224,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_data, data)
- def test_create_source_group(self):
+ def test_create_remote_group(self):
self._setup_security_group_rule({
'port_range_max': 22,
'port_range_min': 22,
@@ -238,7 +249,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'port_range_max': self._security_group_rule.port_range_max,
'port_range_min': self._security_group_rule.port_range_min,
'protocol': self._security_group_rule.protocol,
@@ -248,6 +259,34 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_data, data)
+ def test_create_source_group(self):
+ self._setup_security_group_rule({
+ 'remote_group_id': self._security_group.id,
+ })
+ arglist = [
+ '--ingress',
+ '--src-group', self._security_group.name,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('ingress', True),
+ ('src_group', self._security_group.name),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_security_group_rule.assert_called_once_with(**{
+ 'direction': self._security_group_rule.direction,
+ 'ethertype': self._security_group_rule.ether_type,
+ 'protocol': self._security_group_rule.protocol,
+ 'remote_group_id': self._security_group_rule.remote_group_id,
+ 'security_group_id': self._security_group.id,
+ })
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
def test_create_source_ip(self):
self._setup_security_group_rule({
'protocol': 'icmp',
@@ -269,7 +308,36 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
+ 'protocol': self._security_group_rule.protocol,
+ 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix,
+ 'security_group_id': self._security_group.id,
+ })
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
+ def test_create_remote_ip(self):
+ self._setup_security_group_rule({
+ 'protocol': 'icmp',
+ 'remote_ip_prefix': '10.0.2.0/24',
+ })
+ arglist = [
+ '--protocol', self._security_group_rule.protocol,
+ '--remote-ip', self._security_group_rule.remote_ip_prefix,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('protocol', self._security_group_rule.protocol),
+ ('remote_ip', self._security_group_rule.remote_ip_prefix),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_security_group_rule.assert_called_once_with(**{
+ 'direction': self._security_group_rule.direction,
+ 'ethertype': self._security_group_rule.ether_type,
'protocol': self._security_group_rule.protocol,
'remote_ip_prefix': self._security_group_rule.remote_ip_prefix,
'security_group_id': self._security_group.id,
@@ -280,7 +348,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
def test_create_network_options(self):
self._setup_security_group_rule({
'direction': 'egress',
- 'ethertype': 'IPv6',
+ 'ether_type': 'IPv6',
'port_range_max': 443,
'port_range_min': 443,
'protocol': '6',
@@ -290,7 +358,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
arglist = [
'--dst-port', str(self._security_group_rule.port_range_min),
'--egress',
- '--ethertype', self._security_group_rule.ethertype,
+ '--ethertype', self._security_group_rule.ether_type,
'--project', self.project.name,
'--project-domain', self.domain.name,
'--protocol', self._security_group_rule.protocol,
@@ -300,7 +368,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
('dst_port', (self._security_group_rule.port_range_min,
self._security_group_rule.port_range_max)),
('egress', True),
- ('ethertype', self._security_group_rule.ethertype),
+ ('ethertype', self._security_group_rule.ether_type),
('project', self.project.name),
('project_domain', self.domain.name),
('protocol', self._security_group_rule.protocol),
@@ -312,7 +380,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'port_range_max': self._security_group_rule.port_range_max,
'port_range_min': self._security_group_rule.port_range_min,
'protocol': self._security_group_rule.protocol,
@@ -376,7 +444,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'port_range_min': self._security_group_rule.port_range_min,
'protocol': self._security_group_rule.protocol,
'remote_ip_prefix': self._security_group_rule.remote_ip_prefix,
@@ -387,7 +455,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
def test_create_ipv6_icmp_type_code(self):
self._setup_security_group_rule({
- 'ethertype': 'IPv6',
+ 'ether_type': 'IPv6',
'port_range_min': 139,
'port_range_max': 2,
'protocol': 'ipv6-icmp',
@@ -411,7 +479,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'port_range_min': self._security_group_rule.port_range_min,
'port_range_max': self._security_group_rule.port_range_max,
'protocol': self._security_group_rule.protocol,
@@ -422,7 +490,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
def test_create_icmpv6_type(self):
self._setup_security_group_rule({
- 'ethertype': 'IPv6',
+ 'ether_type': 'IPv6',
'port_range_min': 139,
'protocol': 'icmpv6',
})
@@ -444,7 +512,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.create_security_group_rule.assert_called_once_with(**{
'direction': self._security_group_rule.direction,
- 'ethertype': self._security_group_rule.ethertype,
+ 'ethertype': self._security_group_rule.ether_type,
'port_range_min': self._security_group_rule.port_range_min,
'protocol': self._security_group_rule.protocol,
'security_group_id': self._security_group.id,
@@ -452,6 +520,33 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_data, data)
+ def test_create_with_description(self):
+ self._setup_security_group_rule({
+ 'description': 'Setting SGR',
+ })
+ arglist = [
+ '--description', self._security_group_rule.description,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('description', self._security_group_rule.description),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_security_group_rule.assert_called_once_with(**{
+ 'description': self._security_group_rule.description,
+ 'direction': self._security_group_rule.direction,
+ 'ethertype': self._security_group_rule.ether_type,
+ 'protocol': self._security_group_rule.protocol,
+ 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix,
+ 'security_group_id': self._security_group.id,
+ })
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
@@ -498,6 +593,15 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, [])
+ def test_create_all_remote_options(self):
+ arglist = [
+ '--remote-ip', '10.10.0.0/24',
+ '--remote-group', self._security_group.id,
+ self._security_group.id,
+ ]
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
def test_create_bad_protocol(self):
arglist = [
'--protocol', 'foo',
@@ -588,6 +692,38 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
self.assertEqual(expected_columns, columns)
self.assertEqual(expected_data, data)
+ def test_create_remote_group(self):
+ expected_columns, expected_data = self._setup_security_group_rule({
+ 'from_port': 22,
+ 'to_port': 22,
+ 'group': {'name': self._security_group.name},
+ })
+ arglist = [
+ '--dst-port', str(self._security_group_rule.from_port),
+ '--remote-group', self._security_group.name,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('dst_port', (self._security_group_rule.from_port,
+ self._security_group_rule.to_port)),
+ ('remote_group', self._security_group.name),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.compute.security_group_rules.create.assert_called_once_with(
+ self._security_group.id,
+ self._security_group_rule.ip_protocol,
+ self._security_group_rule.from_port,
+ self._security_group_rule.to_port,
+ self._security_group_rule.ip_range['cidr'],
+ self._security_group.id,
+ )
+ self.assertEqual(expected_columns, columns)
+ self.assertEqual(expected_data, data)
+
def test_create_source_ip(self):
expected_columns, expected_data = self._setup_security_group_rule({
'ip_protocol': 'icmp',
@@ -620,6 +756,38 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
self.assertEqual(expected_columns, columns)
self.assertEqual(expected_data, data)
+ def test_create_remote_ip(self):
+ expected_columns, expected_data = self._setup_security_group_rule({
+ 'ip_protocol': 'icmp',
+ 'from_port': -1,
+ 'to_port': -1,
+ 'ip_range': {'cidr': '10.0.2.0/24'},
+ })
+ arglist = [
+ '--protocol', self._security_group_rule.ip_protocol,
+ '--remote-ip', self._security_group_rule.ip_range['cidr'],
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('protocol', self._security_group_rule.ip_protocol),
+ ('remote_ip', self._security_group_rule.ip_range['cidr']),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.compute.security_group_rules.create.assert_called_once_with(
+ self._security_group.id,
+ self._security_group_rule.ip_protocol,
+ self._security_group_rule.from_port,
+ self._security_group_rule.to_port,
+ self._security_group_rule.ip_range['cidr'],
+ None,
+ )
+ self.assertEqual(expected_columns, columns)
+ self.assertEqual(expected_data, data)
+
def test_create_proto_option(self):
expected_columns, expected_data = self._setup_security_group_rule({
'ip_protocol': 'icmp',
@@ -871,7 +1039,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
security_group_rule._format_network_port_range(
_security_group_rule),
_security_group_rule.direction,
- _security_group_rule.ethertype,
+ _security_group_rule.ether_type,
_security_group_rule.remote_group_id,
))
expected_data_no_group.append((
@@ -942,6 +1110,60 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns_no_group, columns)
self.assertEqual(self.expected_data_no_group, list(data))
+ def test_list_with_protocol(self):
+ self._security_group_rule_tcp.port_range_min = 80
+ arglist = [
+ '--protocol', 'tcp',
+ ]
+ verifylist = [
+ ('protocol', 'tcp'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.security_group_rules.assert_called_once_with(**{
+ 'protocol': 'tcp',
+ })
+ self.assertEqual(self.expected_columns_no_group, columns)
+ self.assertEqual(self.expected_data_no_group, list(data))
+
+ def test_list_with_ingress(self):
+ self._security_group_rule_tcp.port_range_min = 80
+ arglist = [
+ '--ingress',
+ ]
+ verifylist = [
+ ('ingress', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.security_group_rules.assert_called_once_with(**{
+ 'direction': 'ingress',
+ })
+ self.assertEqual(self.expected_columns_no_group, columns)
+ self.assertEqual(self.expected_data_no_group, list(data))
+
+ def test_list_with_wrong_egress(self):
+ self._security_group_rule_tcp.port_range_min = 80
+ arglist = [
+ '--egress',
+ ]
+ verifylist = [
+ ('egress', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.security_group_rules.assert_called_once_with(**{
+ 'direction': 'egress',
+ })
+ self.assertEqual(self.expected_columns_no_group, columns)
+ self.assertEqual(self.expected_data_no_group, list(data))
+
class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
@@ -1075,8 +1297,9 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
network_fakes.FakeSecurityGroupRule.create_one_security_group_rule()
columns = (
+ 'description',
'direction',
- 'ethertype',
+ 'ether_type',
'id',
'port_range_max',
'port_range_min',
@@ -1088,8 +1311,9 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
)
data = (
+ _security_group_rule.description,
_security_group_rule.direction,
- _security_group_rule.ethertype,
+ _security_group_rule.ether_type,
_security_group_rule.id,
_security_group_rule.port_range_max,
_security_group_rule.port_range_min,
diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py
index 2d51aa4a..47de5616 100644
--- a/openstackclient/tests/unit/network/v2/test_subnet.py
+++ b/openstackclient/tests/unit/network/v2/test_subnet.py
@@ -636,7 +636,7 @@ class TestListSubnet(TestSubnet):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'enable_dhcp': True}
+ filters = {'enable_dhcp': True, 'is_dhcp_enabled': True}
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -652,7 +652,7 @@ class TestListSubnet(TestSubnet):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'enable_dhcp': False}
+ filters = {'enable_dhcp': False, 'is_dhcp_enabled': False}
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -685,7 +685,7 @@ class TestListSubnet(TestSubnet):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id}
+ filters = {'tenant_id': project.id, 'project_id': project.id}
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -723,7 +723,7 @@ class TestListSubnet(TestSubnet):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id}
+ filters = {'tenant_id': project.id, 'project_id': project.id}
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -925,13 +925,16 @@ class TestSetSubnet(TestSubnet):
{'host_routes': [{'destination': '10.20.20.0/24',
'nexthop': '10.20.20.1'}],
'allocation_pools': [{'start': '8.8.8.200',
- 'end': '8.8.8.250'}], })
+ 'end': '8.8.8.250'}],
+ 'dns_nameservers': ["10.0.0.1"], })
self.network.find_subnet = mock.Mock(return_value=_testsubnet)
arglist = [
'--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1',
'--no-host-route',
'--allocation-pool', 'start=8.8.8.100,end=8.8.8.150',
'--no-allocation-pool',
+ '--dns-nameserver', '10.1.10.1',
+ '--no-dns-nameservers',
_testsubnet.name,
]
verifylist = [
@@ -939,6 +942,8 @@ class TestSetSubnet(TestSubnet):
"destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]),
('allocation_pools', [{
'start': '8.8.8.100', 'end': '8.8.8.150'}]),
+ ('dns_nameservers', ['10.1.10.1']),
+ ('no_dns_nameservers', True),
('no_host_route', True),
('no_allocation_pool', True),
]
@@ -948,6 +953,7 @@ class TestSetSubnet(TestSubnet):
'host_routes': [{
"destination": "10.30.30.30/24", "nexthop": "10.30.30.1"}],
'allocation_pools': [{'start': '8.8.8.100', 'end': '8.8.8.150'}],
+ 'dns_nameservers': ["10.1.10.1"],
}
self.network.update_subnet.assert_called_once_with(
_testsubnet, **attrs)
diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py
index fa6ffff3..f12537e7 100644
--- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py
+++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py
@@ -435,7 +435,7 @@ class TestListSubnetPool(TestSubnetPool):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'shared': False}
+ filters = {'shared': False, 'is_shared': False}
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -451,7 +451,7 @@ class TestListSubnetPool(TestSubnetPool):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'shared': True}
+ filters = {'shared': True, 'is_shared': True}
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -501,7 +501,7 @@ class TestListSubnetPool(TestSubnetPool):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id}
+ filters = {'tenant_id': project.id, 'project_id': project.id}
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
@@ -521,7 +521,7 @@ class TestListSubnetPool(TestSubnetPool):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id}
+ filters = {'tenant_id': project.id, 'project_id': project.id}
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py
index 3d91da9b..b9fac684 100644
--- a/openstackclient/tests/unit/test_shell.py
+++ b/openstackclient/tests/unit/test_shell.py
@@ -422,7 +422,7 @@ class TestShellArgV(TestShell):
Use the argv supplied by the test runner so we get actual Python
runtime behaviour; we only need to check the type of argv[0]
- which will alwyas be present.
+ which will always be present.
"""
with mock.patch(
diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py
index 3999543c..fff5181d 100644
--- a/openstackclient/tests/unit/volume/v1/fakes.py
+++ b/openstackclient/tests/unit/volume/v1/fakes.py
@@ -23,115 +23,6 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.unit import utils
-volume_id = 'vvvvvvvv-vvvv-vvvv-vvvvvvvv'
-volume_name = 'nigel'
-volume_description = 'Nigel Tufnel'
-volume_status = 'available'
-volume_size = 120
-volume_type = 'to-eleven'
-volume_zone = 'stonehenge'
-volume_metadata = {
- 'Alpha': 'a',
- 'Beta': 'b',
- 'Gamma': 'g',
-}
-volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'"
-
-VOLUME = {
- 'id': volume_id,
- 'display_name': volume_name,
- 'display_description': volume_description,
- 'size': volume_size,
- 'status': volume_status,
- 'attach_status': 'detached',
- 'availability_zone': volume_zone,
- 'volume_type': volume_type,
- 'metadata': volume_metadata,
-}
-
-extension_name = 'SchedulerHints'
-extension_namespace = 'http://docs.openstack.org/'\
- 'block-service/ext/scheduler-hints/api/v2'
-extension_description = 'Pass arbitrary key/value'\
- 'pairs to the scheduler.'
-extension_updated = '2014-02-07T12:00:0-00:00'
-extension_alias = 'OS-SCH-HNT'
-extension_links = '[{"href":'\
- '"https://github.com/openstack/block-api", "type":'\
- ' "text/html", "rel": "describedby"}]'
-
-EXTENSION = {
- 'name': extension_name,
- 'namespace': extension_namespace,
- 'description': extension_description,
- 'updated': extension_updated,
- 'alias': extension_alias,
- 'links': extension_links,
-}
-
-# NOTE(dtroyer): duplicating here the minimum image info needed to test
-# volume create --image until circular references can be
-# avoided by refactoring the test fakes.
-
-image_id = 'im1'
-image_name = 'graven'
-
-
-IMAGE = {
- 'id': image_id,
- 'name': image_name,
-}
-
-type_id = "5520dc9e-6f9b-4378-a719-729911c0f407"
-type_name = "fake-lvmdriver-1"
-
-TYPE = {
- 'id': type_id,
- 'name': type_name
-}
-
-qos_id = '6f2be1de-997b-4230-b76c-a3633b59e8fb'
-qos_consumer = 'front-end'
-qos_default_consumer = 'both'
-qos_name = "fake-qos-specs"
-qos_specs = {
- 'foo': 'bar',
- 'iops': '9001'
-}
-qos_association = {
- 'association_type': 'volume_type',
- 'name': type_name,
- 'id': type_id
-}
-
-QOS = {
- 'id': qos_id,
- 'consumer': qos_consumer,
- 'name': qos_name
-}
-
-QOS_DEFAULT_CONSUMER = {
- 'id': qos_id,
- 'consumer': qos_default_consumer,
- 'name': qos_name
-}
-
-QOS_WITH_SPECS = {
- 'id': qos_id,
- 'consumer': qos_consumer,
- 'name': qos_name,
- 'specs': qos_specs
-}
-
-QOS_WITH_ASSOCIATIONS = {
- 'id': qos_id,
- 'consumer': qos_consumer,
- 'name': qos_name,
- 'specs': qos_specs,
- 'associations': [qos_association]
-}
-
-
class FakeTransfer(object):
"""Fake one or more Transfer."""
@@ -306,6 +197,32 @@ class FakeQos(object):
return qos
@staticmethod
+ def create_one_qos_association(attrs=None):
+ """Create a fake Qos specification association.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with id, name, association_type, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ qos_association_info = {
+ "id": 'type-id-' + uuid.uuid4().hex,
+ "name": 'type-name-' + uuid.uuid4().hex,
+ "association_type": 'volume_type',
+ }
+
+ # Overwrite default attributes.
+ qos_association_info.update(attrs)
+
+ qos_association = fakes.FakeResource(
+ info=copy.deepcopy(qos_association_info),
+ loaded=True)
+ return qos_association
+
+ @staticmethod
def create_qoses(attrs=None, count=2):
"""Create multiple fake Qos specifications.
@@ -447,6 +364,9 @@ class FakeVolumev1Client(object):
self.qos_specs.resource_class = fakes.FakeResource(None, {})
self.volume_types = mock.Mock()
self.volume_types.resource_class = fakes.FakeResource(None, {})
+ self.volume_encryption_types = mock.Mock()
+ self.volume_encryption_types.resource_class = (
+ fakes.FakeResource(None, {}))
self.transfers = mock.Mock()
self.transfers.resource_class = fakes.FakeResource(None, {})
self.volume_snapshots = mock.Mock()
@@ -553,6 +473,34 @@ class FakeType(object):
return mock.Mock(side_effect=types)
+ @staticmethod
+ def create_one_encryption_type(attrs=None):
+ """Create a fake encryption type.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with volume_type_id etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ encryption_info = {
+ "volume_type_id": 'type-id-' + uuid.uuid4().hex,
+ 'provider': 'LuksEncryptor',
+ 'cipher': None,
+ 'key_size': None,
+ 'control_location': 'front-end',
+ }
+
+ # Overwrite default attributes.
+ encryption_info.update(attrs)
+
+ encryption_type = fakes.FakeResource(
+ info=copy.deepcopy(encryption_info),
+ loaded=True)
+ return encryption_type
+
class FakeSnapshot(object):
"""Fake one or more snapshot."""
diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_backup.py
index 32c2fd22..1097d3f1 100644
--- a/openstackclient/tests/unit/volume/v1/test_backup.py
+++ b/openstackclient/tests/unit/volume/v1/test_backup.py
@@ -249,26 +249,65 @@ class TestBackupList(TestBackup):
self.volumes_mock.list.return_value = [self.volume]
self.backups_mock.list.return_value = self.backups
+ self.volumes_mock.get.return_value = self.volume
# Get the command to test
self.cmd = backup.ListVolumeBackup(self.app, None)
def test_backup_list_without_options(self):
arglist = []
- verifylist = [("long", False)]
+ verifylist = [
+ ("long", False),
+ ("name", None),
+ ("status", None),
+ ("volume", None),
+ ('all_projects', False),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ "name": None,
+ "status": None,
+ "volume_id": None,
+ "all_tenants": False,
+ }
+ self.volumes_mock.get.assert_not_called()
+ self.backups_mock.list.assert_called_with(
+ search_opts=search_opts,
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
def test_backup_list_with_options(self):
- arglist = ["--long"]
- verifylist = [("long", True)]
+ arglist = [
+ "--long",
+ "--name", self.backups[0].name,
+ "--status", "error",
+ "--volume", self.volume.id,
+ "--all-projects"
+ ]
+ verifylist = [
+ ("long", True),
+ ("name", self.backups[0].name),
+ ("status", "error"),
+ ("volume", self.volume.id),
+ ('all_projects', True),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ "name": self.backups[0].name,
+ "status": "error",
+ "volume_id": self.volume.id,
+ "all_tenants": True,
+ }
+ self.volumes_mock.get.assert_called_once_with(self.volume.id)
+ self.backups_mock.list.assert_called_with(
+ search_opts=search_opts,
+ )
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py
index 1982980a..e3dc1e78 100644
--- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py
+++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py
@@ -13,14 +13,12 @@
# under the License.
#
-import copy
import mock
from mock import call
from osc_lib import exceptions
from osc_lib import utils
-from openstackclient.tests.unit import fakes
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
from openstackclient.volume.v1 import qos_specs
@@ -39,38 +37,33 @@ class TestQos(volume_fakes.TestVolumev1):
class TestQosAssociate(TestQos):
+ volume_type = volume_fakes.FakeType.create_one_type()
+ qos_spec = volume_fakes.FakeQos.create_one_qos()
+
def setUp(self):
super(TestQosAssociate, self).setUp()
+ self.qos_mock.get.return_value = self.qos_spec
+ self.types_mock.get.return_value = self.volume_type
# Get the command object to test
self.cmd = qos_specs.AssociateQos(self.app, None)
def test_qos_associate(self):
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS),
- loaded=True
- )
- self.types_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.TYPE),
- loaded=True
- )
arglist = [
- volume_fakes.qos_id,
- volume_fakes.type_id
+ self.qos_spec.id,
+ self.volume_type.id
]
verifylist = [
- ('qos_spec', volume_fakes.qos_id),
- ('volume_type', volume_fakes.type_id)
+ ('qos_spec', self.qos_spec.id),
+ ('volume_type', self.volume_type.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.qos_mock.associate.assert_called_with(
- volume_fakes.qos_id,
- volume_fakes.type_id
+ self.qos_spec.id,
+ self.volume_type.id
)
self.assertIsNone(result)
@@ -81,112 +74,86 @@ class TestQosCreate(TestQos):
'consumer',
'id',
'name',
- )
- datalist = (
- volume_fakes.qos_consumer,
- volume_fakes.qos_id,
- volume_fakes.qos_name
+ 'properties'
)
def setUp(self):
super(TestQosCreate, self).setUp()
-
+ self.new_qos_spec = volume_fakes.FakeQos.create_one_qos()
+ self.datalist = (
+ self.new_qos_spec.consumer,
+ self.new_qos_spec.id,
+ self.new_qos_spec.name,
+ utils.format_dict(self.new_qos_spec.specs)
+ )
+ self.qos_mock.create.return_value = self.new_qos_spec
# Get the command object to test
self.cmd = qos_specs.CreateQos(self.app, None)
def test_qos_create_without_properties(self):
- self.qos_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER),
- loaded=True
- )
-
arglist = [
- volume_fakes.qos_name,
+ self.new_qos_spec.name,
]
verifylist = [
- ('name', volume_fakes.qos_name),
+ ('name', self.new_qos_spec.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.qos_mock.create.assert_called_with(
- volume_fakes.qos_name,
- {'consumer': volume_fakes.qos_default_consumer}
+ self.new_qos_spec.name,
+ {'consumer': 'both'}
)
self.assertEqual(self.columns, columns)
- datalist = (
- volume_fakes.qos_default_consumer,
- volume_fakes.qos_id,
- volume_fakes.qos_name
- )
- self.assertEqual(datalist, data)
+ self.assertEqual(self.datalist, data)
def test_qos_create_with_consumer(self):
- self.qos_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS),
- loaded=True
- )
-
arglist = [
- volume_fakes.qos_name,
- '--consumer', volume_fakes.qos_consumer
+ '--consumer', self.new_qos_spec.consumer,
+ self.new_qos_spec.name,
]
verifylist = [
- ('name', volume_fakes.qos_name),
- ('consumer', volume_fakes.qos_consumer)
+ ('consumer', self.new_qos_spec.consumer),
+ ('name', self.new_qos_spec.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.qos_mock.create.assert_called_with(
- volume_fakes.qos_name,
- {'consumer': volume_fakes.qos_consumer}
+ self.new_qos_spec.name,
+ {'consumer': self.new_qos_spec.consumer}
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
def test_qos_create_with_properties(self):
- self.qos_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS_WITH_SPECS),
- loaded=True
- )
-
arglist = [
- volume_fakes.qos_name,
- '--consumer', volume_fakes.qos_consumer,
+ '--consumer', self.new_qos_spec.consumer,
'--property', 'foo=bar',
- '--property', 'iops=9001'
+ '--property', 'iops=9001',
+ self.new_qos_spec.name,
]
verifylist = [
- ('name', volume_fakes.qos_name),
- ('consumer', volume_fakes.qos_consumer),
- ('property', volume_fakes.qos_specs)
+ ('consumer', self.new_qos_spec.consumer),
+ ('property', self.new_qos_spec.specs),
+ ('name', self.new_qos_spec.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- specs = volume_fakes.qos_specs.copy()
- specs.update({'consumer': volume_fakes.qos_consumer})
+ self.new_qos_spec.specs.update(
+ {'consumer': self.new_qos_spec.consumer})
self.qos_mock.create.assert_called_with(
- volume_fakes.qos_name,
- specs
+ self.new_qos_spec.name,
+ self.new_qos_spec.specs
)
- columns = self.columns + (
- 'specs',
- )
- self.assertEqual(columns, columns)
- datalist = self.datalist + (
- volume_fakes.qos_specs,
- )
- self.assertEqual(datalist, data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
class TestQosDelete(TestQos):
@@ -294,79 +261,62 @@ class TestQosDelete(TestQos):
class TestQosDisassociate(TestQos):
+ volume_type = volume_fakes.FakeType.create_one_type()
+ qos_spec = volume_fakes.FakeQos.create_one_qos()
+
def setUp(self):
super(TestQosDisassociate, self).setUp()
+ self.qos_mock.get.return_value = self.qos_spec
+ self.types_mock.get.return_value = self.volume_type
# Get the command object to test
self.cmd = qos_specs.DisassociateQos(self.app, None)
def test_qos_disassociate_with_volume_type(self):
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS),
- loaded=True
- )
- self.types_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.TYPE),
- loaded=True
- )
arglist = [
- volume_fakes.qos_id,
- '--volume-type', volume_fakes.type_id
+ '--volume-type', self.volume_type.id,
+ self.qos_spec.id,
]
verifylist = [
- ('qos_spec', volume_fakes.qos_id),
- ('volume_type', volume_fakes.type_id)
+ ('volume_type', self.volume_type.id),
+ ('qos_spec', self.qos_spec.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.qos_mock.disassociate.assert_called_with(
- volume_fakes.qos_id,
- volume_fakes.type_id
+ self.qos_spec.id,
+ self.volume_type.id
)
self.assertIsNone(result)
def test_qos_disassociate_with_all_volume_types(self):
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS),
- loaded=True
- )
-
arglist = [
- volume_fakes.qos_id,
- '--all'
+ '--all',
+ self.qos_spec.id,
]
verifylist = [
- ('qos_spec', volume_fakes.qos_id)
+ ('qos_spec', self.qos_spec.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id)
+ self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id)
self.assertIsNone(result)
class TestQosList(TestQos):
+ qos_spec = volume_fakes.FakeQos.create_one_qos()
+ qos_association = volume_fakes.FakeQos.create_one_qos_association()
+
def setUp(self):
super(TestQosList, self).setUp()
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS),
- loaded=True,
- )
- self.qos_mock.list.return_value = [self.qos_mock.get.return_value]
- self.qos_mock.get_associations.return_value = [fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.qos_association),
- loaded=True,
- )]
+ self.qos_mock.list.return_value = [self.qos_spec]
+ self.qos_mock.get_associations.return_value = [self.qos_association]
# Get the command object to test
self.cmd = qos_specs.ListQos(self.app, None)
@@ -385,85 +335,76 @@ class TestQosList(TestQos):
'Name',
'Consumer',
'Associations',
- 'Specs',
+ 'Properties',
)
self.assertEqual(collist, columns)
datalist = ((
- volume_fakes.qos_id,
- volume_fakes.qos_name,
- volume_fakes.qos_consumer,
- volume_fakes.type_name,
- utils.format_dict(volume_fakes.qos_specs),
+ self.qos_spec.id,
+ self.qos_spec.name,
+ self.qos_spec.consumer,
+ self.qos_association.name,
+ utils.format_dict(self.qos_spec.specs),
), )
self.assertEqual(datalist, tuple(data))
class TestQosSet(TestQos):
+ qos_spec = volume_fakes.FakeQos.create_one_qos()
+
def setUp(self):
super(TestQosSet, self).setUp()
+ self.qos_mock.get.return_value = self.qos_spec
# Get the command object to test
self.cmd = qos_specs.SetQos(self.app, None)
def test_qos_set_with_properties_with_id(self):
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS_WITH_SPECS),
- loaded=True
- )
arglist = [
- volume_fakes.qos_id,
'--property', 'foo=bar',
- '--property', 'iops=9001'
+ '--property', 'iops=9001',
+ self.qos_spec.id,
]
verifylist = [
- ('qos_spec', volume_fakes.qos_id),
- ('property', volume_fakes.qos_specs)
+ ('property', self.qos_spec.specs),
+ ('qos_spec', self.qos_spec.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.qos_mock.set_keys.assert_called_with(
- volume_fakes.qos_id,
- volume_fakes.qos_specs
+ self.qos_spec.id,
+ self.qos_spec.specs
)
self.assertIsNone(result)
class TestQosShow(TestQos):
+ qos_spec = volume_fakes.FakeQos.create_one_qos()
+ qos_association = volume_fakes.FakeQos.create_one_qos_association()
+
def setUp(self):
super(TestQosShow, self).setUp()
-
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS),
- loaded=True,
- )
- self.qos_mock.get_associations.return_value = [fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.qos_association),
- loaded=True,
- )]
-
+ self.qos_mock.get.return_value = self.qos_spec
+ self.qos_mock.get_associations.return_value = [self.qos_association]
# Get the command object to test
self.cmd = qos_specs.ShowQos(self.app, None)
def test_qos_show(self):
arglist = [
- volume_fakes.qos_id
+ self.qos_spec.id
]
verifylist = [
- ('qos_spec', volume_fakes.qos_id)
+ ('qos_spec', self.qos_spec.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.qos_mock.get.assert_called_with(
- volume_fakes.qos_id
+ self.qos_spec.id
)
collist = (
@@ -471,60 +412,57 @@ class TestQosShow(TestQos):
'consumer',
'id',
'name',
- 'specs'
+ 'properties'
)
self.assertEqual(collist, columns)
datalist = (
- volume_fakes.type_name,
- volume_fakes.qos_consumer,
- volume_fakes.qos_id,
- volume_fakes.qos_name,
- utils.format_dict(volume_fakes.qos_specs),
+ self.qos_association.name,
+ self.qos_spec.consumer,
+ self.qos_spec.id,
+ self.qos_spec.name,
+ utils.format_dict(self.qos_spec.specs),
)
self.assertEqual(datalist, tuple(data))
class TestQosUnset(TestQos):
+ qos_spec = volume_fakes.FakeQos.create_one_qos()
+
def setUp(self):
super(TestQosUnset, self).setUp()
+ self.qos_mock.get.return_value = self.qos_spec
# Get the command object to test
self.cmd = qos_specs.UnsetQos(self.app, None)
def test_qos_unset_with_properties(self):
- self.qos_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.QOS),
- loaded=True
- )
arglist = [
- volume_fakes.qos_id,
'--property', 'iops',
- '--property', 'foo'
+ '--property', 'foo',
+ self.qos_spec.id,
]
-
verifylist = [
- ('qos_spec', volume_fakes.qos_id),
- ('property', ['iops', 'foo'])
+ ('property', ['iops', 'foo']),
+ ('qos_spec', self.qos_spec.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.qos_mock.unset_keys.assert_called_with(
- volume_fakes.qos_id,
+ self.qos_spec.id,
['iops', 'foo']
)
self.assertIsNone(result)
def test_qos_unset_nothing(self):
arglist = [
- volume_fakes.qos_id,
+ self.qos_spec.id,
]
verifylist = [
- ('qos_spec', volume_fakes.qos_id),
+ ('qos_spec', self.qos_spec.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py
index edfbdc19..87a62b0a 100644
--- a/openstackclient/tests/unit/volume/v1/test_snapshot.py
+++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py
@@ -19,7 +19,7 @@ from osc_lib import exceptions
from osc_lib import utils
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
-from openstackclient.volume.v1 import snapshot
+from openstackclient.volume.v1 import volume_snapshot
class TestSnapshot(volume_fakes.TestVolumev1):
@@ -67,20 +67,20 @@ class TestSnapshotCreate(TestSnapshot):
self.volumes_mock.get.return_value = self.volume
self.snapshots_mock.create.return_value = self.new_snapshot
# Get the command object to test
- self.cmd = snapshot.CreateSnapshot(self.app, None)
+ self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None)
def test_snapshot_create(self):
arglist = [
- "--name", self.new_snapshot.display_name,
+ "--volume", self.new_snapshot.volume_id,
"--description", self.new_snapshot.display_description,
"--force",
- self.new_snapshot.volume_id,
+ self.new_snapshot.display_name,
]
verifylist = [
- ("name", self.new_snapshot.display_name),
+ ("volume", self.new_snapshot.volume_id),
("description", self.new_snapshot.display_description),
("force", True),
- ("volume", self.new_snapshot.volume_id),
+ ("snapshot_name", self.new_snapshot.display_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -97,7 +97,7 @@ class TestSnapshotCreate(TestSnapshot):
def test_snapshot_create_without_name(self):
arglist = [
- self.new_snapshot.volume_id,
+ "--volume", self.new_snapshot.volume_id,
"--description", self.new_snapshot.display_description,
"--force"
]
@@ -119,6 +119,32 @@ class TestSnapshotCreate(TestSnapshot):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_snapshot_create_without_volume(self):
+ arglist = [
+ "--description", self.new_snapshot.display_description,
+ "--force",
+ self.new_snapshot.display_name
+ ]
+ verifylist = [
+ ("description", self.new_snapshot.display_description),
+ ("force", True),
+ ("snapshot_name", self.new_snapshot.display_name)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.get.assert_called_once_with(
+ self.new_snapshot.display_name)
+ self.snapshots_mock.create.assert_called_once_with(
+ self.new_snapshot.volume_id,
+ True,
+ self.new_snapshot.display_name,
+ self.new_snapshot.display_description,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestSnapshotDelete(TestSnapshot):
@@ -132,7 +158,7 @@ class TestSnapshotDelete(TestSnapshot):
self.snapshots_mock.delete.return_value = None
# Get the command object to mock
- self.cmd = snapshot.DeleteSnapshot(self.app, None)
+ self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None)
def test_snapshot_delete(self):
arglist = [
@@ -242,9 +268,10 @@ class TestSnapshotList(TestSnapshot):
super(TestSnapshotList, self).setUp()
self.volumes_mock.list.return_value = [self.volume]
+ self.volumes_mock.get.return_value = self.volume
self.snapshots_mock.list.return_value = self.snapshots
# Get the command to test
- self.cmd = snapshot.ListSnapshot(self.app, None)
+ self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None)
def test_snapshot_list_without_options(self):
arglist = []
@@ -257,7 +284,13 @@ class TestSnapshotList(TestSnapshot):
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.list.assert_called_once_with(
- search_opts={'all_tenants': False})
+ search_opts={
+ 'all_tenants': False,
+ 'display_name': None,
+ 'status': None,
+ 'volume_id': None
+ }
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -274,11 +307,88 @@ class TestSnapshotList(TestSnapshot):
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.list.assert_called_once_with(
- search_opts={'all_tenants': False}
+ search_opts={
+ 'all_tenants': False,
+ 'display_name': None,
+ 'status': None,
+ 'volume_id': None
+ }
)
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
+ def test_snapshot_list_name_option(self):
+ arglist = [
+ '--name', self.snapshots[0].display_name,
+ ]
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('name', self.snapshots[0].display_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ search_opts={
+ 'all_tenants': False,
+ 'display_name': self.snapshots[0].display_name,
+ 'status': None,
+ 'volume_id': None
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_snapshot_list_status_option(self):
+ arglist = [
+ '--status', self.snapshots[0].status,
+ ]
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('status', self.snapshots[0].status),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ search_opts={
+ 'all_tenants': False,
+ 'display_name': None,
+ 'status': self.snapshots[0].status,
+ 'volume_id': None
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_snapshot_list_volumeid_option(self):
+ arglist = [
+ '--volume', self.volume.id,
+ ]
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('volume', self.volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ search_opts={
+ 'all_tenants': False,
+ 'display_name': None,
+ 'status': None,
+ 'volume_id': self.volume.id
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
def test_snapshot_list_all_projects(self):
arglist = [
'--all-projects',
@@ -292,7 +402,13 @@ class TestSnapshotList(TestSnapshot):
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.list.assert_called_once_with(
- search_opts={'all_tenants': True})
+ search_opts={
+ 'all_tenants': True,
+ 'display_name': None,
+ 'status': None,
+ 'volume_id': None
+ }
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -307,21 +423,23 @@ class TestSnapshotSet(TestSnapshot):
self.snapshots_mock.get.return_value = self.snapshot
self.snapshots_mock.set_metadata.return_value = None
# Get the command object to mock
- self.cmd = snapshot.SetSnapshot(self.app, None)
+ self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None)
def test_snapshot_set_all(self):
arglist = [
"--name", "new_snapshot",
"--description", "new_description",
- "--property", "x=y",
- "--property", "foo=foo",
+ "--property", "foo_1=foo_1",
+ "--property", "foo_2=foo_2",
+ "--no-property",
self.snapshot.id,
]
- new_property = {"x": "y", "foo": "foo"}
+ new_property = {"foo_1": "foo_1", "foo_2": "foo_2"}
verifylist = [
("name", "new_snapshot"),
("description", "new_description"),
("property", new_property),
+ ("no_property", True),
("snapshot", self.snapshot.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -333,8 +451,11 @@ class TestSnapshotSet(TestSnapshot):
"display_description": "new_description",
}
self.snapshot.update.assert_called_with(**kwargs)
+ self.snapshots_mock.delete_metadata.assert_called_with(
+ self.snapshot.id, ["foo"]
+ )
self.snapshots_mock.set_metadata.assert_called_with(
- self.snapshot.id, new_property
+ self.snapshot.id, {"foo_2": "foo_2", "foo_1": "foo_1"}
)
self.assertIsNone(result)
@@ -404,7 +525,7 @@ class TestSnapshotShow(TestSnapshot):
self.snapshots_mock.get.return_value = self.snapshot
# Get the command object to test
- self.cmd = snapshot.ShowSnapshot(self.app, None)
+ self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None)
def test_snapshot_show(self):
arglist = [
@@ -432,7 +553,7 @@ class TestSnapshotUnset(TestSnapshot):
self.snapshots_mock.get.return_value = self.snapshot
self.snapshots_mock.delete_metadata.return_value = None
# Get the command object to mock
- self.cmd = snapshot.UnsetSnapshot(self.app, None)
+ self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None)
def test_snapshot_unset(self):
arglist = [
diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py
index 23a1186d..dcdd3d56 100644
--- a/openstackclient/tests/unit/volume/v1/test_type.py
+++ b/openstackclient/tests/unit/volume/v1/test_type.py
@@ -31,6 +31,10 @@ class TestType(volume_fakes.TestVolumev1):
self.types_mock = self.app.client_manager.volume.volume_types
self.types_mock.reset_mock()
+ self.encryption_types_mock = (
+ self.app.client_manager.volume.volume_encryption_types)
+ self.encryption_types_mock.reset_mock()
+
class TestTypeCreate(TestType):
@@ -75,6 +79,67 @@ class TestTypeCreate(TestType):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_type_create_with_encryption(self):
+ encryption_info = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': '128',
+ 'control_location': 'front-end',
+ }
+ encryption_type = volume_fakes.FakeType.create_one_encryption_type(
+ attrs=encryption_info
+ )
+ self.new_volume_type = volume_fakes.FakeType.create_one_type(
+ attrs={'encryption': encryption_info})
+ self.types_mock.create.return_value = self.new_volume_type
+ self.encryption_types_mock.create.return_value = encryption_type
+ encryption_columns = (
+ 'description',
+ 'encryption',
+ 'id',
+ 'is_public',
+ 'name',
+ )
+ encryption_data = (
+ self.new_volume_type.description,
+ utils.format_dict(encryption_info),
+ self.new_volume_type.id,
+ True,
+ self.new_volume_type.name,
+ )
+ arglist = [
+ '--encryption-provider', 'LuksEncryptor',
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-key-size', '128',
+ '--encryption-control-location', 'front-end',
+ self.new_volume_type.name,
+ ]
+ verifylist = [
+ ('encryption_provider', 'LuksEncryptor'),
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_key_size', 128),
+ ('encryption_control_location', 'front-end'),
+ ('name', self.new_volume_type.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.create.assert_called_with(
+ self.new_volume_type.name,
+ )
+ body = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': 128,
+ 'control_location': 'front-end',
+ }
+ self.encryption_types_mock.create.assert_called_with(
+ self.new_volume_type,
+ body,
+ )
+ self.assertEqual(encryption_columns, columns)
+ self.assertEqual(encryption_data, data)
+
class TestTypeDelete(TestType):
@@ -156,27 +221,31 @@ class TestTypeList(TestType):
volume_types = volume_fakes.FakeType.create_types()
- columns = (
+ columns = [
"ID",
- "Name"
- )
- columns_long = (
+ "Name",
+ "Is Public",
+ ]
+ columns_long = [
"ID",
"Name",
+ "Is Public",
"Properties"
- )
+ ]
data = []
for t in volume_types:
data.append((
t.id,
t.name,
+ t.is_public,
))
data_long = []
for t in volume_types:
data_long.append((
t.id,
t.name,
+ t.is_public,
utils.format_dict(t.extra_specs),
))
@@ -184,6 +253,8 @@ class TestTypeList(TestType):
super(TestTypeList, self).setUp()
self.types_mock.list.return_value = self.volume_types
+ self.encryption_types_mock.create.return_value = None
+ self.encryption_types_mock.update.return_value = None
# get the command to test
self.cmd = volume_type.ListVolumeType(self.app, None)
@@ -191,6 +262,7 @@ class TestTypeList(TestType):
arglist = []
verifylist = [
("long", False),
+ ("encryption_type", False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -213,6 +285,47 @@ class TestTypeList(TestType):
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
+ def test_type_list_with_encryption(self):
+ encryption_type = volume_fakes.FakeType.create_one_encryption_type(
+ attrs={'volume_type_id': self.volume_types[0].id})
+ encryption_info = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': None,
+ 'key_size': None,
+ 'control_location': 'front-end',
+ }
+ encryption_columns = self.columns + [
+ "Encryption",
+ ]
+ encryption_data = []
+ encryption_data.append((
+ self.volume_types[0].id,
+ self.volume_types[0].name,
+ self.volume_types[0].is_public,
+ utils.format_dict(encryption_info),
+ ))
+ encryption_data.append((
+ self.volume_types[1].id,
+ self.volume_types[1].name,
+ self.volume_types[1].is_public,
+ '-',
+ ))
+
+ self.encryption_types_mock.list.return_value = [encryption_type]
+ arglist = [
+ "--encryption-type",
+ ]
+ verifylist = [
+ ("encryption_type", True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.encryption_types_mock.list.assert_called_once_with()
+ self.types_mock.list.assert_called_once_with()
+ self.assertEqual(encryption_columns, columns)
+ self.assertEqual(encryption_data, list(data))
+
class TestTypeSet(TestType):
@@ -256,6 +369,60 @@ class TestTypeSet(TestType):
{'myprop': 'myvalue'})
self.assertIsNone(result)
+ def test_type_set_new_encryption(self):
+ arglist = [
+ '--encryption-provider', 'LuksEncryptor',
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-key-size', '128',
+ '--encryption-control-location', 'front-end',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_provider', 'LuksEncryptor'),
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_key_size', 128),
+ ('encryption_control_location', 'front-end'),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ body = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': 128,
+ 'control_location': 'front-end',
+ }
+ self.encryption_types_mock.create.assert_called_with(
+ self.volume_type,
+ body,
+ )
+ self.assertIsNone(result)
+
+ def test_type_set_new_encryption_without_provider(self):
+ arglist = [
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-key-size', '128',
+ '--encryption-control-location', 'front-end',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_key_size', 128),
+ ('encryption_control_location', 'front-end'),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual("Command Failed: One or more of"
+ " the operations failed",
+ str(e))
+ self.encryption_types_mock.create.assert_not_called()
+ self.encryption_types_mock.update.assert_not_called()
+
class TestTypeShow(TestType):
@@ -289,7 +456,8 @@ class TestTypeShow(TestType):
self.volume_type.id
]
verifylist = [
- ("volume_type", self.volume_type.id)
+ ("volume_type", self.volume_type.id),
+ ("encryption_type", False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -299,6 +467,50 @@ class TestTypeShow(TestType):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_type_show_with_encryption(self):
+ encryption_type = volume_fakes.FakeType.create_one_encryption_type()
+ encryption_info = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': None,
+ 'key_size': None,
+ 'control_location': 'front-end',
+ }
+ self.volume_type = volume_fakes.FakeType.create_one_type(
+ attrs={'encryption': encryption_info})
+ self.types_mock.get.return_value = self.volume_type
+ self.encryption_types_mock.get.return_value = encryption_type
+ encryption_columns = (
+ 'description',
+ 'encryption',
+ 'id',
+ 'is_public',
+ 'name',
+ 'properties',
+ )
+ encryption_data = (
+ self.volume_type.description,
+ utils.format_dict(encryption_info),
+ self.volume_type.id,
+ True,
+ self.volume_type.name,
+ utils.format_dict(self.volume_type.extra_specs)
+ )
+ arglist = [
+ '--encryption-type',
+ self.volume_type.id
+ ]
+ verifylist = [
+ ('encryption_type', True),
+ ("volume_type", self.volume_type.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.get.assert_called_with(self.volume_type.id)
+ self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
+ self.assertEqual(encryption_columns, columns)
+ self.assertEqual(encryption_data, data)
+
class TestTypeUnset(TestType):
@@ -313,13 +525,14 @@ class TestTypeUnset(TestType):
# Get the command object to test
self.cmd = volume_type.UnsetVolumeType(self.app, None)
- def test_type_unset(self):
+ def test_type_unset_property(self):
arglist = [
'--property', 'property',
'--property', 'multi_property',
self.volume_type.id,
]
verifylist = [
+ ('encryption_type', False),
('property', ['property', 'multi_property']),
('volume_type', self.volume_type.id),
]
@@ -329,6 +542,7 @@ class TestTypeUnset(TestType):
result = self.cmd.take_action(parsed_args)
self.volume_type.unset_keys.assert_called_once_with(
['property', 'multi_property'])
+ self.encryption_types_mock.delete.assert_not_called()
self.assertIsNone(result)
def test_type_unset_failed_with_missing_volume_type_argument(self):
@@ -358,3 +572,18 @@ class TestTypeUnset(TestType):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
+
+ def test_type_unset_encryption_type(self):
+ arglist = [
+ '--encryption-type',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_type', True),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.encryption_types_mock.delete.assert_called_with(self.volume_type)
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py
index 73c00844..d46a7ba9 100644
--- a/openstackclient/tests/unit/volume/v1/test_volume.py
+++ b/openstackclient/tests/unit/volume/v1/test_volume.py
@@ -14,15 +14,14 @@
#
import argparse
-import copy
import mock
from mock import call
from osc_lib import exceptions
from osc_lib import utils
-from openstackclient.tests.unit import fakes
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
+from openstackclient.tests.unit.image.v1 import fakes as image_fakes
from openstackclient.tests.unit import utils as tests_utils
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
from openstackclient.volume.v1 import volume
@@ -58,10 +57,6 @@ class TestVolume(volume_fakes.TestVolumev1):
return volumes
-# TODO(dtroyer): The volume create tests are incomplete, only the minimal
-# options and the options that require additional processing
-# are implemented at this time.
-
class TestVolumeCreate(TestVolume):
project = identity_fakes.FakeProject.create_one_project()
@@ -321,19 +316,16 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(self.datalist, data)
def test_volume_create_image_id(self):
- self.images_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.IMAGE),
- loaded=True,
- )
+ image = image_fakes.FakeImage.create_one_image()
+ self.images_mock.get.return_value = image
arglist = [
- '--image', volume_fakes.image_id,
+ '--image', image.id,
'--size', str(self.new_volume.size),
self.new_volume.display_name,
]
verifylist = [
- ('image', volume_fakes.image_id),
+ ('image', image.id),
('size', self.new_volume.size),
('name', self.new_volume.display_name),
]
@@ -360,26 +352,23 @@ class TestVolumeCreate(TestVolume):
None,
None,
None,
- volume_fakes.image_id,
+ image.id,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
def test_volume_create_image_name(self):
- self.images_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.IMAGE),
- loaded=True,
- )
+ image = image_fakes.FakeImage.create_one_image()
+ self.images_mock.get.return_value = image
arglist = [
- '--image', volume_fakes.image_name,
+ '--image', image.name,
'--size', str(self.new_volume.size),
self.new_volume.display_name,
]
verifylist = [
- ('image', volume_fakes.image_name),
+ ('image', image.name),
('size', self.new_volume.size),
('name', self.new_volume.display_name),
]
@@ -406,7 +395,7 @@ class TestVolumeCreate(TestVolume):
None,
None,
None,
- volume_fakes.image_id,
+ image.id,
)
self.assertEqual(self.columns, columns)
@@ -442,6 +431,142 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
+ def test_volume_create_with_bootable_and_readonly(self):
+ arglist = [
+ '--bootable',
+ '--read-only',
+ '--size', str(self.new_volume.size),
+ self.new_volume.display_name,
+ ]
+ verifylist = [
+ ('bootable', True),
+ ('non_bootable', False),
+ ('read_only', True),
+ ('read_write', False),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.display_name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ self.new_volume.size,
+ None,
+ None,
+ self.new_volume.display_name,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
+ self.volumes_mock.set_bootable.assert_called_with(
+ self.new_volume.id, True)
+ self.volumes_mock.update_readonly_flag.assert_called_with(
+ self.new_volume.id, True)
+
+ def test_volume_create_with_nonbootable_and_readwrite(self):
+ arglist = [
+ '--non-bootable',
+ '--read-write',
+ '--size', str(self.new_volume.size),
+ self.new_volume.display_name,
+ ]
+ verifylist = [
+ ('bootable', False),
+ ('non_bootable', True),
+ ('read_only', False),
+ ('read_write', True),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.display_name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ self.new_volume.size,
+ None,
+ None,
+ self.new_volume.display_name,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
+ self.volumes_mock.set_bootable.assert_called_with(
+ self.new_volume.id, False)
+ self.volumes_mock.update_readonly_flag.assert_called_with(
+ self.new_volume.id, False)
+
+ @mock.patch.object(volume.LOG, 'error')
+ def test_volume_create_with_bootable_and_readonly_fail(
+ self, mock_error):
+
+ self.volumes_mock.set_bootable.side_effect = (
+ exceptions.CommandError())
+
+ self.volumes_mock.update_readonly_flag.side_effect = (
+ exceptions.CommandError())
+
+ arglist = [
+ '--bootable',
+ '--read-only',
+ '--size', str(self.new_volume.size),
+ self.new_volume.display_name,
+ ]
+ verifylist = [
+ ('bootable', True),
+ ('non_bootable', False),
+ ('read_only', True),
+ ('read_write', False),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.display_name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ self.new_volume.size,
+ None,
+ None,
+ self.new_volume.display_name,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ )
+
+ self.assertEqual(2, mock_error.call_count)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
+ self.volumes_mock.set_bootable.assert_called_with(
+ self.new_volume.id, True)
+ self.volumes_mock.update_readonly_flag.assert_called_with(
+ self.new_volume.id, True)
+
def test_volume_create_without_size(self):
arglist = [
self.new_volume.display_name,
@@ -739,6 +864,68 @@ class TestVolumeList(TestVolume):
self.cmd, arglist, verifylist)
+class TestVolumeMigrate(TestVolume):
+
+ _volume = volume_fakes.FakeVolume.create_one_volume()
+
+ def setUp(self):
+ super(TestVolumeMigrate, self).setUp()
+
+ self.volumes_mock.get.return_value = self._volume
+ self.volumes_mock.migrate_volume.return_value = None
+ # Get the command object to test
+ self.cmd = volume.MigrateVolume(self.app, None)
+
+ def test_volume_migrate(self):
+ arglist = [
+ "--host", "host@backend-name#pool",
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", False),
+ ("host", "host@backend-name#pool"),
+ ("volume", self._volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.get.assert_called_once_with(self._volume.id)
+ self.volumes_mock.migrate_volume.assert_called_once_with(
+ self._volume.id, "host@backend-name#pool", False)
+ self.assertIsNone(result)
+
+ def test_volume_migrate_with_option(self):
+ arglist = [
+ "--force-host-copy",
+ "--host", "host@backend-name#pool",
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", True),
+ ("host", "host@backend-name#pool"),
+ ("volume", self._volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.get.assert_called_once_with(self._volume.id)
+ self.volumes_mock.migrate_volume.assert_called_once_with(
+ self._volume.id, "host@backend-name#pool", True)
+ self.assertIsNone(result)
+
+ def test_volume_migrate_without_host(self):
+ arglist = [
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", False),
+ ("volume", self._volume.id),
+ ]
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+
class TestVolumeSet(TestVolume):
_volume = volume_fakes.FakeVolume.create_one_volume()
@@ -844,8 +1031,7 @@ class TestVolumeSet(TestVolume):
)
self.assertIsNone(result)
- @mock.patch.object(volume.LOG, 'error')
- def test_volume_set_size_smaller(self, mock_log_error):
+ def test_volume_set_size_smaller(self):
self._volume.status = 'available'
arglist = [
'--size', '1',
@@ -860,15 +1046,11 @@ class TestVolumeSet(TestVolume):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- result = self.cmd.take_action(parsed_args)
-
- mock_log_error.assert_called_with("New size must be greater "
- "than %s GB",
- self._volume.size)
- self.assertIsNone(result)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
- @mock.patch.object(volume.LOG, 'error')
- def test_volume_set_size_not_available(self, mock_log_error):
+ def test_volume_set_size_not_available(self):
self._volume.status = 'error'
arglist = [
'--size', '130',
@@ -883,22 +1065,23 @@ class TestVolumeSet(TestVolume):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- result = self.cmd.take_action(parsed_args)
-
- mock_log_error.assert_called_with("Volume is in %s state, it must be "
- "available before size can be "
- "extended", 'error')
- self.assertIsNone(result)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
def test_volume_set_property(self):
arglist = [
+ '--no-property',
'--property', 'myprop=myvalue',
self._volume.display_name,
]
verifylist = [
+ ('read_only', False),
+ ('read_write', False),
('name', None),
('description', None),
('size', None),
+ ('no_property', True),
('property', {'myprop': 'myvalue'}),
('volume', self._volume.display_name),
('bootable', False),
@@ -916,6 +1099,11 @@ class TestVolumeSet(TestVolume):
self._volume.id,
metadata
)
+ self.volumes_mock.delete_metadata.assert_called_with(
+ self._volume.id,
+ self._volume.metadata.keys()
+ )
+ self.volumes_mock.update_readonly_flag.assert_not_called()
self.assertIsNone(result)
def test_volume_set_bootable(self):
@@ -943,6 +1131,44 @@ class TestVolumeSet(TestVolume):
self.volumes_mock.set_bootable.assert_called_with(
self._volume.id, verifylist[index][0][1])
+ def test_volume_set_readonly(self):
+ arglist = [
+ '--read-only',
+ self._volume.id
+ ]
+ verifylist = [
+ ('read_only', True),
+ ('read_write', False),
+ ('volume', self._volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.update_readonly_flag.assert_called_once_with(
+ self._volume.id,
+ True)
+ self.assertIsNone(result)
+
+ def test_volume_set_read_write(self):
+ arglist = [
+ '--read-write',
+ self._volume.id
+ ]
+ verifylist = [
+ ('read_only', False),
+ ('read_write', True),
+ ('volume', self._volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.update_readonly_flag.assert_called_once_with(
+ self._volume.id,
+ False)
+ self.assertIsNone(result)
+
class TestVolumeShow(TestVolume):
diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py
index 5e1d16e1..d321c71a 100644
--- a/openstackclient/tests/unit/volume/v2/fakes.py
+++ b/openstackclient/tests/unit/volume/v2/fakes.py
@@ -208,6 +208,9 @@ class FakeVolumeClient(object):
self.volume_types.resource_class = fakes.FakeResource(None, {})
self.volume_type_access = mock.Mock()
self.volume_type_access.resource_class = fakes.FakeResource(None, {})
+ self.volume_encryption_types = mock.Mock()
+ self.volume_encryption_types.resource_class = (
+ fakes.FakeResource(None, {}))
self.restores = mock.Mock()
self.restores.resource_class = fakes.FakeResource(None, {})
self.qos_specs = mock.Mock()
@@ -224,6 +227,8 @@ class FakeVolumeClient(object):
self.quota_classes.resource_class = fakes.FakeResource(None, {})
self.consistencygroups = mock.Mock()
self.consistencygroups.resource_class = fakes.FakeResource(None, {})
+ self.cgsnapshots = mock.Mock()
+ self.cgsnapshots.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
@@ -248,10 +253,7 @@ class TestVolume(utils.TestCommand):
class FakeVolume(object):
- """Fake one or more volumes.
-
- TODO(xiexs): Currently, only volume API v2 is supported by this class.
- """
+ """Fake one or more volumes."""
@staticmethod
def create_one_volume(attrs=None):
@@ -481,7 +483,7 @@ class FakeBackup(object):
If backups list is provided, then initialize the Mock object with the
list. Otherwise create one.
- :param List volumes:
+ :param List backups:
A list of FakeResource objects faking backups
:param Integer count:
The number of backups to be faked
@@ -547,6 +549,106 @@ class FakeConsistencyGroup(object):
return consistency_groups
+ @staticmethod
+ def get_consistency_groups(consistency_groups=None, count=2):
+ """Note:
+
+ Get an iterable MagicMock object with a list of faked
+ consistency_groups.
+
+ If consistency_groups list is provided, then initialize
+ the Mock object with the list. Otherwise create one.
+
+ :param List consistency_groups:
+ A list of FakeResource objects faking consistency_groups
+ :param Integer count:
+ The number of consistency_groups to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ consistency_groups
+ """
+ if consistency_groups is None:
+ consistency_groups = (FakeConsistencyGroup.
+ create_consistency_groups(count))
+
+ return mock.Mock(side_effect=consistency_groups)
+
+
+class FakeConsistencyGroupSnapshot(object):
+ """Fake one or more consistency group snapshot."""
+
+ @staticmethod
+ def create_one_consistency_group_snapshot(attrs=None):
+ """Create a fake consistency group snapshot.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with id, name, description, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ consistency_group_snapshot_info = {
+ "id": 'id-' + uuid.uuid4().hex,
+ "name": 'backup-name-' + uuid.uuid4().hex,
+ "description": 'description-' + uuid.uuid4().hex,
+ "status": "error",
+ "consistencygroup_id": 'consistency-group-id' + uuid.uuid4().hex,
+ "created_at": 'time-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ consistency_group_snapshot_info.update(attrs)
+
+ consistency_group_snapshot = fakes.FakeResource(
+ info=copy.deepcopy(consistency_group_snapshot_info),
+ loaded=True)
+ return consistency_group_snapshot
+
+ @staticmethod
+ def create_consistency_group_snapshots(attrs=None, count=2):
+ """Create multiple fake consistency group snapshots.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of consistency group snapshots to fake
+ :return:
+ A list of FakeResource objects faking the
+ consistency group snapshots
+ """
+ consistency_group_snapshots = []
+ for i in range(0, count):
+ consistency_group_snapshot = (
+ FakeConsistencyGroupSnapshot.
+ create_one_consistency_group_snapshot(attrs)
+ )
+ consistency_group_snapshots.append(consistency_group_snapshot)
+
+ return consistency_group_snapshots
+
+ @staticmethod
+ def get_consistency_group_snapshots(snapshots=None, count=2):
+ """Get an iterable MagicMock object with a list of faked cgsnapshots.
+
+ If consistenct group snapshots list is provided, then initialize
+ the Mock object with the list. Otherwise create one.
+
+ :param List snapshots:
+ A list of FakeResource objects faking consistency group snapshots
+ :param Integer count:
+ The number of consistency group snapshots to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ consistency groups
+ """
+ if snapshots is None:
+ snapshots = (FakeConsistencyGroupSnapshot.
+ create_consistency_group_snapshots(count))
+
+ return mock.Mock(side_effect=snapshots)
+
class FakeExtension(object):
"""Fake one or more extension."""
@@ -665,7 +767,7 @@ class FakeQos(object):
If qoses list is provided, then initialize the Mock object with the
list. Otherwise create one.
- :param List volumes:
+ :param List qoses:
A list of FakeResource objects faking qoses
:param Integer count:
The number of qoses to be faked
@@ -738,7 +840,7 @@ class FakeSnapshot(object):
If snapshots list is provided, then initialize the Mock object with the
list. Otherwise create one.
- :param List volumes:
+ :param List snapshots:
A list of FakeResource objects faking snapshots
:param Integer count:
The number of snapshots to be faked
@@ -804,3 +906,101 @@ 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)
+
+ @staticmethod
+ def create_one_encryption_type(attrs=None):
+ """Create a fake encryption type.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with volume_type_id etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ encryption_info = {
+ "volume_type_id": 'type-id-' + uuid.uuid4().hex,
+ 'provider': 'LuksEncryptor',
+ 'cipher': None,
+ 'key_size': None,
+ 'control_location': 'front-end',
+ }
+
+ # Overwrite default attributes.
+ encryption_info.update(attrs)
+
+ encryption_type = fakes.FakeResource(
+ info=copy.deepcopy(encryption_info),
+ loaded=True)
+ return encryption_type
+
+
+class FakeQuota(object):
+ """Fake quota"""
+
+ @staticmethod
+ def create_one_vol_quota(attrs=None):
+ """Create one quota"""
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'backups': 100,
+ 'backup_gigabytes': 100,
+ 'gigabytes': 10,
+ 'per_volume_gigabytes': 10,
+ 'snapshots': 0,
+ 'volumes': 10}
+
+ quota_attrs.update(attrs)
+
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+ quota.project_id = quota_attrs['id']
+
+ return quota
+
+ @staticmethod
+ def create_one_default_vol_quota(attrs=None):
+ """Create one quota"""
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'backups': 100,
+ 'backup_gigabytes': 100,
+ 'gigabytes': 100,
+ 'per_volume_gigabytes': 100,
+ 'snapshots': 100,
+ 'volumes': 100}
+
+ quota_attrs.update(attrs)
+
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+ quota.project_id = quota_attrs['id']
+
+ return quota
diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py
index 306c9eb3..a8e81c7e 100644
--- a/openstackclient/tests/unit/volume/v2/test_backup.py
+++ b/openstackclient/tests/unit/volume/v2/test_backup.py
@@ -280,26 +280,78 @@ class TestBackupList(TestBackup):
self.volumes_mock.list.return_value = [self.volume]
self.backups_mock.list.return_value = self.backups
+ self.volumes_mock.get.return_value = self.volume
+ self.backups_mock.get.return_value = self.backups[0]
# Get the command to test
self.cmd = backup.ListVolumeBackup(self.app, None)
def test_backup_list_without_options(self):
arglist = []
- verifylist = [("long", False)]
+ verifylist = [
+ ("long", False),
+ ("name", None),
+ ("status", None),
+ ("volume", None),
+ ("marker", None),
+ ("limit", None),
+ ('all_projects', False),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ "name": None,
+ "status": None,
+ "volume_id": None,
+ 'all_tenants': False,
+ }
+ self.volumes_mock.get.assert_not_called()
+ self.backups_mock.get.assert_not_called()
+ self.backups_mock.list.assert_called_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
def test_backup_list_with_options(self):
- arglist = ["--long"]
- verifylist = [("long", True)]
+ arglist = [
+ "--long",
+ "--name", self.backups[0].name,
+ "--status", "error",
+ "--volume", self.volume.id,
+ "--marker", self.backups[0].id,
+ "--all-projects",
+ "--limit", "3",
+ ]
+ verifylist = [
+ ("long", True),
+ ("name", self.backups[0].name),
+ ("status", "error"),
+ ("volume", self.volume.id),
+ ("marker", self.backups[0].id),
+ ('all_projects', True),
+ ("limit", 3),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ search_opts = {
+ "name": self.backups[0].name,
+ "status": "error",
+ "volume_id": self.volume.id,
+ 'all_tenants': True,
+ }
+ self.volumes_mock.get.assert_called_once_with(self.volume.id)
+ self.backups_mock.get.assert_called_once_with(self.backups[0].id)
+ self.backups_mock.list.assert_called_with(
+ search_opts=search_opts,
+ marker=self.backups[0].id,
+ limit=3,
+ )
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
@@ -366,6 +418,30 @@ class TestBackupSet(TestBackup):
self.backup.id, **{'name': 'new_name'})
self.assertIsNone(result)
+ def test_backup_set_description(self):
+ arglist = [
+ '--description', 'new_description',
+ self.backup.id,
+ ]
+ verifylist = [
+ ('name', None),
+ ('description', 'new_description'),
+ ('backup', self.backup.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'description': 'new_description'
+ }
+ self.backups_mock.update.assert_called_once_with(
+ self.backup.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_backup_set_state(self):
arglist = [
'--state', 'error',
diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
index 00e1b60e..6eeeae39 100644
--- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py
+++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
@@ -12,6 +12,10 @@
# under the License.
#
+import mock
+from mock import call
+
+from osc_lib import exceptions
from osc_lib import utils
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
@@ -28,6 +32,379 @@ class TestConsistencyGroup(volume_fakes.TestVolume):
self.app.client_manager.volume.consistencygroups)
self.consistencygroups_mock.reset_mock()
+ self.cgsnapshots_mock = (
+ self.app.client_manager.volume.cgsnapshots)
+ self.cgsnapshots_mock.reset_mock()
+
+ self.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()
+ new_consistency_group = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
+ consistency_group_snapshot = (
+ volume_fakes.
+ FakeConsistencyGroupSnapshot.
+ create_one_consistency_group_snapshot()
+ )
+
+ columns = (
+ 'availability_zone',
+ 'created_at',
+ 'description',
+ 'id',
+ 'name',
+ 'status',
+ 'volume_types',
+ )
+ data = (
+ new_consistency_group.availability_zone,
+ new_consistency_group.created_at,
+ new_consistency_group.description,
+ new_consistency_group.id,
+ new_consistency_group.name,
+ new_consistency_group.status,
+ new_consistency_group.volume_types,
+ )
+
+ def setUp(self):
+ super(TestConsistencyGroupCreate, self).setUp()
+ self.consistencygroups_mock.create.return_value = (
+ self.new_consistency_group)
+ self.consistencygroups_mock.create_from_src.return_value = (
+ self.new_consistency_group)
+ self.consistencygroups_mock.get.return_value = (
+ self.new_consistency_group)
+ self.types_mock.get.return_value = self.volume_type
+ self.cgsnapshots_mock.get.return_value = (
+ self.consistency_group_snapshot)
+
+ # Get the command object to test
+ self.cmd = consistency_group.CreateConsistencyGroup(self.app, None)
+
+ def test_consistency_group_create(self):
+ arglist = [
+ '--volume-type', self.volume_type.id,
+ '--description', self.new_consistency_group.description,
+ '--availability-zone',
+ self.new_consistency_group.availability_zone,
+ self.new_consistency_group.name,
+ ]
+ verifylist = [
+ ('volume_type', self.volume_type.id),
+ ('description', self.new_consistency_group.description),
+ ('availability_zone',
+ self.new_consistency_group.availability_zone),
+ ('name', self.new_consistency_group.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.types_mock.get.assert_called_once_with(
+ self.volume_type.id)
+ self.consistencygroups_mock.get.assert_not_called()
+ self.consistencygroups_mock.create.assert_called_once_with(
+ self.volume_type.id,
+ name=self.new_consistency_group.name,
+ description=self.new_consistency_group.description,
+ availability_zone=self.new_consistency_group.availability_zone,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_consistency_group_create_without_name(self):
+ arglist = [
+ '--volume-type', self.volume_type.id,
+ '--description', self.new_consistency_group.description,
+ '--availability-zone',
+ self.new_consistency_group.availability_zone,
+ ]
+ verifylist = [
+ ('volume_type', self.volume_type.id),
+ ('description', self.new_consistency_group.description),
+ ('availability_zone',
+ self.new_consistency_group.availability_zone),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.types_mock.get.assert_called_once_with(
+ self.volume_type.id)
+ self.consistencygroups_mock.get.assert_not_called()
+ self.consistencygroups_mock.create.assert_called_once_with(
+ self.volume_type.id,
+ name=None,
+ description=self.new_consistency_group.description,
+ availability_zone=self.new_consistency_group.availability_zone,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_consistency_group_create_from_source(self):
+ arglist = [
+ '--consistency-group-source', self.new_consistency_group.id,
+ '--description', self.new_consistency_group.description,
+ self.new_consistency_group.name,
+ ]
+ verifylist = [
+ ('consistency_group_source', self.new_consistency_group.id),
+ ('description', self.new_consistency_group.description),
+ ('name', self.new_consistency_group.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.types_mock.get.assert_not_called()
+ self.consistencygroups_mock.get.assert_called_once_with(
+ self.new_consistency_group.id)
+ self.consistencygroups_mock.create_from_src.assert_called_with(
+ None,
+ self.new_consistency_group.id,
+ name=self.new_consistency_group.name,
+ description=self.new_consistency_group.description,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_consistency_group_create_from_snapshot(self):
+ arglist = [
+ '--consistency-group-snapshot', self.consistency_group_snapshot.id,
+ '--description', self.new_consistency_group.description,
+ self.new_consistency_group.name,
+ ]
+ verifylist = [
+ ('consistency_group_snapshot', self.consistency_group_snapshot.id),
+ ('description', self.new_consistency_group.description),
+ ('name', self.new_consistency_group.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.types_mock.get.assert_not_called()
+ self.cgsnapshots_mock.get.assert_called_once_with(
+ self.consistency_group_snapshot.id)
+ self.consistencygroups_mock.create_from_src.assert_called_with(
+ self.consistency_group_snapshot.id,
+ None,
+ name=self.new_consistency_group.name,
+ description=self.new_consistency_group.description,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestConsistencyGroupDelete(TestConsistencyGroup):
+
+ consistency_groups =\
+ volume_fakes.FakeConsistencyGroup.create_consistency_groups(count=2)
+
+ def setUp(self):
+ super(TestConsistencyGroupDelete, self).setUp()
+
+ self.consistencygroups_mock.get = volume_fakes.FakeConsistencyGroup.\
+ get_consistency_groups(self.consistency_groups)
+ self.consistencygroups_mock.delete.return_value = None
+
+ # Get the command object to mock
+ self.cmd = consistency_group.DeleteConsistencyGroup(self.app, None)
+
+ def test_consistency_group_delete(self):
+ arglist = [
+ self.consistency_groups[0].id
+ ]
+ verifylist = [
+ ("consistency_groups", [self.consistency_groups[0].id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.consistencygroups_mock.delete.assert_called_with(
+ self.consistency_groups[0].id, False)
+ self.assertIsNone(result)
+
+ def test_consistency_group_delete_with_force(self):
+ arglist = [
+ '--force',
+ self.consistency_groups[0].id,
+ ]
+ verifylist = [
+ ('force', True),
+ ("consistency_groups", [self.consistency_groups[0].id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.consistencygroups_mock.delete.assert_called_with(
+ self.consistency_groups[0].id, True)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_consistency_groups(self):
+ arglist = []
+ for b in self.consistency_groups:
+ arglist.append(b.id)
+ verifylist = [
+ ('consistency_groups', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for b in self.consistency_groups:
+ calls.append(call(b.id, False))
+ self.consistencygroups_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_consistency_groups_with_exception(self):
+ arglist = [
+ self.consistency_groups[0].id,
+ 'unexist_consistency_group',
+ ]
+ verifylist = [
+ ('consistency_groups', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.consistency_groups[0],
+ exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 consistency groups failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.consistencygroups_mock,
+ self.consistency_groups[0].id)
+ find_mock.assert_any_call(self.consistencygroups_mock,
+ 'unexist_consistency_group')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.consistencygroups_mock.delete.assert_called_once_with(
+ self.consistency_groups[0].id, False
+ )
+
class TestConsistencyGroupList(TestConsistencyGroup):
@@ -120,3 +497,211 @@ class TestConsistencyGroupList(TestConsistencyGroup):
detailed=True, search_opts={'all_tenants': False})
self.assertEqual(self.columns_long, columns)
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 = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
+
+ def setUp(self):
+ super(TestConsistencyGroupSet, self).setUp()
+
+ self.consistencygroups_mock.get.return_value = (
+ self.consistency_group)
+ # Get the command object to test
+ self.cmd = consistency_group.SetConsistencyGroup(self.app, None)
+
+ def test_consistency_group_set_name(self):
+ new_name = 'new_name'
+ arglist = [
+ '--name', new_name,
+ self.consistency_group.id,
+ ]
+ verifylist = [
+ ('name', new_name),
+ ('description', None),
+ ('consistency_group', self.consistency_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'name': new_name,
+ }
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self.consistency_group.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ def test_consistency_group_set_description(self):
+ new_description = 'new_description'
+ arglist = [
+ '--description', new_description,
+ self.consistency_group.id,
+ ]
+ verifylist = [
+ ('name', None),
+ ('description', new_description),
+ ('consistency_group', self.consistency_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'description': new_description,
+ }
+ self.consistencygroups_mock.update.assert_called_once_with(
+ self.consistency_group.id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+
+class TestConsistencyGroupShow(TestConsistencyGroup):
+ columns = (
+ 'availability_zone',
+ 'created_at',
+ 'description',
+ 'id',
+ 'name',
+ 'status',
+ 'volume_types',
+ )
+
+ def setUp(self):
+ super(TestConsistencyGroupShow, self).setUp()
+
+ self.consistency_group = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
+ self.data = (
+ self.consistency_group.availability_zone,
+ self.consistency_group.created_at,
+ self.consistency_group.description,
+ self.consistency_group.id,
+ self.consistency_group.name,
+ self.consistency_group.status,
+ self.consistency_group.volume_types,
+ )
+ self.consistencygroups_mock.get.return_value = self.consistency_group
+ self.cmd = consistency_group.ShowConsistencyGroup(self.app, None)
+
+ def test_consistency_group_show(self):
+ arglist = [
+ self.consistency_group.id
+ ]
+ verifylist = [
+ ("consistency_group", self.consistency_group.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.consistencygroups_mock.get.assert_called_once_with(
+ self.consistency_group.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py
new file mode 100644
index 00000000..3bfe93df
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py
@@ -0,0 +1,351 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from mock import call
+
+from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
+from openstackclient.volume.v2 import consistency_group_snapshot
+
+
+class TestConsistencyGroupSnapshot(volume_fakes.TestVolume):
+
+ def setUp(self):
+ super(TestConsistencyGroupSnapshot, self).setUp()
+
+ # Get a shortcut to the TransferManager Mock
+ self.cgsnapshots_mock = (
+ self.app.client_manager.volume.cgsnapshots)
+ self.cgsnapshots_mock.reset_mock()
+ self.consistencygroups_mock = (
+ self.app.client_manager.volume.consistencygroups)
+ self.consistencygroups_mock.reset_mock()
+
+
+class TestConsistencyGroupSnapshotCreate(TestConsistencyGroupSnapshot):
+
+ _consistency_group_snapshot = (
+ volume_fakes.
+ FakeConsistencyGroupSnapshot.
+ create_one_consistency_group_snapshot()
+ )
+ consistency_group = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
+
+ columns = (
+ 'consistencygroup_id',
+ 'created_at',
+ 'description',
+ 'id',
+ 'name',
+ 'status',
+ )
+ data = (
+ _consistency_group_snapshot.consistencygroup_id,
+ _consistency_group_snapshot.created_at,
+ _consistency_group_snapshot.description,
+ _consistency_group_snapshot.id,
+ _consistency_group_snapshot.name,
+ _consistency_group_snapshot.status,
+ )
+
+ def setUp(self):
+ super(TestConsistencyGroupSnapshotCreate, self).setUp()
+ self.cgsnapshots_mock.create.return_value = (
+ self._consistency_group_snapshot)
+ self.consistencygroups_mock.get.return_value = (
+ self.consistency_group)
+
+ # Get the command object to test
+ self.cmd = (consistency_group_snapshot.
+ CreateConsistencyGroupSnapshot(self.app, None))
+
+ def test_consistency_group_snapshot_create(self):
+ arglist = [
+ '--consistency-group', self.consistency_group.id,
+ '--description', self._consistency_group_snapshot.description,
+ self._consistency_group_snapshot.name,
+ ]
+ verifylist = [
+ ('consistency_group', self.consistency_group.id),
+ ('description', self._consistency_group_snapshot.description),
+ ('snapshot_name', self._consistency_group_snapshot.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.consistencygroups_mock.get.assert_called_once_with(
+ self.consistency_group.id)
+ self.cgsnapshots_mock.create.assert_called_once_with(
+ self.consistency_group.id,
+ name=self._consistency_group_snapshot.name,
+ description=self._consistency_group_snapshot.description,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_consistency_group_snapshot_create_no_consistency_group(self):
+ arglist = [
+ '--description', self._consistency_group_snapshot.description,
+ self._consistency_group_snapshot.name,
+ ]
+ verifylist = [
+ ('description', self._consistency_group_snapshot.description),
+ ('snapshot_name', self._consistency_group_snapshot.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.consistencygroups_mock.get.assert_called_once_with(
+ self._consistency_group_snapshot.name)
+ self.cgsnapshots_mock.create.assert_called_once_with(
+ self.consistency_group.id,
+ name=self._consistency_group_snapshot.name,
+ description=self._consistency_group_snapshot.description,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestConsistencyGroupSnapshotDelete(TestConsistencyGroupSnapshot):
+
+ consistency_group_snapshots = (
+ volume_fakes.FakeConsistencyGroupSnapshot.
+ create_consistency_group_snapshots(count=2)
+ )
+
+ def setUp(self):
+ super(TestConsistencyGroupSnapshotDelete, self).setUp()
+
+ self.cgsnapshots_mock.get = (
+ volume_fakes.FakeConsistencyGroupSnapshot.
+ get_consistency_group_snapshots(self.consistency_group_snapshots)
+ )
+ self.cgsnapshots_mock.delete.return_value = None
+
+ # Get the command object to mock
+ self.cmd = (consistency_group_snapshot.
+ DeleteConsistencyGroupSnapshot(self.app, None))
+
+ def test_consistency_group_snapshot_delete(self):
+ arglist = [
+ self.consistency_group_snapshots[0].id
+ ]
+ verifylist = [
+ ("consistency_group_snapshot",
+ [self.consistency_group_snapshots[0].id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.cgsnapshots_mock.delete.assert_called_once_with(
+ self.consistency_group_snapshots[0].id)
+ self.assertIsNone(result)
+
+ def test_multiple_consistency_group_snapshots_delete(self):
+ arglist = []
+ for c in self.consistency_group_snapshots:
+ arglist.append(c.id)
+ verifylist = [
+ ('consistency_group_snapshot', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for c in self.consistency_group_snapshots:
+ calls.append(call(c.id))
+ self.cgsnapshots_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+
+class TestConsistencyGroupSnapshotList(TestConsistencyGroupSnapshot):
+
+ consistency_group_snapshots = (
+ volume_fakes.FakeConsistencyGroupSnapshot.
+ create_consistency_group_snapshots(count=2)
+ )
+ consistency_group = (
+ volume_fakes.FakeConsistencyGroup.create_one_consistency_group()
+ )
+
+ columns = [
+ 'ID',
+ 'Status',
+ 'Name',
+ ]
+ columns_long = [
+ 'ID',
+ 'Status',
+ 'ConsistencyGroup ID',
+ 'Name',
+ 'Description',
+ 'Created At',
+ ]
+ data = []
+ for c in consistency_group_snapshots:
+ data.append((
+ c.id,
+ c.status,
+ c.name,
+ ))
+ data_long = []
+ for c in consistency_group_snapshots:
+ data_long.append((
+ c.id,
+ c.status,
+ c.consistencygroup_id,
+ c.name,
+ c.description,
+ c.created_at,
+ ))
+
+ def setUp(self):
+ super(TestConsistencyGroupSnapshotList, self).setUp()
+
+ self.cgsnapshots_mock.list.return_value = (
+ self.consistency_group_snapshots)
+ self.consistencygroups_mock.get.return_value = self.consistency_group
+ # Get the command to test
+ self.cmd = (
+ consistency_group_snapshot.
+ ListConsistencyGroupSnapshot(self.app, None)
+ )
+
+ def test_consistency_group_snapshot_list_without_options(self):
+ arglist = []
+ verifylist = [
+ ("all_projects", False),
+ ("long", False),
+ ("status", None),
+ ("consistency_group", None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ search_opts = {
+ 'all_tenants': False,
+ 'status': None,
+ 'consistencygroup_id': None,
+ }
+ self.cgsnapshots_mock.list.assert_called_once_with(
+ detailed=True, search_opts=search_opts)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_consistency_group_snapshot_list_with_long(self):
+ arglist = [
+ "--long",
+ ]
+ verifylist = [
+ ("all_projects", False),
+ ("long", True),
+ ("status", None),
+ ("consistency_group", None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ search_opts = {
+ 'all_tenants': False,
+ 'status': None,
+ 'consistencygroup_id': None,
+ }
+ self.cgsnapshots_mock.list.assert_called_once_with(
+ detailed=True, search_opts=search_opts)
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
+
+ def test_consistency_group_snapshot_list_with_options(self):
+ arglist = [
+ "--all-project",
+ "--status", self.consistency_group_snapshots[0].status,
+ "--consistency-group", self.consistency_group.id,
+ ]
+ verifylist = [
+ ("all_projects", True),
+ ("long", False),
+ ("status", self.consistency_group_snapshots[0].status),
+ ("consistency_group", self.consistency_group.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ search_opts = {
+ 'all_tenants': True,
+ 'status': self.consistency_group_snapshots[0].status,
+ 'consistencygroup_id': self.consistency_group.id,
+ }
+ self.consistencygroups_mock.get.assert_called_once_with(
+ self.consistency_group.id)
+ self.cgsnapshots_mock.list.assert_called_once_with(
+ detailed=True, search_opts=search_opts)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestConsistencyGroupSnapshotShow(TestConsistencyGroupSnapshot):
+
+ _consistency_group_snapshot = (
+ volume_fakes.
+ FakeConsistencyGroupSnapshot.
+ create_one_consistency_group_snapshot()
+ )
+
+ columns = (
+ 'consistencygroup_id',
+ 'created_at',
+ 'description',
+ 'id',
+ 'name',
+ 'status',
+ )
+ data = (
+ _consistency_group_snapshot.consistencygroup_id,
+ _consistency_group_snapshot.created_at,
+ _consistency_group_snapshot.description,
+ _consistency_group_snapshot.id,
+ _consistency_group_snapshot.name,
+ _consistency_group_snapshot.status,
+ )
+
+ def setUp(self):
+ super(TestConsistencyGroupSnapshotShow, self).setUp()
+
+ self.cgsnapshots_mock.get.return_value = (
+ self._consistency_group_snapshot)
+ self.cmd = (consistency_group_snapshot.
+ ShowConsistencyGroupSnapshot(self.app, None))
+
+ def test_consistency_group_snapshot_show(self):
+ arglist = [
+ self._consistency_group_snapshot.id
+ ]
+ verifylist = [
+ ("consistency_group_snapshot", self._consistency_group_snapshot.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.cgsnapshots_mock.get.assert_called_once_with(
+ self._consistency_group_snapshot.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py
index 7597e852..35d9a345 100644
--- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py
+++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py
@@ -70,24 +70,26 @@ class TestQosAssociate(TestQos):
class TestQosCreate(TestQos):
- new_qos_spec = volume_fakes.FakeQos.create_one_qos()
columns = (
'consumer',
'id',
'name',
- 'specs'
- )
- data = (
- new_qos_spec.consumer,
- new_qos_spec.id,
- new_qos_spec.name,
- new_qos_spec.specs
+ 'properties'
)
def setUp(self):
super(TestQosCreate, self).setUp()
+ self.new_qos_spec = volume_fakes.FakeQos.create_one_qos()
self.qos_mock.create.return_value = self.new_qos_spec
+
+ self.data = (
+ self.new_qos_spec.consumer,
+ self.new_qos_spec.id,
+ self.new_qos_spec.name,
+ utils.format_dict(self.new_qos_spec.specs)
+ )
+
# Get the command object to test
self.cmd = qos_specs.CreateQos(self.app, None)
@@ -147,11 +149,11 @@ class TestQosCreate(TestQos):
columns, data = self.cmd.take_action(parsed_args)
- self.new_qos_spec.specs.update(
- {'consumer': self.new_qos_spec.consumer})
self.qos_mock.create.assert_called_with(
self.new_qos_spec.name,
- self.new_qos_spec.specs
+ {'consumer': self.new_qos_spec.consumer,
+ 'foo': 'bar',
+ 'iops': '9001'}
)
self.assertEqual(self.columns, columns)
@@ -307,7 +309,7 @@ class TestQosList(TestQos):
'Name',
'Consumer',
'Associations',
- 'Specs',
+ 'Properties',
)
data = []
for q in qos_specs:
@@ -383,7 +385,7 @@ class TestQosShow(TestQos):
'consumer',
'id',
'name',
- 'specs'
+ 'properties'
)
data = (
qos_association.name,
diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py
index d355662d..1ad97e85 100644
--- a/openstackclient/tests/unit/volume/v2/test_snapshot.py
+++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py
@@ -19,8 +19,9 @@ from mock import call
from osc_lib import exceptions
from osc_lib import utils
+from openstackclient.tests.unit.identity.v3 import fakes as project_fakes
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
-from openstackclient.volume.v2 import snapshot
+from openstackclient.volume.v2 import volume_snapshot
class TestSnapshot(volume_fakes.TestVolume):
@@ -32,6 +33,8 @@ class TestSnapshot(volume_fakes.TestVolume):
self.snapshots_mock.reset_mock()
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
+ self.project_mock = self.app.client_manager.identity.projects
+ self.project_mock.reset_mock()
class TestSnapshotCreate(TestSnapshot):
@@ -67,24 +70,25 @@ class TestSnapshotCreate(TestSnapshot):
self.volumes_mock.get.return_value = self.volume
self.snapshots_mock.create.return_value = self.new_snapshot
+ self.snapshots_mock.manage.return_value = self.new_snapshot
# Get the command object to test
- self.cmd = snapshot.CreateSnapshot(self.app, None)
+ self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None)
def test_snapshot_create(self):
arglist = [
- "--name", self.new_snapshot.name,
+ "--volume", self.new_snapshot.volume_id,
"--description", self.new_snapshot.description,
"--force",
'--property', 'Alpha=a',
'--property', 'Beta=b',
- self.new_snapshot.volume_id,
+ self.new_snapshot.name,
]
verifylist = [
- ("name", self.new_snapshot.name),
+ ("volume", self.new_snapshot.volume_id),
("description", self.new_snapshot.description),
("force", True),
('property', {'Alpha': 'a', 'Beta': 'b'}),
- ("volume", self.new_snapshot.volume_id),
+ ("snapshot_name", self.new_snapshot.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -102,7 +106,7 @@ class TestSnapshotCreate(TestSnapshot):
def test_snapshot_create_without_name(self):
arglist = [
- self.new_snapshot.volume_id,
+ "--volume", self.new_snapshot.volume_id,
"--description", self.new_snapshot.description,
"--force"
]
@@ -125,6 +129,60 @@ class TestSnapshotCreate(TestSnapshot):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_snapshot_create_without_volume(self):
+ arglist = [
+ "--description", self.new_snapshot.description,
+ "--force",
+ self.new_snapshot.name
+ ]
+ verifylist = [
+ ("description", self.new_snapshot.description),
+ ("force", True),
+ ("snapshot_name", self.new_snapshot.name)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.get.assert_called_once_with(
+ self.new_snapshot.name)
+ self.snapshots_mock.create.assert_called_once_with(
+ self.new_snapshot.volume_id,
+ force=True,
+ name=self.new_snapshot.name,
+ description=self.new_snapshot.description,
+ metadata=None,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_snapshot_create_without_remote_source(self):
+ arglist = [
+ '--remote-source', 'source-name=test_source_name',
+ '--remote-source', 'source-id=test_source_id',
+ '--volume', self.new_snapshot.volume_id,
+ ]
+ ref_dict = {'source-name': 'test_source_name',
+ 'source-id': 'test_source_id'}
+ verifylist = [
+ ('remote_source', ref_dict),
+ ('volume', self.new_snapshot.volume_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.manage.assert_called_with(
+ volume_id=self.new_snapshot.volume_id,
+ ref=ref_dict,
+ name=None,
+ description=None,
+ metadata=None,
+ )
+ self.snapshots_mock.create.assert_not_called()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestSnapshotDelete(TestSnapshot):
@@ -138,7 +196,7 @@ class TestSnapshotDelete(TestSnapshot):
self.snapshots_mock.delete.return_value = None
# Get the command object to mock
- self.cmd = snapshot.DeleteSnapshot(self.app, None)
+ self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None)
def test_snapshot_delete(self):
arglist = [
@@ -152,7 +210,24 @@ class TestSnapshotDelete(TestSnapshot):
result = self.cmd.take_action(parsed_args)
self.snapshots_mock.delete.assert_called_with(
- self.snapshots[0].id)
+ self.snapshots[0].id, False)
+ self.assertIsNone(result)
+
+ def test_snapshot_delete_with_force(self):
+ arglist = [
+ '--force',
+ self.snapshots[0].id
+ ]
+ verifylist = [
+ ('force', True),
+ ("snapshots", [self.snapshots[0].id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.delete.assert_called_with(
+ self.snapshots[0].id, True)
self.assertIsNone(result)
def test_delete_multiple_snapshots(self):
@@ -168,7 +243,7 @@ class TestSnapshotDelete(TestSnapshot):
calls = []
for s in self.snapshots:
- calls.append(call(s.id))
+ calls.append(call(s.id, False))
self.snapshots_mock.delete.assert_has_calls(calls)
self.assertIsNone(result)
@@ -199,13 +274,14 @@ class TestSnapshotDelete(TestSnapshot):
self.assertEqual(2, find_mock.call_count)
self.snapshots_mock.delete.assert_called_once_with(
- self.snapshots[0].id
+ self.snapshots[0].id, False
)
class TestSnapshotList(TestSnapshot):
volume = volume_fakes.FakeVolume.create_one_volume()
+ project = project_fakes.FakeProject.create_one_project()
snapshots = volume_fakes.FakeSnapshot.create_snapshots(
attrs={'volume_id': volume.name}, count=3)
@@ -248,22 +324,32 @@ class TestSnapshotList(TestSnapshot):
super(TestSnapshotList, self).setUp()
self.volumes_mock.list.return_value = [self.volume]
+ self.volumes_mock.get.return_value = self.volume
+ self.project_mock.get.return_value = self.project
self.snapshots_mock.list.return_value = self.snapshots
# Get the command to test
- self.cmd = snapshot.ListSnapshot(self.app, None)
+ self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None)
def test_snapshot_list_without_options(self):
arglist = []
verifylist = [
('all_projects', False),
- ("long", False)
+ ('long', False)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.list.assert_called_once_with(
- limit=None, marker=None, search_opts={'all_tenants': False})
+ limit=None, marker=None,
+ search_opts={
+ 'all_tenants': False,
+ 'name': None,
+ 'status': None,
+ 'project_id': None,
+ 'volume_id': None
+ }
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -271,11 +357,13 @@ class TestSnapshotList(TestSnapshot):
arglist = [
"--long",
"--limit", "2",
+ "--project", self.project.id,
"--marker", self.snapshots[0].id,
]
verifylist = [
("long", True),
("limit", 2),
+ ("project", self.project.id),
("marker", self.snapshots[0].id),
('all_projects', False),
]
@@ -286,7 +374,13 @@ class TestSnapshotList(TestSnapshot):
self.snapshots_mock.list.assert_called_once_with(
limit=2,
marker=self.snapshots[0].id,
- search_opts={'all_tenants': False}
+ search_opts={
+ 'all_tenants': True,
+ 'project_id': self.project.id,
+ 'name': None,
+ 'status': None,
+ 'volume_id': None
+ }
)
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
@@ -304,7 +398,93 @@ class TestSnapshotList(TestSnapshot):
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.list.assert_called_once_with(
- limit=None, marker=None, search_opts={'all_tenants': True})
+ limit=None, marker=None,
+ search_opts={
+ 'all_tenants': True,
+ 'name': None,
+ 'status': None,
+ 'project_id': None,
+ 'volume_id': None
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_snapshot_list_name_option(self):
+ arglist = [
+ '--name', self.snapshots[0].name,
+ ]
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('name', self.snapshots[0].name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ limit=None, marker=None,
+ search_opts={
+ 'all_tenants': False,
+ 'name': self.snapshots[0].name,
+ 'status': None,
+ 'project_id': None,
+ 'volume_id': None
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_snapshot_list_status_option(self):
+ arglist = [
+ '--status', self.snapshots[0].status,
+ ]
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('status', self.snapshots[0].status),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ limit=None, marker=None,
+ search_opts={
+ 'all_tenants': False,
+ 'name': None,
+ 'status': self.snapshots[0].status,
+ 'project_id': None,
+ 'volume_id': None
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_snapshot_list_volumeid_option(self):
+ arglist = [
+ '--volume', self.volume.id,
+ ]
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('volume', self.volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ limit=None, marker=None,
+ search_opts={
+ 'all_tenants': False,
+ 'name': None,
+ 'status': None,
+ 'project_id': None,
+ 'volume_id': self.volume.id
+ }
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -330,9 +510,25 @@ class TestSnapshotSet(TestSnapshot):
self.snapshots_mock.set_metadata.return_value = None
self.snapshots_mock.update.return_value = None
# Get the command object to mock
- self.cmd = snapshot.SetSnapshot(self.app, None)
+ self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None)
+
+ def test_snapshot_set_no_option(self):
+ arglist = [
+ self.snapshot.id,
+ ]
+ verifylist = [
+ ("snapshot", self.snapshot.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- def test_snapshot_set(self):
+ result = self.cmd.take_action(parsed_args)
+ self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot)
+ self.assertNotCalled(self.snapshots_mock.reset_state)
+ self.assertNotCalled(self.snapshots_mock.update)
+ self.assertNotCalled(self.snapshots_mock.set_metadata)
+ self.assertIsNone(result)
+
+ def test_snapshot_set_name_and_property(self):
arglist = [
"--name", "new_snapshot",
"--property", "x=y",
@@ -359,6 +555,51 @@ class TestSnapshotSet(TestSnapshot):
)
self.assertIsNone(result)
+ def test_snapshot_set_with_no_property(self):
+ arglist = [
+ "--no-property",
+ self.snapshot.id,
+ ]
+ verifylist = [
+ ("no_property", True),
+ ("snapshot", self.snapshot.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot)
+ self.assertNotCalled(self.snapshots_mock.reset_state)
+ self.assertNotCalled(self.snapshots_mock.update)
+ self.assertNotCalled(self.snapshots_mock.set_metadata)
+ self.snapshots_mock.delete_metadata.assert_called_with(
+ self.snapshot.id, ["foo"]
+ )
+ self.assertIsNone(result)
+
+ def test_snapshot_set_with_no_property_and_property(self):
+ arglist = [
+ "--no-property",
+ "--property", "foo_1=bar_1",
+ self.snapshot.id,
+ ]
+ verifylist = [
+ ("no_property", True),
+ ("property", {"foo_1": "bar_1"}),
+ ("snapshot", self.snapshot.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot)
+ self.assertNotCalled(self.snapshots_mock.reset_state)
+ self.assertNotCalled(self.snapshots_mock.update)
+ self.snapshots_mock.delete_metadata.assert_called_with(
+ self.snapshot.id, ["foo"]
+ )
+ self.snapshots_mock.set_metadata.assert_called_once_with(
+ self.snapshot.id, {"foo_1": "bar_1"})
+ self.assertIsNone(result)
+
def test_snapshot_set_state_to_error(self):
arglist = [
"--state", "error",
@@ -457,7 +698,7 @@ class TestSnapshotShow(TestSnapshot):
self.snapshots_mock.get.return_value = self.snapshot
# Get the command object to test
- self.cmd = snapshot.ShowSnapshot(self.app, None)
+ self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None)
def test_snapshot_show(self):
arglist = [
@@ -485,7 +726,7 @@ class TestSnapshotUnset(TestSnapshot):
self.snapshots_mock.get.return_value = self.snapshot
self.snapshots_mock.delete_metadata.return_value = None
# Get the command object to mock
- self.cmd = snapshot.UnsetSnapshot(self.app, None)
+ self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None)
def test_snapshot_unset(self):
arglist = [
diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py
index 84f87e3b..4023d55b 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
@@ -35,6 +36,10 @@ class TestType(volume_fakes.TestVolume):
self.app.client_manager.volume.volume_type_access)
self.types_access_mock.reset_mock()
+ self.encryption_types_mock = (
+ self.app.client_manager.volume.volume_encryption_types)
+ self.encryption_types_mock.reset_mock()
+
self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
@@ -130,15 +135,78 @@ class TestTypeCreate(TestType):
self.cmd.take_action,
parsed_args)
+ def test_type_create_with_encryption(self):
+ encryption_info = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': '128',
+ 'control_location': 'front-end',
+ }
+ encryption_type = volume_fakes.FakeType.create_one_encryption_type(
+ attrs=encryption_info
+ )
+ self.new_volume_type = volume_fakes.FakeType.create_one_type(
+ attrs={'encryption': encryption_info})
+ self.types_mock.create.return_value = self.new_volume_type
+ self.encryption_types_mock.create.return_value = encryption_type
+ encryption_columns = (
+ 'description',
+ 'encryption',
+ 'id',
+ 'is_public',
+ 'name',
+ )
+ encryption_data = (
+ self.new_volume_type.description,
+ utils.format_dict(encryption_info),
+ self.new_volume_type.id,
+ True,
+ self.new_volume_type.name,
+ )
+ arglist = [
+ '--encryption-provider', 'LuksEncryptor',
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-key-size', '128',
+ '--encryption-control-location', 'front-end',
+ self.new_volume_type.name,
+ ]
+ verifylist = [
+ ('encryption_provider', 'LuksEncryptor'),
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_key_size', 128),
+ ('encryption_control_location', 'front-end'),
+ ('name', self.new_volume_type.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.create.assert_called_with(
+ self.new_volume_type.name,
+ description=None,
+ )
+ body = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': 128,
+ 'control_location': 'front-end',
+ }
+ self.encryption_types_mock.create.assert_called_with(
+ self.new_volume_type,
+ body,
+ )
+ self.assertEqual(encryption_columns, columns)
+ self.assertEqual(encryption_data, data)
+
class TestTypeDelete(TestType):
- 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 +214,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):
@@ -165,24 +279,31 @@ class TestTypeList(TestType):
columns = [
"ID",
- "Name"
+ "Name",
+ "Is Public",
]
columns_long = columns + [
"Description",
"Properties"
]
-
+ data_with_default_type = [(
+ volume_types[0].id,
+ volume_types[0].name,
+ True
+ )]
data = []
for t in volume_types:
data.append((
t.id,
t.name,
+ t.is_public,
))
data_long = []
for t in volume_types:
data_long.append((
t.id,
t.name,
+ t.is_public,
t.description,
utils.format_dict(t.extra_specs),
))
@@ -191,6 +312,7 @@ class TestTypeList(TestType):
super(TestTypeList, self).setUp()
self.types_mock.list.return_value = self.volume_types
+ self.types_mock.default.return_value = self.volume_types[0]
# get the command to test
self.cmd = volume_type.ListVolumeType(self.app, None)
@@ -200,6 +322,7 @@ class TestTypeList(TestType):
("long", False),
("private", False),
("public", False),
+ ("default", False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -217,6 +340,7 @@ class TestTypeList(TestType):
("long", True),
("private", False),
("public", True),
+ ("default", False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -233,6 +357,7 @@ class TestTypeList(TestType):
("long", False),
("private", True),
("public", False),
+ ("default", False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -241,6 +366,65 @@ class TestTypeList(TestType):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_type_list_with_default_option(self):
+ arglist = [
+ "--default",
+ ]
+ verifylist = [
+ ("encryption_type", False),
+ ("long", False),
+ ("private", False),
+ ("public", False),
+ ("default", True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.default.assert_called_once_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data_with_default_type, list(data))
+
+ def test_type_list_with_encryption(self):
+ encryption_type = volume_fakes.FakeType.create_one_encryption_type(
+ attrs={'volume_type_id': self.volume_types[0].id})
+ encryption_info = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': None,
+ 'key_size': None,
+ 'control_location': 'front-end',
+ }
+ encryption_columns = self.columns + [
+ "Encryption",
+ ]
+ encryption_data = []
+ encryption_data.append((
+ self.volume_types[0].id,
+ self.volume_types[0].name,
+ self.volume_types[0].is_public,
+ utils.format_dict(encryption_info),
+ ))
+ encryption_data.append((
+ self.volume_types[1].id,
+ self.volume_types[1].name,
+ self.volume_types[1].is_public,
+ '-',
+ ))
+
+ self.encryption_types_mock.list.return_value = [encryption_type]
+ arglist = [
+ "--encryption-type",
+ ]
+ verifylist = [
+ ("encryption_type", True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.encryption_types_mock.list.assert_called_once_with()
+ self.types_mock.list.assert_called_once_with(is_public=None)
+ self.assertEqual(encryption_columns, columns)
+ self.assertEqual(encryption_data, list(data))
+
class TestTypeSet(TestType):
@@ -255,6 +439,8 @@ class TestTypeSet(TestType):
# Return a project
self.projects_mock.get.return_value = self.project
+ self.encryption_types_mock.create.return_value = None
+ self.encryption_types_mock.update.return_value = None
# Get the command object to test
self.cmd = volume_type.SetVolumeType(self.app, None)
@@ -378,6 +564,107 @@ class TestTypeSet(TestType):
self.project.id,
)
+ def test_type_set_new_encryption(self):
+ self.encryption_types_mock.update.side_effect = (
+ exceptions.NotFound('NotFound'))
+ arglist = [
+ '--encryption-provider', 'LuksEncryptor',
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-key-size', '128',
+ '--encryption-control-location', 'front-end',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_provider', 'LuksEncryptor'),
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_key_size', 128),
+ ('encryption_control_location', 'front-end'),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ body = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': 128,
+ 'control_location': 'front-end',
+ }
+ self.encryption_types_mock.update.assert_called_with(
+ self.volume_type,
+ body,
+ )
+ self.encryption_types_mock.create.assert_called_with(
+ self.volume_type,
+ body,
+ )
+ self.assertIsNone(result)
+
+ @mock.patch.object(utils, 'find_resource')
+ def test_type_set_existing_encryption(self, mock_find):
+ mock_find.side_effect = [self.volume_type,
+ "existing_encryption_type"]
+ arglist = [
+ '--encryption-provider', 'LuksEncryptor',
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-control-location', 'front-end',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_provider', 'LuksEncryptor'),
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_control_location', 'front-end'),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ body = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': 'aes-xts-plain64',
+ 'control_location': 'front-end',
+ }
+ self.encryption_types_mock.update.assert_called_with(
+ self.volume_type,
+ body,
+ )
+ self.encryption_types_mock.create.assert_not_called()
+ self.assertIsNone(result)
+
+ def test_type_set_new_encryption_without_provider(self):
+ self.encryption_types_mock.update.side_effect = (
+ exceptions.NotFound('NotFound'))
+ arglist = [
+ '--encryption-cipher', 'aes-xts-plain64',
+ '--encryption-key-size', '128',
+ '--encryption-control-location', 'front-end',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_cipher', 'aes-xts-plain64'),
+ ('encryption_key_size', 128),
+ ('encryption_control_location', 'front-end'),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual("Command Failed: One or more of"
+ " the operations failed",
+ str(e))
+ body = {
+ 'cipher': 'aes-xts-plain64',
+ 'key_size': 128,
+ 'control_location': 'front-end',
+ }
+ self.encryption_types_mock.update.assert_called_with(
+ self.volume_type,
+ body,
+ )
+ self.encryption_types_mock.create.assert_not_called()
+
class TestTypeShow(TestType):
@@ -413,6 +700,7 @@ class TestTypeShow(TestType):
self.volume_type.id
]
verifylist = [
+ ("encryption_type", False),
("volume_type", self.volume_type.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -488,6 +776,52 @@ class TestTypeShow(TestType):
)
self.assertEqual(private_type_data, data)
+ def test_type_show_with_encryption(self):
+ encryption_type = volume_fakes.FakeType.create_one_encryption_type()
+ encryption_info = {
+ 'provider': 'LuksEncryptor',
+ 'cipher': None,
+ 'key_size': None,
+ 'control_location': 'front-end',
+ }
+ self.volume_type = volume_fakes.FakeType.create_one_type(
+ attrs={'encryption': encryption_info})
+ self.types_mock.get.return_value = self.volume_type
+ self.encryption_types_mock.get.return_value = encryption_type
+ encryption_columns = (
+ 'access_project_ids',
+ 'description',
+ 'encryption',
+ 'id',
+ 'is_public',
+ 'name',
+ 'properties',
+ )
+ encryption_data = (
+ None,
+ self.volume_type.description,
+ utils.format_dict(encryption_info),
+ self.volume_type.id,
+ True,
+ self.volume_type.name,
+ utils.format_dict(self.volume_type.extra_specs)
+ )
+ arglist = [
+ '--encryption-type',
+ self.volume_type.id
+ ]
+ verifylist = [
+ ('encryption_type', True),
+ ("volume_type", self.volume_type.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.get.assert_called_with(self.volume_type.id)
+ self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
+ self.assertEqual(encryption_columns, columns)
+ self.assertEqual(encryption_data, data)
+
class TestTypeUnset(TestType):
@@ -549,6 +883,7 @@ class TestTypeUnset(TestType):
self.volume_type.id,
]
verifylist = [
+ ('encryption_type', False),
('project', ''),
('volume_type', self.volume_type.id),
]
@@ -557,7 +892,7 @@ class TestTypeUnset(TestType):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
-
+ self.encryption_types_mock.delete.assert_not_called()
self.assertFalse(self.types_access_mock.remove_project_access.called)
def test_type_unset_failed_with_missing_volume_type_argument(self):
@@ -573,3 +908,18 @@ class TestTypeUnset(TestType):
self.cmd,
arglist,
verifylist)
+
+ def test_type_unset_encryption_type(self):
+ arglist = [
+ '--encryption-type',
+ self.volume_type.id,
+ ]
+ verifylist = [
+ ('encryption_type', True),
+ ('volume_type', self.volume_type.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.encryption_types_mock.delete.assert_called_with(self.volume_type)
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py
index f4a7c142..fbe719f3 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume.py
@@ -46,6 +46,9 @@ class TestVolume(volume_fakes.TestVolume):
self.snapshots_mock = self.app.client_manager.volume.volume_snapshots
self.snapshots_mock.reset_mock()
+ self.types_mock = self.app.client_manager.volume.volume_types
+ self.types_mock.reset_mock()
+
self.consistencygroups_mock = (
self.app.client_manager.volume.consistencygroups)
self.consistencygroups_mock.reset_mock()
@@ -447,6 +450,154 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
+ def test_volume_create_with_bootable_and_readonly(self):
+ arglist = [
+ '--bootable',
+ '--read-only',
+ '--size', str(self.new_volume.size),
+ self.new_volume.name,
+ ]
+ verifylist = [
+ ('bootable', True),
+ ('non_bootable', False),
+ ('read_only', True),
+ ('read_write', False),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ size=self.new_volume.size,
+ snapshot_id=None,
+ name=self.new_volume.name,
+ description=None,
+ volume_type=None,
+ user_id=None,
+ project_id=None,
+ availability_zone=None,
+ metadata=None,
+ imageRef=None,
+ source_volid=None,
+ consistencygroup_id=None,
+ source_replica=None,
+ multiattach=False,
+ scheduler_hints=None,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
+ self.volumes_mock.set_bootable.assert_called_with(
+ self.new_volume.id, True)
+ self.volumes_mock.update_readonly_flag.assert_called_with(
+ self.new_volume.id, True)
+
+ def test_volume_create_with_nonbootable_and_readwrite(self):
+ arglist = [
+ '--non-bootable',
+ '--read-write',
+ '--size', str(self.new_volume.size),
+ self.new_volume.name,
+ ]
+ verifylist = [
+ ('bootable', False),
+ ('non_bootable', True),
+ ('read_only', False),
+ ('read_write', True),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ size=self.new_volume.size,
+ snapshot_id=None,
+ name=self.new_volume.name,
+ description=None,
+ volume_type=None,
+ user_id=None,
+ project_id=None,
+ availability_zone=None,
+ metadata=None,
+ imageRef=None,
+ source_volid=None,
+ consistencygroup_id=None,
+ source_replica=None,
+ multiattach=False,
+ scheduler_hints=None,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
+ self.volumes_mock.set_bootable.assert_called_with(
+ self.new_volume.id, False)
+ self.volumes_mock.update_readonly_flag.assert_called_with(
+ self.new_volume.id, False)
+
+ @mock.patch.object(volume.LOG, 'error')
+ def test_volume_create_with_bootable_and_readonly_fail(
+ self, mock_error):
+
+ self.volumes_mock.set_bootable.side_effect = (
+ exceptions.CommandError())
+
+ self.volumes_mock.update_readonly_flag.side_effect = (
+ exceptions.CommandError())
+
+ arglist = [
+ '--bootable',
+ '--read-only',
+ '--size', str(self.new_volume.size),
+ self.new_volume.name,
+ ]
+ verifylist = [
+ ('bootable', True),
+ ('non_bootable', False),
+ ('read_only', True),
+ ('read_write', False),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ size=self.new_volume.size,
+ snapshot_id=None,
+ name=self.new_volume.name,
+ description=None,
+ volume_type=None,
+ user_id=None,
+ project_id=None,
+ availability_zone=None,
+ metadata=None,
+ imageRef=None,
+ source_volid=None,
+ consistencygroup_id=None,
+ source_replica=None,
+ multiattach=False,
+ scheduler_hints=None,
+ )
+
+ self.assertEqual(2, mock_error.call_count)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, data)
+ self.volumes_mock.set_bootable.assert_called_with(
+ self.new_volume.id, True)
+ self.volumes_mock.update_readonly_flag.assert_called_with(
+ self.new_volume.id, True)
+
def test_volume_create_with_source_replicated(self):
self.volumes_mock.get.return_value = self.new_volume
arglist = [
@@ -672,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']
@@ -702,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']
@@ -734,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']
@@ -764,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']
@@ -795,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']
@@ -825,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']
@@ -855,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']
@@ -885,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']
@@ -916,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',
@@ -996,17 +1264,128 @@ class TestVolumeList(TestVolume):
self.cmd, arglist, verifylist)
+class TestVolumeMigrate(TestVolume):
+
+ _volume = volume_fakes.FakeVolume.create_one_volume()
+
+ def setUp(self):
+ super(TestVolumeMigrate, self).setUp()
+
+ self.volumes_mock.get.return_value = self._volume
+ self.volumes_mock.migrate_volume.return_value = None
+ # Get the command object to test
+ self.cmd = volume.MigrateVolume(self.app, None)
+
+ def test_volume_migrate(self):
+ arglist = [
+ "--host", "host@backend-name#pool",
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", False),
+ ("lock_volume", False),
+ ("unlock_volume", False),
+ ("host", "host@backend-name#pool"),
+ ("volume", self._volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.get.assert_called_once_with(self._volume.id)
+ self.volumes_mock.migrate_volume.assert_called_once_with(
+ self._volume.id, "host@backend-name#pool", False, False)
+ self.assertIsNone(result)
+
+ def test_volume_migrate_with_option(self):
+ arglist = [
+ "--force-host-copy",
+ "--lock-volume",
+ "--host", "host@backend-name#pool",
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", True),
+ ("lock_volume", True),
+ ("unlock_volume", False),
+ ("host", "host@backend-name#pool"),
+ ("volume", self._volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.get.assert_called_once_with(self._volume.id)
+ self.volumes_mock.migrate_volume.assert_called_once_with(
+ self._volume.id, "host@backend-name#pool", True, True)
+ self.assertIsNone(result)
+
+ def test_volume_migrate_with_unlock_volume(self):
+ arglist = [
+ "--unlock-volume",
+ "--host", "host@backend-name#pool",
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", False),
+ ("lock_volume", False),
+ ("unlock_volume", True),
+ ("host", "host@backend-name#pool"),
+ ("volume", self._volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.get.assert_called_once_with(self._volume.id)
+ self.volumes_mock.migrate_volume.assert_called_once_with(
+ self._volume.id, "host@backend-name#pool", False, False)
+ self.assertIsNone(result)
+
+ def test_volume_migrate_without_host(self):
+ arglist = [
+ self._volume.id,
+ ]
+ verifylist = [
+ ("force_host_copy", False),
+ ("lock_volume", False),
+ ("unlock_volume", False),
+ ("volume", self._volume.id),
+ ]
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+
class TestVolumeSet(TestVolume):
+ volume_type = volume_fakes.FakeType.create_one_type()
+
def setUp(self):
super(TestVolumeSet, self).setUp()
self.new_volume = volume_fakes.FakeVolume.create_one_volume()
self.volumes_mock.get.return_value = self.new_volume
+ self.types_mock.get.return_value = self.volume_type
# Get the command object to test
self.cmd = volume.SetVolume(self.app, None)
+ def test_volume_set_property(self):
+ arglist = [
+ '--property', 'a=b',
+ '--property', 'c=d',
+ self.new_volume.id,
+ ]
+ verifylist = [
+ ('property', {'a': 'b', 'c': 'd'}),
+ ('volume', self.new_volume.id),
+ ('bootable', False),
+ ('non_bootable', False)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+ self.volumes_mock.set_metadata.assert_called_with(
+ self.new_volume.id, parsed_args.property)
+
def test_volume_set_image_property(self):
arglist = [
'--image-property', 'Alpha=a',
@@ -1033,6 +1412,8 @@ class TestVolumeSet(TestVolume):
self.new_volume.id
]
verifylist = [
+ ('read_only', False),
+ ('read_write', False),
('state', 'error'),
('volume', self.new_volume.id)
]
@@ -1042,6 +1423,7 @@ class TestVolumeSet(TestVolume):
result = self.cmd.take_action(parsed_args)
self.volumes_mock.reset_state.assert_called_with(
self.new_volume.id, 'error')
+ self.volumes_mock.update_readonly_flag.assert_not_called()
self.assertIsNone(result)
def test_volume_set_state_failed(self):
@@ -1090,6 +1472,104 @@ class TestVolumeSet(TestVolume):
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, verifylist[index][0][1])
+ def test_volume_set_readonly(self):
+ arglist = [
+ '--read-only',
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('read_only', True),
+ ('read_write', False),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.update_readonly_flag.assert_called_once_with(
+ self.new_volume.id,
+ True)
+ self.assertIsNone(result)
+
+ def test_volume_set_read_write(self):
+ arglist = [
+ '--read-write',
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('read_only', False),
+ ('read_write', True),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.update_readonly_flag.assert_called_once_with(
+ self.new_volume.id,
+ False)
+ self.assertIsNone(result)
+
+ def test_volume_set_type(self):
+ arglist = [
+ '--type', self.volume_type.id,
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('retype_policy', None),
+ ('type', self.volume_type.id),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.retype.assert_called_once_with(
+ self.new_volume.id,
+ self.volume_type.id,
+ 'never')
+ self.assertIsNone(result)
+
+ def test_volume_set_type_with_policy(self):
+ arglist = [
+ '--retype-policy', 'on-demand',
+ '--type', self.volume_type.id,
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('retype_policy', 'on-demand'),
+ ('type', self.volume_type.id),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.retype.assert_called_once_with(
+ self.new_volume.id,
+ self.volume_type.id,
+ 'on-demand')
+ self.assertIsNone(result)
+
+ @mock.patch.object(volume.LOG, 'warning')
+ def test_volume_set_with_only_retype_policy(self, mock_warning):
+ arglist = [
+ '--retype-policy', 'on-demand',
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('retype_policy', 'on-demand'),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.retype.assert_not_called()
+ mock_warning.assert_called_with("'--retype-policy' option will "
+ "not work without '--type' option")
+ self.assertIsNone(result)
+
class TestVolumeShow(TestVolume):
diff --git a/openstackclient/tests/unit/volume/v2/test_volume_host.py b/openstackclient/tests/unit/volume/v2/test_volume_host.py
new file mode 100644
index 00000000..b024329a
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v2/test_volume_host.py
@@ -0,0 +1,117 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from openstackclient.tests.unit.volume.v2 import fakes as host_fakes
+from openstackclient.volume.v2 import volume_host
+
+
+class TestVolumeHost(host_fakes.TestVolume):
+
+ def setUp(self):
+ super(TestVolumeHost, self).setUp()
+
+ self.host_mock = self.app.client_manager.volume.services
+ self.host_mock.reset_mock()
+
+
+class TestVolumeHostSet(TestVolumeHost):
+
+ service = host_fakes.FakeService.create_one_service()
+
+ def setUp(self):
+ super(TestVolumeHostSet, self).setUp()
+
+ self.host_mock.freeze_host.return_value = None
+ self.host_mock.thaw_host.return_value = None
+
+ # Get the command object to mock
+ self.cmd = volume_host.SetVolumeHost(self.app, None)
+
+ def test_volume_host_set_nothing(self):
+ arglist = [
+ self.service.host,
+ ]
+ verifylist = [
+ ('host', self.service.host),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.host_mock.freeze_host.assert_not_called()
+ self.host_mock.thaw_host.assert_not_called()
+ self.assertIsNone(result)
+
+ def test_volume_host_set_enable(self):
+ arglist = [
+ '--enable',
+ self.service.host,
+ ]
+ verifylist = [
+ ('enable', True),
+ ('host', self.service.host),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.host_mock.thaw_host.assert_called_with(self.service.host)
+ self.host_mock.freeze_host.assert_not_called()
+ self.assertIsNone(result)
+
+ def test_volume_host_set_disable(self):
+ arglist = [
+ '--disable',
+ self.service.host,
+ ]
+ verifylist = [
+ ('disable', True),
+ ('host', self.service.host),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.host_mock.freeze_host.assert_called_with(self.service.host)
+ self.host_mock.thaw_host.assert_not_called()
+ self.assertIsNone(result)
+
+
+class TestVolumeHostFailover(TestVolumeHost):
+
+ service = host_fakes.FakeService.create_one_service()
+
+ def setUp(self):
+ super(TestVolumeHostFailover, self).setUp()
+
+ self.host_mock.failover_host.return_value = None
+
+ # Get the command object to mock
+ self.cmd = volume_host.FailoverVolumeHost(self.app, None)
+
+ def test_volume_host_failover(self):
+ arglist = [
+ '--volume-backend', 'backend_test',
+ self.service.host,
+ ]
+ verifylist = [
+ ('volume_backend', 'backend_test'),
+ ('host', self.service.host),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.host_mock.failover_host.assert_called_with(
+ self.service.host, 'backend_test')
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/volume/v3/__init__.py b/openstackclient/tests/unit/volume/v3/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v3/__init__.py
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index ade5a95f..c4b0dfca 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -27,7 +27,8 @@ API_VERSION_OPTION = 'os_volume_api_version'
API_NAME = "volume"
API_VERSIONS = {
"1": "cinderclient.v1.client.Client",
- "2": "cinderclient.v2.client.Client"
+ "2": "cinderclient.v2.client.Client",
+ "3": "cinderclient.v3.client.Client",
}
diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py
index c9d0ca0d..9ac1302a 100644
--- a/openstackclient/volume/v1/backup.py
+++ b/openstackclient/volume/v1/backup.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateVolumeBackup(command.ShowOne):
- """Create new volume backup"""
+ _description = _("Create new volume backup")
def get_parser(self, prog_name):
parser = super(CreateVolumeBackup, self).get_parser(prog_name)
@@ -73,7 +73,7 @@ class CreateVolumeBackup(command.ShowOne):
class CreateBackup(CreateVolumeBackup):
- """Create new backup"""
+ _description = _("Create new backup")
# TODO(Huanxuan Ao): Remove this class and ``backup create`` command
# two cycles after Newton.
@@ -90,7 +90,7 @@ class CreateBackup(CreateVolumeBackup):
class DeleteVolumeBackup(command.Command):
- """Delete volume backup(s)"""
+ _description = _("Delete volume backup(s)")
def get_parser(self, prog_name):
parser = super(DeleteVolumeBackup, self).get_parser(prog_name)
@@ -125,7 +125,7 @@ class DeleteVolumeBackup(command.Command):
class DeleteBackup(DeleteVolumeBackup):
- """Delete backup(s)"""
+ _description = _("Delete backup(s)")
# TODO(Huanxuan Ao): Remove this class and ``backup delete`` command
# two cycles after Newton.
@@ -142,7 +142,7 @@ class DeleteBackup(DeleteVolumeBackup):
class ListVolumeBackup(command.Lister):
- """List volume backups"""
+ _description = _("List volume backups")
def get_parser(self, prog_name):
parser = super(ListVolumeBackup, self).get_parser(prog_name)
@@ -152,9 +152,36 @@ class ListVolumeBackup(command.Lister):
default=False,
help=_('List additional fields in output'),
)
+ parser.add_argument(
+ "--name",
+ metavar="<name>",
+ help=_("Filters results by the backup name")
+ )
+ parser.add_argument(
+ "--status",
+ metavar="<status>",
+ choices=['creating', 'available', 'deleting',
+ 'error', 'restoring', 'error_restoring'],
+ help=_("Filters results by the backup status "
+ "('creating', 'available', 'deleting', "
+ "'error', 'restoring' or 'error_restoring')")
+ )
+ parser.add_argument(
+ "--volume",
+ metavar="<volume>",
+ help=_("Filters results by the volume which they "
+ "backup (name or ID)")
+ )
+ parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help=_('Include all projects (admin only)'),
+ )
return parser
def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
def _format_volume_id(volume_id):
"""Return a volume name if available
@@ -180,13 +207,25 @@ class ListVolumeBackup(command.Lister):
# Cache the volume list
volume_cache = {}
try:
- for s in self.app.client_manager.volume.volumes.list():
+ for s in volume_client.volumes.list():
volume_cache[s.id] = s
except Exception:
# Just forget it if there's any trouble
pass
- data = self.app.client_manager.volume.backups.list()
+ filter_volume_id = None
+ if parsed_args.volume:
+ filter_volume_id = utils.find_resource(volume_client.volumes,
+ parsed_args.volume).id
+ search_opts = {
+ 'name': parsed_args.name,
+ 'status': parsed_args.status,
+ 'volume_id': filter_volume_id,
+ 'all_tenants': parsed_args.all_projects,
+ }
+ data = volume_client.backups.list(
+ search_opts=search_opts,
+ )
return (column_headers,
(utils.get_item_properties(
@@ -196,7 +235,7 @@ class ListVolumeBackup(command.Lister):
class ListBackup(ListVolumeBackup):
- """List backups"""
+ _description = _("List backups")
# TODO(Huanxuan Ao): Remove this class and ``backup list`` command
# two cycles after Newton.
@@ -213,7 +252,7 @@ class ListBackup(ListVolumeBackup):
class RestoreVolumeBackup(command.Command):
- """Restore volume backup"""
+ _description = _("Restore volume backup")
def get_parser(self, prog_name):
parser = super(RestoreVolumeBackup, self).get_parser(prog_name)
@@ -240,7 +279,7 @@ class RestoreVolumeBackup(command.Command):
class RestoreBackup(RestoreVolumeBackup):
- """Restore backup"""
+ _description = _("Restore backup")
# TODO(Huanxuan Ao): Remove this class and ``backup restore`` command
# two cycles after Newton.
@@ -257,7 +296,7 @@ class RestoreBackup(RestoreVolumeBackup):
class ShowVolumeBackup(command.ShowOne):
- """Display volume backup details"""
+ _description = _("Display volume backup details")
def get_parser(self, prog_name):
parser = super(ShowVolumeBackup, self).get_parser(prog_name)
@@ -277,7 +316,7 @@ class ShowVolumeBackup(command.ShowOne):
class ShowBackup(ShowVolumeBackup):
- """Display backup details"""
+ _description = _("Display backup details")
# TODO(Huanxuan Ao): Remove this class and ``backup show`` command
# two cycles after Newton.
diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py
index 93c24a21..bae8c1ab 100644
--- a/openstackclient/volume/v1/qos_specs.py
+++ b/openstackclient/volume/v1/qos_specs.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class AssociateQos(command.Command):
- """Associate a QoS specification to a volume type"""
+ _description = _("Associate a QoS specification to a volume type")
def get_parser(self, prog_name):
parser = super(AssociateQos, self).get_parser(prog_name)
@@ -57,7 +57,7 @@ class AssociateQos(command.Command):
class CreateQos(command.ShowOne):
- """Create new QoS specification"""
+ _description = _("Create new QoS specification")
def get_parser(self, prog_name):
parser = super(CreateQos, self).get_parser(prog_name)
@@ -94,12 +94,14 @@ class CreateQos(command.ShowOne):
specs.update(parsed_args.property)
qos_spec = volume_client.qos_specs.create(parsed_args.name, specs)
-
+ qos_spec._info.update(
+ {'properties': utils.format_dict(qos_spec._info.pop('specs'))}
+ )
return zip(*sorted(six.iteritems(qos_spec._info)))
class DeleteQos(command.Command):
- """Delete QoS specification"""
+ _description = _("Delete QoS specification")
def get_parser(self, prog_name):
parser = super(DeleteQos, self).get_parser(prog_name)
@@ -139,7 +141,7 @@ class DeleteQos(command.Command):
class DisassociateQos(command.Command):
- """Disassociate a QoS specification from a volume type"""
+ _description = _("Disassociate a QoS specification from a volume type")
def get_parser(self, prog_name):
parser = super(DisassociateQos, self).get_parser(prog_name)
@@ -177,7 +179,7 @@ class DisassociateQos(command.Command):
class ListQos(command.Lister):
- """List QoS specifications"""
+ _description = _("List QoS specifications")
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
@@ -190,8 +192,10 @@ class ListQos(command.Lister):
for association in qos_associations]
qos._info.update({'associations': associations})
+ display_columns = (
+ 'ID', 'Name', 'Consumer', 'Associations', 'Properties')
columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs')
- return (columns,
+ return (display_columns,
(utils.get_dict_properties(
s._info, columns,
formatters={
@@ -202,7 +206,7 @@ class ListQos(command.Lister):
class SetQos(command.Command):
- """Set QoS specification properties"""
+ _description = _("Set QoS specification properties")
def get_parser(self, prog_name):
parser = super(SetQos, self).get_parser(prog_name)
@@ -231,7 +235,7 @@ class SetQos(command.Command):
class ShowQos(command.ShowOne):
- """Display QoS specification details"""
+ _description = _("Display QoS specification details")
def get_parser(self, prog_name):
parser = super(ShowQos, self).get_parser(prog_name)
@@ -254,13 +258,14 @@ class ShowQos(command.ShowOne):
qos_spec._info.update({
'associations': utils.format_list(associations)
})
- qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)})
+ qos_spec._info.update(
+ {'properties': utils.format_dict(qos_spec._info.pop('specs'))})
return zip(*sorted(six.iteritems(qos_spec._info)))
class UnsetQos(command.Command):
- """Unset QoS specification properties"""
+ _description = _("Unset QoS specification properties")
def get_parser(self, prog_name):
parser = super(UnsetQos, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py
index 867c4b9c..d468c6ff 100644
--- a/openstackclient/volume/v1/service.py
+++ b/openstackclient/volume/v1/service.py
@@ -22,7 +22,7 @@ from openstackclient.i18n import _
class ListService(command.Lister):
- """List service command"""
+ _description = _("List service command")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
@@ -76,7 +76,7 @@ class ListService(command.Lister):
class SetService(command.Command):
- """Set volume service properties"""
+ _description = _("Set volume service properties")
def get_parser(self, prog_name):
parser = super(SetService, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py
index bc92c0f5..e9e3894b 100644
--- a/openstackclient/volume/v1/snapshot.py
+++ b/openstackclient/volume/v1/snapshot.py
@@ -13,6 +13,10 @@
# under the License.
#
+# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete",
+# "snapshot set", "snapshot show" and "snapshot unset"
+# commands two cycles after Ocata.
+
"""Volume v1 Snapshot action implementations"""
import copy
@@ -27,11 +31,13 @@ import six
from openstackclient.i18n import _
+deprecated = True
+LOG_DEP = logging.getLogger('deprecated')
LOG = logging.getLogger(__name__)
class CreateSnapshot(command.ShowOne):
- """Create new snapshot"""
+ _description = _("Create new snapshot")
def get_parser(self, prog_name):
parser = super(CreateSnapshot, self).get_parser(prog_name)
@@ -61,6 +67,8 @@ class CreateSnapshot(command.ShowOne):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot create" instead.'))
volume_client = self.app.client_manager.volume
volume_id = utils.find_resource(volume_client.volumes,
parsed_args.volume).id
@@ -79,7 +87,7 @@ class CreateSnapshot(command.ShowOne):
class DeleteSnapshot(command.Command):
- """Delete snapshot(s)"""
+ _description = _("Delete snapshot(s)")
def get_parser(self, prog_name):
parser = super(DeleteSnapshot, self).get_parser(prog_name)
@@ -92,6 +100,8 @@ class DeleteSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot delete" instead.'))
volume_client = self.app.client_manager.volume
result = 0
@@ -114,7 +124,7 @@ class DeleteSnapshot(command.Command):
class ListSnapshot(command.Lister):
- """List snapshots"""
+ _description = _("List snapshots")
def get_parser(self, prog_name):
parser = super(ListSnapshot, self).get_parser(prog_name)
@@ -133,6 +143,8 @@ class ListSnapshot(command.Lister):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot list" instead.'))
def _format_volume_id(volume_id):
"""Return a volume name if available
@@ -185,7 +197,7 @@ class ListSnapshot(command.Lister):
class SetSnapshot(command.Command):
- """Set snapshot properties"""
+ _description = _("Set snapshot properties")
def get_parser(self, prog_name):
parser = super(SetSnapshot, self).get_parser(prog_name)
@@ -214,6 +226,8 @@ class SetSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot set" instead.'))
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot)
@@ -246,7 +260,7 @@ class SetSnapshot(command.Command):
class ShowSnapshot(command.ShowOne):
- """Display snapshot details"""
+ _description = _("Display snapshot details")
def get_parser(self, prog_name):
parser = super(ShowSnapshot, self).get_parser(prog_name)
@@ -258,6 +272,8 @@ class ShowSnapshot(command.ShowOne):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot show" instead.'))
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot)
@@ -270,7 +286,7 @@ class ShowSnapshot(command.ShowOne):
class UnsetSnapshot(command.Command):
- """Unset snapshot properties"""
+ _description = _("Unset snapshot properties")
def get_parser(self, prog_name):
parser = super(UnsetSnapshot, self).get_parser(prog_name)
@@ -289,6 +305,8 @@ class UnsetSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot unset" instead.'))
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(
volume_client.volume_snapshots, parsed_args.snapshot)
diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py
index cafe8ce6..8e1097f5 100644
--- a/openstackclient/volume/v1/volume.py
+++ b/openstackclient/volume/v1/volume.py
@@ -45,7 +45,7 @@ def _check_size_arg(args):
class CreateVolume(command.ShowOne):
- """Create new volume"""
+ _description = _("Create new volume")
def get_parser(self, prog_name):
parser = super(CreateVolume, self).get_parser(prog_name)
@@ -114,6 +114,28 @@ class CreateVolume(command.ShowOne):
help=_('Set a property on this volume '
'(repeat option to set multiple properties)'),
)
+ bootable_group = parser.add_mutually_exclusive_group()
+ bootable_group.add_argument(
+ "--bootable",
+ action="store_true",
+ help=_("Mark volume as bootable")
+ )
+ bootable_group.add_argument(
+ "--non-bootable",
+ action="store_true",
+ help=_("Mark volume as non-bootable (default)")
+ )
+ readonly_group = parser.add_mutually_exclusive_group()
+ readonly_group.add_argument(
+ "--read-only",
+ action="store_true",
+ help=_("Set volume to read-only access mode")
+ )
+ readonly_group.add_argument(
+ "--read-write",
+ action="store_true",
+ help=_("Set volume to read-write access mode (default)")
+ )
return parser
@@ -166,6 +188,22 @@ class CreateVolume(command.ShowOne):
parsed_args.property,
image,
)
+
+ if parsed_args.bootable or parsed_args.non_bootable:
+ try:
+ volume_client.volumes.set_bootable(
+ volume.id, parsed_args.bootable)
+ except Exception as e:
+ LOG.error(_("Failed to set volume bootable property: %s"), e)
+ if parsed_args.read_only or parsed_args.read_write:
+ try:
+ volume_client.volumes.update_readonly_flag(
+ volume.id,
+ parsed_args.read_only)
+ except Exception as e:
+ LOG.error(_("Failed to set volume read-only access "
+ "mode flag: %s"), e)
+
# Map 'metadata' column to 'properties'
volume._info.update(
{
@@ -178,7 +216,7 @@ class CreateVolume(command.ShowOne):
class DeleteVolume(command.Command):
- """Delete volume(s)"""
+ _description = _("Delete volume(s)")
def get_parser(self, prog_name):
parser = super(DeleteVolume, self).get_parser(prog_name)
@@ -223,7 +261,7 @@ class DeleteVolume(command.Command):
class ListVolume(command.Lister):
- """List volumes"""
+ _description = _("List volumes")
def get_parser(self, prog_name):
parser = super(ListVolume, self).get_parser(prog_name)
@@ -344,8 +382,39 @@ class ListVolume(command.Lister):
) for s in data))
+class MigrateVolume(command.Command):
+ _description = _("Migrate volume to a new host")
+
+ def get_parser(self, prog_name):
+ parser = super(MigrateVolume, self).get_parser(prog_name)
+ parser.add_argument(
+ 'volume',
+ metavar="<volume>",
+ help=_("Volume to migrate (name or ID)")
+ )
+ parser.add_argument(
+ '--host',
+ metavar="<host>",
+ required=True,
+ help=_("Destination host (takes the form: host@backend-name#pool)")
+ )
+ parser.add_argument(
+ '--force-host-copy',
+ action="store_true",
+ help=_("Enable generic host-based force-migration, "
+ "which bypasses driver optimizations")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
+ volume_client.volumes.migrate_volume(volume.id, parsed_args.host,
+ parsed_args.force_host_copy,)
+
+
class SetVolume(command.Command):
- """Set volume properties"""
+ _description = _("Set volume properties")
def get_parser(self, prog_name):
parser = super(SetVolume, self).get_parser(prog_name)
@@ -371,6 +440,15 @@ class SetVolume(command.Command):
help=_('Extend volume size in GB'),
)
parser.add_argument(
+ "--no-property",
+ dest="no_property",
+ action="store_true",
+ help=_("Remove all properties from <volume> "
+ "(specify both --no-property and --property to "
+ "remove the current properties before setting "
+ "new properties.)"),
+ )
+ parser.add_argument(
'--property',
metavar='<key=value>',
action=parseractions.KeyValueAction,
@@ -388,42 +466,91 @@ class SetVolume(command.Command):
action="store_true",
help=_("Mark volume as non-bootable")
)
+ readonly_group = parser.add_mutually_exclusive_group()
+ readonly_group.add_argument(
+ "--read-only",
+ action="store_true",
+ help=_("Set volume to read-only access mode")
+ )
+ readonly_group.add_argument(
+ "--read-write",
+ action="store_true",
+ help=_("Set volume to read-write access mode")
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
+ result = 0
if parsed_args.size:
- if volume.status != 'available':
- LOG.error(_("Volume is in %s state, it must be available "
- "before size can be extended"), volume.status)
- return
- if parsed_args.size <= volume.size:
- LOG.error(_("New size must be greater than %s GB"),
- volume.size)
- return
- volume_client.volumes.extend(volume.id, parsed_args.size)
+ try:
+ if volume.status != 'available':
+ msg = (_("Volume is in %s state, it must be available "
+ "before size can be extended") % volume.status)
+ raise exceptions.CommandError(msg)
+ if parsed_args.size <= volume.size:
+ msg = (_("New size must be greater than %s GB")
+ % volume.size)
+ raise exceptions.CommandError(msg)
+ volume_client.volumes.extend(volume.id, parsed_args.size)
+ except Exception as e:
+ LOG.error(_("Failed to set volume size: %s"), e)
+ result += 1
+
+ if parsed_args.no_property:
+ try:
+ volume_client.volumes.delete_metadata(
+ volume.id, volume.metadata.keys())
+ except Exception as e:
+ LOG.error(_("Failed to clean volume properties: %s"), e)
+ result += 1
if parsed_args.property:
- volume_client.volumes.set_metadata(volume.id, parsed_args.property)
+ try:
+ volume_client.volumes.set_metadata(
+ volume.id,
+ parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to set volume property: %s"), e)
+ result += 1
if parsed_args.bootable or parsed_args.non_bootable:
try:
volume_client.volumes.set_bootable(
volume.id, parsed_args.bootable)
except Exception as e:
LOG.error(_("Failed to set volume bootable property: %s"), e)
+ result += 1
+ if parsed_args.read_only or parsed_args.read_write:
+ try:
+ volume_client.volumes.update_readonly_flag(
+ volume.id,
+ parsed_args.read_only)
+ except Exception as e:
+ LOG.error(_("Failed to set volume read-only access "
+ "mode flag: %s"), e)
+ result += 1
kwargs = {}
if parsed_args.name:
kwargs['display_name'] = parsed_args.name
if parsed_args.description:
kwargs['display_description'] = parsed_args.description
if kwargs:
- volume_client.volumes.update(volume.id, **kwargs)
+ try:
+ volume_client.volumes.update(volume.id, **kwargs)
+ except Exception as e:
+ LOG.error(_("Failed to update volume display name "
+ "or display description: %s"), e)
+ result += 1
+
+ if result > 0:
+ raise exceptions.CommandError(_("One or more of the "
+ "set operations failed"))
class ShowVolume(command.ShowOne):
- """Show volume details"""
+ _description = _("Show volume details")
def get_parser(self, prog_name):
parser = super(ShowVolume, self).get_parser(prog_name)
@@ -453,7 +580,7 @@ class ShowVolume(command.ShowOne):
class UnsetVolume(command.Command):
- """Unset volume properties"""
+ _description = _("Unset volume properties")
def get_parser(self, prog_name):
parser = super(UnsetVolume, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py
new file mode 100644
index 00000000..f22c338b
--- /dev/null
+++ b/openstackclient/volume/v1/volume_snapshot.py
@@ -0,0 +1,355 @@
+# Copyright 2012-2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Volume v1 Snapshot action implementations"""
+
+import copy
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateVolumeSnapshot(command.ShowOne):
+ _description = _("Create new volume snapshot")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshot_name',
+ metavar='<snapshot-name>',
+ nargs="?",
+ help=_('Name of the snapshot (default to None)'),
+ )
+ parser.add_argument(
+ '--volume',
+ metavar='<volume>',
+ help=_('Volume to snapshot (name or ID) '
+ '(default is <snapshot-name>)'),
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('Description of the snapshot'),
+ )
+ parser.add_argument(
+ '--force',
+ dest='force',
+ action='store_true',
+ default=False,
+ help=_('Create a snapshot attached to an instance. '
+ 'Default is False'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ volume = parsed_args.volume
+ if not parsed_args.volume:
+ volume = parsed_args.snapshot_name
+ volume_id = utils.find_resource(volume_client.volumes,
+ volume).id
+ snapshot = volume_client.volume_snapshots.create(
+ volume_id,
+ parsed_args.force,
+ parsed_args.snapshot_name,
+ parsed_args.description
+ )
+
+ snapshot._info.update(
+ {'properties': utils.format_dict(snapshot._info.pop('metadata'))}
+ )
+
+ return zip(*sorted(six.iteritems(snapshot._info)))
+
+
+class DeleteVolumeSnapshot(command.Command):
+ _description = _("Delete volume snapshot(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshots',
+ metavar='<snapshot>',
+ nargs="+",
+ help=_('Snapshot(s) to delete (name or ID)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ result = 0
+
+ for i in parsed_args.snapshots:
+ try:
+ snapshot_id = utils.find_resource(
+ volume_client.volume_snapshots, i).id
+ volume_client.volume_snapshots.delete(snapshot_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete snapshot with "
+ "name or ID '%(snapshot)s': %(e)s"),
+ {'snapshot': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.snapshots)
+ msg = (_("%(result)s of %(total)s snapshots failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListVolumeSnapshot(command.Lister):
+ _description = _("List volume snapshots")
+
+ def get_parser(self, prog_name):
+ parser = super(ListVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help=_('Include all projects (admin only)'),
+ )
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_('List additional fields in output'),
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ default=None,
+ help=_('Filters results by a name.')
+ )
+ parser.add_argument(
+ '--status',
+ metavar='<status>',
+ choices=['available', 'error', 'creating', 'deleting',
+ 'error-deleting'],
+ help=_("Filters results by a status. "
+ "('available', 'error', 'creating', 'deleting'"
+ " or 'error-deleting')")
+ )
+ parser.add_argument(
+ '--volume',
+ metavar='<volume>',
+ default=None,
+ help=_('Filters results by a volume (name or ID).')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+
+ def _format_volume_id(volume_id):
+ """Return a volume name if available
+
+ :param volume_id: a volume ID
+ :rtype: either the volume ID or name
+ """
+
+ volume = volume_id
+ if volume_id in volume_cache.keys():
+ volume = volume_cache[volume_id].display_name
+ return volume
+
+ if parsed_args.long:
+ columns = ['ID', 'Display Name', 'Display Description', 'Status',
+ 'Size', 'Created At', 'Volume ID', 'Metadata']
+ column_headers = copy.deepcopy(columns)
+ column_headers[6] = 'Volume'
+ column_headers[7] = 'Properties'
+ else:
+ columns = ['ID', 'Display Name', 'Display Description', 'Status',
+ 'Size']
+ column_headers = copy.deepcopy(columns)
+
+ # Always update Name and Description
+ column_headers[1] = 'Name'
+ column_headers[2] = 'Description'
+
+ # Cache the volume list
+ volume_cache = {}
+ try:
+ for s in volume_client.volumes.list():
+ volume_cache[s.id] = s
+ except Exception:
+ # Just forget it if there's any trouble
+ pass
+
+ volume_id = None
+ if parsed_args.volume:
+ volume_id = utils.find_resource(
+ volume_client.volumes, parsed_args.volume).id
+
+ search_opts = {
+ 'all_tenants': parsed_args.all_projects,
+ 'display_name': parsed_args.name,
+ 'status': parsed_args.status,
+ 'volume_id': volume_id,
+ }
+
+ data = volume_client.volume_snapshots.list(
+ search_opts=search_opts)
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={'Metadata': utils.format_dict,
+ 'Volume ID': _format_volume_id},
+ ) for s in data))
+
+
+class SetVolumeSnapshot(command.Command):
+ _description = _("Set volume snapshot properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshot',
+ metavar='<snapshot>',
+ help=_('Snapshot to modify (name or ID)')
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help=_('New snapshot name')
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('New snapshot description')
+ )
+ parser.add_argument(
+ "--no-property",
+ dest="no_property",
+ action="store_true",
+ help=_("Remove all properties from <snapshot> "
+ "(specify both --no-property and --property to "
+ "remove the current properties before setting "
+ "new properties.)"),
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help=_('Property to add/change for this snapshot '
+ '(repeat option to set multiple properties)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ snapshot = utils.find_resource(volume_client.volume_snapshots,
+ parsed_args.snapshot)
+
+ result = 0
+ if parsed_args.no_property:
+ try:
+ key_list = snapshot.metadata.keys()
+ volume_client.volume_snapshots.delete_metadata(
+ snapshot.id,
+ list(key_list),
+ )
+ except Exception as e:
+ LOG.error(_("Failed to clean snapshot properties: %s"), e)
+ result += 1
+
+ if parsed_args.property:
+ try:
+ volume_client.volume_snapshots.set_metadata(
+ snapshot.id, parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to set snapshot property: %s"), e)
+ result += 1
+
+ kwargs = {}
+ if parsed_args.name:
+ kwargs['display_name'] = parsed_args.name
+ if parsed_args.description:
+ kwargs['display_description'] = parsed_args.description
+ if kwargs:
+ try:
+ snapshot.update(**kwargs)
+ except Exception as e:
+ LOG.error(_("Failed to update snapshot display name "
+ "or display description: %s"), e)
+ result += 1
+
+ if result > 0:
+ raise exceptions.CommandError(_("One or more of the "
+ "set operations failed"))
+
+
+class ShowVolumeSnapshot(command.ShowOne):
+ _description = _("Display volume snapshot details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshot',
+ metavar='<snapshot>',
+ help=_('Snapshot to display (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ snapshot = utils.find_resource(volume_client.volume_snapshots,
+ parsed_args.snapshot)
+
+ snapshot._info.update(
+ {'properties': utils.format_dict(snapshot._info.pop('metadata'))}
+ )
+
+ return zip(*sorted(six.iteritems(snapshot._info)))
+
+
+class UnsetVolumeSnapshot(command.Command):
+ _description = _("Unset volume snapshot properties")
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshot',
+ metavar='<snapshot>',
+ help=_('Snapshot to modify (name or ID)'),
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ action='append',
+ help=_('Property to remove from snapshot '
+ '(repeat option to remove multiple properties)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ snapshot = utils.find_resource(
+ volume_client.volume_snapshots, parsed_args.snapshot)
+
+ if parsed_args.property:
+ volume_client.volume_snapshots.delete_metadata(
+ snapshot.id,
+ parsed_args.property,
+ )
diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py
index 4d6f2161..f24d5a56 100644
--- a/openstackclient/volume/v1/volume_transfer_request.py
+++ b/openstackclient/volume/v1/volume_transfer_request.py
@@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__)
class AcceptTransferRequest(command.ShowOne):
- """Accept volume transfer request."""
+ _description = _("Accept volume transfer request.")
def get_parser(self, prog_name):
parser = super(AcceptTransferRequest, self).get_parser(prog_name)
@@ -56,7 +56,7 @@ class AcceptTransferRequest(command.ShowOne):
class CreateTransferRequest(command.ShowOne):
- """Create volume transfer request."""
+ _description = _("Create volume transfer request.")
def get_parser(self, prog_name):
parser = super(CreateTransferRequest, self).get_parser(prog_name)
@@ -85,7 +85,7 @@ class CreateTransferRequest(command.ShowOne):
class DeleteTransferRequest(command.Command):
- """Delete volume transfer request(s)."""
+ _description = _("Delete volume transfer request(s).")
def get_parser(self, prog_name):
parser = super(DeleteTransferRequest, self).get_parser(prog_name)
@@ -120,7 +120,7 @@ class DeleteTransferRequest(command.Command):
class ListTransferRequest(command.Lister):
- """Lists all volume transfer requests."""
+ _description = _("Lists all volume transfer requests.")
def get_parser(self, prog_name):
parser = super(ListTransferRequest, self).get_parser(prog_name)
@@ -151,7 +151,7 @@ class ListTransferRequest(command.Lister):
class ShowTransferRequest(command.ShowOne):
- """Show volume transfer request details."""
+ _description = _("Show volume transfer request details.")
def get_parser(self, prog_name):
parser = super(ShowTransferRequest, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py
index 61e9f7fc..f9baa5be 100644
--- a/openstackclient/volume/v1/volume_type.py
+++ b/openstackclient/volume/v1/volume_type.py
@@ -29,8 +29,28 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
+def _create_encryption_type(volume_client, volume_type, parsed_args):
+ if not parsed_args.encryption_provider:
+ msg = _("'--encryption-provider' should be specified while "
+ "creating a new encryption type")
+ raise exceptions.CommandError(msg)
+ # set the default of control location while creating
+ control_location = 'front-end'
+ if parsed_args.encryption_control_location:
+ control_location = parsed_args.encryption_control_location
+ body = {
+ 'provider': parsed_args.encryption_provider,
+ 'cipher': parsed_args.encryption_cipher,
+ 'key_size': parsed_args.encryption_key_size,
+ 'control_location': control_location
+ }
+ encryption = volume_client.volume_encryption_types.create(
+ volume_type, body)
+ return encryption
+
+
class CreateVolumeType(command.ShowOne):
- """Create new volume type"""
+ _description = _("Create new volume type")
def get_parser(self, prog_name):
parser = super(CreateVolumeType, self).get_parser(prog_name)
@@ -46,6 +66,42 @@ class CreateVolumeType(command.ShowOne):
help=_('Set a property on this volume type '
'(repeat option to set multiple properties)'),
)
+ # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
+ parser.add_argument(
+ '--encryption-provider',
+ metavar='<provider>',
+ help=_('Set the class that provides encryption support for '
+ 'this volume type (e.g "LuksEncryptor") (admin only) '
+ '(This option is required when setting encryption type '
+ 'of a volume. Consider using other encryption options '
+ 'such as: "--encryption-cipher", "--encryption-key-size" '
+ 'and "--encryption-control-location")'),
+ )
+ parser.add_argument(
+ '--encryption-cipher',
+ metavar='<cipher>',
+ help=_('Set the encryption algorithm or mode for this '
+ 'volume type (e.g "aes-xts-plain64") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-key-size',
+ metavar='<key-size>',
+ type=int,
+ help=_('Set the size of the encryption key of this '
+ 'volume type (e.g "128" or "256") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-control-location',
+ metavar='<control-location>',
+ choices=['front-end', 'back-end'],
+ help=_('Set the notional service where the encryption is '
+ 'performed ("front-end" or "back-end") (admin only) '
+ '(The default value for this option is "front-end" '
+ 'when setting encryption type of a volume. Consider '
+ 'using other encryption options such as: '
+ '"--encryption-cipher", "--encryption-key-size" and '
+ '"--encryption-provider")'),
+ )
return parser
def take_action(self, parsed_args):
@@ -55,13 +111,28 @@ class CreateVolumeType(command.ShowOne):
if parsed_args.property:
result = volume_type.set_keys(parsed_args.property)
volume_type._info.update({'properties': utils.format_dict(result)})
+ if (parsed_args.encryption_provider or
+ parsed_args.encryption_cipher or
+ parsed_args.encryption_key_size or
+ parsed_args.encryption_control_location):
+ try:
+ # create new encryption
+ encryption = _create_encryption_type(
+ volume_client, volume_type, parsed_args)
+ except Exception as e:
+ LOG.error(_("Failed to set encryption information for this "
+ "volume type: %s"), e)
+ # add encryption info in result
+ encryption._info.pop("volume_type_id", None)
+ volume_type._info.update(
+ {'encryption': utils.format_dict(encryption._info)})
volume_type._info.pop("os-volume-type-access:is_public", None)
return zip(*sorted(six.iteritems(volume_type._info)))
class DeleteVolumeType(command.Command):
- """Delete volume type(s)"""
+ _description = _("Delete volume type(s)")
def get_parser(self, prog_name):
parser = super(DeleteVolumeType, self).get_parser(prog_name)
@@ -97,7 +168,7 @@ class DeleteVolumeType(command.Command):
class ListVolumeType(command.Lister):
- """List volume types"""
+ _description = _("List volume types")
def get_parser(self, prog_name):
parser = super(ListVolumeType, self).get_parser(prog_name)
@@ -107,25 +178,63 @@ class ListVolumeType(command.Lister):
default=False,
help=_('List additional fields in output')
)
+ parser.add_argument(
+ "--encryption-type",
+ action="store_true",
+ help=_("Display encryption information for each volume type "
+ "(admin only)"),
+ )
return parser
def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
if parsed_args.long:
- columns = ('ID', 'Name', 'Extra Specs')
- column_headers = ('ID', 'Name', 'Properties')
+ columns = ['ID', 'Name', 'Is Public', 'Extra Specs']
+ column_headers = ['ID', 'Name', 'Is Public', 'Properties']
else:
- columns = ('ID', 'Name')
- column_headers = columns
- data = self.app.client_manager.volume.volume_types.list()
+ columns = ['ID', 'Name', 'Is Public']
+ column_headers = ['ID', 'Name', 'Is Public']
+ data = volume_client.volume_types.list()
+
+ def _format_encryption_info(type_id, encryption_data=None):
+ encryption_data = encryption
+ encryption_info = '-'
+ if type_id in encryption_data.keys():
+ encryption_info = encryption_data[type_id]
+ return encryption_info
+
+ if parsed_args.encryption_type:
+ encryption = {}
+ for d in volume_client.volume_encryption_types.list():
+ volume_type_id = d._info['volume_type_id']
+ # remove some redundant information
+ del_key = [
+ 'deleted',
+ 'created_at',
+ 'updated_at',
+ 'deleted_at',
+ 'volume_type_id'
+ ]
+ for key in del_key:
+ d._info.pop(key, None)
+ # save the encryption information with their volume type ID
+ encryption[volume_type_id] = utils.format_dict(d._info)
+ # We need to get volume type ID, then show encryption
+ # information according to the ID, so use "id" to keep
+ # difference to the real "ID" column.
+ columns += ['id']
+ column_headers += ['Encryption']
+
return (column_headers,
(utils.get_item_properties(
s, columns,
- formatters={'Extra Specs': utils.format_dict},
+ formatters={'Extra Specs': utils.format_dict,
+ 'id': _format_encryption_info},
) for s in data))
class SetVolumeType(command.Command):
- """Set volume type properties"""
+ _description = _("Set volume type properties")
def get_parser(self, prog_name):
parser = super(SetVolumeType, self).get_parser(prog_name)
@@ -141,6 +250,42 @@ class SetVolumeType(command.Command):
help=_('Set a property on this volume type '
'(repeat option to set multiple properties)'),
)
+ # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
+ parser.add_argument(
+ '--encryption-provider',
+ metavar='<provider>',
+ help=_('Set the class that provides encryption support for '
+ 'this volume type (e.g "LuksEncryptor") (admin only) '
+ '(This option is required when setting encryption type '
+ 'of a volume. Consider using other encryption options '
+ 'such as: "--encryption-cipher", "--encryption-key-size" '
+ 'and "--encryption-control-location")'),
+ )
+ parser.add_argument(
+ '--encryption-cipher',
+ metavar='<cipher>',
+ help=_('Set the encryption algorithm or mode for this '
+ 'volume type (e.g "aes-xts-plain64") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-key-size',
+ metavar='<key-size>',
+ type=int,
+ help=_('Set the size of the encryption key of this '
+ 'volume type (e.g "128" or "256") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-control-location',
+ metavar='<control-location>',
+ choices=['front-end', 'back-end'],
+ help=_('Set the notional service where the encryption is '
+ 'performed ("front-end" or "back-end") (admin only) '
+ '(The default value for this option is "front-end" '
+ 'when setting encryption type of a volume. Consider '
+ 'using other encryption options such as: '
+ '"--encryption-cipher", "--encryption-key-size" and '
+ '"--encryption-provider")'),
+ )
return parser
def take_action(self, parsed_args):
@@ -148,12 +293,33 @@ class SetVolumeType(command.Command):
volume_type = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type)
+ result = 0
if parsed_args.property:
- volume_type.set_keys(parsed_args.property)
+ try:
+ volume_type.set_keys(parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to set volume type property: %s"), e)
+ result += 1
+
+ if (parsed_args.encryption_provider or
+ parsed_args.encryption_cipher or
+ parsed_args.encryption_key_size or
+ parsed_args.encryption_control_location):
+ try:
+ _create_encryption_type(
+ volume_client, volume_type, parsed_args)
+ except Exception as e:
+ LOG.error(_("Failed to set encryption information for this "
+ "volume type: %s"), e)
+ result += 1
+
+ if result > 0:
+ raise exceptions.CommandError(_("Command Failed: One or more of"
+ " the operations failed"))
class ShowVolumeType(command.ShowOne):
- """Display volume type details"""
+ _description = _("Display volume type details")
def get_parser(self, prog_name):
parser = super(ShowVolumeType, self).get_parser(prog_name)
@@ -162,6 +328,12 @@ class ShowVolumeType(command.ShowOne):
metavar="<volume-type>",
help=_("Volume type to display (name or ID)")
)
+ parser.add_argument(
+ "--encryption-type",
+ action="store_true",
+ help=_("Display encryption information of this volume type "
+ "(admin only)"),
+ )
return parser
def take_action(self, parsed_args):
@@ -170,12 +342,23 @@ class ShowVolumeType(command.ShowOne):
volume_client.volume_types, parsed_args.volume_type)
properties = utils.format_dict(volume_type._info.pop('extra_specs'))
volume_type._info.update({'properties': properties})
+ if parsed_args.encryption_type:
+ # show encryption type information for this volume type
+ try:
+ encryption = volume_client.volume_encryption_types.get(
+ volume_type.id)
+ encryption._info.pop("volume_type_id", None)
+ volume_type._info.update(
+ {'encryption': utils.format_dict(encryption._info)})
+ except Exception as e:
+ LOG.error(_("Failed to display the encryption information "
+ "of this volume type: %s"), e)
volume_type._info.pop("os-volume-type-access:is_public", None)
return zip(*sorted(six.iteritems(volume_type._info)))
class UnsetVolumeType(command.Command):
- """Unset volume type properties"""
+ _description = _("Unset volume type properties")
def get_parser(self, prog_name):
parser = super(UnsetVolumeType, self).get_parser(prog_name)
@@ -191,6 +374,12 @@ class UnsetVolumeType(command.Command):
help=_('Remove a property from this volume type '
'(repeat option to remove multiple properties)'),
)
+ parser.add_argument(
+ "--encryption-type",
+ action="store_true",
+ help=_("Remove the encryption type for this volume type "
+ "(admin oly)"),
+ )
return parser
def take_action(self, parsed_args):
@@ -200,5 +389,21 @@ class UnsetVolumeType(command.Command):
parsed_args.volume_type,
)
+ result = 0
if parsed_args.property:
- volume_type.unset_keys(parsed_args.property)
+ try:
+ volume_type.unset_keys(parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to unset volume type property: %s"), e)
+ result += 1
+ if parsed_args.encryption_type:
+ try:
+ volume_client.volume_encryption_types.delete(volume_type)
+ except Exception as e:
+ LOG.error(_("Failed to remove the encryption type for this "
+ "volume type: %s"), e)
+ result += 1
+
+ if result > 0:
+ raise exceptions.CommandError(_("Command Failed: One or more of"
+ " the operations failed"))
diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py
index 2ca35b24..00389fcb 100644
--- a/openstackclient/volume/v2/backup.py
+++ b/openstackclient/volume/v2/backup.py
@@ -17,6 +17,7 @@
import copy
import logging
+from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -29,7 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateVolumeBackup(command.ShowOne):
- """Create new volume backup"""
+ _description = _("Create new volume backup")
def get_parser(self, prog_name):
parser = super(CreateVolumeBackup, self).get_parser(prog_name)
@@ -94,7 +95,7 @@ class CreateVolumeBackup(command.ShowOne):
class CreateBackup(CreateVolumeBackup):
- """Create new backup"""
+ _description = _("Create new backup")
# TODO(Huanxuan Ao): Remove this class and ``backup create`` command
# two cycles after Newton.
@@ -111,7 +112,7 @@ class CreateBackup(CreateVolumeBackup):
class DeleteVolumeBackup(command.Command):
- """Delete volume backup(s)"""
+ _description = _("Delete volume backup(s)")
def get_parser(self, prog_name):
parser = super(DeleteVolumeBackup, self).get_parser(prog_name)
@@ -152,7 +153,7 @@ class DeleteVolumeBackup(command.Command):
class DeleteBackup(DeleteVolumeBackup):
- """Delete backup(s)"""
+ _description = _("Delete backup(s)")
# TODO(Huanxuan Ao): Remove this class and ``backup delete`` command
# two cycles after Newton.
@@ -169,7 +170,7 @@ class DeleteBackup(DeleteVolumeBackup):
class ListVolumeBackup(command.Lister):
- """List volume backups"""
+ _description = _("List volume backups")
def get_parser(self, prog_name):
parser = super(ListVolumeBackup, self).get_parser(prog_name)
@@ -179,9 +180,48 @@ class ListVolumeBackup(command.Lister):
default=False,
help=_("List additional fields in output")
)
+ parser.add_argument(
+ "--name",
+ metavar="<name>",
+ help=_("Filters results by the backup name")
+ )
+ parser.add_argument(
+ "--status",
+ metavar="<status>",
+ choices=['creating', 'available', 'deleting',
+ 'error', 'restoring', 'error_restoring'],
+ help=_("Filters results by the backup status "
+ "('creating', 'available', 'deleting', "
+ "'error', 'restoring' or 'error_restoring')")
+ )
+ parser.add_argument(
+ "--volume",
+ metavar="<volume>",
+ help=_("Filters results by the volume which they "
+ "backup (name or ID)")
+ )
+ parser.add_argument(
+ '--marker',
+ metavar='<marker>',
+ help=_('The last backup of the previous page (name or ID)'),
+ )
+ parser.add_argument(
+ '--limit',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ metavar='<limit>',
+ help=_('Maximum number of backups to display'),
+ )
+ parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help=_('Include all projects (admin only)'),
+ )
return parser
def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
def _format_volume_id(volume_id):
"""Return a volume name if available
@@ -207,13 +247,31 @@ class ListVolumeBackup(command.Lister):
# Cache the volume list
volume_cache = {}
try:
- for s in self.app.client_manager.volume.volumes.list():
+ for s in volume_client.volumes.list():
volume_cache[s.id] = s
except Exception:
# Just forget it if there's any trouble
pass
- data = self.app.client_manager.volume.backups.list()
+ filter_volume_id = None
+ if parsed_args.volume:
+ filter_volume_id = utils.find_resource(volume_client.volumes,
+ parsed_args.volume).id
+ marker_backup_id = None
+ if parsed_args.marker:
+ marker_backup_id = utils.find_resource(volume_client.backups,
+ parsed_args.marker).id
+ search_opts = {
+ 'name': parsed_args.name,
+ 'status': parsed_args.status,
+ 'volume_id': filter_volume_id,
+ 'all_tenants': parsed_args.all_projects,
+ }
+ data = volume_client.backups.list(
+ search_opts=search_opts,
+ marker=marker_backup_id,
+ limit=parsed_args.limit,
+ )
return (column_headers,
(utils.get_item_properties(
@@ -223,7 +281,7 @@ class ListVolumeBackup(command.Lister):
class ListBackup(ListVolumeBackup):
- """List backups"""
+ _description = _("List backups")
# TODO(Huanxuan Ao): Remove this class and ``backup list`` command
# two cycles after Newton.
@@ -240,7 +298,7 @@ class ListBackup(ListVolumeBackup):
class RestoreVolumeBackup(command.ShowOne):
- """Restore volume backup"""
+ _description = _("Restore volume backup")
def get_parser(self, prog_name):
parser = super(RestoreVolumeBackup, self).get_parser(prog_name)
@@ -265,7 +323,7 @@ class RestoreVolumeBackup(command.ShowOne):
class RestoreBackup(RestoreVolumeBackup):
- """Restore backup"""
+ _description = _("Restore backup")
# TODO(Huanxuan Ao): Remove this class and ``backup restore`` command
# two cycles after Newton.
@@ -282,7 +340,7 @@ class RestoreBackup(RestoreVolumeBackup):
class SetVolumeBackup(command.Command):
- """Set volume backup properties"""
+ _description = _("Set volume backup properties")
def get_parser(self, prog_name):
parser = super(SetVolumeBackup, self).get_parser(prog_name)
@@ -344,7 +402,7 @@ class SetVolumeBackup(command.Command):
class ShowVolumeBackup(command.ShowOne):
- """Display volume backup details"""
+ _description = _("Display volume backup details")
def get_parser(self, prog_name):
parser = super(ShowVolumeBackup, self).get_parser(prog_name)
@@ -364,7 +422,7 @@ class ShowVolumeBackup(command.ShowOne):
class ShowBackup(ShowVolumeBackup):
- """Display backup details"""
+ _description = _("Display backup details")
# TODO(Huanxuan Ao): Remove this class and ``backup show`` command
# two cycles after Newton.
diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py
index 39f2d577..0a932f84 100644
--- a/openstackclient/volume/v2/consistency_group.py
+++ b/openstackclient/volume/v2/consistency_group.py
@@ -14,21 +14,206 @@
"""Volume v2 consistency group action implementations"""
+import logging
+
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
+import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
+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.")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateConsistencyGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ "name",
+ metavar="<name>",
+ nargs="?",
+ help=_("Name of new consistency group (default to None)")
+ )
+ exclusive_group = parser.add_mutually_exclusive_group(required=True)
+ exclusive_group.add_argument(
+ "--volume-type",
+ metavar="<volume-type>",
+ help=_("Volume type of this consistency group (name or ID)")
+ )
+ exclusive_group.add_argument(
+ "--consistency-group-source",
+ metavar="<consistency-group>",
+ help=_("Existing consistency group (name or ID)")
+ )
+ exclusive_group.add_argument(
+ "--consistency-group-snapshot",
+ metavar="<consistency-group-snapshot>",
+ help=_("Existing consistency group snapshot (name or ID)")
+ )
+ parser.add_argument(
+ "--description",
+ metavar="<description>",
+ help=_("Description of this consistency group")
+ )
+ parser.add_argument(
+ "--availability-zone",
+ metavar="<availability-zone>",
+ help=_("Availability zone for this consistency group "
+ "(not available if creating consistency group "
+ "from source)"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ if parsed_args.volume_type:
+ volume_type_id = utils.find_resource(
+ volume_client.volume_types,
+ parsed_args.volume_type).id
+ consistency_group = volume_client.consistencygroups.create(
+ volume_type_id,
+ name=parsed_args.name,
+ description=parsed_args.description,
+ availability_zone=parsed_args.availability_zone
+ )
+ else:
+ if parsed_args.availability_zone:
+ msg = _("'--availability-zone' option will not work "
+ "if creating consistency group from source")
+ LOG.warning(msg)
+
+ consistency_group_id = None
+ consistency_group_snapshot = None
+ if parsed_args.consistency_group_source:
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups,
+ parsed_args.consistency_group_source).id
+ elif parsed_args.consistency_group_snapshot:
+ consistency_group_snapshot = utils.find_resource(
+ volume_client.cgsnapshots,
+ parsed_args.consistency_group_snapshot).id
+
+ consistency_group = (
+ volume_client.consistencygroups.create_from_src(
+ consistency_group_snapshot,
+ consistency_group_id,
+ name=parsed_args.name,
+ description=parsed_args.description
+ )
+ )
+
+ return zip(*sorted(six.iteritems(consistency_group._info)))
+
+
+class DeleteConsistencyGroup(command.Command):
+ _description = _("Delete consistency group(s).")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteConsistencyGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'consistency_groups',
+ metavar='<consistency-group>',
+ nargs="+",
+ help=_('Consistency group(s) to delete (name or ID)'),
+ )
+ parser.add_argument(
+ '--force',
+ action='store_true',
+ default=False,
+ help=_("Allow delete in state other than error or available"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ result = 0
+
+ for i in parsed_args.consistency_groups:
+ try:
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups, i).id
+ volume_client.consistencygroups.delete(
+ consistency_group_id, parsed_args.force)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete consistency group with "
+ "name or ID '%(consistency_group)s':%(e)s")
+ % {'consistency_group': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.consistency_groups)
+ msg = (_("%(result)s of %(total)s consistency groups failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
class ListConsistencyGroup(command.Lister):
- """List consistency groups."""
+ _description = _("List consistency groups.")
def get_parser(self, prog_name):
parser = super(ListConsistencyGroup, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action="store_true",
- help=_('Show detail for all projects. Admin only. '
+ help=_('Show details for all projects. Admin only. '
'(defaults to False)')
)
parser.add_argument(
@@ -55,3 +240,98 @@ class ListConsistencyGroup(command.Lister):
s, columns,
formatters={'Volume Types': utils.format_list})
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")
+
+ def get_parser(self, prog_name):
+ parser = super(SetConsistencyGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'consistency_group',
+ metavar='<consistency-group>',
+ help=_('Consistency group to modify (name or ID)')
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help=_('New consistency group name'),
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('New consistency group description'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ kwargs = {}
+ if parsed_args.name:
+ kwargs['name'] = parsed_args.name
+ if parsed_args.description:
+ kwargs['description'] = parsed_args.description
+ if kwargs:
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups,
+ parsed_args.consistency_group).id
+ volume_client.consistencygroups.update(
+ consistency_group_id, **kwargs)
+
+
+class ShowConsistencyGroup(command.ShowOne):
+ _description = _("Display consistency group details.")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowConsistencyGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ "consistency_group",
+ metavar="<consistency-group>",
+ help=_("Consistency group to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ consistency_group = utils.find_resource(
+ volume_client.consistencygroups,
+ parsed_args.consistency_group)
+ return zip(*sorted(six.iteritems(consistency_group._info)))
diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py
new file mode 100644
index 00000000..540deb01
--- /dev/null
+++ b/openstackclient/volume/v2/consistency_group_snapshot.py
@@ -0,0 +1,190 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Volume v2 consistency group snapshot action implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateConsistencyGroupSnapshot(command.ShowOne):
+ _description = _("Create new consistency group snapshot.")
+
+ def get_parser(self, prog_name):
+ parser = super(
+ CreateConsistencyGroupSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ "snapshot_name",
+ metavar="<snapshot-name>",
+ nargs="?",
+ help=_("Name of new consistency group snapshot (default to None)")
+ )
+ parser.add_argument(
+ "--consistency-group",
+ metavar="<consistency-group>",
+ help=_("Consistency group to snapshot (name or ID) "
+ "(default to be the same as <snapshot-name>)")
+ )
+ parser.add_argument(
+ "--description",
+ metavar="<description>",
+ help=_("Description of this consistency group snapshot")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ consistency_group = parsed_args.consistency_group
+ if not parsed_args.consistency_group:
+ # If "--consistency-group" not specified, then consistency_group
+ # will be the same as the new consistency group snapshot name
+ consistency_group = parsed_args.snapshot_name
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups,
+ consistency_group).id
+ consistency_group_snapshot = volume_client.cgsnapshots.create(
+ consistency_group_id,
+ name=parsed_args.snapshot_name,
+ description=parsed_args.description,
+ )
+
+ return zip(*sorted(six.iteritems(consistency_group_snapshot._info)))
+
+
+class DeleteConsistencyGroupSnapshot(command.Command):
+ _description = _("Delete consistency group snapshot(s).")
+
+ def get_parser(self, prog_name):
+ parser = super(
+ DeleteConsistencyGroupSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ "consistency_group_snapshot",
+ metavar="<consistency-group-snapshot>",
+ nargs="+",
+ help=_("Consistency group snapshot(s) to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ result = 0
+
+ for snapshot in parsed_args.consistency_group_snapshot:
+ try:
+ snapshot_id = utils.find_resource(volume_client.cgsnapshots,
+ snapshot).id
+
+ volume_client.cgsnapshots.delete(snapshot_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete consistency group snapshot "
+ "with name or ID '%(snapshot)s': %(e)s")
+ % {'snapshot': snapshot, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.consistency_group_snapshot)
+ msg = (_("%(result)s of %(total)s consistency group snapshots "
+ "failed to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListConsistencyGroupSnapshot(command.Lister):
+ _description = _("List consistency group snapshots.")
+
+ def get_parser(self, prog_name):
+ parser = super(
+ ListConsistencyGroupSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ '--all-projects',
+ action="store_true",
+ help=_('Show detail for all projects (admin only) '
+ '(defaults to False)')
+ )
+ parser.add_argument(
+ '--long',
+ action="store_true",
+ help=_('List additional fields in output')
+ )
+ parser.add_argument(
+ '--status',
+ metavar="<status>",
+ choices=['available', 'error', 'creating', 'deleting',
+ 'error-deleting'],
+ help=_('Filters results by a status ("available", "error", '
+ '"creating", "deleting" or "error_deleting")')
+ )
+ parser.add_argument(
+ '--consistency-group',
+ metavar="<consistency-group>",
+ help=_('Filters results by a consistency group (name or ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ if parsed_args.long:
+ columns = ['ID', 'Status', 'ConsistencyGroup ID',
+ 'Name', 'Description', 'Created At']
+ else:
+ columns = ['ID', 'Status', 'Name']
+ volume_client = self.app.client_manager.volume
+ consistency_group_id = None
+ if parsed_args.consistency_group:
+ consistency_group_id = utils.find_resource(
+ volume_client.consistencygroups,
+ parsed_args.consistency_group,
+ ).id
+ search_opts = {
+ 'all_tenants': parsed_args.all_projects,
+ 'status': parsed_args.status,
+ 'consistencygroup_id': consistency_group_id,
+ }
+ consistency_group_snapshots = volume_client.cgsnapshots.list(
+ detailed=True,
+ search_opts=search_opts,
+ )
+
+ return (columns, (
+ utils.get_item_properties(
+ s, columns)
+ for s in consistency_group_snapshots))
+
+
+class ShowConsistencyGroupSnapshot(command.ShowOne):
+ _description = _("Display consistency group snapshot details")
+
+ def get_parser(self, prog_name):
+ parser = super(
+ ShowConsistencyGroupSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ "consistency_group_snapshot",
+ metavar="<consistency-group-snapshot>",
+ help=_("Consistency group snapshot to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ consistency_group_snapshot = utils.find_resource(
+ volume_client.cgsnapshots,
+ parsed_args.consistency_group_snapshot)
+ return zip(*sorted(six.iteritems(consistency_group_snapshot._info)))
diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py
index 9797f1a6..8e1d67b5 100644
--- a/openstackclient/volume/v2/qos_specs.py
+++ b/openstackclient/volume/v2/qos_specs.py
@@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
class AssociateQos(command.Command):
- """Associate a QoS specification to a volume type"""
+ _description = _("Associate a QoS specification to a volume type")
def get_parser(self, prog_name):
parser = super(AssociateQos, self).get_parser(prog_name)
@@ -57,7 +57,7 @@ class AssociateQos(command.Command):
class CreateQos(command.ShowOne):
- """Create new QoS specification"""
+ _description = _("Create new QoS specification")
def get_parser(self, prog_name):
parser = super(CreateQos, self).get_parser(prog_name)
@@ -95,11 +95,14 @@ class CreateQos(command.ShowOne):
qos_spec = volume_client.qos_specs.create(parsed_args.name, specs)
+ qos_spec._info.update(
+ {'properties': utils.format_dict(qos_spec._info.pop('specs'))}
+ )
return zip(*sorted(six.iteritems(qos_spec._info)))
class DeleteQos(command.Command):
- """Delete QoS specification"""
+ _description = _("Delete QoS specification")
def get_parser(self, prog_name):
parser = super(DeleteQos, self).get_parser(prog_name)
@@ -139,7 +142,7 @@ class DeleteQos(command.Command):
class DisassociateQos(command.Command):
- """Disassociate a QoS specification from a volume type"""
+ _description = _("Disassociate a QoS specification from a volume type")
def get_parser(self, prog_name):
parser = super(DisassociateQos, self).get_parser(prog_name)
@@ -177,7 +180,7 @@ class DisassociateQos(command.Command):
class ListQos(command.Lister):
- """List QoS specifications"""
+ _description = _("List QoS specifications")
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
@@ -190,8 +193,11 @@ class ListQos(command.Lister):
for association in qos_associations]
qos._info.update({'associations': associations})
+ display_columns = (
+ 'ID', 'Name', 'Consumer', 'Associations', 'Properties')
+
columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs')
- return (columns,
+ return (display_columns,
(utils.get_dict_properties(
s._info, columns,
formatters={
@@ -202,7 +208,7 @@ class ListQos(command.Lister):
class SetQos(command.Command):
- """Set QoS specification properties"""
+ _description = _("Set QoS specification properties")
def get_parser(self, prog_name):
parser = super(SetQos, self).get_parser(prog_name)
@@ -231,7 +237,7 @@ class SetQos(command.Command):
class ShowQos(command.ShowOne):
- """Display QoS specification details"""
+ _description = _("Display QoS specification details")
def get_parser(self, prog_name):
parser = super(ShowQos, self).get_parser(prog_name)
@@ -254,13 +260,14 @@ class ShowQos(command.ShowOne):
qos_spec._info.update({
'associations': utils.format_list(associations)
})
- qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)})
+ qos_spec._info.update(
+ {'properties': utils.format_dict(qos_spec._info.pop('specs'))})
return zip(*sorted(six.iteritems(qos_spec._info)))
class UnsetQos(command.Command):
- """Unset QoS specification properties"""
+ _description = _("Unset QoS specification properties")
def get_parser(self, prog_name):
parser = super(UnsetQos, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py
index 867c4b9c..d468c6ff 100644
--- a/openstackclient/volume/v2/service.py
+++ b/openstackclient/volume/v2/service.py
@@ -22,7 +22,7 @@ from openstackclient.i18n import _
class ListService(command.Lister):
- """List service command"""
+ _description = _("List service command")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
@@ -76,7 +76,7 @@ class ListService(command.Lister):
class SetService(command.Command):
- """Set volume service properties"""
+ _description = _("Set volume service properties")
def get_parser(self, prog_name):
parser = super(SetService, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py
index 4994e0da..a18887e3 100644
--- a/openstackclient/volume/v2/snapshot.py
+++ b/openstackclient/volume/v2/snapshot.py
@@ -12,6 +12,10 @@
# under the License.
#
+# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete",
+# "snapshot set", "snapshot show" and "snapshot unset"
+# commands two cycles after Ocata.
+
"""Volume v2 snapshot action implementations"""
import copy
@@ -26,11 +30,13 @@ import six
from openstackclient.i18n import _
+deprecated = True
+LOG_DEP = logging.getLogger('deprecated')
LOG = logging.getLogger(__name__)
class CreateSnapshot(command.ShowOne):
- """Create new snapshot"""
+ _description = _("Create new snapshot")
def get_parser(self, prog_name):
parser = super(CreateSnapshot, self).get_parser(prog_name)
@@ -66,6 +72,8 @@ class CreateSnapshot(command.ShowOne):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot create" instead.'))
volume_client = self.app.client_manager.volume
volume_id = utils.find_resource(
volume_client.volumes, parsed_args.volume).id
@@ -83,7 +91,7 @@ class CreateSnapshot(command.ShowOne):
class DeleteSnapshot(command.Command):
- """Delete volume snapshot(s)"""
+ _description = _("Delete volume snapshot(s)")
def get_parser(self, prog_name):
parser = super(DeleteSnapshot, self).get_parser(prog_name)
@@ -96,6 +104,8 @@ class DeleteSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot delete" instead.'))
volume_client = self.app.client_manager.volume
result = 0
@@ -118,7 +128,7 @@ class DeleteSnapshot(command.Command):
class ListSnapshot(command.Lister):
- """List snapshots"""
+ _description = _("List snapshots")
def get_parser(self, prog_name):
parser = super(ListSnapshot, self).get_parser(prog_name)
@@ -149,6 +159,8 @@ class ListSnapshot(command.Lister):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot list" instead.'))
def _format_volume_id(volume_id):
"""Return a volume name if available
@@ -199,7 +211,7 @@ class ListSnapshot(command.Lister):
class SetSnapshot(command.Command):
- """Set snapshot properties"""
+ _description = _("Set snapshot properties")
def get_parser(self, prog_name):
parser = super(SetSnapshot, self).get_parser(prog_name)
@@ -239,6 +251,8 @@ class SetSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot set" instead.'))
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot)
@@ -280,7 +294,7 @@ class SetSnapshot(command.Command):
class ShowSnapshot(command.ShowOne):
- """Display snapshot details"""
+ _description = _("Display snapshot details")
def get_parser(self, prog_name):
parser = super(ShowSnapshot, self).get_parser(prog_name)
@@ -292,6 +306,8 @@ class ShowSnapshot(command.ShowOne):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot show" instead.'))
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(
volume_client.volume_snapshots, parsed_args.snapshot)
@@ -302,7 +318,7 @@ class ShowSnapshot(command.ShowOne):
class UnsetSnapshot(command.Command):
- """Unset snapshot properties"""
+ _description = _("Unset snapshot properties")
def get_parser(self, prog_name):
parser = super(UnsetSnapshot, self).get_parser(prog_name)
@@ -322,6 +338,8 @@ class UnsetSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
+ LOG_DEP.warning(_('This command has been deprecated. '
+ 'Please use "volume snapshot unset" instead.'))
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(
volume_client.volume_snapshots, parsed_args.snapshot)
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index e7405114..c361d700 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -45,7 +45,7 @@ def _check_size_arg(args):
class CreateVolume(command.ShowOne):
- """Create new volume"""
+ _description = _("Create new volume")
def get_parser(self, prog_name):
parser = super(CreateVolume, self).get_parser(prog_name)
@@ -132,6 +132,28 @@ class CreateVolume(command.ShowOne):
help=_("Allow volume to be attached more than once "
"(default to False)")
)
+ bootable_group = parser.add_mutually_exclusive_group()
+ bootable_group.add_argument(
+ "--bootable",
+ action="store_true",
+ help=_("Mark volume as bootable")
+ )
+ bootable_group.add_argument(
+ "--non-bootable",
+ action="store_true",
+ help=_("Mark volume as non-bootable (default)")
+ )
+ readonly_group = parser.add_mutually_exclusive_group()
+ readonly_group.add_argument(
+ "--read-only",
+ action="store_true",
+ help=_("Set volume to read-only access mode")
+ )
+ readonly_group.add_argument(
+ "--read-write",
+ action="store_true",
+ help=_("Set volume to read-write access mode (default)")
+ )
return parser
def take_action(self, parsed_args):
@@ -199,6 +221,22 @@ class CreateVolume(command.ShowOne):
multiattach=parsed_args.multi_attach,
scheduler_hints=parsed_args.hint,
)
+
+ if parsed_args.bootable or parsed_args.non_bootable:
+ try:
+ volume_client.volumes.set_bootable(
+ volume.id, parsed_args.bootable)
+ except Exception as e:
+ LOG.error(_("Failed to set volume bootable property: %s"), e)
+ if parsed_args.read_only or parsed_args.read_write:
+ try:
+ volume_client.volumes.update_readonly_flag(
+ volume.id,
+ parsed_args.read_only)
+ except Exception as e:
+ LOG.error(_("Failed to set volume read-only access "
+ "mode flag: %s"), e)
+
# Remove key links from being displayed
volume._info.update(
{
@@ -211,7 +249,7 @@ class CreateVolume(command.ShowOne):
class DeleteVolume(command.Command):
- """Delete volume(s)"""
+ _description = _("Delete volume(s)")
def get_parser(self, prog_name):
parser = super(DeleteVolume, self).get_parser(prog_name)
@@ -263,7 +301,7 @@ class DeleteVolume(command.Command):
class ListVolume(command.Lister):
- """List volumes"""
+ _description = _("List volumes")
def get_parser(self, prog_name):
parser = super(ListVolume, self).get_parser(prog_name)
@@ -379,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,
@@ -409,8 +450,55 @@ class ListVolume(command.Lister):
) for s in data))
+class MigrateVolume(command.Command):
+ _description = _("Migrate volume to a new host")
+
+ def get_parser(self, prog_name):
+ parser = super(MigrateVolume, self).get_parser(prog_name)
+ parser.add_argument(
+ 'volume',
+ metavar="<volume>",
+ help=_("Volume to migrate (name or ID)")
+ )
+ parser.add_argument(
+ '--host',
+ metavar="<host>",
+ required=True,
+ help=_("Destination host (takes the form: host@backend-name#pool)")
+ )
+ parser.add_argument(
+ '--force-host-copy',
+ action="store_true",
+ help=_("Enable generic host-based force-migration, "
+ "which bypasses driver optimizations")
+ )
+ lock_group = parser.add_mutually_exclusive_group()
+ lock_group.add_argument(
+ '--lock-volume',
+ action="store_true",
+ help=_("If specified, the volume state will be locked "
+ "and will not allow a migration to be aborted "
+ "(possibly by another operation)")
+ )
+ lock_group.add_argument(
+ '--unlock-volume',
+ action="store_true",
+ help=_("If specified, the volume state will not be "
+ "locked and the a migration can be aborted "
+ "(default) (possibly by another operation)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
+ volume_client.volumes.migrate_volume(volume.id, parsed_args.host,
+ parsed_args.force_host_copy,
+ parsed_args.lock_volume,)
+
+
class SetVolume(command.Command):
- """Set volume properties"""
+ _description = _("Set volume properties")
def get_parser(self, prog_name):
parser = super(SetVolume, self).get_parser(prog_name)
@@ -436,6 +524,15 @@ class SetVolume(command.Command):
help=_('New volume description'),
)
parser.add_argument(
+ "--no-property",
+ dest="no_property",
+ action="store_true",
+ help=_("Remove all properties from <volume> "
+ "(specify both --no-property and --property to "
+ "remove the current properties before setting "
+ "new properties.)"),
+ )
+ parser.add_argument(
'--property',
metavar='<key=value>',
action=parseractions.KeyValueAction,
@@ -462,6 +559,19 @@ class SetVolume(command.Command):
'in the database with no regard to actual status, '
'exercise caution when using)'),
)
+ parser.add_argument(
+ '--type',
+ metavar='<volume-type>',
+ help=_('New volume type (name or ID)'),
+ )
+ parser.add_argument(
+ '--retype-policy',
+ metavar='<retype-policy>',
+ choices=['never', 'on-demand'],
+ help=_('Migration policy while re-typing volume '
+ '("never" or "on-demand", default is "never" ) '
+ '(available only when --type option is specified)'),
+ )
bootable_group = parser.add_mutually_exclusive_group()
bootable_group.add_argument(
"--bootable",
@@ -473,6 +583,17 @@ class SetVolume(command.Command):
action="store_true",
help=_("Mark volume as non-bootable")
)
+ readonly_group = parser.add_mutually_exclusive_group()
+ readonly_group.add_argument(
+ "--read-only",
+ action="store_true",
+ help=_("Set volume to read-only access mode")
+ )
+ readonly_group.add_argument(
+ "--read-write",
+ action="store_true",
+ help=_("Set volume to read-write access mode")
+ )
return parser
def take_action(self, parsed_args):
@@ -484,16 +605,25 @@ class SetVolume(command.Command):
try:
if volume.status != 'available':
msg = (_("Volume is in %s state, it must be available "
- "before size can be extended"), volume.status)
+ "before size can be extended") % volume.status)
raise exceptions.CommandError(msg)
if parsed_args.size <= volume.size:
- msg = _("New size must be greater than %s GB"), volume.size
+ msg = (_("New size must be greater than %s GB")
+ % volume.size)
raise exceptions.CommandError(msg)
volume_client.volumes.extend(volume.id, parsed_args.size)
except Exception as e:
LOG.error(_("Failed to set volume size: %s"), e)
result += 1
+ if parsed_args.no_property:
+ try:
+ volume_client.volumes.delete_metadata(
+ volume.id, volume.metadata.keys())
+ except Exception as e:
+ LOG.error(_("Failed to clean volume properties: %s"), e)
+ result += 1
+
if parsed_args.property:
try:
volume_client.volumes.set_metadata(
@@ -522,6 +652,37 @@ class SetVolume(command.Command):
except Exception as e:
LOG.error(_("Failed to set volume bootable property: %s"), e)
result += 1
+ if parsed_args.read_only or parsed_args.read_write:
+ try:
+ volume_client.volumes.update_readonly_flag(
+ volume.id,
+ parsed_args.read_only)
+ except Exception as e:
+ LOG.error(_("Failed to set volume read-only access "
+ "mode flag: %s"), e)
+ result += 1
+ if parsed_args.type:
+ # get the migration policy
+ migration_policy = 'never'
+ if parsed_args.retype_policy:
+ migration_policy = parsed_args.retype_policy
+ try:
+ # find the volume type
+ volume_type = utils.find_resource(
+ volume_client.volume_types,
+ parsed_args.type)
+ # reset to the new volume type
+ volume_client.volumes.retype(
+ volume.id,
+ volume_type.id,
+ migration_policy)
+ except Exception as e:
+ LOG.error(_("Failed to set volume type: %s"), e)
+ result += 1
+ elif parsed_args.retype_policy:
+ # If the "--retype-policy" is specified without "--type"
+ LOG.warning(_("'--retype-policy' option will not work "
+ "without '--type' option"))
kwargs = {}
if parsed_args.name:
@@ -542,7 +703,7 @@ class SetVolume(command.Command):
class ShowVolume(command.ShowOne):
- """Display volume details"""
+ _description = _("Display volume details")
def get_parser(self, prog_name):
parser = super(ShowVolume, self).get_parser(prog_name)
@@ -573,7 +734,7 @@ class ShowVolume(command.ShowOne):
class UnsetVolume(command.Command):
- """Unset volume properties"""
+ _description = _("Unset volume properties")
def get_parser(self, prog_name):
parser = super(UnsetVolume, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py
new file mode 100644
index 00000000..2fdeb968
--- /dev/null
+++ b/openstackclient/volume/v2/volume_host.py
@@ -0,0 +1,75 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Volume v2 host action implementations"""
+
+from osc_lib.command import command
+
+from openstackclient.i18n import _
+
+
+class FailoverVolumeHost(command.Command):
+ _description = _("Failover volume host to different backend")
+
+ def get_parser(self, prog_name):
+ parser = super(FailoverVolumeHost, self).get_parser(prog_name)
+ parser.add_argument(
+ "host",
+ metavar="<host-name>",
+ help=_("Name of volume host")
+ )
+ parser.add_argument(
+ "--volume-backend",
+ metavar="<backend-id>",
+ required=True,
+ help=_("The ID of the volume backend replication "
+ "target where the host will failover to (required)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ service_client = self.app.client_manager.volume
+ service_client.services.failover_host(parsed_args.host,
+ parsed_args.volume_backend)
+
+
+class SetVolumeHost(command.Command):
+ _description = _("Set volume host properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetVolumeHost, self).get_parser(prog_name)
+ parser.add_argument(
+ "host",
+ metavar="<host-name>",
+ help=_("Name of volume host")
+ )
+ enabled_group = parser.add_mutually_exclusive_group()
+ enabled_group.add_argument(
+ "--disable",
+ action="store_true",
+ help=_("Freeze and disable the specified volume host")
+ )
+ enabled_group.add_argument(
+ "--enable",
+ action="store_true",
+ help=_("Thaw and enable the specified volume host")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ service_client = self.app.client_manager.volume
+ if parsed_args.enable:
+ service_client.services.thaw_host(parsed_args.host)
+ if parsed_args.disable:
+ service_client.services.freeze_host(parsed_args.host)
diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py
new file mode 100644
index 00000000..f12cfed9
--- /dev/null
+++ b/openstackclient/volume/v2/volume_snapshot.py
@@ -0,0 +1,440 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Volume v2 snapshot action implementations"""
+
+import copy
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateVolumeSnapshot(command.ShowOne):
+ _description = _("Create new volume snapshot")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ "snapshot_name",
+ metavar="<snapshot-name>",
+ nargs="?",
+ help=_("Name of the new snapshot (default to None)")
+ )
+ parser.add_argument(
+ "--volume",
+ metavar="<volume>",
+ help=_("Volume to snapshot (name or ID) "
+ "(default is <snapshot-name>)")
+ )
+ parser.add_argument(
+ "--description",
+ metavar="<description>",
+ help=_("Description of the snapshot")
+ )
+ parser.add_argument(
+ "--force",
+ action="store_true",
+ default=False,
+ help=_("Create a snapshot attached to an instance. "
+ "Default is False")
+ )
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help=_("Set a property to this snapshot "
+ "(repeat option to set multiple properties)"),
+ )
+ parser.add_argument(
+ "--remote-source",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help=_("The attribute(s) of the exsiting remote volume snapshot "
+ "(admin required) (repeat option to specify multiple "
+ "attributes) e.g.: '--remote-source source-name=test_name "
+ "--remote-source source-id=test_id'"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ volume = parsed_args.volume
+ if not parsed_args.volume:
+ volume = parsed_args.snapshot_name
+ volume_id = utils.find_resource(
+ volume_client.volumes, volume).id
+ if parsed_args.remote_source:
+ # Create a new snapshot from an existing remote snapshot source
+ if parsed_args.force:
+ msg = (_("'--force' option will not work when you create "
+ "new volume snapshot from an existing remote "
+ "volume snapshot"))
+ LOG.warning(msg)
+ snapshot = volume_client.volume_snapshots.manage(
+ volume_id=volume_id,
+ ref=parsed_args.remote_source,
+ name=parsed_args.snapshot_name,
+ description=parsed_args.description,
+ metadata=parsed_args.property,
+ )
+ else:
+ # create a new snapshot from scratch
+ snapshot = volume_client.volume_snapshots.create(
+ volume_id,
+ force=parsed_args.force,
+ name=parsed_args.snapshot_name,
+ description=parsed_args.description,
+ metadata=parsed_args.property,
+ )
+ snapshot._info.update(
+ {'properties': utils.format_dict(snapshot._info.pop('metadata'))}
+ )
+ return zip(*sorted(six.iteritems(snapshot._info)))
+
+
+class DeleteVolumeSnapshot(command.Command):
+ _description = _("Delete volume snapshot(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ "snapshots",
+ metavar="<snapshot>",
+ nargs="+",
+ help=_("Snapshot(s) to delete (name or ID)")
+ )
+ parser.add_argument(
+ '--force',
+ action='store_true',
+ help=_("Attempt forced removal of snapshot(s), "
+ "regardless of state (defaults to False)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ result = 0
+
+ for i in parsed_args.snapshots:
+ try:
+ snapshot_id = utils.find_resource(
+ volume_client.volume_snapshots, i).id
+ volume_client.volume_snapshots.delete(
+ snapshot_id, parsed_args.force)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete snapshot with "
+ "name or ID '%(snapshot)s': %(e)s")
+ % {'snapshot': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.snapshots)
+ msg = (_("%(result)s of %(total)s snapshots failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListVolumeSnapshot(command.Lister):
+ _description = _("List volume snapshots")
+
+ def get_parser(self, prog_name):
+ parser = super(ListVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help=_('Include all projects (admin only)'),
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_('Filter results by project (name or ID) (admin only)')
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_('List additional fields in output'),
+ )
+ parser.add_argument(
+ '--marker',
+ metavar='<marker>',
+ help=_('The last snapshot ID of the previous page'),
+ )
+ parser.add_argument(
+ '--limit',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ metavar='<limit>',
+ help=_('Maximum number of snapshots to display'),
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ default=None,
+ help=_('Filters results by a name.')
+ )
+ parser.add_argument(
+ '--status',
+ metavar='<status>',
+ choices=['available', 'error', 'creating', 'deleting',
+ 'error-deleting'],
+ help=_("Filters results by a status. "
+ "('available', 'error', 'creating', 'deleting'"
+ " or 'error-deleting')")
+ )
+ parser.add_argument(
+ '--volume',
+ metavar='<volume>',
+ default=None,
+ help=_('Filters results by a volume (name or ID).')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ identity_client = self.app.client_manager.identity
+
+ def _format_volume_id(volume_id):
+ """Return a volume name if available
+
+ :param volume_id: a volume ID
+ :rtype: either the volume ID or name
+ """
+
+ volume = volume_id
+ if volume_id in volume_cache.keys():
+ volume = volume_cache[volume_id].name
+ return volume
+
+ if parsed_args.long:
+ columns = ['ID', 'Name', 'Description', 'Status',
+ 'Size', 'Created At', 'Volume ID', 'Metadata']
+ column_headers = copy.deepcopy(columns)
+ column_headers[6] = 'Volume'
+ column_headers[7] = 'Properties'
+ else:
+ columns = ['ID', 'Name', 'Description', 'Status', 'Size']
+ column_headers = copy.deepcopy(columns)
+
+ # Cache the volume list
+ volume_cache = {}
+ try:
+ for s in volume_client.volumes.list():
+ volume_cache[s.id] = s
+ except Exception:
+ # Just forget it if there's any trouble
+ pass
+
+ volume_id = None
+ if parsed_args.volume:
+ volume_id = utils.find_resource(
+ volume_client.volumes, parsed_args.volume).id
+
+ project_id = None
+ if parsed_args.project:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain).id
+
+ # set value of 'all_tenants' when using project option
+ all_projects = True if parsed_args.project else \
+ parsed_args.all_projects
+
+ search_opts = {
+ 'all_tenants': all_projects,
+ 'project_id': project_id,
+ 'name': parsed_args.name,
+ 'status': parsed_args.status,
+ 'volume_id': volume_id,
+ }
+
+ data = volume_client.volume_snapshots.list(
+ search_opts=search_opts,
+ marker=parsed_args.marker,
+ limit=parsed_args.limit,
+ )
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={'Metadata': utils.format_dict,
+ 'Volume ID': _format_volume_id},
+ ) for s in data))
+
+
+class SetVolumeSnapshot(command.Command):
+ _description = _("Set volume snapshot properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshot',
+ metavar='<snapshot>',
+ help=_('Snapshot to modify (name or ID)')
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help=_('New snapshot name')
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('New snapshot description')
+ )
+ parser.add_argument(
+ "--no-property",
+ dest="no_property",
+ action="store_true",
+ help=_("Remove all properties from <snapshot> "
+ "(specify both --no-property and --property to "
+ "remove the current properties before setting "
+ "new properties.)"),
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help=_('Property to add/change for this snapshot '
+ '(repeat option to set multiple properties)'),
+ )
+ parser.add_argument(
+ '--state',
+ metavar='<state>',
+ choices=['available', 'error', 'creating', 'deleting',
+ 'error-deleting'],
+ help=_('New snapshot state. ("available", "error", "creating", '
+ '"deleting", or "error_deleting") (admin only) '
+ '(This option simply changes the state of the snapshot '
+ 'in the database with no regard to actual status, '
+ 'exercise caution when using)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ snapshot = utils.find_resource(volume_client.volume_snapshots,
+ parsed_args.snapshot)
+
+ result = 0
+ if parsed_args.no_property:
+ try:
+ key_list = snapshot.metadata.keys()
+ volume_client.volume_snapshots.delete_metadata(
+ snapshot.id,
+ list(key_list),
+ )
+ except Exception as e:
+ LOG.error(_("Failed to clean snapshot properties: %s"), e)
+ result += 1
+
+ if parsed_args.property:
+ try:
+ volume_client.volume_snapshots.set_metadata(
+ snapshot.id, parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to set snapshot property: %s"), e)
+ result += 1
+
+ if parsed_args.state:
+ try:
+ volume_client.volume_snapshots.reset_state(
+ snapshot.id, parsed_args.state)
+ except Exception as e:
+ LOG.error(_("Failed to set snapshot state: %s"), e)
+ result += 1
+
+ kwargs = {}
+ if parsed_args.name:
+ kwargs['name'] = parsed_args.name
+ if parsed_args.description:
+ kwargs['description'] = parsed_args.description
+ if kwargs:
+ try:
+ volume_client.volume_snapshots.update(
+ snapshot.id, **kwargs)
+ except Exception as e:
+ LOG.error(_("Failed to update snapshot name "
+ "or description: %s"), e)
+ result += 1
+
+ if result > 0:
+ raise exceptions.CommandError(_("One or more of the "
+ "set operations failed"))
+
+
+class ShowVolumeSnapshot(command.ShowOne):
+ _description = _("Display volume snapshot details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ "snapshot",
+ metavar="<snapshot>",
+ help=_("Snapshot to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ snapshot = utils.find_resource(
+ volume_client.volume_snapshots, parsed_args.snapshot)
+ snapshot._info.update(
+ {'properties': utils.format_dict(snapshot._info.pop('metadata'))}
+ )
+ return zip(*sorted(six.iteritems(snapshot._info)))
+
+
+class UnsetVolumeSnapshot(command.Command):
+ _description = _("Unset volume snapshot properties")
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name)
+ parser.add_argument(
+ 'snapshot',
+ metavar='<snapshot>',
+ help=_('Snapshot to modify (name or ID)'),
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ action='append',
+ default=[],
+ help=_('Property to remove from snapshot '
+ '(repeat option to remove multiple properties)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ snapshot = utils.find_resource(
+ volume_client.volume_snapshots, parsed_args.snapshot)
+
+ if parsed_args.property:
+ volume_client.volume_snapshots.delete_metadata(
+ snapshot.id,
+ parsed_args.property,
+ )
diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py
index 9008fe3c..aefe594a 100644
--- a/openstackclient/volume/v2/volume_transfer_request.py
+++ b/openstackclient/volume/v2/volume_transfer_request.py
@@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__)
class AcceptTransferRequest(command.ShowOne):
- """Accept volume transfer request."""
+ _description = _("Accept volume transfer request.")
def get_parser(self, prog_name):
parser = super(AcceptTransferRequest, self).get_parser(prog_name)
@@ -56,7 +56,7 @@ class AcceptTransferRequest(command.ShowOne):
class CreateTransferRequest(command.ShowOne):
- """Create volume transfer request."""
+ _description = _("Create volume transfer request.")
def get_parser(self, prog_name):
parser = super(CreateTransferRequest, self).get_parser(prog_name)
@@ -85,7 +85,7 @@ class CreateTransferRequest(command.ShowOne):
class DeleteTransferRequest(command.Command):
- """Delete volume transfer request(s)."""
+ _description = _("Delete volume transfer request(s).")
def get_parser(self, prog_name):
parser = super(DeleteTransferRequest, self).get_parser(prog_name)
@@ -120,7 +120,7 @@ class DeleteTransferRequest(command.Command):
class ListTransferRequest(command.Lister):
- """Lists all volume transfer requests."""
+ _description = _("Lists all volume transfer requests.")
def get_parser(self, prog_name):
parser = super(ListTransferRequest, self).get_parser(prog_name)
@@ -151,7 +151,7 @@ class ListTransferRequest(command.Lister):
class ShowTransferRequest(command.ShowOne):
- """Show volume transfer request details."""
+ _description = _("Show volume transfer request details.")
def get_parser(self, prog_name):
parser = super(ShowTransferRequest, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index 80a1f21b..8d2901f2 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -29,8 +29,46 @@ from openstackclient.identity import common as identity_common
LOG = logging.getLogger(__name__)
+def _create_encryption_type(volume_client, volume_type, parsed_args):
+ if not parsed_args.encryption_provider:
+ msg = _("'--encryption-provider' should be specified while "
+ "creating a new encryption type")
+ raise exceptions.CommandError(msg)
+ # set the default of control location while creating
+ control_location = 'front-end'
+ if parsed_args.encryption_control_location:
+ control_location = parsed_args.encryption_control_location
+ body = {
+ 'provider': parsed_args.encryption_provider,
+ 'cipher': parsed_args.encryption_cipher,
+ 'key_size': parsed_args.encryption_key_size,
+ 'control_location': control_location
+ }
+ encryption = volume_client.volume_encryption_types.create(
+ volume_type, body)
+ return encryption
+
+
+def _set_encryption_type(volume_client, volume_type, parsed_args):
+ # update the existing encryption type
+ body = {}
+ for attr in ['provider', 'cipher', 'key_size', 'control_location']:
+ info = getattr(parsed_args, 'encryption_' + attr, None)
+ if info is not None:
+ body[attr] = info
+ try:
+ volume_client.volume_encryption_types.update(volume_type, body)
+ except Exception as e:
+ if type(e).__name__ == 'NotFound':
+ # create new encryption type
+ LOG.warning(_("No existing encryption type found, creating "
+ "new encryption type for this volume type ..."))
+ _create_encryption_type(
+ volume_client, volume_type, parsed_args)
+
+
class CreateVolumeType(command.ShowOne):
- """Create new volume type"""
+ _description = _("Create new volume type")
def get_parser(self, prog_name):
parser = super(CreateVolumeType, self).get_parser(prog_name)
@@ -70,6 +108,42 @@ class CreateVolumeType(command.ShowOne):
help=_("Allow <project> to access private type (name or ID) "
"(Must be used with --private option)"),
)
+ # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
+ parser.add_argument(
+ '--encryption-provider',
+ metavar='<provider>',
+ help=_('Set the class that provides encryption support for '
+ 'this volume type (e.g "LuksEncryptor") (admin only) '
+ '(This option is required when setting encryption type '
+ 'of a volume. Consider using other encryption options '
+ 'such as: "--encryption-cipher", "--encryption-key-size" '
+ 'and "--encryption-control-location")'),
+ )
+ parser.add_argument(
+ '--encryption-cipher',
+ metavar='<cipher>',
+ help=_('Set the encryption algorithm or mode for this '
+ 'volume type (e.g "aes-xts-plain64") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-key-size',
+ metavar='<key-size>',
+ type=int,
+ help=_('Set the size of the encryption key of this '
+ 'volume type (e.g "128" or "256") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-control-location',
+ metavar='<control-location>',
+ choices=['front-end', 'back-end'],
+ help=_('Set the notional service where the encryption is '
+ 'performed ("front-end" or "back-end") (admin only) '
+ '(The default value for this option is "front-end" '
+ 'when setting encryption type of a volume. Consider '
+ 'using other encryption options such as: '
+ '"--encryption-cipher", "--encryption-key-size" and '
+ '"--encryption-provider")'),
+ )
identity_common.add_project_domain_option_to_parser(parser)
return parser
@@ -110,13 +184,28 @@ class CreateVolumeType(command.ShowOne):
if parsed_args.property:
result = volume_type.set_keys(parsed_args.property)
volume_type._info.update({'properties': utils.format_dict(result)})
+ if (parsed_args.encryption_provider or
+ parsed_args.encryption_cipher or
+ parsed_args.encryption_key_size or
+ parsed_args.encryption_control_location):
+ try:
+ # create new encryption
+ encryption = _create_encryption_type(
+ volume_client, volume_type, parsed_args)
+ except Exception as e:
+ LOG.error(_("Failed to set encryption information for this "
+ "volume type: %s"), e)
+ # add encryption info in result
+ encryption._info.pop("volume_type_id", None)
+ volume_type._info.update(
+ {'encryption': utils.format_dict(encryption._info)})
volume_type._info.pop("os-volume-type-access:is_public", None)
return zip(*sorted(six.iteritems(volume_type._info)))
class DeleteVolumeType(command.Command):
- """Delete volume type(s)"""
+ _description = _("Delete volume type(s)")
def get_parser(self, prog_name):
parser = super(DeleteVolumeType, self).get_parser(prog_name)
@@ -152,7 +241,7 @@ class DeleteVolumeType(command.Command):
class ListVolumeType(command.Lister):
- """List volume types"""
+ _description = _("List volume types")
def get_parser(self, prog_name):
parser = super(ListVolumeType, self).get_parser(prog_name)
@@ -160,9 +249,16 @@ class ListVolumeType(command.Lister):
'--long',
action='store_true',
default=False,
- help=_('List additional fields in output'))
+ help=_('List additional fields in output')
+ )
public_group = parser.add_mutually_exclusive_group()
public_group.add_argument(
+ "--default",
+ action='store_true',
+ default=False,
+ help=_('List the default volume type')
+ )
+ public_group.add_argument(
"--public",
action="store_true",
help=_("List only public types")
@@ -172,32 +268,73 @@ class ListVolumeType(command.Lister):
action="store_true",
help=_("List only private types (admin only)")
)
+ parser.add_argument(
+ "--encryption-type",
+ action="store_true",
+ help=_("Display encryption information for each volume type "
+ "(admin only)"),
+ )
return parser
def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
if parsed_args.long:
- columns = ['ID', 'Name', 'Description', 'Extra Specs']
- column_headers = ['ID', 'Name', 'Description', 'Properties']
+ columns = ['ID', 'Name', 'Is Public', 'Description', 'Extra Specs']
+ column_headers = [
+ 'ID', 'Name', 'Is Public', 'Description', 'Properties']
+ else:
+ columns = ['ID', 'Name', 'Is Public']
+ column_headers = ['ID', 'Name', 'Is Public']
+ if parsed_args.default:
+ data = [volume_client.volume_types.default()]
else:
- columns = ['ID', 'Name']
- column_headers = columns
+ is_public = None
+ if parsed_args.public:
+ is_public = True
+ if parsed_args.private:
+ is_public = False
+ data = volume_client.volume_types.list(
+ is_public=is_public)
+
+ def _format_encryption_info(type_id, encryption_data=None):
+ encryption_data = encryption
+ encryption_info = '-'
+ if type_id in encryption_data.keys():
+ encryption_info = encryption_data[type_id]
+ return encryption_info
+
+ if parsed_args.encryption_type:
+ encryption = {}
+ for d in volume_client.volume_encryption_types.list():
+ volume_type_id = d._info['volume_type_id']
+ # remove some redundant information
+ del_key = [
+ 'deleted',
+ 'created_at',
+ 'updated_at',
+ 'deleted_at',
+ 'volume_type_id'
+ ]
+ for key in del_key:
+ d._info.pop(key, None)
+ # save the encryption information with their volume type ID
+ encryption[volume_type_id] = utils.format_dict(d._info)
+ # We need to get volume type ID, then show encryption
+ # information according to the ID, so use "id" to keep
+ # difference to the real "ID" column.
+ columns += ['id']
+ column_headers += ['Encryption']
- is_public = None
- if parsed_args.public:
- is_public = True
- if parsed_args.private:
- is_public = False
- data = self.app.client_manager.volume.volume_types.list(
- is_public=is_public)
return (column_headers,
(utils.get_item_properties(
s, columns,
- formatters={'Extra Specs': utils.format_dict},
+ formatters={'Extra Specs': utils.format_dict,
+ 'id': _format_encryption_info},
) for s in data))
class SetVolumeType(command.Command):
- """Set volume type properties"""
+ _description = _("Set volume type properties")
def get_parser(self, prog_name):
parser = super(SetVolumeType, self).get_parser(prog_name)
@@ -213,7 +350,7 @@ class SetVolumeType(command.Command):
)
parser.add_argument(
'--description',
- metavar='<name>',
+ metavar='<description>',
help=_('Set volume type description'),
)
parser.add_argument(
@@ -230,7 +367,43 @@ class SetVolumeType(command.Command):
'(admin only)'),
)
identity_common.add_project_domain_option_to_parser(parser)
-
+ # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
+ parser.add_argument(
+ '--encryption-provider',
+ metavar='<provider>',
+ help=_('Set the class that provides encryption support for '
+ 'this volume type (e.g "LuksEncryptor") (admin only) '
+ '(This option is required when setting encryption type '
+ 'of a volume for the first time. Consider using other '
+ 'encryption options such as: "--encryption-cipher", '
+ '"--encryption-key-size" and '
+ '"--encryption-control-location")'),
+ )
+ parser.add_argument(
+ '--encryption-cipher',
+ metavar='<cipher>',
+ help=_('Set the encryption algorithm or mode for this '
+ 'volume type (e.g "aes-xts-plain64") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-key-size',
+ metavar='<key-size>',
+ type=int,
+ help=_('Set the size of the encryption key of this '
+ 'volume type (e.g "128" or "256") (admin only)'),
+ )
+ parser.add_argument(
+ '--encryption-control-location',
+ metavar='<control-location>',
+ choices=['front-end', 'back-end'],
+ help=_('Set the notional service where the encryption is '
+ 'performed ("front-end" or "back-end") (admin only) '
+ '(The default value for this option is "front-end" '
+ 'when setting encryption type of a volume for the '
+ 'first time. Consider using other encryption options '
+ 'such as: "--encryption-cipher", "--encryption-key-size" '
+ 'and "--encryption-provider")'),
+ )
return parser
def take_action(self, parsed_args):
@@ -239,7 +412,6 @@ class SetVolumeType(command.Command):
volume_type = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type)
-
result = 0
kwargs = {}
if parsed_args.name:
@@ -280,13 +452,24 @@ class SetVolumeType(command.Command):
"project: %s"), e)
result += 1
+ if (parsed_args.encryption_provider or
+ parsed_args.encryption_cipher or
+ parsed_args.encryption_key_size or
+ parsed_args.encryption_control_location):
+ try:
+ _set_encryption_type(volume_client, volume_type, parsed_args)
+ except Exception as e:
+ LOG.error(_("Failed to set encryption information for this "
+ "volume type: %s"), e)
+ result += 1
+
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
" the operations failed"))
class ShowVolumeType(command.ShowOne):
- """Display volume type details"""
+ _description = _("Display volume type details")
def get_parser(self, prog_name):
parser = super(ShowVolumeType, self).get_parser(prog_name)
@@ -295,6 +478,12 @@ class ShowVolumeType(command.ShowOne):
metavar="<volume-type>",
help=_("Volume type to display (name or ID)")
)
+ parser.add_argument(
+ "--encryption-type",
+ action="store_true",
+ help=_("Display encryption information of this volume type "
+ "(admin only)"),
+ )
return parser
def take_action(self, parsed_args):
@@ -319,12 +508,23 @@ class ShowVolumeType(command.ShowOne):
'%(type)s: %(e)s')
LOG.error(msg % {'type': volume_type.id, 'e': e})
volume_type._info.update({'access_project_ids': access_project_ids})
+ if parsed_args.encryption_type:
+ # show encryption type information for this volume type
+ try:
+ encryption = volume_client.volume_encryption_types.get(
+ volume_type.id)
+ encryption._info.pop("volume_type_id", None)
+ volume_type._info.update(
+ {'encryption': utils.format_dict(encryption._info)})
+ except Exception as e:
+ LOG.error(_("Failed to display the encryption information "
+ "of this volume type: %s"), e)
volume_type._info.pop("os-volume-type-access:is_public", None)
return zip(*sorted(six.iteritems(volume_type._info)))
class UnsetVolumeType(command.Command):
- """Unset volume type properties"""
+ _description = _("Unset volume type properties")
def get_parser(self, prog_name):
parser = super(UnsetVolumeType, self).get_parser(prog_name)
@@ -347,7 +547,12 @@ class UnsetVolumeType(command.Command):
' (admin only)'),
)
identity_common.add_project_domain_option_to_parser(parser)
-
+ parser.add_argument(
+ "--encryption-type",
+ action="store_true",
+ help=_("Remove the encryption type for this volume type "
+ "(admin only)"),
+ )
return parser
def take_action(self, parsed_args):
@@ -381,6 +586,13 @@ class UnsetVolumeType(command.Command):
LOG.error(_("Failed to remove volume type access from "
"project: %s"), e)
result += 1
+ if parsed_args.encryption_type:
+ try:
+ volume_client.volume_encryption_types.delete(volume_type)
+ except Exception as e:
+ LOG.error(_("Failed to remove the encryption type for this "
+ "volume type: %s"), e)
+ result += 1
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
diff --git a/openstackclient/volume/v3/__init__.py b/openstackclient/volume/v3/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/openstackclient/volume/v3/__init__.py