summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/compute/client.py45
-rw-r--r--openstackclient/network/common.py37
-rw-r--r--openstackclient/network/v2/address_scope.py2
-rw-r--r--openstackclient/network/v2/network.py61
-rw-r--r--openstackclient/network/v2/port.py2
-rw-r--r--openstackclient/network/v2/router.py22
-rw-r--r--openstackclient/network/v2/security_group_rule.py188
-rw-r--r--openstackclient/network/v2/subnet.py20
-rw-r--r--openstackclient/network/v2/subnet_pool.py2
-rw-r--r--openstackclient/tests/compute/v2/fakes.py19
-rw-r--r--openstackclient/tests/image/v2/test_image.py91
-rw-r--r--openstackclient/tests/network/v2/fakes.py10
-rw-r--r--openstackclient/tests/network/v2/test_network.py169
-rw-r--r--openstackclient/tests/network/v2/test_router.py36
-rw-r--r--openstackclient/tests/network/v2/test_security_group_rule.py242
-rw-r--r--openstackclient/tests/network/v2/test_subnet.py16
-rw-r--r--openstackclient/tests/volume/v2/fakes.py54
-rw-r--r--openstackclient/tests/volume/v2/test_backup.py252
-rw-r--r--openstackclient/tests/volume/v2/test_snapshot.py20
-rw-r--r--openstackclient/volume/v2/snapshot.py14
20 files changed, 1018 insertions, 284 deletions
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 1481ed65..82f09cec 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -41,8 +41,18 @@ def make_client(instance):
version = _compute_api_version
else:
version = instance._api_version[API_NAME]
+ from novaclient import api_versions
+ # convert to APIVersion object
+ version = api_versions.get_api_version(version)
+
+ if version.is_latest():
+ import novaclient
+ # NOTE(RuiChen): executing version discovery make sense, but that need
+ # an initialized REST client, it's not available now,
+ # fallback to use the max version of novaclient side.
+ version = novaclient.API_MAX_VERSION
- LOG.debug('Instantiating compute client for V%s', version)
+ LOG.debug('Instantiating compute client for %s', version)
# Set client http_log_debug to True if verbosity level is high enough
http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
@@ -91,30 +101,27 @@ def check_api_version(check_version):
"""
# Defer client imports until we actually need them
- try:
- from novaclient import api_versions
- except ImportError:
- # Retain previous behaviour
- return False
-
import novaclient
+ from novaclient import api_versions
global _compute_api_version
- # Copy some logic from novaclient 2.27.0 for basic version detection
+ # Copy some logic from novaclient 3.3.0 for basic version detection
# NOTE(dtroyer): This is only enough to resume operations using API
# version 2.0 or any valid version supplied by the user.
_compute_api_version = api_versions.get_api_version(check_version)
- if _compute_api_version > api_versions.APIVersion("2.0"):
- if not _compute_api_version.matches(
- novaclient.API_MIN_VERSION,
- novaclient.API_MAX_VERSION,
- ):
- raise exceptions.CommandError(
- "versions supported by client: %s - %s" % (
- novaclient.API_MIN_VERSION.get_string(),
- novaclient.API_MAX_VERSION.get_string(),
- ),
- )
+ # Bypass X.latest format microversion
+ if not _compute_api_version.is_latest():
+ if _compute_api_version > api_versions.APIVersion("2.0"):
+ if not _compute_api_version.matches(
+ novaclient.API_MIN_VERSION,
+ novaclient.API_MAX_VERSION,
+ ):
+ raise exceptions.CommandError(
+ "versions supported by client: %s - %s" % (
+ novaclient.API_MIN_VERSION.get_string(),
+ novaclient.API_MAX_VERSION.get_string(),
+ ),
+ )
return True
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index 1e2c4cce..a3047d84 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -15,6 +15,7 @@ import abc
import six
from openstackclient.common import command
+from openstackclient.common import exceptions
@six.add_metaclass(abc.ABCMeta)
@@ -69,6 +70,42 @@ class NetworkAndComputeCommand(command.Command):
@six.add_metaclass(abc.ABCMeta)
+class NetworkAndComputeDelete(NetworkAndComputeCommand):
+ """Network and Compute Delete
+
+ Delete class for commands that support implementation via
+ the network or compute endpoint. Such commands have different
+ implementations for take_action() and may even have different
+ arguments. This class supports bulk deletion, and error handling
+ following the rules in doc/source/command-errors.rst.
+ """
+
+ def take_action(self, parsed_args):
+ ret = 0
+ resources = getattr(parsed_args, self.resource, [])
+
+ for r in resources:
+ self.r = r
+ try:
+ if self.app.client_manager.is_network_endpoint_enabled():
+ self.take_action_network(self.app.client_manager.network,
+ parsed_args)
+ else:
+ self.take_action_compute(self.app.client_manager.compute,
+ parsed_args)
+ except Exception as e:
+ self.app.log.error("Failed to delete %s with name or ID "
+ "'%s': %s" % (self.resource, r, e))
+ ret += 1
+
+ if ret:
+ total = len(resources)
+ msg = "%s of %s %ss failed to delete." % (ret, total,
+ self.resource)
+ raise exceptions.CommandError(msg)
+
+
+@six.add_metaclass(abc.ABCMeta)
class NetworkAndComputeLister(command.Lister):
"""Network and Compute Lister
diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py
index fac0849f..614900c9 100644
--- a/openstackclient/network/v2/address_scope.py
+++ b/openstackclient/network/v2/address_scope.py
@@ -185,7 +185,7 @@ class SetAddressScope(command.Command):
if parsed_args.no_share:
attrs['shared'] = False
if attrs == {}:
- msg = "Nothing specified to be set."
+ msg = _("Nothing specified to be set.")
raise exceptions.CommandError(msg)
client.update_address_scope(obj, **attrs)
diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py
index 4b77971a..bf01e2ec 100644
--- a/openstackclient/network/v2/network.py
+++ b/openstackclient/network/v2/network.py
@@ -32,7 +32,7 @@ def _format_router_external(item):
_formatters = {
'subnets': utils.format_list,
'admin_state_up': _format_admin_state,
- 'router_external': _format_router_external,
+ 'router:external': _format_router_external,
'availability_zones': utils.format_list,
'availability_zone_hints': utils.format_list,
}
@@ -43,9 +43,6 @@ def _get_columns(item):
if 'tenant_id' in columns:
columns.remove('tenant_id')
columns.append('project_id')
- if 'router:external' in columns:
- columns.remove('router:external')
- columns.append('router_external')
return tuple(sorted(columns))
@@ -93,11 +90,17 @@ 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
+ # Update VLAN Transparency for networks
+ if parsed_args.transparent_vlan:
+ attrs['vlan_transparent'] = True
+ if parsed_args.no_transparent_vlan:
+ attrs['vlan_transparent'] = False
return attrs
-def _add_provider_network_options(parser):
- # Add provider network options
+def _add_additional_network_options(parser):
+ # Add additional network options
+
parser.add_argument(
'--provider-network-type',
metavar='<provider-network-type>',
@@ -119,6 +122,16 @@ def _add_provider_network_options(parser):
help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN "
"networks"))
+ vlan_transparent_grp = parser.add_mutually_exclusive_group()
+ vlan_transparent_grp.add_argument(
+ '--transparent-vlan',
+ action='store_true',
+ help=_("Make the network VLAN transparent"))
+ vlan_transparent_grp.add_argument(
+ '--no-transparent-vlan',
+ action='store_true',
+ help=_("Do not make the network VLAN transparent"))
+
def _get_attrs_compute(client_manager, parsed_args):
attrs = {}
@@ -206,10 +219,10 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
default_router_grp.add_argument(
'--no-default',
action='store_true',
- help=_("Do not use the network as the default external network. "
+ help=_("Do not use the network as the default external network "
"(default)")
)
- _add_provider_network_options(parser)
+ _add_additional_network_options(parser)
return parser
def update_parser_compute(self, parser):
@@ -235,30 +248,30 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
return (columns, data)
-class DeleteNetwork(common.NetworkAndComputeCommand):
+class DeleteNetwork(common.NetworkAndComputeDelete):
"""Delete network(s)"""
+ # Used by base class to find resources in parsed_args.
+ resource = 'network'
+ r = None
+
def update_parser_common(self, parser):
parser.add_argument(
'network',
metavar="<network>",
nargs="+",
- help=("Network(s) to delete (name or ID)")
+ help=_("Network(s) to delete (name or ID)")
)
+
return parser
def take_action_network(self, client, parsed_args):
- for network in parsed_args.network:
- obj = client.find_network(network)
- client.delete_network(obj)
+ obj = client.find_network(self.r, ignore_missing=False)
+ client.delete_network(obj)
def take_action_compute(self, client, parsed_args):
- for network in parsed_args.network:
- network = utils.find_resource(
- client.networks,
- network,
- )
- client.networks.delete(network.id)
+ network = utils.find_resource(client.networks, self.r)
+ client.networks.delete(network.id)
class ListNetwork(common.NetworkAndComputeLister):
@@ -269,13 +282,13 @@ class ListNetwork(common.NetworkAndComputeLister):
'--external',
action='store_true',
default=False,
- help='List external networks',
+ help=_("List external networks")
)
parser.add_argument(
'--long',
action='store_true',
default=False,
- help='List additional fields in output',
+ help=_("List additional fields in output")
)
return parser
@@ -290,7 +303,7 @@ class ListNetwork(common.NetworkAndComputeLister):
'shared',
'subnets',
'provider_network_type',
- 'router_external',
+ 'router:external',
'availability_zones',
)
column_headers = (
@@ -413,7 +426,7 @@ class SetNetwork(command.Command):
action='store_true',
help=_("Do not use the network as the default external network")
)
- _add_provider_network_options(parser)
+ _add_additional_network_options(parser)
return parser
def take_action(self, parsed_args):
@@ -422,7 +435,7 @@ class SetNetwork(command.Command):
attrs = _get_attrs(self.app.client_manager, parsed_args)
if attrs == {}:
- msg = "Nothing specified to be set"
+ msg = _("Nothing specified to be set")
raise exceptions.CommandError(msg)
client.update_network(obj, **attrs)
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 3cc76a6e..ca02281f 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -414,7 +414,7 @@ class SetPort(command.Command):
attrs['fixed_ips'] = []
if attrs == {}:
- msg = "Nothing specified to be set"
+ msg = _("Nothing specified to be set")
raise exceptions.CommandError(msg)
client.update_port(obj, **attrs)
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index a32ab5ea..a2f0df1d 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -13,7 +13,9 @@
"""Router action implementations"""
+import argparse
import json
+import logging
from openstackclient.common import command
from openstackclient.common import exceptions
@@ -23,6 +25,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _format_admin_state(state):
return 'UP' if state else 'DOWN'
@@ -379,10 +384,15 @@ class SetRouter(command.Command):
"(repeat option to set multiple routes)")
)
routes_group.add_argument(
- '--clear-routes',
+ '--no-route',
action='store_true',
help=_("Clear routes associated with the router")
)
+ 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
@@ -401,8 +411,14 @@ class SetRouter(command.Command):
attrs = _get_attrs(self.app.client_manager, parsed_args)
# Get the route attributes.
- if parsed_args.clear_routes:
+ if parsed_args.no_route:
+ attrs['routes'] = []
+ elif parsed_args.clear_routes:
attrs['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.
@@ -411,7 +427,7 @@ class SetRouter(command.Command):
attrs['routes'] = obj.routes + parsed_args.routes
if attrs == {}:
- msg = "Nothing specified to be set"
+ msg = _("Nothing specified to be set")
raise exceptions.CommandError(msg)
client.update_router(obj, **attrs)
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index 5b22a0dd..5abe9b9d 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -36,9 +36,21 @@ def _format_security_group_rule_show(obj):
def _format_network_port_range(rule):
+ # Display port range or ICMP type and code. For example:
+ # - ICMP type: 'type=3'
+ # - ICMP type and code: 'type=3:code=0'
+ # - ICMP code: Not supported
+ # - Matching port range: '443:443'
+ # - Different port range: '22:24'
+ # - Single port: '80:80'
+ # - No port range: ''
port_range = ''
- if (rule.protocol != 'icmp' and
- (rule.port_range_min or rule.port_range_max)):
+ 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:
@@ -61,6 +73,17 @@ def _convert_to_lowercase(string):
return string.lower()
+def _is_icmp_protocol(protocol):
+ # NOTE(rtheis): Neutron has deprecated protocol icmpv6.
+ # However, while the OSC CLI doesn't document the protocol,
+ # the code must still handle it. In addition, handle both
+ # protocol names and numbers.
+ if protocol in ['icmp', 'icmpv6', 'ipv6-icmp', '1', '58']:
+ return True
+ else:
+ return False
+
+
class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
"""Create a new security group rule"""
@@ -68,19 +91,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
parser.add_argument(
'group',
metavar='<group>',
- help='Create rule in this security group (name or ID)',
- )
- # TODO(rtheis): Add support for additional protocols for network.
- # Until then, continue enforcing the compute choices. When additional
- # protocols are added, the default ethertype must be determined
- # based on the protocol.
- parser.add_argument(
- "--proto",
- metavar="<proto>",
- default="tcp",
- choices=['icmp', 'tcp', 'udp'],
- type=_convert_to_lowercase,
- help=_("IP protocol (icmp, tcp, udp; default: tcp)")
+ help=_("Create rule in this security group (name or ID)")
)
source_group = parser.add_mutually_exclusive_group()
source_group.add_argument(
@@ -94,17 +105,49 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
metavar="<group>",
help=_("Source security group (name or ID)")
)
- parser.add_argument(
- "--dst-port",
- metavar="<port-range>",
- default=(0, 0),
- action=parseractions.RangeAction,
- help=_("Destination port, may be a single port or port range: "
- "137:139 (only required for IP protocols tcp and udp)")
- )
return parser
def update_parser_network(self, parser):
+ parser.add_argument(
+ '--dst-port',
+ metavar='<port-range>',
+ action=parseractions.RangeAction,
+ help=_("Destination port, may be a single port or a starting and "
+ "ending port range: 137:139. Required for IP protocols TCP "
+ "and UDP. Ignored for ICMP IP protocols.")
+ )
+ parser.add_argument(
+ '--icmp-type',
+ metavar='<icmp-type>',
+ type=int,
+ help=_("ICMP type for ICMP IP protocols")
+ )
+ parser.add_argument(
+ '--icmp-code',
+ metavar='<icmp-code>',
+ type=int,
+ help=_("ICMP code for ICMP IP protocols")
+ )
+ # NOTE(rtheis): Support either protocol option name for now.
+ # However, consider deprecating and then removing --proto in
+ # a future release.
+ protocol_group = parser.add_mutually_exclusive_group()
+ protocol_group.add_argument(
+ '--protocol',
+ metavar='<protocol>',
+ type=_convert_to_lowercase,
+ help=_("IP protocol (ah, dccp, 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]; "
+ "default: tcp)")
+ )
+ protocol_group.add_argument(
+ '--proto',
+ metavar='<proto>',
+ type=_convert_to_lowercase,
+ help=argparse.SUPPRESS
+ )
direction_group = parser.add_mutually_exclusive_group()
direction_group.add_argument(
'--ingress',
@@ -120,7 +163,8 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
'--ethertype',
metavar='<ethertype>',
choices=['IPv4', 'IPv6'],
- help=_("Ethertype of network traffic (IPv4, IPv6; default: IPv4)")
+ help=_("Ethertype of network traffic "
+ "(IPv4, IPv6; default: based on IP protocol)")
)
parser.add_argument(
'--project',
@@ -130,6 +174,55 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
identity_common.add_project_domain_option_to_parser(parser)
return parser
+ def update_parser_compute(self, parser):
+ parser.add_argument(
+ '--dst-port',
+ metavar='<port-range>',
+ default=(0, 0),
+ action=parseractions.RangeAction,
+ help=_("Destination port, may be a single port or a starting and "
+ "ending port range: 137:139. Required for IP protocols TCP "
+ "and UDP. Ignored for ICMP IP protocols.")
+ )
+ # NOTE(rtheis): Support either protocol option name for now.
+ # However, consider deprecating and then removing --proto in
+ # a future release.
+ protocol_group = parser.add_mutually_exclusive_group()
+ protocol_group.add_argument(
+ '--protocol',
+ metavar='<protocol>',
+ choices=['icmp', 'tcp', 'udp'],
+ type=_convert_to_lowercase,
+ help=_("IP protocol (icmp, tcp, udp; default: tcp)")
+ )
+ protocol_group.add_argument(
+ '--proto',
+ metavar='<proto>',
+ choices=['icmp', 'tcp', 'udp'],
+ type=_convert_to_lowercase,
+ help=argparse.SUPPRESS
+ )
+ return parser
+
+ def _get_protocol(self, parsed_args):
+ protocol = 'tcp'
+ if parsed_args.protocol is not None:
+ protocol = parsed_args.protocol
+ if parsed_args.proto is not None:
+ protocol = parsed_args.proto
+ return protocol
+
+ def _is_ipv6_protocol(self, protocol):
+ # NOTE(rtheis): Neutron has deprecated protocol icmpv6.
+ # However, while the OSC CLI doesn't document the protocol,
+ # the code must still handle it. In addition, handle both
+ # protocol names and numbers.
+ if (protocol.startswith('ipv6-') or
+ protocol in ['icmpv6', '41', '43', '44', '58', '59', '60']):
+ return True
+ else:
+ return False
+
def take_action_network(self, client, parsed_args):
# Get the security group ID to hold the rule.
security_group_id = client.find_security_group(
@@ -139,24 +232,50 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
# Build the create attributes.
attrs = {}
+ attrs['protocol'] = self._get_protocol(parsed_args)
+
# NOTE(rtheis): A direction must be specified and ingress
# is the default.
if parsed_args.ingress or not parsed_args.egress:
attrs['direction'] = 'ingress'
if parsed_args.egress:
attrs['direction'] = 'egress'
+
+ # NOTE(rtheis): Use ethertype specified else default based
+ # on IP protocol.
if parsed_args.ethertype:
attrs['ethertype'] = parsed_args.ethertype
+ elif self._is_ipv6_protocol(attrs['protocol']):
+ attrs['ethertype'] = 'IPv6'
else:
- # NOTE(rtheis): Default based on protocol is IPv4 for now.
- # Once IPv6 protocols are added, this will need to be updated.
attrs['ethertype'] = 'IPv4'
- # TODO(rtheis): Add port range support (type and code) for icmp
- # protocol. Until then, continue ignoring the port range.
- if parsed_args.proto != 'icmp':
+
+ # NOTE(rtheis): Validate the port range and ICMP type and code.
+ # It would be ideal if argparse could do this.
+ if parsed_args.dst_port and (parsed_args.icmp_type or
+ parsed_args.icmp_code):
+ msg = _('Argument --dst-port not allowed with arguments '
+ '--icmp-type and --icmp-code')
+ raise exceptions.CommandError(msg)
+ if parsed_args.icmp_type is None and parsed_args.icmp_code is not None:
+ msg = _('Argument --icmp-type required with argument --icmp-code')
+ raise exceptions.CommandError(msg)
+ is_icmp_protocol = _is_icmp_protocol(attrs['protocol'])
+ if not is_icmp_protocol and (parsed_args.icmp_type or
+ parsed_args.icmp_code):
+ msg = _('ICMP IP protocol required with arguments '
+ '--icmp-type and --icmp-code')
+ raise exceptions.CommandError(msg)
+ # NOTE(rtheis): For backwards compatibility, continue ignoring
+ # the destination port range when an ICMP IP protocol is specified.
+ if parsed_args.dst_port and not is_icmp_protocol:
attrs['port_range_min'] = parsed_args.dst_port[0]
attrs['port_range_max'] = parsed_args.dst_port[1]
- attrs['protocol'] = parsed_args.proto
+ if parsed_args.icmp_type:
+ attrs['port_range_min'] = parsed_args.icmp_type
+ if parsed_args.icmp_code:
+ attrs['port_range_max'] = parsed_args.icmp_code
+
if parsed_args.src_group is not None:
attrs['remote_group_id'] = client.find_security_group(
parsed_args.src_group,
@@ -187,7 +306,8 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
client.security_groups,
parsed_args.group,
)
- if parsed_args.proto == 'icmp':
+ protocol = self._get_protocol(parsed_args)
+ if protocol == 'icmp':
from_port, to_port = -1, -1
else:
from_port, to_port = parsed_args.dst_port
@@ -203,7 +323,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
src_ip = '0.0.0.0/0'
obj = client.security_group_rules.create(
group.id,
- parsed_args.proto,
+ protocol,
from_port,
to_port,
src_ip,
@@ -399,8 +519,8 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne):
break
if obj is None:
- msg = "Could not find security group rule " \
- "with ID %s" % parsed_args.rule
+ msg = _("Could not find security group rule with ID ") + \
+ parsed_args.rule
raise exceptions.CommandError(msg)
# NOTE(rtheis): Format security group rule
diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py
index fb441cbf..e7e1be99 100644
--- a/openstackclient/network/v2/subnet.py
+++ b/openstackclient/network/v2/subnet.py
@@ -141,9 +141,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
gateway = parsed_args.gateway.lower()
if not is_create and gateway == 'auto':
- raise exceptions.CommandError("Auto option is not available"
- " for Subnet Set. Valid options are"
- " <ip-address> or none")
+ msg = _("Auto option is not available for Subnet Set. "
+ "Valid options are <ip-address> or none")
+ raise exceptions.CommandError(msg)
elif gateway != 'auto':
if gateway == 'none':
attrs['gateway_ip'] = None
@@ -296,10 +296,22 @@ class ListSubnet(command.Lister):
default=False,
help=_("List additional fields in output")
)
+ parser.add_argument(
+ '--ip-version',
+ type=int,
+ choices=[4, 6],
+ metavar='<ip-version>',
+ dest='ip_version',
+ help=_("List only subnets of given IP version in output"
+ "Allowed values for IP version are 4 and 6."),
+ )
return parser
def take_action(self, parsed_args):
- data = self.app.client_manager.network.subnets()
+ filters = {}
+ if parsed_args.ip_version:
+ filters['ip_version'] = parsed_args.ip_version
+ data = self.app.client_manager.network.subnets(**filters)
headers = ('ID', 'Name', 'Network', 'Subnet')
columns = ('id', 'name', 'network_id', 'cidr')
diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py
index f1174dda..a1a94426 100644
--- a/openstackclient/network/v2/subnet_pool.py
+++ b/openstackclient/network/v2/subnet_pool.py
@@ -287,7 +287,7 @@ class SetSubnetPool(command.Command):
attrs = _get_attrs(self.app.client_manager, parsed_args)
if attrs == {}:
- msg = "Nothing specified to be set"
+ msg = _("Nothing specified to be set")
raise exceptions.CommandError(msg)
# Existing prefixes must be a subset of the new prefixes.
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index b3f3fb49..70fc386f 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -859,6 +859,25 @@ class FakeNetwork(object):
return networks
+ @staticmethod
+ def get_networks(networks=None, count=2):
+ """Get an iterable MagicMock object with a list of faked networks.
+
+ If networks list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List networks:
+ A list of FakeResource objects faking networks
+ :param int count:
+ The number of networks to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ networks
+ """
+ if networks is None:
+ networks = FakeNetwork.create_networks(count=count)
+ return mock.Mock(side_effect=networks)
+
class FakeHost(object):
"""Fake one host."""
diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py
index 0248f30b..33f25331 100644
--- a/openstackclient/tests/image/v2/test_image.py
+++ b/openstackclient/tests/image/v2/test_image.py
@@ -20,6 +20,7 @@ import warlock
from glanceclient.v2 import schemas
from openstackclient.common import exceptions
+from openstackclient.common import utils as common_utils
from openstackclient.image.v2 import image
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -341,28 +342,31 @@ class TestImageCreate(TestImage):
class TestAddProjectToImage(TestImage):
+ _image = image_fakes.FakeImage.create_one_image()
+
columns = (
'image_id',
'member_id',
'status',
)
+
datalist = (
- image_fakes.image_id,
+ _image.id,
identity_fakes.project_id,
- image_fakes.member_status,
+ image_fakes.member_status
)
def setUp(self):
super(TestAddProjectToImage, 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
+
+ # Update the image_id in the MEMBER dict
+ self.new_member = copy.deepcopy(image_fakes.MEMBER)
+ self.new_member['image_id'] = self._image.id
self.image_members_mock.create.return_value = fakes.FakeModel(
- copy.deepcopy(image_fakes.MEMBER),
+ self.new_member,
)
self.project_mock.get.return_value = fakes.FakeResource(
None,
@@ -379,11 +383,11 @@ class TestAddProjectToImage(TestImage):
def test_add_project_to_image_no_option(self):
arglist = [
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id,
]
verifylist = [
- ('image', image_fakes.image_id),
+ ('image', self._image.id),
('project', identity_fakes.project_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -393,20 +397,21 @@ class TestAddProjectToImage(TestImage):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
self.image_members_mock.create.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id
)
+
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
def test_add_project_to_image_with_option(self):
arglist = [
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id,
'--project-domain', identity_fakes.domain_id,
]
verifylist = [
- ('image', image_fakes.image_id),
+ ('image', self._image.id),
('project', identity_fakes.project_id),
('project_domain', identity_fakes.domain_id),
]
@@ -417,7 +422,7 @@ class TestAddProjectToImage(TestImage):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
self.image_members_mock.create.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id
)
self.assertEqual(self.columns, columns)
@@ -468,25 +473,26 @@ class TestImageDelete(TestImage):
class TestImageList(TestImage):
+ _image = image_fakes.FakeImage.create_one_image()
+
columns = (
'ID',
'Name',
'Status',
)
+
datalist = (
- (
- image_fakes.image_id,
- image_fakes.image_name,
- '',
- ),
- )
+ _image.id,
+ _image.name,
+ '',
+ ),
def setUp(self):
super(TestImageList, self).setUp()
self.api_mock = mock.Mock()
self.api_mock.image_list.side_effect = [
- [copy.deepcopy(image_fakes.IMAGE)], [],
+ [image_fakes.FakeImage.get_image_info(self._image)], [],
]
self.app.client_manager.image.api = self.api_mock
@@ -611,23 +617,24 @@ class TestImageList(TestImage):
self.assertEqual(collist, columns)
datalist = ((
- image_fakes.image_id,
- image_fakes.image_name,
+ self._image.id,
+ self._image.name,
'',
'',
'',
'',
- 'public',
- False,
- image_fakes.image_owner,
- '',
+ self._image.visibility,
+ self._image.protected,
+ self._image.owner,
+ common_utils.format_list(self._image.tags),
), )
self.assertEqual(datalist, tuple(data))
@mock.patch('openstackclient.api.utils.simple_filter')
def test_image_list_property_option(self, sf_mock):
sf_mock.return_value = [
- copy.deepcopy(image_fakes.IMAGE),
+ copy.deepcopy(
+ image_fakes.FakeImage.get_image_info(self._image)),
]
arglist = [
@@ -644,7 +651,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with()
sf_mock.assert_called_with(
- [image_fakes.IMAGE],
+ [image_fakes.FakeImage.get_image_info(self._image)],
attr='a',
value='1',
property_field='properties',
@@ -656,7 +663,8 @@ class TestImageList(TestImage):
@mock.patch('openstackclient.common.utils.sort_items')
def test_image_list_sort_option(self, si_mock):
si_mock.return_value = [
- copy.deepcopy(image_fakes.IMAGE)
+ copy.deepcopy(
+ image_fakes.FakeImage.get_image_info(self._image))
]
arglist = ['--sort', 'name:asc']
@@ -669,10 +677,9 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with()
si_mock.assert_called_with(
- [image_fakes.IMAGE],
+ [image_fakes.FakeImage.get_image_info(self._image)],
'name:asc'
)
-
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
@@ -720,12 +727,10 @@ class TestRemoveProjectImage(TestImage):
def setUp(self):
super(TestRemoveProjectImage, self).setUp()
+ self._image = image_fakes.FakeImage.create_one_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.get.return_value = self._image
+
self.project_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.PROJECT),
@@ -742,11 +747,11 @@ class TestRemoveProjectImage(TestImage):
def test_remove_project_image_no_options(self):
arglist = [
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id,
]
verifylist = [
- ('image', image_fakes.image_id),
+ ('image', self._image.id),
('project', identity_fakes.project_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -754,19 +759,19 @@ class TestRemoveProjectImage(TestImage):
result = self.cmd.take_action(parsed_args)
self.image_members_mock.delete.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id,
)
self.assertIsNone(result)
def test_remove_project_image_with_options(self):
arglist = [
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id,
'--project-domain', identity_fakes.domain_id,
]
verifylist = [
- ('image', image_fakes.image_id),
+ ('image', self._image.id),
('project', identity_fakes.project_id),
('project_domain', identity_fakes.domain_id),
]
@@ -775,7 +780,7 @@ class TestRemoveProjectImage(TestImage):
result = self.cmd.take_action(parsed_args)
self.image_members_mock.delete.assert_called_with(
- image_fakes.image_id,
+ self._image.id,
identity_fakes.project_id,
)
self.assertIsNone(result)
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index 7c4604bd..84ede381 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -184,8 +184,7 @@ class FakeNetwork(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object, with id, name, admin_state_up,
- router_external, status, subnets, tenant_id
+ A FakeResource object, with id, name, etc.
"""
attrs = attrs or {}
@@ -199,7 +198,7 @@ class FakeNetwork(object):
'shared': False,
'subnets': ['a', 'b'],
'provider_network_type': 'vlan',
- 'router_external': True,
+ 'router:external': True,
'availability_zones': [],
'availability_zone_hints': [],
'is_default': False,
@@ -213,6 +212,7 @@ 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']
return network
@@ -496,8 +496,8 @@ class FakeSecurityGroupRule(object):
'direction': 'ingress',
'ethertype': 'IPv4',
'id': 'security-group-rule-id-' + uuid.uuid4().hex,
- 'port_range_max': 0,
- 'port_range_min': 0,
+ 'port_range_max': None,
+ 'port_range_min': None,
'protocol': 'tcp',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py
index 1269b0a1..ba810f16 100644
--- a/openstackclient/tests/network/v2/test_network.py
+++ b/openstackclient/tests/network/v2/test_network.py
@@ -14,6 +14,7 @@
import copy
import mock
+from mock import call
from openstackclient.common import exceptions
from openstackclient.common import utils
from openstackclient.network.v2 import network
@@ -55,7 +56,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'name',
'project_id',
'provider_network_type',
- 'router_external',
+ 'router:external',
'shared',
'status',
'subnets',
@@ -70,7 +71,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
_network.name,
_network.project_id,
_network.provider_network_type,
- network._format_router_external(_network.router_external),
+ network._format_router_external(_network.is_router_external),
_network.shared,
_network.status,
utils.format_list(_network.subnets),
@@ -148,6 +149,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
"--provider-network-type", "vlan",
"--provider-physical-network", "physnet1",
"--provider-segment", "400",
+ "--transparent-vlan",
self._network.name,
]
verifylist = [
@@ -161,6 +163,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
('provider_network_type', 'vlan'),
('physical_network', 'physnet1'),
('segmentation_id', '400'),
+ ('transparent_vlan', True),
('name', self._network.name),
]
@@ -178,6 +181,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1',
'provider:segmentation_id': '400',
+ 'vlan_transparent': True,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -223,7 +227,7 @@ class TestCreateNetworkIdentityV2(TestNetwork):
'name',
'project_id',
'provider_network_type',
- 'router_external',
+ 'router:external',
'shared',
'status',
'subnets',
@@ -238,7 +242,7 @@ class TestCreateNetworkIdentityV2(TestNetwork):
_network.name,
_network.project_id,
_network.provider_network_type,
- network._format_router_external(_network.router_external),
+ network._format_router_external(_network.is_router_external),
_network.shared,
_network.status,
utils.format_list(_network.subnets),
@@ -320,33 +324,88 @@ class TestCreateNetworkIdentityV2(TestNetwork):
class TestDeleteNetwork(TestNetwork):
- # The network to delete.
- _network = network_fakes.FakeNetwork.create_one_network()
-
def setUp(self):
super(TestDeleteNetwork, self).setUp()
+ # The networks to delete
+ self._networks = network_fakes.FakeNetwork.create_networks(count=3)
+
self.network.delete_network = mock.Mock(return_value=None)
- self.network.find_network = mock.Mock(return_value=self._network)
+ self.network.find_network = network_fakes.FakeNetwork.get_networks(
+ networks=self._networks)
# Get the command object to test
self.cmd = network.DeleteNetwork(self.app, self.namespace)
- def test_delete(self):
+ def test_delete_one_network(self):
arglist = [
- self._network.name,
+ self._networks[0].name,
]
verifylist = [
- ('network', [self._network.name]),
+ ('network', [self._networks[0].name]),
]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.delete_network.assert_called_once_with(self._networks[0])
+ self.assertIsNone(result)
+
+ def test_delete_multiple_networks(self):
+ arglist = []
+ for n in self._networks:
+ arglist.append(n.id)
+ verifylist = [
+ ('network', arglist),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
result = self.cmd.take_action(parsed_args)
- self.network.delete_network.assert_called_once_with(self._network)
+ calls = []
+ for n in self._networks:
+ calls.append(call(n))
+ self.network.delete_network.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_delete_multiple_networks_exception(self):
+ arglist = [
+ self._networks[0].id,
+ 'xxxx-yyyy-zzzz',
+ self._networks[1].id,
+ ]
+ verifylist = [
+ ('network', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # Fake exception in find_network()
+ ret_find = [
+ self._networks[0],
+ exceptions.NotFound('404'),
+ self._networks[1],
+ ]
+ self.network.find_network = mock.Mock(side_effect=ret_find)
+
+ # Fake exception in delete_network()
+ ret_delete = [
+ None,
+ exceptions.NotFound('404'),
+ ]
+ self.network.delete_network = mock.Mock(side_effect=ret_delete)
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ # The second call of find_network() should fail. So delete_network()
+ # was only called twice.
+ calls = [
+ call(self._networks[0]),
+ call(self._networks[1]),
+ ]
+ self.network.delete_network.assert_has_calls(calls)
+
class TestListNetwork(TestNetwork):
@@ -390,7 +449,7 @@ class TestListNetwork(TestNetwork):
net.shared,
utils.format_list(net.subnets),
net.provider_network_type,
- network._format_router_external(net.router_external),
+ network._format_router_external(net.is_router_external),
utils.format_list(net.availability_zones),
))
@@ -486,6 +545,7 @@ class TestSetNetwork(TestNetwork):
'--provider-network-type', 'vlan',
'--provider-physical-network', 'physnet1',
'--provider-segment', '400',
+ '--no-transparent-vlan',
]
verifylist = [
('network', self._network.name),
@@ -497,6 +557,7 @@ class TestSetNetwork(TestNetwork):
('provider_network_type', 'vlan'),
('physical_network', 'physnet1'),
('segmentation_id', '400'),
+ ('no_transparent_vlan', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -511,6 +572,7 @@ class TestSetNetwork(TestNetwork):
'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1',
'provider:segmentation_id': '400',
+ 'vlan_transparent': False,
}
self.network.update_network.assert_called_once_with(
self._network, **attrs)
@@ -565,7 +627,7 @@ class TestShowNetwork(TestNetwork):
'name',
'project_id',
'provider_network_type',
- 'router_external',
+ 'router:external',
'shared',
'status',
'subnets',
@@ -580,7 +642,7 @@ class TestShowNetwork(TestNetwork):
_network.name,
_network.project_id,
_network.provider_network_type,
- network._format_router_external(_network.router_external),
+ network._format_router_external(_network.is_router_external),
_network.shared,
_network.status,
utils.format_list(_network.subnets),
@@ -746,36 +808,97 @@ class TestCreateNetworkCompute(TestNetworkCompute):
class TestDeleteNetworkCompute(TestNetworkCompute):
- # The network to delete.
- _network = compute_fakes.FakeNetwork.create_one_network()
-
def setUp(self):
super(TestDeleteNetworkCompute, self).setUp()
self.app.client_manager.network_endpoint_enabled = False
+ # The networks to delete
+ self._networks = compute_fakes.FakeNetwork.create_networks(count=3)
+
self.compute.networks.delete.return_value = None
# Return value of utils.find_resource()
- self.compute.networks.get.return_value = self._network
+ self.compute.networks.get = \
+ compute_fakes.FakeNetwork.get_networks(networks=self._networks)
# Get the command object to test
self.cmd = network.DeleteNetwork(self.app, None)
- def test_network_delete(self):
+ def test_delete_one_network(self):
arglist = [
- self._network.label,
+ self._networks[0].label,
]
verifylist = [
- ('network', [self._network.label]),
+ ('network', [self._networks[0].label]),
]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.compute.networks.delete.assert_called_once_with(
+ self._networks[0].id)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_networks(self):
+ arglist = []
+ for n in self._networks:
+ arglist.append(n.label)
+ verifylist = [
+ ('network', arglist),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
result = self.cmd.take_action(parsed_args)
- self.compute.networks.delete.assert_called_once_with(self._network.id)
+ calls = []
+ for n in self._networks:
+ calls.append(call(n.id))
+ self.compute.networks.delete.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_delete_multiple_networks_exception(self):
+ arglist = [
+ self._networks[0].id,
+ 'xxxx-yyyy-zzzz',
+ self._networks[1].id,
+ ]
+ verifylist = [
+ ('network', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # Fake exception in utils.find_resource()
+ # In compute v2, we use utils.find_resource() to find a network.
+ # It calls get() several times, but find() only one time. So we
+ # choose to fake get() always raise exception, then pass through.
+ # And fake find() to find the real network or not.
+ self.compute.networks.get.side_effect = Exception()
+ ret_find = [
+ self._networks[0],
+ Exception(),
+ self._networks[1],
+ ]
+ self.compute.networks.find.side_effect = ret_find
+
+ # Fake exception in delete()
+ ret_delete = [
+ None,
+ Exception(),
+ ]
+ self.compute.networks.delete = mock.Mock(side_effect=ret_delete)
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ # The second call of utils.find_resource() should fail. So delete()
+ # was only called twice.
+ calls = [
+ call(self._networks[0].id),
+ call(self._networks[1].id),
+ ]
+ self.compute.networks.delete.assert_has_calls(calls)
+
class TestListNetworkCompute(TestNetworkCompute):
diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py
index 655e86c9..99b41d2d 100644
--- a/openstackclient/tests/network/v2/test_router.py
+++ b/openstackclient/tests/network/v2/test_router.py
@@ -495,6 +495,42 @@ class TestSetRouter(TestRouter):
self._router, **attrs)
self.assertIsNone(result)
+ def test_set_no_route(self):
+ arglist = [
+ self._router.name,
+ '--no-route',
+ ]
+ verifylist = [
+ ('router', self._router.name),
+ ('no_route', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'routes': [],
+ }
+ self.network.update_router.assert_called_once_with(
+ self._router, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_route_no_route(self):
+ arglist = [
+ self._router.name,
+ '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1',
+ '--no-route',
+ ]
+ verifylist = [
+ ('router', self._router.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)
+
def test_set_clear_routes(self):
arglist = [
self._router.name,
diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py
index bd903f9e..2a64b884 100644
--- a/openstackclient/tests/network/v2/test_security_group_rule.py
+++ b/openstackclient/tests/network/v2/test_security_group_rule.py
@@ -14,6 +14,7 @@
import copy
import mock
+from openstackclient.common import exceptions
from openstackclient.network import utils as network_utils
from openstackclient.network.v2 import security_group_rule
from openstackclient.tests.compute.v2 import fakes as compute_fakes
@@ -131,22 +132,40 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, [])
- def test_create_bad_protocol(self):
+ def test_create_bad_ethertype(self):
arglist = [
- '--protocol', 'foo',
+ '--ethertype', 'foo',
self._security_group.id,
]
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, [])
- def test_create_bad_ethertype(self):
+ def test_create_all_protocol_options(self):
arglist = [
- '--ethertype', 'foo',
+ '--protocol', 'tcp',
+ '--proto', 'tcp',
self._security_group.id,
]
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, [])
+ def test_create_all_port_range_options(self):
+ arglist = [
+ '--dst-port', '80:80',
+ '--icmp-type', '3',
+ '--icmp-code', '1',
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('dst_port', (80, 80)),
+ ('icmp_type', 3),
+ ('icmp_code', 1),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
def test_create_default_rule(self):
self._setup_security_group_rule({
'port_range_max': 443,
@@ -177,6 +196,36 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_data, data)
+ def test_create_proto_option(self):
+ self._setup_security_group_rule({
+ 'protocol': 'icmp',
+ 'remote_ip_prefix': '10.0.2.0/24',
+ })
+ arglist = [
+ '--proto', self._security_group_rule.protocol,
+ '--src-ip', self._security_group_rule.remote_ip_prefix,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('proto', self._security_group_rule.protocol),
+ ('protocol', None),
+ ('src_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.ethertype,
+ '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_source_group(self):
self._setup_security_group_rule({
'port_range_max': 22,
@@ -215,17 +264,15 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
def test_create_source_ip(self):
self._setup_security_group_rule({
'protocol': 'icmp',
- 'port_range_max': -1,
- 'port_range_min': -1,
'remote_ip_prefix': '10.0.2.0/24',
})
arglist = [
- '--proto', self._security_group_rule.protocol,
+ '--protocol', self._security_group_rule.protocol,
'--src-ip', self._security_group_rule.remote_ip_prefix,
self._security_group.id,
]
verifylist = [
- ('proto', self._security_group_rule.protocol),
+ ('protocol', self._security_group_rule.protocol),
('src_ip', self._security_group_rule.remote_ip_prefix),
('group', self._security_group.id),
]
@@ -249,6 +296,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
'ethertype': 'IPv6',
'port_range_max': 443,
'port_range_min': 443,
+ 'protocol': '6',
'remote_group_id': None,
'remote_ip_prefix': None,
})
@@ -258,6 +306,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
'--ethertype', self._security_group_rule.ethertype,
'--project', identity_fakes.project_name,
'--project-domain', identity_fakes.domain_name,
+ '--protocol', self._security_group_rule.protocol,
self._security_group.id,
]
verifylist = [
@@ -267,6 +316,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
('ethertype', self._security_group_rule.ethertype),
('project', identity_fakes.project_name),
('project_domain', identity_fakes.domain_name),
+ ('protocol', self._security_group_rule.protocol),
('group', self._security_group.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -285,6 +335,136 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_data, data)
+ def test_create_tcp_with_icmp_type(self):
+ arglist = [
+ '--protocol', 'tcp',
+ '--icmp-type', '15',
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('protocol', 'tcp'),
+ ('icmp_type', 15),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ def test_create_icmp_code(self):
+ arglist = [
+ '--protocol', '1',
+ '--icmp-code', '1',
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('protocol', '1'),
+ ('icmp_code', 1),
+ ('group', self._security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ def test_create_icmp_type(self):
+ self._setup_security_group_rule({
+ 'port_range_min': 15,
+ 'protocol': 'icmp',
+ 'remote_ip_prefix': '0.0.0.0/0',
+ })
+ arglist = [
+ '--icmp-type', str(self._security_group_rule.port_range_min),
+ '--protocol', self._security_group_rule.protocol,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('dst_port', None),
+ ('icmp_type', self._security_group_rule.port_range_min),
+ ('icmp_code', None),
+ ('protocol', self._security_group_rule.protocol),
+ ('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.ethertype,
+ '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,
+ 'security_group_id': self._security_group.id,
+ })
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
+ def test_create_ipv6_icmp_type_code(self):
+ self._setup_security_group_rule({
+ 'ethertype': 'IPv6',
+ 'port_range_min': 139,
+ 'port_range_max': 2,
+ 'protocol': 'ipv6-icmp',
+ })
+ arglist = [
+ '--icmp-type', str(self._security_group_rule.port_range_min),
+ '--icmp-code', str(self._security_group_rule.port_range_max),
+ '--protocol', self._security_group_rule.protocol,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('dst_port', None),
+ ('icmp_type', self._security_group_rule.port_range_min),
+ ('icmp_code', self._security_group_rule.port_range_max),
+ ('protocol', self._security_group_rule.protocol),
+ ('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.ethertype,
+ '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,
+ 'security_group_id': self._security_group.id,
+ })
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
+ def test_create_icmpv6_type(self):
+ self._setup_security_group_rule({
+ 'ethertype': 'IPv6',
+ 'port_range_min': 139,
+ 'protocol': 'icmpv6',
+ })
+ arglist = [
+ '--icmp-type', str(self._security_group_rule.port_range_min),
+ '--protocol', self._security_group_rule.protocol,
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('dst_port', None),
+ ('icmp_type', self._security_group_rule.port_range_min),
+ ('icmp_code', None),
+ ('protocol', self._security_group_rule.protocol),
+ ('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.ethertype,
+ 'port_range_min': self._security_group_rule.port_range_min,
+ 'protocol': self._security_group_rule.protocol,
+ 'security_group_id': self._security_group.id,
+ })
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
@@ -337,10 +517,21 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, [])
+ def test_create_all_protocol_options(self):
+ arglist = [
+ '--protocol', 'tcp',
+ '--proto', 'tcp',
+ self._security_group.id,
+ ]
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
def test_create_network_options(self):
arglist = [
'--ingress',
'--ethertype', 'IPv4',
+ '--icmp-type', '3',
+ '--icmp-code', '11',
'--project', identity_fakes.project_name,
'--project-domain', identity_fakes.domain_name,
self._security_group.id,
@@ -416,12 +607,45 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
'ip_range': {'cidr': '10.0.2.0/24'},
})
arglist = [
+ '--protocol', self._security_group_rule.ip_protocol,
+ '--src-ip', self._security_group_rule.ip_range['cidr'],
+ self._security_group.id,
+ ]
+ verifylist = [
+ ('protocol', self._security_group_rule.ip_protocol),
+ ('src_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',
+ 'from_port': -1,
+ 'to_port': -1,
+ 'ip_range': {'cidr': '10.0.2.0/24'},
+ })
+ arglist = [
'--proto', self._security_group_rule.ip_protocol,
'--src-ip', self._security_group_rule.ip_range['cidr'],
self._security_group.id,
]
verifylist = [
('proto', self._security_group_rule.ip_protocol),
+ ('protocol', None),
('src_ip', self._security_group_rule.ip_range['cidr']),
('group', self._security_group.id),
]
@@ -522,8 +746,6 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
_security_group_rule_icmp = \
network_fakes.FakeSecurityGroupRule.create_one_security_group_rule({
'protocol': 'icmp',
- 'port_range_max': -1,
- 'port_range_min': -1,
'remote_ip_prefix': '10.0.2.0/24',
'security_group_id': _security_group.id,
})
diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py
index 1923286d..22c288f9 100644
--- a/openstackclient/tests/network/v2/test_subnet.py
+++ b/openstackclient/tests/network/v2/test_subnet.py
@@ -469,6 +469,22 @@ class TestListSubnet(TestSubnet):
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
+ def test_subnet_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)
+ filters = {'ip_version': 4}
+
+ self.network.subnets.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestSetSubnet(TestSubnet):
diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py
index fc45e47b..c4155c69 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -498,3 +498,57 @@ class FakeAvailabilityZone(object):
availability_zones.append(availability_zone)
return availability_zones
+
+
+class FakeBackup(object):
+ """Fake one or more backup."""
+
+ @staticmethod
+ def create_one_backup(attrs=None):
+ """Create a fake backup.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with id, name, volume_id, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ backup_info = {
+ "id": 'backup-id-' + uuid.uuid4().hex,
+ "name": 'backup-name-' + uuid.uuid4().hex,
+ "volume_id": 'volume-id-' + uuid.uuid4().hex,
+ "description": 'description-' + uuid.uuid4().hex,
+ "object_count": None,
+ "container": 'container-' + uuid.uuid4().hex,
+ "size": random.randint(1, 20),
+ "status": "error",
+ "availability_zone": 'zone' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ backup_info.update(attrs)
+
+ backup = fakes.FakeResource(
+ info=copy.deepcopy(backup_info),
+ loaded=True)
+ return backup
+
+ @staticmethod
+ def create_backups(attrs=None, count=2):
+ """Create multiple fake backups.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of backups to fake
+ :return:
+ A list of FakeResource objects faking the backups
+ """
+ backups = []
+ for i in range(0, count):
+ backup = FakeBackup.create_one_backup(attrs)
+ backups.append(backup)
+
+ return backups
diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py
index 0e906e7b..8a151a91 100644
--- a/openstackclient/tests/volume/v2/test_backup.py
+++ b/openstackclient/tests/volume/v2/test_backup.py
@@ -12,9 +12,6 @@
# under the License.
#
-import copy
-
-from openstackclient.tests import fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import backup
@@ -34,83 +31,101 @@ class TestBackup(volume_fakes.TestVolume):
class TestBackupCreate(TestBackup):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ new_backup = volume_fakes.FakeBackup.create_one_backup(
+ attrs={'volume_id': volume.id})
+
+ columns = (
+ 'availability_zone',
+ 'container',
+ 'description',
+ 'id',
+ 'name',
+ 'object_count',
+ 'size',
+ 'status',
+ 'volume_id',
+ )
+ data = (
+ new_backup.availability_zone,
+ new_backup.container,
+ new_backup.description,
+ new_backup.id,
+ new_backup.name,
+ new_backup.object_count,
+ new_backup.size,
+ new_backup.status,
+ new_backup.volume_id,
+ )
+
def setUp(self):
super(TestBackupCreate, self).setUp()
- self.volumes_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.VOLUME),
- loaded=True
- )
+ self.volumes_mock.get.return_value = self.volume
+ self.backups_mock.create.return_value = self.new_backup
- self.backups_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.BACKUP),
- loaded=True
- )
# Get the command object to test
self.cmd = backup.CreateBackup(self.app, None)
def test_backup_create(self):
arglist = [
- volume_fakes.volume_id,
- "--name", volume_fakes.backup_name,
- "--description", volume_fakes.backup_description,
- "--container", volume_fakes.backup_name
+ "--name", self.new_backup.name,
+ "--description", self.new_backup.description,
+ "--container", self.new_backup.container,
+ self.new_backup.volume_id,
]
verifylist = [
- ("volume", volume_fakes.volume_id),
- ("name", volume_fakes.backup_name),
- ("description", volume_fakes.backup_description),
- ("container", volume_fakes.backup_name)
+ ("name", self.new_backup.name),
+ ("description", self.new_backup.description),
+ ("container", self.new_backup.container),
+ ("volume", self.new_backup.volume_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.backups_mock.create.assert_called_with(
- volume_fakes.volume_id,
- container=volume_fakes.backup_name,
- name=volume_fakes.backup_name,
- description=volume_fakes.backup_description
+ self.new_backup.volume_id,
+ container=self.new_backup.container,
+ name=self.new_backup.name,
+ description=self.new_backup.description
)
- self.assertEqual(columns, volume_fakes.BACKUP_columns)
- self.assertEqual(data, volume_fakes.BACKUP_data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
def test_backup_create_without_name(self):
arglist = [
- volume_fakes.volume_id,
- "--description", volume_fakes.backup_description,
- "--container", volume_fakes.backup_name
+ "--description", self.new_backup.description,
+ "--container", self.new_backup.container,
+ self.new_backup.volume_id,
]
verifylist = [
- ("volume", volume_fakes.volume_id),
- ("description", volume_fakes.backup_description),
- ("container", volume_fakes.backup_name)
+ ("description", self.new_backup.description),
+ ("container", self.new_backup.container),
+ ("volume", self.new_backup.volume_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.backups_mock.create.assert_called_with(
- volume_fakes.volume_id,
- container=volume_fakes.backup_name,
+ self.new_backup.volume_id,
+ container=self.new_backup.container,
name=None,
- description=volume_fakes.backup_description
+ description=self.new_backup.description
)
- self.assertEqual(columns, volume_fakes.BACKUP_columns)
- self.assertEqual(data, volume_fakes.BACKUP_data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
class TestBackupDelete(TestBackup):
+ backup = volume_fakes.FakeBackup.create_one_backup()
+
def setUp(self):
super(TestBackupDelete, self).setUp()
- self.backups_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.BACKUP),
- loaded=True)
+ self.backups_mock.get.return_value = self.backup
self.backups_mock.delete.return_value = None
# Get the command object to mock
@@ -118,21 +133,25 @@ class TestBackupDelete(TestBackup):
def test_backup_delete(self):
arglist = [
- volume_fakes.backup_id
+ self.backup.id
]
verifylist = [
- ("backups", [volume_fakes.backup_id])
+ ("backups", [self.backup.id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.backups_mock.delete.assert_called_with(volume_fakes.backup_id)
+ self.backups_mock.delete.assert_called_with(self.backup.id)
self.assertIsNone(result)
class TestBackupList(TestBackup):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ backups = volume_fakes.FakeBackup.create_backups(
+ attrs={'volume_id': volume.name}, count=3)
+
columns = [
'ID',
'Name',
@@ -140,33 +159,39 @@ class TestBackupList(TestBackup):
'Status',
'Size',
]
- datalist = (
- (
- volume_fakes.backup_id,
- volume_fakes.backup_name,
- volume_fakes.backup_description,
- volume_fakes.backup_status,
- volume_fakes.backup_size
- ),
- )
+ columns_long = columns + [
+ 'Availability Zone',
+ 'Volume',
+ 'Container',
+ ]
+
+ data = []
+ for b in backups:
+ data.append((
+ b.id,
+ b.name,
+ b.description,
+ b.status,
+ b.size,
+ ))
+ data_long = []
+ for b in backups:
+ data_long.append((
+ b.id,
+ b.name,
+ b.description,
+ b.status,
+ b.size,
+ b.availability_zone,
+ b.volume_id,
+ b.container,
+ ))
def setUp(self):
super(TestBackupList, self).setUp()
- self.volumes_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.VOLUME),
- loaded=True
- )
- ]
- self.backups_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.BACKUP),
- loaded=True
- )
- ]
+ self.volumes_mock.list.return_value = [self.volume]
+ self.backups_mock.list.return_value = self.backups
# Get the command to test
self.cmd = backup.ListBackup(self.app, None)
@@ -178,7 +203,7 @@ class TestBackupList(TestBackup):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist, tuple(data))
+ self.assertEqual(self.data, list(data))
def test_backup_list_with_options(self):
arglist = ["--long"]
@@ -187,86 +212,87 @@ class TestBackupList(TestBackup):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- columns = self.columns + [
- 'Availability Zone',
- 'Volume',
- 'Container',
- ]
-
- self.assertEqual(columns, columns)
-
- datalist = ((
- volume_fakes.backup_id,
- volume_fakes.backup_name,
- volume_fakes.backup_description,
- volume_fakes.backup_status,
- volume_fakes.backup_size,
- volume_fakes.volume_availability_zone,
- volume_fakes.backup_volume_id,
- volume_fakes.backup_container
- ),)
- self.assertEqual(datalist, tuple(data))
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
class TestBackupRestore(TestBackup):
+ volume = volume_fakes.FakeVolume.create_one_volume()
+ backup = volume_fakes.FakeBackup.create_one_backup(
+ attrs={'volume_id': volume.id})
+
def setUp(self):
super(TestBackupRestore, self).setUp()
- self.backups_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.BACKUP),
- loaded=True
- )
- self.volumes_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.VOLUME),
- loaded=True
- )
+ self.backups_mock.get.return_value = self.backup
+ self.volumes_mock.get.return_value = self.volume
self.restores_mock.restore.return_value = None
# Get the command object to mock
self.cmd = backup.RestoreBackup(self.app, None)
def test_backup_restore(self):
arglist = [
- volume_fakes.backup_id,
- volume_fakes.volume_id
+ self.backup.id,
+ self.backup.volume_id
]
verifylist = [
- ("backup", volume_fakes.backup_id),
- ("volume", volume_fakes.volume_id)
+ ("backup", self.backup.id),
+ ("volume", self.backup.volume_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.restores_mock.restore.assert_called_with(volume_fakes.backup_id,
- volume_fakes.volume_id)
+ self.restores_mock.restore.assert_called_with(self.backup.id,
+ self.backup.volume_id)
self.assertIsNone(result)
class TestBackupShow(TestBackup):
+ backup = volume_fakes.FakeBackup.create_one_backup()
+
+ columns = (
+ 'availability_zone',
+ 'container',
+ 'description',
+ 'id',
+ 'name',
+ 'object_count',
+ 'size',
+ 'status',
+ 'volume_id',
+ )
+ data = (
+ backup.availability_zone,
+ backup.container,
+ backup.description,
+ backup.id,
+ backup.name,
+ backup.object_count,
+ backup.size,
+ backup.status,
+ backup.volume_id,
+ )
+
def setUp(self):
super(TestBackupShow, self).setUp()
- self.backups_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.BACKUP),
- loaded=True)
+ self.backups_mock.get.return_value = self.backup
# Get the command object to test
self.cmd = backup.ShowBackup(self.app, None)
def test_backup_show(self):
arglist = [
- volume_fakes.backup_id
+ self.backup.id
]
verifylist = [
- ("backup", volume_fakes.backup_id)
+ ("backup", self.backup.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.backups_mock.get.assert_called_with(volume_fakes.backup_id)
+ self.backups_mock.get.assert_called_with(self.backup.id)
- self.assertEqual(volume_fakes.BACKUP_columns, columns)
- self.assertEqual(volume_fakes.BACKUP_data, data)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py
index 8c75dfb2..d2fa5e07 100644
--- a/openstackclient/tests/volume/v2/test_snapshot.py
+++ b/openstackclient/tests/volume/v2/test_snapshot.py
@@ -229,7 +229,6 @@ class TestSnapshotList(TestSnapshot):
class TestSnapshotSet(TestSnapshot):
-
def setUp(self):
super(TestSnapshotSet, self).setUp()
@@ -270,6 +269,23 @@ class TestSnapshotSet(TestSnapshot):
)
self.assertIsNone(result)
+ def test_snapshot_set_state_to_error(self):
+ arglist = [
+ "--state", "error",
+ volume_fakes.snapshot_id
+ ]
+ verifylist = [
+ ("state", "error"),
+ ("snapshot", volume_fakes.snapshot_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.reset_state.assert_called_with(
+ volume_fakes.snapshot_id, "error")
+ self.assertIsNone(result)
+
class TestSnapshotShow(TestSnapshot):
@@ -300,7 +316,6 @@ class TestSnapshotShow(TestSnapshot):
class TestSnapshotUnset(TestSnapshot):
-
def setUp(self):
super(TestSnapshotUnset, self).setUp()
@@ -322,6 +337,7 @@ class TestSnapshotUnset(TestSnapshot):
("snapshot", volume_fakes.snapshot_id),
("property", ["foo"])
]
+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py
index f124a5e2..65cb9a75 100644
--- a/openstackclient/volume/v2/snapshot.py
+++ b/openstackclient/volume/v2/snapshot.py
@@ -179,6 +179,14 @@ class SetSnapshot(command.Command):
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. Valid values are available, '
+ 'error, creating, deleting, and error-deleting.',
+ )
return parser
def take_action(self, parsed_args):
@@ -192,13 +200,17 @@ class SetSnapshot(command.Command):
if parsed_args.description:
kwargs['description'] = parsed_args.description
- if not kwargs and not parsed_args.property:
+ if (not kwargs and not parsed_args.property and not
+ parsed_args.state):
self.app.log.error("No changes requested\n")
return
if parsed_args.property:
volume_client.volume_snapshots.set_metadata(snapshot.id,
parsed_args.property)
+ if parsed_args.state:
+ volume_client.volume_snapshots.reset_state(snapshot.id,
+ parsed_args.state)
volume_client.volume_snapshots.update(snapshot.id, **kwargs)