summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/utils.py4
-rw-r--r--openstackclient/common/availability_zone.py66
-rw-r--r--openstackclient/common/utils.py45
-rw-r--r--openstackclient/compute/v2/security_group.py22
-rw-r--r--openstackclient/image/v2/image.py30
-rw-r--r--openstackclient/network/common.py62
-rw-r--r--openstackclient/network/v2/network.py65
-rw-r--r--openstackclient/network/v2/port.py56
-rw-r--r--openstackclient/network/v2/router.py19
-rw-r--r--openstackclient/network/v2/security_group.py40
-rw-r--r--openstackclient/tests/common/test_availability_zone.py65
-rw-r--r--openstackclient/tests/common/test_utils.py31
-rw-r--r--openstackclient/tests/image/v2/test_image.py57
-rw-r--r--openstackclient/tests/network/test_common.py103
-rw-r--r--openstackclient/tests/network/v2/fakes.py176
-rw-r--r--openstackclient/tests/network/v2/test_network.py19
-rw-r--r--openstackclient/tests/network/v2/test_port.py87
-rw-r--r--openstackclient/tests/network/v2/test_router.py28
-rw-r--r--openstackclient/tests/network/v2/test_security_group.py99
-rw-r--r--openstackclient/tests/volume/v2/fakes.py5
-rw-r--r--openstackclient/tests/volume/v2/test_volume.py82
21 files changed, 986 insertions, 175 deletions
diff --git a/openstackclient/api/utils.py b/openstackclient/api/utils.py
index b7ff7f23..fa759cd3 100644
--- a/openstackclient/api/utils.py
+++ b/openstackclient/api/utils.py
@@ -27,11 +27,11 @@ def simple_filter(
be changed if any filtering occurs.
:param string attr:
The name of the attribute to filter. If attr does not exist no
- match will succeed and no rows will be retrurned. If attr is
+ match will succeed and no rows will be returned. If attr is
None no filtering will be performed and all rows will be returned.
:param sring value:
The value to filter. None is considered to be a 'no filter' value.
- '' matches agains a Python empty string.
+ '' matches against a Python empty string.
:param string property_field:
The name of the data field containing a property dict to filter.
If property_field is None, attr is a field name. If property_field
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py
index fa5aee47..a941418b 100644
--- a/openstackclient/common/availability_zone.py
+++ b/openstackclient/common/availability_zone.py
@@ -30,6 +30,8 @@ def _xform_common_availability_zone(az, zone_info):
if hasattr(az, 'zoneName'):
zone_info['zone_name'] = az.zoneName
+ zone_info['zone_resource'] = ''
+
def _xform_compute_availability_zone(az, include_extra):
result = []
@@ -69,6 +71,18 @@ def _xform_volume_availability_zone(az):
return result
+def _xform_network_availability_zone(az):
+ result = []
+ zone_info = {}
+ zone_info['zone_name'] = getattr(az, 'name', '')
+ zone_info['zone_status'] = getattr(az, 'state', '')
+ if 'unavailable' == zone_info['zone_status']:
+ zone_info['zone_status'] = 'not available'
+ zone_info['zone_resource'] = getattr(az, 'resource', '')
+ result.append(zone_info)
+ return result
+
+
class ListAvailabilityZone(command.Lister):
"""List availability zones and their status"""
@@ -80,6 +94,11 @@ class ListAvailabilityZone(command.Lister):
default=False,
help='List compute availability zones')
parser.add_argument(
+ '--network',
+ action='store_true',
+ default=False,
+ help='List network availability zones')
+ parser.add_argument(
'--volume',
action='store_true',
default=False,
@@ -92,7 +111,7 @@ class ListAvailabilityZone(command.Lister):
)
return parser
- def get_compute_availability_zones(self, parsed_args):
+ def _get_compute_availability_zones(self, parsed_args):
compute_client = self.app.client_manager.compute
try:
data = compute_client.availability_zones.list()
@@ -108,36 +127,63 @@ class ListAvailabilityZone(command.Lister):
result += _xform_compute_availability_zone(zone, parsed_args.long)
return result
- def get_volume_availability_zones(self, parsed_args):
+ def _get_volume_availability_zones(self, parsed_args):
volume_client = self.app.client_manager.volume
+ data = []
try:
data = volume_client.availability_zones.list()
- except Exception:
- message = "Availability zones list not supported by " \
- "Block Storage API"
- self.log.warning(message)
+ except Exception as e:
+ self.log.debug('Volume availability zone exception: ' + str(e))
+ if parsed_args.volume:
+ message = "Availability zones list not supported by " \
+ "Block Storage API"
+ self.log.warning(message)
result = []
for zone in data:
result += _xform_volume_availability_zone(zone)
return result
+ def _get_network_availability_zones(self, parsed_args):
+ network_client = self.app.client_manager.network
+ data = []
+ try:
+ # Verify that the extension exists.
+ network_client.find_extension('Availability Zone',
+ ignore_missing=False)
+ data = network_client.availability_zones()
+ except Exception as e:
+ self.log.debug('Network availability zone exception: ' + str(e))
+ if parsed_args.network:
+ message = "Availability zones list not supported by " \
+ "Network API"
+ self.log.warning(message)
+
+ result = []
+ for zone in data:
+ result += _xform_network_availability_zone(zone)
+ return result
+
def take_action(self, parsed_args):
if parsed_args.long:
- columns = ('Zone Name', 'Zone Status',
+ columns = ('Zone Name', 'Zone Status', 'Zone Resource',
'Host Name', 'Service Name', 'Service Status')
else:
columns = ('Zone Name', 'Zone Status')
# Show everything by default.
- show_all = (not parsed_args.compute and not parsed_args.volume)
+ show_all = (not parsed_args.compute and
+ not parsed_args.volume and
+ not parsed_args.network)
result = []
if parsed_args.compute or show_all:
- result += self.get_compute_availability_zones(parsed_args)
+ result += self._get_compute_availability_zones(parsed_args)
if parsed_args.volume or show_all:
- result += self.get_volume_availability_zones(parsed_args)
+ result += self._get_volume_availability_zones(parsed_args)
+ if parsed_args.network or show_all:
+ result += self._get_network_availability_zones(parsed_args)
return (columns,
(utils.get_dict_properties(
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index 3ae30c8f..4142f830 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -26,33 +26,6 @@ from oslo_utils import importutils
from openstackclient.common import exceptions
-class log_method(object):
-
- def __init__(self, log=None, level=logging.DEBUG):
- self._log = log
- self._level = level
-
- def __call__(self, func):
- func_name = func.__name__
- if not self._log:
- self._log = logging.getLogger(func.__class__.__name__)
-
- @six.wraps(func)
- def wrapper(*args, **kwargs):
- if self._log.isEnabledFor(self._level):
- pretty_args = []
- if args:
- pretty_args.extend(str(a) for a in args)
- if kwargs:
- pretty_args.extend(
- "%s=%s" % (k, v) for k, v in six.iteritems(kwargs))
- self._log.log(self._level, "%s(%s)",
- func_name, ", ".join(pretty_args))
- return func(*args, **kwargs)
-
- return wrapper
-
-
def find_resource(manager, name_or_id, **kwargs):
"""Helper for the _find_* methods.
@@ -169,6 +142,16 @@ def format_list(data, separator=', '):
return separator.join(sorted(data))
+def format_list_of_dicts(data):
+ """Return a formatted string of key value pairs for each dict
+
+ :param data: a list of dicts
+ :rtype: a string formatted to key='value' with dicts separated by new line
+ """
+
+ return '\n'.join(format_dict(i) for i in data)
+
+
def get_field(item, field):
try:
if isinstance(item, dict):
@@ -331,6 +314,8 @@ def wait_for_status(status_f,
def wait_for_delete(manager,
res_id,
status_field='status',
+ error_status=['error'],
+ exception_name=['NotFound'],
sleep_time=5,
timeout=300,
callback=None):
@@ -341,6 +326,8 @@ def wait_for_delete(manager,
:param status_field: the status attribute in the returned resource object,
this is used to check for error states while the resource is being
deleted
+ :param error_status: a list of status strings for error
+ :param exception_name: a list of exception strings for deleted case
:param sleep_time: wait this long between checks (seconds)
:param timeout: check until this long (seconds)
:param callback: called per sleep cycle, useful to display progress; this
@@ -357,12 +344,12 @@ def wait_for_delete(manager,
# handle a NotFound exception here without parsing the message
res = manager.get(res_id)
except Exception as ex:
- if type(ex).__name__ == 'NotFound':
+ if type(ex).__name__ in exception_name:
return True
raise
status = getattr(res, status_field, '').lower()
- if status == 'error':
+ if status in error_status:
return False
if callback:
diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py
index b975a505..2a8908d7 100644
--- a/openstackclient/compute/v2/security_group.py
+++ b/openstackclient/compute/v2/security_group.py
@@ -169,28 +169,6 @@ class CreateSecurityGroupRule(command.ShowOne):
return zip(*sorted(six.iteritems(info)))
-class DeleteSecurityGroup(command.Command):
- """Delete a security group"""
-
- def get_parser(self, prog_name):
- parser = super(DeleteSecurityGroup, self).get_parser(prog_name)
- parser.add_argument(
- 'group',
- metavar='<group>',
- help='Security group to delete (name or ID)',
- )
- return parser
-
- def take_action(self, parsed_args):
-
- compute_client = self.app.client_manager.compute
- data = utils.find_resource(
- compute_client.security_groups,
- parsed_args.group,
- )
- compute_client.security_groups.delete(data.id)
-
-
class DeleteSecurityGroupRule(command.Command):
"""Delete a security group rule"""
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 39a53195..3f162181 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -431,6 +431,20 @@ class ListImage(command.Lister):
"(default: asc), multiple keys and directions can be "
"specified separated by comma",
)
+ parser.add_argument(
+ "--limit",
+ metavar="<limit>",
+ type=int,
+ help="Maximum number of images to display.",
+ )
+ parser.add_argument(
+ '--marker',
+ metavar='<marker>',
+ default=None,
+ help="The last image (name or ID) of the previous page. Display "
+ "list of images after marker. Display all images if not "
+ "specified."
+ )
return parser
def take_action(self, parsed_args):
@@ -443,6 +457,11 @@ class ListImage(command.Lister):
kwargs['private'] = True
if parsed_args.shared:
kwargs['shared'] = True
+ if parsed_args.limit:
+ kwargs['limit'] = parsed_args.limit
+ if parsed_args.marker:
+ kwargs['marker'] = utils.find_resource(image_client.images,
+ parsed_args.marker).id
if parsed_args.long:
columns = (
@@ -474,16 +493,7 @@ class ListImage(command.Lister):
column_headers = columns
# List of image data received
- data = []
- # 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']
+ data = image_client.api.image_list(**kwargs)
if parsed_args.property:
# NOTE(dtroyer): coerce to a list to subscript it in py3
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
new file mode 100644
index 00000000..c539dd05
--- /dev/null
+++ b/openstackclient/network/common.py
@@ -0,0 +1,62 @@
+# 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 abc
+import six
+
+from openstackclient.common import command
+
+
+@six.add_metaclass(abc.ABCMeta)
+class NetworkAndComputeCommand(command.Command):
+ """Network and Compute Command"""
+
+ 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)
+
+ def get_parser(self, prog_name):
+ self.log.debug('get_parser(%s)', prog_name)
+ parser = super(NetworkAndComputeCommand, self).get_parser(prog_name)
+ parser = self.update_parser_common(parser)
+ self.log.debug('common parser: %s', parser)
+ if self.app.client_manager.is_network_endpoint_enabled():
+ return self.update_parser_network(parser)
+ else:
+ return self.update_parser_compute(parser)
+
+ def update_parser_common(self, parser):
+ """Default is no updates to parser."""
+ return parser
+
+ def update_parser_network(self, parser):
+ """Default is no updates to parser."""
+ return parser
+
+ def update_parser_compute(self, parser):
+ """Default is no updates to parser."""
+ return parser
+
+ @abc.abstractmethod
+ def take_action_network(self, client, parsed_args):
+ """Override to do something useful."""
+ pass
+
+ @abc.abstractmethod
+ def take_action_compute(self, client, parsed_args):
+ """Override to do something useful."""
+ pass
diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py
index fc94fd82..61237219 100644
--- a/openstackclient/network/v2/network.py
+++ b/openstackclient/network/v2/network.py
@@ -47,6 +47,33 @@ def _get_columns(item):
return tuple(sorted(columns))
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
+ if parsed_args.admin_state is not None:
+ attrs['admin_state_up'] = parsed_args.admin_state
+ if parsed_args.shared is not None:
+ attrs['shared'] = parsed_args.shared
+
+ # "network set" command doesn't support setting project.
+ 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
+
+ # "network set" command doesn't support setting availability zone hints.
+ if 'availability_zone_hints' in parsed_args and \
+ parsed_args.availability_zone_hints is not None:
+ attrs['availability_zone_hints'] = parsed_args.availability_zone_hints
+
+ return attrs
+
+
class CreateNetwork(command.ShowOne):
"""Create new network"""
@@ -105,31 +132,14 @@ class CreateNetwork(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.network
- body = self.get_body(parsed_args)
- obj = client.create_network(**body)
+
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_network(**attrs)
columns = _get_columns(obj)
+
data = utils.get_item_properties(obj, columns, formatters=_formatters)
return (columns, data)
- def get_body(self, parsed_args):
- body = {'name': str(parsed_args.name),
- 'admin_state_up': parsed_args.admin_state}
- if parsed_args.shared is not None:
- body['shared'] = parsed_args.shared
- if 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
- body['tenant_id'] = project_id
- if parsed_args.availability_zone_hints is not None:
- body['availability_zone_hints'] = \
- parsed_args.availability_zone_hints
-
- return body
-
class DeleteNetwork(command.Command):
"""Delete network(s)"""
@@ -271,18 +281,13 @@ class SetNetwork(command.Command):
client = self.app.client_manager.network
obj = client.find_network(parsed_args.identifier, ignore_missing=False)
- if parsed_args.name is not None:
- obj.name = str(parsed_args.name)
- if parsed_args.admin_state is not None:
- obj.admin_state_up = parsed_args.admin_state
- if parsed_args.shared is not None:
- obj.shared = parsed_args.shared
-
- if not obj.is_dirty:
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ if attrs == {}:
msg = "Nothing specified to be set"
raise exceptions.CommandError(msg)
- client.update_network(obj)
+ client.update_network(obj, **attrs)
+ return
class ShowNetwork(command.ShowOne):
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 0d5b183e..46cb031f 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -14,6 +14,42 @@
"""Port action implementations"""
from openstackclient.common import command
+from openstackclient.common import utils
+
+
+def _format_admin_state(state):
+ return 'UP' if state else 'DOWN'
+
+
+_formatters = {
+ '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,
+ 'extra_dhcp_opts': utils.format_list_of_dicts,
+ 'fixed_ips': utils.format_list_of_dicts,
+ 'security_groups': utils.format_list,
+}
+
+
+def _get_columns(item):
+ columns = 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 sorted(columns)
class DeletePort(command.Command):
@@ -35,3 +71,23 @@ class DeletePort(command.Command):
for port in parsed_args.port:
res = client.find_port(port)
client.delete_port(res)
+
+
+class ShowPort(command.ShowOne):
+ """Display port details"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowPort, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port',
+ metavar="<port>",
+ help="Port to display (name or ID)"
+ )
+ return parser
+
+ 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)
+ data = utils.get_item_properties(obj, columns, formatters=_formatters)
+ return (tuple(columns), data)
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index 6c8acb63..60db816a 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -35,6 +35,8 @@ def _format_external_gateway_info(info):
_formatters = {
'admin_state_up': _format_admin_state,
'external_gateway_info': _format_external_gateway_info,
+ 'availability_zones': utils.format_list,
+ 'availability_zone_hints': utils.format_list,
}
@@ -46,6 +48,9 @@ def _get_attrs(client_manager, parsed_args):
attrs['admin_state_up'] = parsed_args.admin_state_up
if parsed_args.distributed is not None:
attrs['distributed'] = parsed_args.distributed
+ if ('availability_zone_hints' in parsed_args
+ and parsed_args.availability_zone_hints is not None):
+ attrs['availability_zone_hints'] = parsed_args.availability_zone_hints
# "router set" command doesn't support setting project.
if 'project' in parsed_args and parsed_args.project is not None:
identity_client = client_manager.identity
@@ -96,9 +101,19 @@ class CreateRouter(command.ShowOne):
)
parser.add_argument(
'--project',
- metavar='<poroject>',
+ metavar='<project>',
help="Owner's project (name or ID)",
)
+ parser.add_argument(
+ '--availability-zone-hint',
+ metavar='<availability-zone>',
+ action='append',
+ dest='availability_zone_hints',
+ help='Availability Zone in which to create this router '
+ '(requires the Router Availability Zone extension, '
+ 'this option can be repeated).',
+ )
+
identity_common.add_project_domain_option_to_parser(parser)
return parser
@@ -176,10 +191,12 @@ class ListRouter(command.Lister):
columns = columns + (
'routes',
'external_gateway_info',
+ 'availability_zones'
)
column_headers = column_headers + (
'Routes',
'External gateway info',
+ 'Availability zones'
)
data = client.routers()
diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py
new file mode 100644
index 00000000..4e122f21
--- /dev/null
+++ b/openstackclient/network/v2/security_group.py
@@ -0,0 +1,40 @@
+# 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.
+#
+
+"""Security Group action implementations"""
+
+from openstackclient.common import utils
+from openstackclient.network import common
+
+
+class DeleteSecurityGroup(common.NetworkAndComputeCommand):
+ """Delete a security group"""
+
+ def update_parser_common(self, parser):
+ parser.add_argument(
+ 'group',
+ metavar='<group>',
+ help='Security group to delete (name or ID)',
+ )
+ return parser
+
+ def take_action_network(self, client, parsed_args):
+ obj = client.find_security_group(parsed_args.group)
+ client.delete_security_group(obj)
+
+ def take_action_compute(self, client, parsed_args):
+ data = utils.find_resource(
+ client.security_groups,
+ parsed_args.group,
+ )
+ client.security_groups.delete(data.id)
diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py
index 232b56c9..e44455c7 100644
--- a/openstackclient/tests/common/test_availability_zone.py
+++ b/openstackclient/tests/common/test_availability_zone.py
@@ -11,11 +11,13 @@
# under the License.
#
+import mock
import six
from openstackclient.common import availability_zone
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes
+from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils
from openstackclient.tests.volume.v2 import fakes as volume_fakes
@@ -33,6 +35,7 @@ def _build_compute_az_datalist(compute_az, long_datalist=False):
datalist += (
compute_az.zoneName,
'available',
+ '',
host,
service,
'enabled :-) ' + state['updated_at'],
@@ -51,6 +54,23 @@ def _build_volume_az_datalist(volume_az, long_datalist=False):
datalist = (
volume_az.zoneName,
'available',
+ '', '', '', '',
+ )
+ return (datalist,)
+
+
+def _build_network_az_datalist(network_az, long_datalist=False):
+ datalist = ()
+ if not long_datalist:
+ datalist = (
+ network_az.name,
+ network_az.state,
+ )
+ else:
+ datalist = (
+ network_az.name,
+ network_az.state,
+ network_az.resource,
'', '', '',
)
return (datalist,)
@@ -79,6 +99,16 @@ class TestAvailabilityZone(utils.TestCommand):
self.volume_azs_mock = volume_client.availability_zones
self.volume_azs_mock.reset_mock()
+ network_client = network_fakes.FakeNetworkV2Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
+ self.app.client_manager.network = network_client
+
+ network_client.availability_zones = mock.Mock()
+ network_client.find_extension = mock.Mock()
+ self.network_azs_mock = network_client.availability_zones
+
class TestAvailabilityZoneList(TestAvailabilityZone):
@@ -86,11 +116,14 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
compute_fakes.FakeAvailabilityZone.create_availability_zones()
volume_azs = \
volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1)
+ network_azs = \
+ network_fakes.FakeAvailabilityZone.create_availability_zones()
short_columnslist = ('Zone Name', 'Zone Status')
long_columnslist = (
'Zone Name',
'Zone Status',
+ 'Zone Resource',
'Host Name',
'Service Name',
'Service Status',
@@ -101,6 +134,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.return_value = self.compute_azs
self.volume_azs_mock.list.return_value = self.volume_azs
+ self.network_azs_mock.return_value = self.network_azs
# Get the command object to test
self.cmd = availability_zone.ListAvailabilityZone(self.app, None)
@@ -115,6 +149,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_called_with()
self.volume_azs_mock.list.assert_called_with()
+ self.network_azs_mock.assert_called_with()
self.assertEqual(self.short_columnslist, columns)
datalist = ()
@@ -122,6 +157,8 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
datalist += _build_compute_az_datalist(compute_az)
for volume_az in self.volume_azs:
datalist += _build_volume_az_datalist(volume_az)
+ for network_az in self.network_azs:
+ datalist += _build_network_az_datalist(network_az)
self.assertEqual(datalist, tuple(data))
def test_availability_zone_list_long(self):
@@ -138,6 +175,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_called_with()
self.volume_azs_mock.list.assert_called_with()
+ self.network_azs_mock.assert_called_with()
self.assertEqual(self.long_columnslist, columns)
datalist = ()
@@ -147,6 +185,9 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
for volume_az in self.volume_azs:
datalist += _build_volume_az_datalist(volume_az,
long_datalist=True)
+ for network_az in self.network_azs:
+ datalist += _build_network_az_datalist(network_az,
+ long_datalist=True)
self.assertEqual(datalist, tuple(data))
def test_availability_zone_list_compute(self):
@@ -163,6 +204,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_called_with()
self.volume_azs_mock.list.assert_not_called()
+ self.network_azs_mock.assert_not_called()
self.assertEqual(self.short_columnslist, columns)
datalist = ()
@@ -184,9 +226,32 @@ class TestAvailabilityZoneList(TestAvailabilityZone):
self.compute_azs_mock.list.assert_not_called()
self.volume_azs_mock.list.assert_called_with()
+ self.network_azs_mock.assert_not_called()
self.assertEqual(self.short_columnslist, columns)
datalist = ()
for volume_az in self.volume_azs:
datalist += _build_volume_az_datalist(volume_az)
self.assertEqual(datalist, tuple(data))
+
+ def test_availability_zone_list_network(self):
+ arglist = [
+ '--network',
+ ]
+ verifylist = [
+ ('network', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.compute_azs_mock.list.assert_not_called()
+ self.volume_azs_mock.list.assert_not_called()
+ self.network_azs_mock.assert_called_with()
+
+ self.assertEqual(self.short_columnslist, columns)
+ datalist = ()
+ for network_az in self.network_azs:
+ datalist += _build_network_az_datalist(network_az)
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
index 064ad417..09545079 100644
--- a/openstackclient/tests/common/test_utils.py
+++ b/openstackclient/tests/common/test_utils.py
@@ -212,6 +212,28 @@ class TestUtils(test_utils.TestCase):
self.assertFalse(utils.wait_for_delete(manager, res_id))
self.assertFalse(mock_sleep.called)
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_delete_error_with_overrides(self, mock_sleep):
+ # Tests that we fail if the resource is my_status=failed
+ resource = mock.MagicMock(my_status='FAILED')
+ mock_get = mock.Mock(return_value=resource)
+ manager = mock.MagicMock(get=mock_get)
+ res_id = str(uuid.uuid4())
+ self.assertFalse(utils.wait_for_delete(manager, res_id,
+ status_field='my_status',
+ error_status=['failed']))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep):
+ # Tests that we succeed if the resource is specific exception
+ mock_get = mock.Mock(side_effect=Exception)
+ manager = mock.MagicMock(get=mock_get)
+ res_id = str(uuid.uuid4())
+ self.assertTrue(utils.wait_for_delete(manager, res_id,
+ exception_name=['Exception']))
+ self.assertFalse(mock_sleep.called)
+
def test_build_kwargs_dict_value_set(self):
self.assertEqual({'arg_bla': 'bla'},
utils.build_kwargs_dict('arg_bla', 'bla'))
@@ -348,6 +370,15 @@ class TestFindResource(test_utils.TestCase):
self.assertEqual(expected, utils.format_list(['a', 'b', 'c']))
self.assertEqual(expected, utils.format_list(['c', 'b', 'a']))
+ def test_format_list_of_dicts(self):
+ expected = "a='b', c='d'\ne='f'"
+ sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}]
+ unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}]
+ self.assertEqual(expected, utils.format_list_of_dicts(sorted_data))
+ self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data))
+ self.assertEqual('', utils.format_list_of_dicts([]))
+ self.assertEqual('', utils.format_list_of_dicts([{}]))
+
def test_format_list_separator(self):
expected = 'a\nb\nc'
actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n')
diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py
index 41abdaab..d399c9ed 100644
--- a/openstackclient/tests/image/v2/test_image.py
+++ b/openstackclient/tests/image/v2/test_image.py
@@ -498,9 +498,7 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with(
- marker=image_fakes.image_id,
- )
+ self.api_mock.image_list.assert_called_with()
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
@@ -521,7 +519,6 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
public=True,
- marker=image_fakes.image_id,
)
self.assertEqual(self.columns, columns)
@@ -543,7 +540,6 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
private=True,
- marker=image_fakes.image_id,
)
self.assertEqual(self.columns, columns)
@@ -565,7 +561,6 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
shared=True,
- marker=image_fakes.image_id,
)
self.assertEqual(self.columns, columns)
@@ -582,9 +577,7 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with(
- marker=image_fakes.image_id,
- )
+ self.api_mock.image_list.assert_called_with()
collist = (
'ID',
@@ -630,9 +623,7 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with(
- marker=image_fakes.image_id,
- )
+ self.api_mock.image_list.assert_called_with()
sf_mock.assert_called_with(
[image_fakes.IMAGE],
attr='a',
@@ -655,9 +646,7 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with(
- marker=image_fakes.image_id,
- )
+ self.api_mock.image_list.assert_called_with()
si_mock.assert_called_with(
[image_fakes.IMAGE],
'name:asc'
@@ -666,6 +655,44 @@ class TestImageList(TestImage):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
+ def test_image_list_limit_option(self):
+ arglist = [
+ '--limit', str(1),
+ ]
+ verifylist = [
+ ('limit', 1),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.api_mock.image_list.assert_called_with(
+ limit=1,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(len(self.datalist), len(tuple(data)))
+
+ @mock.patch('openstackclient.common.utils.find_resource')
+ def test_image_list_marker_option(self, fr_mock):
+ # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id
+ # operation. Will fix this by using FakeImage class instead
+ # of IMAGE dict.
+ fr_mock.return_value = mock.Mock()
+ fr_mock.return_value.id = image_fakes.image_id
+
+ arglist = [
+ '--marker', image_fakes.image_name,
+ ]
+ verifylist = [
+ ('marker', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.api_mock.image_list.assert_called_with(
+ marker=image_fakes.image_id,
+ )
+
class TestRemoveProjectImage(TestImage):
diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py
new file mode 100644
index 00000000..a3396b9d
--- /dev/null
+++ b/openstackclient/tests/network/test_common.py
@@ -0,0 +1,103 @@
+# 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 argparse
+import mock
+
+from openstackclient.network import common
+from openstackclient.tests import utils
+
+
+class FakeNetworkAndComputeCommand(common.NetworkAndComputeCommand):
+ def update_parser_common(self, parser):
+ parser.add_argument(
+ 'common',
+ metavar='<common>',
+ help='Common argument',
+ )
+ return parser
+
+ def update_parser_network(self, parser):
+ parser.add_argument(
+ 'network',
+ metavar='<network>',
+ help='Network argument',
+ )
+ return parser
+
+ def update_parser_compute(self, parser):
+ parser.add_argument(
+ 'compute',
+ metavar='<compute>',
+ help='Compute argument',
+ )
+ return parser
+
+ def take_action_network(self, client, parsed_args):
+ client.network_action(parsed_args)
+ return 'take_action_network'
+
+ def take_action_compute(self, client, parsed_args):
+ client.compute_action(parsed_args)
+ return 'take_action_compute'
+
+
+class TestNetworkAndComputeCommand(utils.TestCommand):
+ def setUp(self):
+ super(TestNetworkAndComputeCommand, self).setUp()
+
+ self.namespace = argparse.Namespace()
+
+ # Create network client mocks.
+ self.app.client_manager.network = mock.Mock()
+ self.network = self.app.client_manager.network
+ self.network.network_action = mock.Mock(return_value=None)
+
+ # Create compute client mocks.
+ self.app.client_manager.compute = mock.Mock()
+ self.compute = self.app.client_manager.compute
+ self.compute.compute_action = mock.Mock(return_value=None)
+
+ # Get the command object to test
+ self.cmd = FakeNetworkAndComputeCommand(self.app, self.namespace)
+
+ def test_take_action_network(self):
+ arglist = [
+ 'common',
+ 'network'
+ ]
+ verifylist = [
+ ('common', 'common'),
+ ('network', 'network')
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.network.network_action.assert_called_with(parsed_args)
+ self.assertEqual('take_action_network', result)
+
+ def test_take_action_compute(self):
+ arglist = [
+ 'common',
+ 'compute'
+ ]
+ verifylist = [
+ ('common', 'common'),
+ ('compute', 'compute')
+ ]
+
+ self.app.client_manager.network_endpoint_enabled = False
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.compute.compute_action.assert_called_with(parsed_args)
+ self.assertEqual('take_action_compute', result)
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index 4c862bd3..516995eb 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -69,6 +69,59 @@ class TestNetworkV2(utils.TestCommand):
)
+class FakeAvailabilityZone(object):
+ """Fake one or more network availability zones (AZs)."""
+
+ @staticmethod
+ def create_one_availability_zone(attrs={}, methods={}):
+ """Create a fake AZ.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :return:
+ A FakeResource object with name, state, etc.
+ """
+ # Set default attributes.
+ availability_zone = {
+ 'name': uuid.uuid4().hex,
+ 'state': 'available',
+ 'resource': 'network',
+ }
+
+ # Overwrite default attributes.
+ availability_zone.update(attrs)
+
+ availability_zone = fakes.FakeResource(
+ info=copy.deepcopy(availability_zone),
+ methods=methods,
+ loaded=True)
+ return availability_zone
+
+ @staticmethod
+ def create_availability_zones(attrs={}, methods={}, count=2):
+ """Create multiple fake AZs.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :param int count:
+ The number of AZs to fake
+ :return:
+ A list of FakeResource objects faking the AZs
+ """
+ availability_zones = []
+ for i in range(0, count):
+ availability_zone = \
+ FakeAvailabilityZone.create_one_availability_zone(
+ attrs, methods)
+ availability_zones.append(availability_zone)
+
+ return availability_zones
+
+
class FakeNetwork(object):
"""Fake one or more networks."""
@@ -96,7 +149,6 @@ class FakeNetwork(object):
'subnets': ['a', 'b'],
'provider_network_type': 'vlan',
'router_external': True,
- 'is_dirty': True,
'availability_zones': [],
'availability_zone_hints': [],
}
@@ -172,15 +224,31 @@ class FakePort(object):
:param Dictionary methods:
A dictionary with all methods
:return:
- A FakeResource object, with id, name, admin_state_up,
- status, tenant_id
+ A FakeResource object, with id, name, etc.
"""
+
# Set default attributes.
port_attrs = {
+ 'admin_state_up': True,
+ 'allowed_address_pairs': [{}],
+ 'binding:host_id': 'binding-host-id-' + uuid.uuid4().hex,
+ 'binding:profile': {},
+ 'binding:vif_details': {},
+ 'binding:vif_type': 'ovs',
+ 'binding:vnic_type': 'normal',
+ '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': [{}],
'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': [],
'status': 'ACTIVE',
- 'admin_state_up': True,
'tenant_id': 'project-id-' + uuid.uuid4().hex,
}
@@ -188,7 +256,16 @@ class FakePort(object):
port_attrs.update(attrs)
# Set default methods.
- port_methods = {}
+ port_methods = {
+ 'keys': ['admin_state_up', 'allowed_address_pairs',
+ 'binding:host_id', 'binding:profile',
+ 'binding:vif_details', 'binding:vif_type',
+ 'binding:vnic_type', 'device_id', 'device_owner',
+ 'dns_assignment', 'dns_name', 'extra_dhcp_opts',
+ 'fixed_ips', 'id', 'mac_address', 'name',
+ 'network_id', 'port_security_enabled',
+ 'security_groups', 'status', 'tenant_id'],
+ }
# Overwrite default methods.
port_methods.update(methods)
@@ -196,6 +273,15 @@ class FakePort(object):
port = fakes.FakeResource(info=copy.deepcopy(port_attrs),
methods=copy.deepcopy(port_methods),
loaded=True)
+
+ # Set attributes with special mappings.
+ 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']
+
return port
@staticmethod
@@ -263,6 +349,8 @@ class FakeRouter(object):
'tenant_id': 'project-id-' + uuid.uuid4().hex,
'routes': [],
'external_gateway_info': {},
+ 'availability_zone_hints': [],
+ 'availability_zones': [],
}
# Overwrite default attributes.
@@ -321,6 +409,84 @@ class FakeRouter(object):
return mock.MagicMock(side_effect=routers)
+class FakeSecurityGroup(object):
+ """Fake one or more security groups."""
+
+ @staticmethod
+ def create_one_security_group(attrs={}, methods={}):
+ """Create a fake security group.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :return:
+ A FakeResource object, with id, name, etc.
+ """
+ # Set default attributes.
+ security_group_attrs = {
+ '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,
+ 'security_group_rules': [],
+ }
+
+ # Overwrite default attributes.
+ security_group_attrs.update(attrs)
+
+ # Set default methods.
+ security_group_methods = {}
+
+ # Overwrite default methods.
+ security_group_methods.update(methods)
+
+ security_group = fakes.FakeResource(
+ info=copy.deepcopy(security_group_attrs),
+ methods=copy.deepcopy(security_group_methods),
+ loaded=True)
+ return security_group
+
+ @staticmethod
+ def create_security_groups(attrs={}, methods={}, count=2):
+ """Create multiple fake security groups.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :param int count:
+ The number of security groups to fake
+ :return:
+ A list of FakeResource objects faking the security groups
+ """
+ security_groups = []
+ for i in range(0, count):
+ security_groups.append(
+ FakeRouter.create_one_security_group(attrs, methods))
+
+ return security_groups
+
+ @staticmethod
+ def get_security_groups(security_groups=None, count=2):
+ """Get an iterable MagicMock object with a list of faked security groups.
+
+ If security group list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List security groups:
+ A list of FakeResource objects faking security groups
+ :param int count:
+ The number of security groups to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ security groups
+ """
+ if security_groups is None:
+ security_groups = FakeRouter.create_security_groups(count)
+ return mock.MagicMock(side_effect=security_groups)
+
+
class FakeSubnet(object):
"""Fake one or more subnets."""
diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py
index 37cc6674..f96497a4 100644
--- a/openstackclient/tests/network/v2/test_network.py
+++ b/openstackclient/tests/network/v2/test_network.py
@@ -440,8 +440,6 @@ class TestSetNetwork(TestNetwork):
self.cmd = network.SetNetwork(self.app, self.namespace)
def test_set_this(self):
- self._network.is_dirty = True
-
arglist = [
self._network.name,
'--enable',
@@ -458,12 +456,15 @@ class TestSetNetwork(TestNetwork):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.network.update_network.assert_called_with(self._network)
+ attrs = {
+ 'name': 'noob',
+ 'admin_state_up': True,
+ 'shared': True,
+ }
+ self.network.update_network.assert_called_with(self._network, **attrs)
self.assertIsNone(result)
def test_set_that(self):
- self._network.is_dirty = True
-
arglist = [
self._network.name,
'--disable',
@@ -478,12 +479,14 @@ class TestSetNetwork(TestNetwork):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.network.update_network.assert_called_with(self._network)
+ attrs = {
+ 'admin_state_up': False,
+ 'shared': False,
+ }
+ self.network.update_network.assert_called_with(self._network, **attrs)
self.assertIsNone(result)
def test_set_nothing(self):
- self._network.is_dirty = False
-
arglist = [self._network.name, ]
verifylist = [('identifier', self._network.name), ]
diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py
index a1ddefa1..bc246bd8 100644
--- a/openstackclient/tests/network/v2/test_port.py
+++ b/openstackclient/tests/network/v2/test_port.py
@@ -13,8 +13,10 @@
import mock
+from openstackclient.common import utils
from openstackclient.network.v2 import port
from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils as tests_utils
class TestPort(network_fakes.TestNetworkV2):
@@ -51,3 +53,88 @@ class TestDeletePort(TestPort):
result = self.cmd.take_action(parsed_args)
self.network.delete_port.assert_called_with(self._port)
self.assertIsNone(result)
+
+
+class TestShowPort(TestPort):
+
+ # The port to show.
+ _port = network_fakes.FakePort.create_one_port()
+
+ columns = (
+ 'admin_state_up',
+ 'allowed_address_pairs',
+ 'binding_host_id',
+ 'binding_profile',
+ 'binding_vif_details',
+ 'binding_vif_type',
+ 'binding_vnic_type',
+ 'device_id',
+ 'device_owner',
+ 'dns_assignment',
+ 'dns_name',
+ 'extra_dhcp_opts',
+ 'fixed_ips',
+ 'id',
+ 'mac_address',
+ 'name',
+ 'network_id',
+ 'port_security_enabled',
+ 'project_id',
+ 'security_groups',
+ 'status',
+ )
+
+ data = (
+ port._format_admin_state(_port.admin_state_up),
+ utils.format_list_of_dicts(_port.allowed_address_pairs),
+ _port.binding_host_id,
+ utils.format_dict(_port.binding_profile),
+ utils.format_dict(_port.binding_vif_details),
+ _port.binding_vif_type,
+ _port.binding_vnic_type,
+ _port.device_id,
+ _port.device_owner,
+ utils.format_list_of_dicts(_port.dns_assignment),
+ _port.dns_name,
+ utils.format_list_of_dicts(_port.extra_dhcp_opts),
+ utils.format_list_of_dicts(_port.fixed_ips),
+ _port.id,
+ _port.mac_address,
+ _port.name,
+ _port.network_id,
+ _port.port_security_enabled,
+ _port.project_id,
+ utils.format_list(_port.security_groups),
+ _port.status,
+ )
+
+ def setUp(self):
+ super(TestShowPort, self).setUp()
+
+ self.network.find_port = mock.Mock(return_value=self._port)
+
+ # Get the command object to test
+ self.cmd = port.ShowPort(self.app, self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self._port.name,
+ ]
+ verifylist = [
+ ('port', self._port.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_port.assert_called_with(self._port.name,
+ ignore_missing=False)
+ self.assertEqual(tuple(self.columns), columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py
index fba6e192..98e9f17a 100644
--- a/openstackclient/tests/network/v2/test_router.py
+++ b/openstackclient/tests/network/v2/test_router.py
@@ -14,6 +14,7 @@
import mock
from openstackclient.common import exceptions
+from openstackclient.common import utils as osc_utils
from openstackclient.network.v2 import router
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -88,6 +89,31 @@ class TestCreateRouter(TestRouter):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_create_with_AZ_hints(self):
+ arglist = [
+ self.new_router.name,
+ '--availability-zone-hint', 'fake-az',
+ '--availability-zone-hint', 'fake-az2',
+ ]
+ verifylist = [
+ ('name', self.new_router.name),
+ ('availability_zone_hints', ['fake-az', 'fake-az2']),
+ ('admin_state_up', True),
+ ('distributed', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+ self.network.create_router.assert_called_with(**{
+ 'admin_state_up': True,
+ 'name': self.new_router.name,
+ 'distributed': False,
+ 'availability_zone_hints': ['fake-az', 'fake-az2'],
+ })
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestDeleteRouter(TestRouter):
@@ -135,6 +161,7 @@ class TestListRouter(TestRouter):
columns_long = columns + (
'Routes',
'External gateway info',
+ 'Availability zones'
)
data = []
@@ -155,6 +182,7 @@ class TestListRouter(TestRouter):
data[i] + (
r.routes,
router._format_external_gateway_info(r.external_gateway_info),
+ osc_utils.format_list(r.availability_zones),
)
)
diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py
new file mode 100644
index 00000000..98388ec7
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_security_group.py
@@ -0,0 +1,99 @@
+# 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 security_group
+from openstackclient.tests.network.v2 import fakes as network_fakes
+
+
+class TestSecurityGroup(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestSecurityGroup, self).setUp()
+
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+
+ # Create compute client mocks.
+ self.app.client_manager.compute = mock.Mock()
+ self.compute = self.app.client_manager.compute
+ self.compute.security_groups = mock.Mock()
+
+
+class TestDeleteSecurityGroupNetwork(TestSecurityGroup):
+
+ # The security group to be deleted.
+ _security_group = \
+ network_fakes.FakeSecurityGroup.create_one_security_group()
+
+ def setUp(self):
+ super(TestDeleteSecurityGroupNetwork, self).setUp()
+
+ self.network.delete_security_group = mock.Mock(return_value=None)
+
+ self.network.find_security_group = mock.Mock(
+ return_value=self._security_group)
+
+ # Get the command object to test
+ self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace)
+
+ def test_security_group_delete(self):
+ arglist = [
+ self._security_group.name,
+ ]
+ verifylist = [
+ ('group', self._security_group.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.delete_security_group.assert_called_with(
+ self._security_group)
+ self.assertEqual(None, result)
+
+
+class TestDeleteSecurityGroupCompute(TestSecurityGroup):
+
+ # The security group to be deleted.
+ _security_group = \
+ network_fakes.FakeSecurityGroup.create_one_security_group()
+
+ def setUp(self):
+ super(TestDeleteSecurityGroupCompute, self).setUp()
+
+ self.app.client_manager.network_endpoint_enabled = False
+
+ self.compute.security_groups.delete = mock.Mock(return_value=None)
+
+ self.compute.security_groups.get = mock.Mock(
+ return_value=self._security_group)
+
+ # Get the command object to test
+ self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace)
+
+ def test_security_group_delete(self):
+ arglist = [
+ self._security_group.name,
+ ]
+ verifylist = [
+ ('group', self._security_group.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.compute.security_groups.delete.assert_called_with(
+ self._security_group.id)
+ self.assertEqual(None, result)
diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py
index 2e58e58d..2fc5c8ff 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -256,9 +256,10 @@ class FakeVolume(object):
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex},
'snapshot_id': random.randint(1, 5),
'availability_zone': 'zone' + uuid.uuid4().hex,
- 'attachments': {
+ 'attachments': [{
'device': '/dev/' + uuid.uuid4().hex,
- 'server_id': uuid.uuid4().hex},
+ 'server_id': uuid.uuid4().hex,
+ }, ],
}
# Overwrite default attributes if there are some attributes set
diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py
index 4c583abd..76c7a27a 100644
--- a/openstackclient/tests/volume/v2/test_volume.py
+++ b/openstackclient/tests/volume/v2/test_volume.py
@@ -362,6 +362,47 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(self.datalist, data)
+class TestVolumeDelete(TestVolume):
+ def setUp(self):
+ super(TestVolumeDelete, self).setUp()
+
+ self.volumes_mock.delete.return_value = None
+
+ # Get the command object to mock
+ self.cmd = volume.DeleteVolume(self.app, None)
+
+ def test_volume_delete_one_volume(self):
+ volumes = self.setup_volumes_mock(count=1)
+
+ arglist = [
+ volumes[0].id
+ ]
+ verifylist = [
+ ("volumes", [volumes[0].id])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+ self.volumes_mock.delete.assert_called_with(volumes[0].id)
+
+ def test_volume_delete_multi_volumes(self):
+ volumes = self.setup_volumes_mock(count=3)
+
+ arglist = [v.id for v in volumes]
+ verifylist = [
+ ('volumes', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ calls = [call(v.id) for v in volumes]
+
+ self.volumes_mock.delete.assert_has_calls(calls)
+
+
class TestVolumeList(TestVolume):
columns = [
@@ -695,44 +736,3 @@ class TestVolumeShow(TestVolume):
self.assertEqual(volume_fakes.VOLUME_columns, columns)
self.assertEqual(volume_fakes.VOLUME_data, data)
-
-
-class TestVolumeDelete(TestVolume):
- def setUp(self):
- super(TestVolumeDelete, self).setUp()
-
- self.volumes_mock.delete.return_value = None
-
- # Get the command object to mock
- self.cmd = volume.DeleteVolume(self.app, None)
-
- def test_volume_delete_one_volume(self):
- volumes = self.setup_volumes_mock(count=1)
-
- arglist = [
- volumes[0].id
- ]
- verifylist = [
- ("volumes", [volumes[0].id])
- ]
-
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- self.cmd.take_action(parsed_args)
- self.volumes_mock.delete.assert_called_with(volumes[0].id)
-
- def test_volume_delete_multi_volumes(self):
- volumes = self.setup_volumes_mock(count=3)
-
- arglist = [v.id for v in volumes]
- verifylist = [
- ('volumes', arglist),
- ]
-
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- self.cmd.take_action(parsed_args)
-
- calls = [call(v.id) for v in volumes]
-
- self.volumes_mock.delete.assert_has_calls(calls)