Merge tag '4.1.1' into debian/mitaka

python-neutronclient 4.1.1 release

meta:version: 4.1.1
meta:series: mitaka
meta:release-type: release
meta:announce: openstack-announce@lists.openstack.org
meta:pypi: yes
meta:first: no
This commit is contained in:
Thomas Goirand
2016-03-21 12:41:36 +01:00
99 changed files with 5011 additions and 589 deletions

2
babel.cfg Normal file
View File

@@ -0,0 +1,2 @@
[python: **.py]

View File

@@ -26,13 +26,13 @@ Transition to OpenStack Client
This document details the transition roadmap for moving the neutron client's
OpenStack Networking API support, both the Python library and the ``neutron``
command-line interface (CLI), to the
`OpenStack client (OSC) <https://github.com/openstack/python-openstackclient>`_
`OpenStack Client (OSC) <https://github.com/openstack/python-openstackclient>`_
and the `OpenStack Python SDK <https://github.com/openstack/python-openstacksdk>`_.
This transition is being guided by the
`Deprecate individual CLIs in favour of OSC <https://review.openstack.org/#/c/243348/>`_
OpenStack spec. See the `Neutron RFE <https://bugs.launchpad.net/neutron/+bug/1521291>`_ and
`OSC neutron-client blueprint <https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client>`_
for the overall progress of this transition.
OpenStack spec. See the `Neutron RFE <https://bugs.launchpad.net/neutron/+bug/1521291>`_,
`OSC neutron support etherpad <https://etherpad.openstack.org/p/osc-neutron-support>`_ and
details below for the overall progress of this transition.
Overview
--------
@@ -42,17 +42,15 @@ deprecated and then eventually removed. The ``neutron`` CLI will be replaced
by OSC's networking support available via the ``openstack`` CLI. This is
similar to the deprecation and removal process for the
`keystone client's <https://github.com/openstack/python-keystoneclient>`_
``keystone`` CLI.
The neutron client's Python library won't be deprecated. It will be available
along side the networking support provided by the OpenStack Python SDK. However,
the OpenStack Python SDK will be used to implement OSC's networking support.
``keystone`` CLI. The neutron client's Python library won't be deprecated.
It will be available along side the networking support provided by the
OpenStack Python SDK.
Users of the neutron client's command extensions will need to transition to the
`OSC plugin system <http://docs.openstack.org/developer/python-openstackclient/plugins.html>`_
before the ``neutron`` CLI is removed. Such users will maintain their OSC plugin
within their own project and will be responsible for deprecating and removing
their ``neutron`` CLI extension.
commands within their own project and will be responsible for deprecating and
removing their ``neutron`` CLI extension.
Transition Steps
----------------
@@ -92,13 +90,12 @@ Transition Steps
* `Security Group Rule CRUD <https://bugs.launchpad.net/python-openstackclient/+bug/1519512>`_
6. **In Progress:** OSC enhances its networking support under the
`neutron-client <https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client>`_
OSC spec. At this point and when applicable, enhancements to the ``neutron``
6. **In Progress:** OSC continues enhancing its networking support.
At this point and when applicable, enhancements to the ``neutron``
CLI must also be made to the ``openstack`` CLI and the OpenStack Python SDK.
Enhancements to the networking support in the OpenStack Python SDK will be
handled via bugs. Neutron stadium users of the neutron client's command
extensions should start their transition to the OSC plugin system.
handled via bugs. Users of the neutron client's command extensions should
start their transition to the OSC plugin system.
See the developer guide section below for more information on this step.
7. **Not Started:** Deprecate the ``neutron`` CLI once the criteria below have
@@ -112,64 +109,116 @@ Transition Steps
equivalent to the ``neutron`` CLI and it contains sufficient functional
and unit test coverage.
* Neutron core and advanced services projects, Neutron documentation and
`DevStack <http://docs.openstack.org/developer/devstack/>`_ use ``openstack``
CLI instead of ``neutron`` CLI.
* `Neutron Stadium <http://docs.openstack.org/developer/neutron/stadium/sub_projects.html>`_
projects, Neutron documentation and `DevStack <http://docs.openstack.org/developer/devstack/>`_
use ``openstack`` CLI instead of ``neutron`` CLI.
* Most neutron stadium users of the neutron client's command extensions have
transitioned to the OSC plugin system and use the ``openstack`` CLI instead
of the ``neutron`` CLI.
* Most users of the neutron client's command extensions have transitioned
to the OSC plugin system and use the ``openstack`` CLI instead of the
``neutron`` CLI.
8. **Not Started:** Remove the ``neutron`` CLI after two deprecation cycles.
Developer Guide
---------------
The ``neutron`` CLI version 3.1.1, without extensions, supports over 200
commands while the ``openstack`` CLI version 2.0.1 supports about 20
networking commands. Of the 20 commands, most do not have all of the options
The ``neutron`` CLI version 4.x, without extensions, supports over 200
commands while the ``openstack`` CLI version 2.1.0 supports about 30
networking commands. Of the 30 commands, many do not have all of the options
or arguments of their ``neutron`` CLI equivalent. With this large functional
gap, one critical question for developers during this transition is "Which
CLI do I change?" The answer depends on the state of a command and the
state of the overall transition. Details are outlined in the table
below. Early stages of the transition will require dual maintenance.
Eventually, dual maintenance will be reduced to critical bug fixes only
with feature requests only being made to the ``openstack`` CLI.
gap, a couple critical questions for developers during this transition are "Which
CLI do I change?" and "Where does my CLI belong?" The answer depends on the
state of a command and the state of the overall transition. Details are
outlined in the tables below. Early stages of the transition will require dual
maintenance. Eventually, dual maintenance will be reduced to critical bug fixes
only with feature requests only being made to the ``openstack`` CLI.
+----------------------+------------------------+----------------------------------------------+
| neutron Command | openstack Command | CLI to Change |
+======================+========================+==============================================+
| Exists | Doesn't Exist | neutron |
+----------------------+------------------------+----------------------------------------------+
| Exists | In Progress | neutron and update related OSC bug |
+----------------------+------------------------+----------------------------------------------+
| Exists | Exists | neutron and openstack |
+----------------------+------------------------+----------------------------------------------+
| Doesn't Exist | Doesn't Exist | neutron and openstack |
+----------------------+------------------------+----------------------------------------------+
| Doesn't Exist | Exists | openstack |
+----------------------+------------------------+----------------------------------------------+
**Which CLI do I change?**
When adding or updating an ``openstack`` networking command, changes may
first be required to the OpenStack Python SDK to support the underlying
networking resource object, properties and/or actions. Once the OpenStack
Python SDK changes are merged, the related OSC changes can be merged.
The OSC changes may require an update to the OSC openstacksdk version in the
+----------------------+------------------------+-------------------------------------------------+
| ``neutron`` Command | ``openstack`` Command | CLI to Change |
+======================+========================+=================================================+
| Exists | Doesn't Exist | ``neutron`` |
+----------------------+------------------------+-------------------------------------------------+
| Exists | In Progress | ``neutron`` and ``openstack`` |
| | | (update related blueprint or bug) |
+----------------------+------------------------+-------------------------------------------------+
| Exists | Exists | ``openstack`` |
| | | (assumes command parity resulting in |
| | | ``neutron`` being deprecated) |
+----------------------+------------------------+-------------------------------------------------+
| Doesn't Exist | Doesn't Exist | ``openstack`` |
+----------------------+------------------------+-------------------------------------------------+
**Where does my CLI belong?**
+---------------------------+-------------------+-------------------------------------------------+
| Networking Commands | OSC Plugin | OpenStack Project for ``openstack`` Commands |
+===========================+===================+=================================================+
| Core (Stable) | No | python-openstackclient |
+---------------------------+-------------------+-------------------------------------------------+
| Core (New/Experimental) | Yes | python-neutronclient |
| | | (with possible move to python-openstackclient) |
+---------------------------+-------------------+-------------------------------------------------+
| LBaaS v2 | Yes | neutron-lbaas |
+---------------------------+-------------------+-------------------------------------------------+
| VPNaaS v2 | Yes | neutron-vpnaas |
+---------------------------+-------------------+-------------------------------------------------+
| FWaaS v2 | Yes | neutron-fwaas |
+---------------------------+-------------------+-------------------------------------------------+
| LBaaS v1 | N/A | None (deprecated) |
+---------------------------+-------------------+-------------------------------------------------+
| FWaaS v1 | N/A | None (deprecated) |
+---------------------------+-------------------+-------------------------------------------------+
| Other | Yes | Applicable project owning networking resource |
+---------------------------+-------------------+-------------------------------------------------+
The following network resources are part of the "Core (Stable)" group:
- availability zone
- extension
- floating ip
- network
- port
- quota
- rbac
- router
- security group
- security group rule
- subnet
- subnet pool
When adding or updating an ``openstack`` networking command to
python-openstackclient, changes may first be required to the
OpenStack Python SDK to support the underlying networking resource object,
properties and/or actions. Once the OpenStack Python SDK changes are merged,
the related OSC changes can be merged. The OSC changes may require an update
to the OSC openstacksdk version in the
`requirements.txt <https://github.com/openstack/python-openstackclient/blob/master/requirements.txt>`_
file.
file. ``openstack`` networking commands outside python-openstackclient
are encouraged but not required to use the OpenStack Python SDK.
Neutron stadium users of the neutron client's command extensions must adopt the
When adding an ``openstack`` networking command to python-openstackclient,
you can optionally propose an
`OSC command spec <https://github.com/openstack/python-openstackclient/blob/master/doc/source/specs/commands.rst>`_
which documents the new command interface before proceeding with the implementation.
Users of the neutron client's command extensions must adopt the
`OSC plugin system <http://docs.openstack.org/developer/python-openstackclient/plugins.html>`_
for this transition. Such users will maintain their OSC plugin within their
own project and should follow the guidance in the table above to determine
which CLI to change.
which command to change.
Developer References
--------------------
* See `OSC neutron-client blueprint <https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client>`_
to determine if an ``openstack`` command is in progress. See the ``Related bugs`` list.
* See `OSC neutron support etherpad <https://etherpad.openstack.org/p/osc-neutron-support>`_
to determine if an ``openstack`` command is in progress.
* See `OSC command list <http://docs.openstack.org/developer/python-openstackclient/command-list.html>`_
to determine if an ``openstack`` command exists.
* See `OSC command spec list <https://github.com/openstack/python-openstackclient/tree/master/doc/source/specs/command-objects>`_
to determine if an ``openstack`` command spec exists.
* See `OSC plugin command list <http://docs.openstack.org/developer/python-openstackclient/plugin-commands.html>`_
to determine if an ``openstack`` plugin command exists.
* See `OSC command structure <http://docs.openstack.org/developer/python-openstackclient/commands.html>`_
@@ -178,5 +227,6 @@ Developer References
for guidance on creating new OSC command interfaces.
* See `OSC plugin <http://docs.openstack.org/developer/python-openstackclient/plugins.html>`_
for information on the OSC plugin system to be used for ``neutron`` CLI extensions.
* Create an OSC blueprint: https://blueprints.launchpad.net/python-openstackclient/
* Report an OSC bug: https://bugs.launchpad.net/python-openstackclient/+filebug
* Report an OpenStack Python SDK bug: https://bugs.launchpad.net/python-openstacksdk/+filebug

View File

@@ -54,6 +54,18 @@ Now you can call various methods on the client instance.
Alternatively, you can create a client instance using an auth token
and a service endpoint URL directly.
.. code-block:: python
>>> from neutronclient.v2_0 import client
>>> neutron = client.Client(endpoint_url='http://192.168.206.130:9696/',
token='d3f9226f27774f338019aa2611112ef6')
... token='d3f9226f27774f338019aa2611112ef6')
You can get ``X-Openstack-Request-Id`` as ``request_ids`` from the result.
.. code-block:: python
>>> network = {'name': 'mynetwork', 'admin_state_up': True}
>>> neutron.create_network({'network':network})
>>> networks = neutron.list_networks(name='mynetwork')
>>> print networks.request_ids
['req-978a0160-7ab0-44f0-8a93-08e9a4e785fa']

View File

@@ -1,35 +0,0 @@
# Copyright 2012 OpenStack Foundation.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from cliff import command
class OpenStackCommand(command.Command):
"""Base class for OpenStack commands."""
api = None
def run(self, parsed_args):
if not self.api:
return
else:
return super(OpenStackCommand, self).run(parsed_args)
def get_data(self, parsed_args):
pass
def take_action(self, parsed_args):
return self.get_data(parsed_args)

View File

@@ -60,10 +60,20 @@ class NeutronClientException(NeutronException):
"""
status_code = 0
req_ids_msg = _("Neutron server returns request_ids: %s")
request_ids = []
def __init__(self, message=None, **kwargs):
self.request_ids = kwargs.get('request_ids')
if 'status_code' in kwargs:
self.status_code = kwargs['status_code']
if self.request_ids:
req_ids_msg = self.req_ids_msg % self.request_ids
if message:
message = _('%(msg)s\n%(id)s') % {'msg': message,
'id': req_ids_msg}
else:
message = req_ids_msg
super(NeutronClientException, self).__init__(message, **kwargs)

View File

@@ -31,56 +31,56 @@ class NeutronClientExtension(neutronV20.NeutronCommand):
class ClientExtensionShow(NeutronClientExtension, neutronV20.ShowCommand):
def get_data(self, parsed_args):
def take_action(self, parsed_args):
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
# for any implementers adding extensions with
# regard to any other extension verb.
return self.execute(parsed_args)
def execute(self, parsed_args):
return super(ClientExtensionShow, self).get_data(parsed_args)
return super(ClientExtensionShow, self).take_action(parsed_args)
class ClientExtensionList(NeutronClientExtension, neutronV20.ListCommand):
def get_data(self, parsed_args):
def take_action(self, parsed_args):
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
# for any implementers adding extensions with
# regard to any other extension verb.
return self.execute(parsed_args)
def execute(self, parsed_args):
return super(ClientExtensionList, self).get_data(parsed_args)
return super(ClientExtensionList, self).take_action(parsed_args)
class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand):
def run(self, parsed_args):
def take_action(self, parsed_args):
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
# for any implementers adding extensions with
# regard to any other extension verb.
return self.execute(parsed_args)
def execute(self, parsed_args):
return super(ClientExtensionDelete, self).run(parsed_args)
return super(ClientExtensionDelete, self).take_action(parsed_args)
class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand):
def get_data(self, parsed_args):
def take_action(self, parsed_args):
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
# for any implementers adding extensions with
# regard to any other extension verb.
return self.execute(parsed_args)
def execute(self, parsed_args):
return super(ClientExtensionCreate, self).get_data(parsed_args)
return super(ClientExtensionCreate, self).take_action(parsed_args)
class ClientExtensionUpdate(NeutronClientExtension, neutronV20.UpdateCommand):
def run(self, parsed_args):
def take_action(self, parsed_args):
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
# for any implementers adding extensions with
# regard to any other extension verb.
return self.execute(parsed_args)
def execute(self, parsed_args):
return super(ClientExtensionUpdate, self).run(parsed_args)
return super(ClientExtensionUpdate, self).take_action(parsed_args)

View File

@@ -18,6 +18,7 @@
"""Utilities and helper functions."""
import argparse
import functools
import logging
import netaddr
import os
@@ -46,6 +47,10 @@ def convert_to_uppercase(string):
return string.upper()
def convert_to_lowercase(string):
return string.lower()
def get_client_class(api_name, version, version_map):
"""Returns the client class for the requested API version.
@@ -104,10 +109,21 @@ def str2bool(strbool):
return strbool.lower() == 'true'
def str2dict(strdict):
def str2dict(strdict, required_keys=None, optional_keys=None):
"""Convert key1=value1,key2=value2,... string into dictionary.
:param strdict: key1=value1,key2=value2
:param strdict: string in the form of key1=value1,key2=value2
:param required_keys: list of required keys. All keys in this list must be
specified. Otherwise ArgumentTypeError will be raised.
If this parameter is unspecified, no required key check
will be done.
:param optional_keys: list of optional keys.
This parameter is used for valid key check.
When at least one of required_keys and optional_keys,
a key must be a member of either of required_keys or
optional_keys. Otherwise, ArgumentTypeError will be
raised. When both required_keys and optional_keys are
unspecified, no valid key check will be done.
"""
result = {}
if strdict:
@@ -117,9 +133,29 @@ def str2dict(strdict):
msg = _("invalid key-value '%s', expected format: key=value")
raise argparse.ArgumentTypeError(msg % kv)
result[key] = value
valid_keys = set(required_keys or []) | set(optional_keys or [])
if valid_keys:
invalid_keys = [k for k in result if k not in valid_keys]
if invalid_keys:
msg = _("Invalid key(s) '%(invalid_keys)s' specified. "
"Valid key(s): '%(valid_keys)s'.")
raise argparse.ArgumentTypeError(
msg % {'invalid_keys': ', '.join(sorted(invalid_keys)),
'valid_keys': ', '.join(sorted(valid_keys))})
if required_keys:
not_found_keys = [k for k in required_keys if k not in result]
if not_found_keys:
msg = _("Required key(s) '%s' not specified.")
raise argparse.ArgumentTypeError(msg % ', '.join(not_found_keys))
return result
def str2dict_type(optional_keys=None, required_keys=None):
return functools.partial(str2dict,
optional_keys=optional_keys,
required_keys=required_keys)
def http_log_req(_logger, args, kwargs):
if not _logger.isEnabledFor(logging.DEBUG):
return

View File

@@ -18,17 +18,17 @@ from __future__ import print_function
import abc
import argparse
import functools
import logging
import re
from cliff.formatters import table
from cliff import command
from cliff import lister
from cliff import show
from oslo_serialization import jsonutils
import six
from neutronclient._i18n import _
from neutronclient.common import command
from neutronclient.common import exceptions
from neutronclient.common import utils
@@ -36,6 +36,7 @@ HEX_ELEM = '[0-9A-Fa-f]'
UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
HEX_ELEM + '{4}', HEX_ELEM + '{4}',
HEX_ELEM + '{12}'])
HYPHEN_OPTS = ['tags_any', 'not_tags', 'not_tags_any']
def _get_resource_plural(resource, client):
@@ -69,9 +70,8 @@ def find_resource_by_id(client, resource, resource_id, cmd_resource=None,
not_found_message = (_("Unable to find %(resource)s with id "
"'%(id)s'") %
{'resource': resource, 'id': resource_id})
# 404 is used to simulate server side behavior
raise exceptions.NeutronClientException(
message=not_found_message, status_code=404)
# 404 is raised by exceptions.NotFound to simulate serverside behavior
raise exceptions.NotFound(message=not_found_message)
def find_resourceid_by_id(client, resource, resource_id, cmd_resource=None,
@@ -106,9 +106,8 @@ def _find_resource_by_name(client, resource, name, project_id=None,
not_found_message = (_("Unable to find %(resource)s with name "
"'%(name)s'") %
{'resource': resource, 'name': name})
# 404 is used to simulate server side behavior
raise exceptions.NeutronClientException(
message=not_found_message, status_code=404)
# 404 is raised by exceptions.NotFound to simulate serverside behavior
raise exceptions.NotFound(message=not_found_message)
else:
return info[0]
@@ -119,10 +118,18 @@ def find_resource_by_name_or_id(client, resource, name_or_id,
try:
return find_resource_by_id(client, resource, name_or_id,
cmd_resource, parent_id, fields)
except exceptions.NeutronClientException:
return _find_resource_by_name(client, resource, name_or_id,
project_id, cmd_resource, parent_id,
fields)
except exceptions.NotFound:
try:
return _find_resource_by_name(client, resource, name_or_id,
project_id, cmd_resource, parent_id,
fields)
except exceptions.NotFound:
not_found_message = (_("Unable to find %(resource)s with name "
"or id '%(name_or_id)s'") %
{'resource': resource,
'name_or_id': name_or_id})
raise exceptions.NotFound(
message=not_found_message)
def find_resourceid_by_name_or_id(client, resource, name_or_id,
@@ -353,7 +360,7 @@ def _merge_args(qCmd, parsed_args, _extra_values, value_specs):
if isinstance(arg_value, list):
if value and isinstance(value, list):
if (not arg_value or
type(arg_value[0]) == type(value[0])):
isinstance(arg_value[0], type(value[0]))):
arg_value.extend(value)
_extra_values.pop(key)
@@ -370,20 +377,7 @@ def update_dict(obj, dict, attributes):
dict[attribute] = getattr(obj, attribute)
class TableFormater(table.TableFormatter):
"""This class is used to keep consistency with prettytable 0.6.
https://bugs.launchpad.net/python-neutronclient/+bug/1165962
"""
def emit_list(self, column_names, data, stdout, parsed_args):
if column_names:
super(TableFormater, self).emit_list(column_names, data, stdout,
parsed_args)
else:
stdout.write('\n')
# command.OpenStackCommand is abstract class so that metaclass of
# cliff.command.Command is abstract class so that metaclass of
# subclass must be subclass of metaclass of all its base.
# otherwise metaclass conflict exception is raised.
class NeutronCommandMeta(abc.ABCMeta):
@@ -396,22 +390,17 @@ class NeutronCommandMeta(abc.ABCMeta):
@six.add_metaclass(NeutronCommandMeta)
class NeutronCommand(command.OpenStackCommand):
class NeutronCommand(command.Command):
api = 'network'
values_specs = []
json_indent = None
resource = None
shadow_resource = None
parent_id = None
def __init__(self, app, app_args):
super(NeutronCommand, self).__init__(app, app_args)
# NOTE(markmcclain): This is no longer supported in cliff version 1.5.2
# see https://bugs.launchpad.net/python-neutronclient/+bug/1265926
# if hasattr(self, 'formatters'):
# self.formatters['table'] = TableFormater()
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
return super(NeutronCommand, self).run(parsed_args)
@property
def cmd_resource(self):
@@ -468,7 +457,6 @@ class NeutronCommand(command.OpenStackCommand):
class CreateCommand(NeutronCommand, show.ShowOne):
"""Create a resource for a given tenant."""
api = 'network'
log = None
def get_parser(self, prog_name):
@@ -482,8 +470,7 @@ class CreateCommand(NeutronCommand, show.ShowOne):
self.add_known_arguments(parser)
return parser
def get_data(self, parsed_args):
self.log.debug('get_data(%s)' % parsed_args)
def take_action(self, parsed_args):
self.set_extra_attrs(parsed_args)
neutron_client = self.get_client()
_extra_values = parse_args_to_dict(self.values_specs)
@@ -510,9 +497,9 @@ class CreateCommand(NeutronCommand, show.ShowOne):
class UpdateCommand(NeutronCommand):
"""Update resource's information."""
api = 'network'
log = None
allow_names = True
help_resource = None
def get_parser(self, prog_name):
parser = super(UpdateCommand, self).get_parser(prog_name)
@@ -520,14 +507,15 @@ class UpdateCommand(NeutronCommand):
help_str = _('ID or name of %s to update.')
else:
help_str = _('ID of %s to update.')
if not self.help_resource:
self.help_resource = self.resource
parser.add_argument(
'id', metavar=self.resource.upper(),
help=help_str % self.resource)
help=help_str % self.help_resource)
self.add_known_arguments(parser)
return parser
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
def take_action(self, parsed_args):
self.set_extra_attrs(parsed_args)
neutron_client = self.get_client()
_extra_values = parse_args_to_dict(self.values_specs)
@@ -565,9 +553,9 @@ class UpdateCommand(NeutronCommand):
class DeleteCommand(NeutronCommand):
"""Delete a given resource."""
api = 'network'
log = None
allow_names = True
help_resource = None
def get_parser(self, prog_name):
parser = super(DeleteCommand, self).get_parser(prog_name)
@@ -575,14 +563,15 @@ class DeleteCommand(NeutronCommand):
help_str = _('ID or name of %s to delete.')
else:
help_str = _('ID of %s to delete.')
if not self.help_resource:
self.help_resource = self.resource
parser.add_argument(
'id', metavar=self.resource.upper(),
help=help_str % self.resource)
help=help_str % self.help_resource)
self.add_known_arguments(parser)
return parser
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
def take_action(self, parsed_args):
self.set_extra_attrs(parsed_args)
neutron_client = self.get_client()
obj_deleter = getattr(neutron_client,
@@ -611,13 +600,43 @@ class DeleteCommand(NeutronCommand):
class ListCommand(NeutronCommand, lister.Lister):
"""List resources that belong to a given tenant."""
api = 'network'
log = None
_formatters = {}
list_columns = []
unknown_parts_flag = True
pagination_support = False
sorting_support = False
resource_plural = None
# A list to define arguments for filtering by attribute value
# CLI arguments are shown in the order of this list.
# Each element must be either of a string of an attribute name
# or a dict of a full attribute definitions whose format is:
# {'name': attribute name, (mandatory)
# 'help': help message for CLI (mandatory)
# 'boolean': boolean parameter or not. (Default: False) (optional)
# 'argparse_kwargs': a dict of parameters passed to
# argparse add_argument()
# (Default: {}) (optional)
# }
# For more details, see ListNetworks.filter_attrs.
filter_attrs = []
default_attr_defs = {
'name': {
'help': _("Filter %s according to their name."),
'boolean': False,
},
'tenant_id': {
'help': _('Filter %s belonging to the given tenant.'),
'boolean': False,
},
'admin_state_up': {
'help': _('Filter and list the %s whose administrative '
'state is active'),
'boolean': True,
},
}
def get_parser(self, prog_name):
parser = super(ListCommand, self).get_parser(prog_name)
@@ -627,8 +646,36 @@ class ListCommand(NeutronCommand, lister.Lister):
if self.sorting_support:
add_sorting_argument(parser)
self.add_known_arguments(parser)
self.add_filtering_arguments(parser)
return parser
def add_filtering_arguments(self, parser):
if not self.filter_attrs:
return
group_parser = parser.add_argument_group('filtering arguments')
collection = self.resource_plural or '%ss' % self.resource
for attr in self.filter_attrs:
if isinstance(attr, str):
# Use detail defined in default_attr_defs
attr_name = attr
attr_defs = self.default_attr_defs[attr]
else:
attr_name = attr['name']
attr_defs = attr
option_name = '--%s' % attr_name.replace('_', '-')
params = attr_defs.get('argparse_kwargs', {})
try:
help_msg = attr_defs['help'] % collection
except TypeError:
help_msg = attr_defs['help']
if attr_defs.get('boolean', False):
add_arg_func = functools.partial(utils.add_boolean_argument,
group_parser)
else:
add_arg_func = group_parser.add_argument
add_arg_func(option_name, help=help_msg, **params)
def args2search_opts(self, parsed_args):
search_opts = {}
fields = parsed_args.fields
@@ -636,6 +683,14 @@ class ListCommand(NeutronCommand, lister.Lister):
search_opts.update({'fields': fields})
if parsed_args.show_details:
search_opts.update({'verbose': 'True'})
filter_attrs = [field if isinstance(field, str) else field['name']
for field in self.filter_attrs]
for attr in filter_attrs:
val = getattr(parsed_args, attr, None)
if attr in HYPHEN_OPTS:
attr = attr.replace('_', '-')
if val:
search_opts[attr] = val
return search_opts
def call_server(self, neutron_client, search_opts, parsed_args):
@@ -706,8 +761,7 @@ class ListCommand(NeutronCommand, lister.Lister):
s, _columns, formatters=formatters, )
for s in info), )
def get_data(self, parsed_args):
self.log.debug('get_data(%s)', parsed_args)
def take_action(self, parsed_args):
self.set_extra_attrs(parsed_args)
data = self.retrieve_list(parsed_args)
self.extend_list(data, parsed_args)
@@ -717,9 +771,9 @@ class ListCommand(NeutronCommand, lister.Lister):
class ShowCommand(NeutronCommand, show.ShowOne):
"""Show information of a given resource."""
api = 'network'
log = None
allow_names = True
help_resource = None
def get_parser(self, prog_name):
parser = super(ShowCommand, self).get_parser(prog_name)
@@ -728,14 +782,15 @@ class ShowCommand(NeutronCommand, show.ShowOne):
help_str = _('ID or name of %s to look up.')
else:
help_str = _('ID of %s to look up.')
if not self.help_resource:
self.help_resource = self.resource
parser.add_argument(
'id', metavar=self.resource.upper(),
help=help_str % self.resource)
help=help_str % self.help_resource)
self.add_known_arguments(parser)
return parser
def get_data(self, parsed_args):
self.log.debug('get_data(%s)', parsed_args)
def take_action(self, parsed_args):
self.set_extra_attrs(parsed_args)
neutron_client = self.get_client()

View File

@@ -22,7 +22,7 @@ class ListAddressScope(neutronV20.ListCommand):
"""List address scopes that belong to a given tenant."""
resource = 'address_scope'
list_columns = ['id', 'name']
list_columns = ['id', 'name', 'ip_version']
pagination_support = True
sorting_support = True
@@ -45,10 +45,18 @@ class CreateAddressScope(neutronV20.CreateCommand):
help=_('Set the address scope as shared.'))
parser.add_argument(
'name',
metavar='NAME',
help=_('Specify the name of the address scope.'))
parser.add_argument(
'ip_version',
metavar='IP_VERSION',
type=int,
choices=[4, 6],
help=_('Specify the address family of the address scope.'))
def args2body(self, parsed_args):
body = {'name': parsed_args.name}
body = {'name': parsed_args.name,
'ip_version': parsed_args.ip_version}
if parsed_args.shared:
body['shared'] = True
neutronV20.update_dict(parsed_args, body, ['tenant_id'])

View File

@@ -32,14 +32,15 @@ class AddNetworkToDhcpAgent(neutronV20.NeutronCommand):
parser = super(AddNetworkToDhcpAgent, self).get_parser(prog_name)
parser.add_argument(
'dhcp_agent',
metavar='DHCP_AGENT',
help=_('ID of the DHCP agent.'))
parser.add_argument(
'network',
metavar='NETWORK',
help=_('Network to add.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_net_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'network', parsed_args.network)
@@ -56,14 +57,15 @@ class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand):
parser = super(RemoveNetworkFromDhcpAgent, self).get_parser(prog_name)
parser.add_argument(
'dhcp_agent',
metavar='DHCP_AGENT',
help=_('ID of the DHCP agent.'))
parser.add_argument(
'network',
metavar='NETWORK',
help=_('Network to remove.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_net_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'network', parsed_args.network)
@@ -83,6 +85,7 @@ class ListNetworksOnDhcpAgent(network.ListNetwork):
self).get_parser(prog_name)
parser.add_argument(
'dhcp_agent',
metavar='DHCP_AGENT',
help=_('ID of the DHCP agent.'))
return parser
@@ -105,6 +108,7 @@ class ListDhcpAgentsHostingNetwork(neutronV20.ListCommand):
self).get_parser(prog_name)
parser.add_argument(
'network',
metavar='NETWORK',
help=_('Network to query.'))
return parser
@@ -128,14 +132,15 @@ class AddRouterToL3Agent(neutronV20.NeutronCommand):
parser = super(AddRouterToL3Agent, self).get_parser(prog_name)
parser.add_argument(
'l3_agent',
metavar='L3_AGENT',
help=_('ID of the L3 agent.'))
parser.add_argument(
'router',
metavar='ROUTER',
help=_('Router to add.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'router', parsed_args.router)
@@ -152,14 +157,15 @@ class RemoveRouterFromL3Agent(neutronV20.NeutronCommand):
parser = super(RemoveRouterFromL3Agent, self).get_parser(prog_name)
parser.add_argument(
'l3_agent',
metavar='L3_AGENT',
help=_('ID of the L3 agent.'))
parser.add_argument(
'router',
metavar='ROUTER',
help=_('Router to remove.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'router', parsed_args.router)
@@ -183,6 +189,7 @@ class ListRoutersOnL3Agent(neutronV20.ListCommand):
self).get_parser(prog_name)
parser.add_argument(
'l3_agent',
metavar='L3_AGENT',
help=_('ID of the L3 agent to query.'))
return parser
@@ -204,6 +211,7 @@ class ListL3AgentsHostingRouter(neutronV20.ListCommand):
parser = super(ListL3AgentsHostingRouter,
self).get_parser(prog_name)
parser.add_argument('router',
metavar='ROUTER',
help=_('Router to query.'))
return parser
@@ -236,6 +244,7 @@ class ListPoolsOnLbaasAgent(neutronV20.ListCommand):
parser = super(ListPoolsOnLbaasAgent, self).get_parser(prog_name)
parser.add_argument(
'lbaas_agent',
metavar='LBAAS_AGENT',
help=_('ID of the loadbalancer agent to query.'))
return parser
@@ -260,6 +269,7 @@ class GetLbaasAgentHostingPool(neutronV20.ListCommand):
parser = super(GetLbaasAgentHostingPool,
self).get_parser(prog_name)
parser.add_argument('pool',
metavar='POOL',
help=_('Pool to query.'))
return parser
@@ -289,6 +299,7 @@ class ListLoadBalancersOnLbaasAgent(neutronV20.ListCommand):
prog_name)
parser.add_argument(
'lbaas_agent',
metavar='LBAAS_AGENT',
help=_('ID of the loadbalancer agent to query.'))
return parser
@@ -313,6 +324,7 @@ class GetLbaasAgentHostingLoadBalancer(neutronV20.ListCommand):
parser = super(GetLbaasAgentHostingLoadBalancer,
self).get_parser(prog_name)
parser.add_argument('loadbalancer',
metavar='LOADBALANCER',
help=_('LoadBalancer to query.'))
return parser

View File

@@ -0,0 +1,81 @@
# Copyright 2016 IBM
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import argparse
from cliff import show
from oslo_serialization import jsonutils
from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.neutron import v2_0
class ShowAutoAllocatedTopology(v2_0.NeutronCommand, show.ShowOne):
"""Show the auto-allocated topology of a given tenant."""
resource = 'auto_allocated_topology'
def get_parser(self, prog_name):
parser = super(ShowAutoAllocatedTopology, self).get_parser(prog_name)
parser.add_argument(
'--dry-run',
help=_('Validate the requirements for auto-allocated-topology. '
'(Does not return a topology.)'),
action='store_true')
parser.add_argument(
'--tenant-id', metavar='tenant-id',
help=_('The owner tenant ID.'))
# Allow people to do
# neutron auto-allocated-topology-show <tenant-id>
# (Only useful to users who can look at other tenants' topologies.)
# We use a different name for this arg because the default will
# override whatever is in the named arg otherwise.
parser.add_argument(
'pos_tenant_id',
help=argparse.SUPPRESS, nargs='?')
return parser
def take_action(self, parsed_args):
client = self.get_client()
extra_values = v2_0.parse_args_to_dict(self.values_specs)
if extra_values:
raise exceptions.CommandError(
_("Invalid argument(s): --%s") % ', --'.join(extra_values))
tenant_id = parsed_args.tenant_id or parsed_args.pos_tenant_id
if parsed_args.dry_run:
data = client.validate_auto_allocated_topology_requirements(
tenant_id)
else:
data = client.get_auto_allocated_topology(tenant_id)
if self.resource in data:
for k, v in data[self.resource].items():
if isinstance(v, list):
value = ""
for _item in v:
if value:
value += "\n"
if isinstance(_item, dict):
value += jsonutils.dumps(_item)
else:
value += str(_item)
data[self.resource][k] = value
elif v == "dry-run=pass":
return ("dry-run",), ("pass",)
elif v is None:
data[self.resource][k] = ''
return zip(*sorted(data[self.resource].items()))
else:
return None

View File

@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutronclient.i18n import _
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronv20

View File

@@ -0,0 +1,117 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from __future__ import print_function
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker
def add_common_args(parser):
parser.add_argument('dragent_id',
metavar='BGP_DRAGENT_ID',
help=_('ID of the Dynamic Routing agent.'))
parser.add_argument('bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
class AddBGPSpeakerToDRAgent(neutronV20.NeutronCommand):
"""Add a BGP speaker to a Dynamic Routing agent."""
def get_parser(self, prog_name):
parser = super(AddBGPSpeakerToDRAgent, self).get_parser(prog_name)
add_common_args(parser)
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
_speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
neutron_client.add_bgp_speaker_to_dragent(
parsed_args.dragent_id, {'bgp_speaker_id': _speaker_id})
print(_('Associated BGP speaker %s to the Dynamic Routing agent.')
% parsed_args.bgp_speaker, file=self.app.stdout)
class RemoveBGPSpeakerFromDRAgent(neutronV20.NeutronCommand):
"""Removes a BGP speaker from a Dynamic Routing agent."""
def get_parser(self, prog_name):
parser = super(RemoveBGPSpeakerFromDRAgent, self).get_parser(
prog_name)
add_common_args(parser)
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
_speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
neutron_client.remove_bgp_speaker_from_dragent(parsed_args.dragent_id,
_speaker_id)
print(_('Disassociated BGP speaker %s from the '
'Dynamic Routing agent.')
% parsed_args.bgp_speaker, file=self.app.stdout)
class ListBGPSpeakersOnDRAgent(neutronV20.ListCommand):
"""List BGP speakers hosted by a Dynamic Routing agent."""
list_columns = ['id', 'name', 'local_as', 'ip_version']
resource = 'bgp_speaker'
def get_parser(self, prog_name):
parser = super(ListBGPSpeakersOnDRAgent,
self).get_parser(prog_name)
parser.add_argument(
'dragent_id',
metavar='BGP_DRAGENT_ID',
help=_('ID of the Dynamic Routing agent.'))
return parser
def call_server(self, neutron_client, search_opts, parsed_args):
data = neutron_client.list_bgp_speaker_on_dragent(
parsed_args.dragent_id, **search_opts)
return data
class ListDRAgentsHostingBGPSpeaker(neutronV20.ListCommand):
"""List Dynamic Routing agents hosting a BGP speaker."""
resource = 'agent'
_formatters = {}
list_columns = ['id', 'host', 'admin_state_up', 'alive']
unknown_parts_flag = False
def get_parser(self, prog_name):
parser = super(ListDRAgentsHostingBGPSpeaker,
self).get_parser(prog_name)
parser.add_argument('bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
return parser
def extend_list(self, data, parsed_args):
for agent in data:
agent['alive'] = ":-)" if agent['alive'] else 'xxx'
def call_server(self, neutron_client, search_opts, parsed_args):
_speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
search_opts['bgp_speaker'] = _speaker_id
data = neutron_client.list_dragents_hosting_bgp_speaker(**search_opts)
return data

View File

@@ -0,0 +1,127 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.common import validators
from neutronclient.neutron import v2_0 as neutronv20
def get_bgp_peer_id(client, id_or_name):
return neutronv20.find_resourceid_by_name_or_id(client,
'bgp_peer',
id_or_name)
def validate_peer_attributes(parsed_args):
# Validate AS number
validators.validate_int_range(parsed_args, 'remote_as',
neutronv20.bgp.speaker.MIN_AS_NUM,
neutronv20.bgp.speaker.MAX_AS_NUM)
# Validate password
if parsed_args.auth_type != 'none' and parsed_args.password is None:
raise exceptions.CommandError(_('Must provide password if auth-type '
'is specified.'))
if parsed_args.auth_type == 'none' and parsed_args.password:
raise exceptions.CommandError(_('Must provide auth-type if password '
'is specified.'))
class ListPeers(neutronv20.ListCommand):
"""List BGP peers."""
resource = 'bgp_peer'
list_columns = ['id', 'name', 'peer_ip', 'remote_as']
pagination_support = True
sorting_support = True
class ShowPeer(neutronv20.ShowCommand):
"""Show information of a given BGP peer."""
resource = 'bgp_peer'
class CreatePeer(neutronv20.CreateCommand):
"""Create a BGP Peer."""
resource = 'bgp_peer'
def add_known_arguments(self, parser):
parser.add_argument(
'name',
metavar='NAME',
help=_('Name of the BGP peer to create.'))
parser.add_argument(
'--peer-ip',
metavar='PEER_IP_ADDRESS',
required=True,
help=_('Peer IP address.'))
parser.add_argument(
'--remote-as',
required=True,
metavar='PEER_REMOTE_AS',
help=_('Peer AS number. (Integer in [%(min_val)s, %(max_val)s] '
'is allowed.)') %
{'min_val': neutronv20.bgp.speaker.MIN_AS_NUM,
'max_val': neutronv20.bgp.speaker.MAX_AS_NUM})
parser.add_argument(
'--auth-type',
metavar='PEER_AUTH_TYPE',
choices=['none', 'md5'],
default='none',
type=utils.convert_to_lowercase,
help=_('Authentication algorithm. Supported algorithms: '
'none(default), md5'))
parser.add_argument(
'--password',
metavar='AUTH_PASSWORD',
help=_('Authentication password.'))
def args2body(self, parsed_args):
body = {}
validate_peer_attributes(parsed_args)
neutronv20.update_dict(parsed_args, body,
['name', 'peer_ip',
'remote_as', 'auth_type', 'password'])
return {self.resource: body}
class UpdatePeer(neutronv20.UpdateCommand):
"""Update BGP Peer's information."""
resource = 'bgp_peer'
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help=_('Updated name of the BGP peer.'))
parser.add_argument(
'--password',
metavar='AUTH_PASSWORD',
help=_('Updated authentication password.'))
def args2body(self, parsed_args):
body = {}
neutronv20.update_dict(parsed_args, body, ['name', 'password'])
return {self.resource: body}
class DeletePeer(neutronv20.DeleteCommand):
"""Delete a BGP peer."""
resource = 'bgp_peer'

View File

@@ -0,0 +1,277 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from __future__ import print_function
from neutronclient._i18n import _
from neutronclient.common import utils
from neutronclient.common import validators
from neutronclient.neutron import v2_0 as neutronv20
from neutronclient.neutron.v2_0.bgp import peer as bgp_peer
# Allowed BGP Autonomous number range
MIN_AS_NUM = 1
MAX_AS_NUM = 65535
def get_network_id(client, id_or_name):
return neutronv20.find_resourceid_by_name_or_id(client,
'network',
id_or_name)
def get_bgp_speaker_id(client, id_or_name):
return neutronv20.find_resourceid_by_name_or_id(client,
'bgp_speaker',
id_or_name)
def validate_speaker_attributes(parsed_args):
# Validate AS number
validators.validate_int_range(parsed_args, 'local_as',
MIN_AS_NUM, MAX_AS_NUM)
def add_common_arguments(parser):
utils.add_boolean_argument(
parser, '--advertise-floating-ip-host-routes',
help=_('Whether to enable or disable the advertisement '
'of floating-ip host routes by the BGP speaker. '
'By default floating ip host routes will be '
'advertised by the BGP speaker.'))
utils.add_boolean_argument(
parser, '--advertise-tenant-networks',
help=_('Whether to enable or disable the advertisement '
'of tenant network routes by the BGP speaker. '
'By default tenant network routes will be '
'advertised by the BGP speaker.'))
def args2body_common_arguments(body, parsed_args):
neutronv20.update_dict(parsed_args, body,
['name',
'advertise_floating_ip_host_routes',
'advertise_tenant_networks'])
class ListSpeakers(neutronv20.ListCommand):
"""List BGP speakers."""
resource = 'bgp_speaker'
list_columns = ['id', 'name', 'local_as', 'ip_version']
pagination_support = True
sorting_support = True
class ShowSpeaker(neutronv20.ShowCommand):
"""Show information of a given BGP speaker."""
resource = 'bgp_speaker'
class CreateSpeaker(neutronv20.CreateCommand):
"""Create a BGP Speaker."""
resource = 'bgp_speaker'
def add_known_arguments(self, parser):
parser.add_argument(
'name',
metavar='NAME',
help=_('Name of the BGP speaker to create.'))
parser.add_argument(
'--local-as',
metavar='LOCAL_AS',
required=True,
help=_('Local AS number. (Integer in [%(min_val)s, %(max_val)s] '
'is allowed.)') % {'min_val': MIN_AS_NUM,
'max_val': MAX_AS_NUM})
parser.add_argument(
'--ip-version',
type=int, choices=[4, 6],
default=4,
help=_('IP version for the BGP speaker (default is 4).'))
add_common_arguments(parser)
def args2body(self, parsed_args):
body = {}
validate_speaker_attributes(parsed_args)
body['local_as'] = parsed_args.local_as
body['ip_version'] = parsed_args.ip_version
args2body_common_arguments(body, parsed_args)
return {self.resource: body}
class UpdateSpeaker(neutronv20.UpdateCommand):
"""Update BGP Speaker's information."""
resource = 'bgp_speaker'
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help=_('Name of the BGP speaker to update.'))
add_common_arguments(parser)
def args2body(self, parsed_args):
body = {}
args2body_common_arguments(body, parsed_args)
return {self.resource: body}
class DeleteSpeaker(neutronv20.DeleteCommand):
"""Delete a BGP speaker."""
resource = 'bgp_speaker'
class AddPeerToSpeaker(neutronv20.NeutronCommand):
"""Add a peer to the BGP speaker."""
def get_parser(self, prog_name):
parser = super(AddPeerToSpeaker, self).get_parser(prog_name)
parser.add_argument(
'bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
parser.add_argument(
'bgp_peer',
metavar='BGP_PEER',
help=_('ID or name of the BGP peer to add.'))
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
_speaker_id = get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
_peer_id = bgp_peer.get_bgp_peer_id(neutron_client,
parsed_args.bgp_peer)
neutron_client.add_peer_to_bgp_speaker(_speaker_id,
{'bgp_peer_id': _peer_id})
print(_('Added BGP peer %(peer)s to BGP speaker %(speaker)s.') %
{'peer': parsed_args.bgp_peer,
'speaker': parsed_args.bgp_speaker},
file=self.app.stdout)
class RemovePeerFromSpeaker(neutronv20.NeutronCommand):
"""Remove a peer from the BGP speaker."""
def get_parser(self, prog_name):
parser = super(RemovePeerFromSpeaker, self).get_parser(prog_name)
parser.add_argument(
'bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
parser.add_argument(
'bgp_peer',
metavar='BGP_PEER',
help=_('ID or name of the BGP peer to remove.'))
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
_speaker_id = get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
_peer_id = bgp_peer.get_bgp_peer_id(neutron_client,
parsed_args.bgp_peer)
neutron_client.remove_peer_from_bgp_speaker(_speaker_id,
{'bgp_peer_id': _peer_id})
print(_('Removed BGP peer %(peer)s from BGP speaker %(speaker)s.') %
{'peer': parsed_args.bgp_peer,
'speaker': parsed_args.bgp_speaker},
file=self.app.stdout)
class AddNetworkToSpeaker(neutronv20.NeutronCommand):
"""Add a network to the BGP speaker."""
def get_parser(self, prog_name):
parser = super(AddNetworkToSpeaker, self).get_parser(prog_name)
parser.add_argument(
'bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
parser.add_argument(
'network',
metavar='NETWORK',
help=_('ID or name of the network to add.'))
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
_speaker_id = get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
_net_id = get_network_id(neutron_client,
parsed_args.network)
neutron_client.add_network_to_bgp_speaker(_speaker_id,
{'network_id': _net_id})
print(_('Added network %(net)s to BGP speaker %(speaker)s.') %
{'net': parsed_args.network, 'speaker': parsed_args.bgp_speaker},
file=self.app.stdout)
class RemoveNetworkFromSpeaker(neutronv20.NeutronCommand):
"""Remove a network from the BGP speaker."""
def get_parser(self, prog_name):
parser = super(RemoveNetworkFromSpeaker, self).get_parser(prog_name)
parser.add_argument(
'bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
parser.add_argument(
'network',
metavar='NETWORK',
help=_('ID or name of the network to remove.'))
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
_speaker_id = get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
_net_id = get_network_id(neutron_client,
parsed_args.network)
neutron_client.remove_network_from_bgp_speaker(_speaker_id,
{'network_id': _net_id})
print(_('Removed network %(net)s from BGP speaker %(speaker)s.') %
{'net': parsed_args.network, 'speaker': parsed_args.bgp_speaker},
file=self.app.stdout)
class ListRoutesAdvertisedBySpeaker(neutronv20.ListCommand):
"""List routes advertised by a given BGP speaker."""
list_columns = ['id', 'destination', 'next_hop']
resource = 'advertised_route'
pagination_support = True
sorting_support = True
def get_parser(self, prog_name):
parser = super(ListRoutesAdvertisedBySpeaker,
self).get_parser(prog_name)
parser.add_argument(
'bgp_speaker',
metavar='BGP_SPEAKER',
help=_('ID or name of the BGP speaker.'))
return parser
def call_server(self, neutron_client, search_opts, parsed_args):
_speaker_id = get_bgp_speaker_id(neutron_client,
parsed_args.bgp_speaker)
data = neutron_client.list_route_advertised_from_bgp_speaker(
_speaker_id, **search_opts)
return data

View File

@@ -0,0 +1,67 @@
# Copyright (c) 2016 IBM
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutronclient._i18n import _
def add_dns_argument_create(parser, resource, attribute):
# Add dns_name and dns_domain support to network, port and floatingip
# create
argument = '--dns-%s' % attribute
parser.add_argument(
argument,
help=_('Assign DNS %(attribute)s to the %(resource)s '
'(requires DNS integration '
'extension)') % {'attribute': attribute, 'resource': resource})
def args2body_dns_create(parsed_args, resource, attribute):
# Add dns_name and dns_domain support to network, port and floatingip
# create
destination = 'dns_%s' % attribute
argument = getattr(parsed_args, destination)
if argument:
resource[destination] = argument
def add_dns_argument_update(parser, resource, attribute):
# Add dns_name and dns_domain support to network, port and floatingip
# update
argument = '--dns-%s' % attribute
no_argument = '--no-dns-%s' % attribute
dns_args = parser.add_mutually_exclusive_group()
dns_args.add_argument(
argument,
help=_('Assign DNS %(attribute)s to the %(resource)s '
'(requires DNS integration '
'extension.)') % {'attribute': attribute, 'resource': resource})
dns_args.add_argument(
no_argument, action='store_true',
help=_('Unassign DNS %(attribute)s from the %(resource)s '
'(requires DNS integration '
'extension.)') % {'attribute': attribute, 'resource': resource})
def args2body_dns_update(parsed_args, resource, attribute):
# Add dns_name and dns_domain support to network, port and floatingip
# update
destination = 'dns_%s' % attribute
no_destination = 'no_dns_%s' % attribute
argument = getattr(parsed_args, destination)
no_argument = getattr(parsed_args, no_destination)
if argument:
resource[destination] = argument
if no_argument:
resource[destination] = ""

View File

@@ -118,7 +118,7 @@ class AssociateFlavor(neutronV20.NeutronCommand):
'flavor.'))
return parser
def run(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
flavor_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'flavor', parsed_args.flavor)
@@ -151,7 +151,7 @@ class DisassociateFlavor(neutronV20.NeutronCommand):
'flavor.'))
return parser
def run(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
flavor_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'flavor', parsed_args.flavor)

View File

@@ -20,6 +20,7 @@ import argparse
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.neutron.v2_0 import dns
class ListFloatingIP(neutronV20.ListCommand):
@@ -68,6 +69,8 @@ class CreateFloatingIP(neutronV20.CreateCommand):
'--subnet',
dest='subnet_id',
help=_('Subnet ID on which you want to create the floating IP.'))
dns.add_dns_argument_create(parser, self.resource, 'domain')
dns.add_dns_argument_create(parser, self.resource, 'name')
def args2body(self, parsed_args):
_network_id = neutronV20.find_resourceid_by_name_or_id(
@@ -77,6 +80,8 @@ class CreateFloatingIP(neutronV20.CreateCommand):
['port_id', 'tenant_id',
'fixed_ip_address',
'floating_ip_address', 'subnet_id'])
dns.args2body_dns_create(parsed_args, body, 'domain')
dns.args2body_dns_create(parsed_args, body, 'name')
return {self.resource: body}
@@ -90,7 +95,6 @@ class DeleteFloatingIP(neutronV20.DeleteCommand):
class AssociateFloatingIP(neutronV20.NeutronCommand):
"""Create a mapping between a floating IP and a fixed IP."""
api = 'network'
resource = 'floatingip'
def get_parser(self, prog_name):
@@ -111,8 +115,7 @@ class AssociateFloatingIP(neutronV20.NeutronCommand):
help=argparse.SUPPRESS)
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
update_dict = {}
neutronV20.update_dict(parsed_args, update_dict,
@@ -126,7 +129,6 @@ class AssociateFloatingIP(neutronV20.NeutronCommand):
class DisassociateFloatingIP(neutronV20.NeutronCommand):
"""Remove a mapping from a floating IP to a fixed IP."""
api = 'network'
resource = 'floatingip'
def get_parser(self, prog_name):
@@ -136,8 +138,7 @@ class DisassociateFloatingIP(neutronV20.NeutronCommand):
help=_('ID of the floating IP to disassociate.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
neutron_client.update_floatingip(parsed_args.floatingip_id,
{'floatingip': {'port_id': None}})

View File

@@ -13,11 +13,52 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from neutronclient._i18n import _
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronv20
def add_common_args(parser):
parser.add_argument(
'--name',
help=_('Name for the firewall.'))
parser.add_argument(
'--description',
help=_('Description for the firewall.'))
router = parser.add_mutually_exclusive_group()
router.add_argument(
'--router',
dest='routers',
metavar='ROUTER',
action='append',
help=_('Firewall associated router name or ID (requires FWaaS '
'router insertion extension, this option can be repeated)'))
router.add_argument(
'--no-routers',
action='store_true',
help=_('Associate no routers with the firewall (requires FWaaS '
'router insertion extension)'))
def parse_common_args(client, parsed_args):
body = {}
if parsed_args.policy:
body['firewall_policy_id'] = neutronv20.find_resourceid_by_name_or_id(
client, 'firewall_policy',
parsed_args.policy)
if parsed_args.routers:
body['router_ids'] = [
neutronv20.find_resourceid_by_name_or_id(client, 'router', r)
for r in parsed_args.routers]
elif parsed_args.no_routers:
body['router_ids'] = []
neutronv20.update_dict(parsed_args, body,
['name', 'description'])
return body
class ListFirewall(neutronv20.ListCommand):
"""List firewalls that belong to a given tenant."""
@@ -40,41 +81,20 @@ class CreateFirewall(neutronv20.CreateCommand):
resource = 'firewall'
def add_known_arguments(self, parser):
add_common_args(parser)
parser.add_argument(
'firewall_policy_id', metavar='POLICY',
'policy', metavar='POLICY',
help=_('Firewall policy name or ID.'))
parser.add_argument(
'--name',
help=_('Name for the firewall.'))
parser.add_argument(
'--description',
help=_('Description for the firewall rule.'))
parser.add_argument(
'--admin-state-down',
dest='admin_state',
action='store_false',
help=_('Set admin state up to false.'))
parser.add_argument(
'--router',
dest='routers',
metavar='ROUTER',
action='append',
help=_('Firewall associated router names or IDs (requires FWaaS '
'router insertion extension, this option can be repeated)'))
def args2body(self, parsed_args):
client = self.get_client()
_policy_id = neutronv20.find_resourceid_by_name_or_id(
client, 'firewall_policy',
parsed_args.firewall_policy_id)
body = {'firewall_policy_id': _policy_id,
'admin_state_up': parsed_args.admin_state, }
if parsed_args.routers:
body['router_ids'] = [
neutronv20.find_resourceid_by_name_or_id(client, 'router', r)
for r in parsed_args.routers]
neutronv20.update_dict(parsed_args, body,
['name', 'description', 'tenant_id'])
body = parse_common_args(self.get_client(), parsed_args)
neutronv20.update_dict(parsed_args, body, ['tenant_id'])
body['admin_state_up'] = parsed_args.admin_state
return {self.resource: body}
@@ -84,38 +104,19 @@ class UpdateFirewall(neutronv20.UpdateCommand):
resource = 'firewall'
def add_known_arguments(self, parser):
add_common_args(parser)
parser.add_argument(
'--policy', metavar='POLICY',
help=_('Firewall policy name or ID.'))
router_sg = parser.add_mutually_exclusive_group()
router_sg.add_argument(
'--router',
dest='routers',
metavar='ROUTER',
action='append',
help=_('Firewall associated router names or IDs (requires FWaaS '
'router insertion extension, this option can be repeated)'))
router_sg.add_argument(
'--no-routers',
action='store_true',
help=_('Associate no routers with the firewall (requires FWaaS '
'router insertion extension)'))
utils.add_boolean_argument(
parser, '--admin-state-up', dest='admin_state_up',
help=_('Update the admin state for the firewall'
'(True means UP)'))
def args2body(self, parsed_args):
data = {}
client = self.get_client()
if parsed_args.policy:
_policy_id = neutronv20.find_resourceid_by_name_or_id(
client, 'firewall_policy',
parsed_args.policy)
data['firewall_policy_id'] = _policy_id
if parsed_args.routers:
data['router_ids'] = [
neutronv20.find_resourceid_by_name_or_id(client, 'router', r)
for r in parsed_args.routers]
elif parsed_args.no_routers:
data['router_ids'] = []
return {self.resource: data}
body = parse_common_args(self.get_client(), parsed_args)
neutronv20.update_dict(parsed_args, body, ['admin_state_up'])
return {self.resource: body}
class DeleteFirewall(neutronv20.DeleteCommand):

View File

@@ -19,6 +19,7 @@ from __future__ import print_function
import argparse
from neutronclient._i18n import _
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronv20
@@ -31,14 +32,17 @@ def _format_firewall_rules(firewall_policy):
return ''
def common_add_known_arguments(parser):
def add_common_args(parser):
parser.add_argument(
'--description',
help=_('Description for the firewall policy.'))
parser.add_argument(
'--firewall-rules', type=lambda x: x.split(),
help=_('Ordered list of whitespace-delimited firewall rule '
'names or IDs; e.g., --firewall-rules \"rule1 rule2\"'))
def common_args2body(client, parsed_args):
def parse_common_args(client, parsed_args):
if parsed_args.firewall_rules:
_firewall_rules = []
for f in parsed_args.firewall_rules:
@@ -81,24 +85,20 @@ class CreateFirewallPolicy(neutronv20.CreateCommand):
'name',
metavar='NAME',
help=_('Name for the firewall policy.'))
parser.add_argument(
'--description',
help=_('Description for the firewall policy.'))
parser.add_argument(
'--shared',
dest='shared',
action='store_true',
help=_('Create a shared policy.'),
default=argparse.SUPPRESS)
common_add_known_arguments(parser)
parser.add_argument(
'--audited',
action='store_true',
help=_('Sets audited to True.'),
default=argparse.SUPPRESS)
add_common_args(parser)
def args2body(self, parsed_args):
return common_args2body(self.get_client(), parsed_args)
return parse_common_args(self.get_client(), parsed_args)
class UpdateFirewallPolicy(neutronv20.UpdateCommand):
@@ -107,10 +107,21 @@ class UpdateFirewallPolicy(neutronv20.UpdateCommand):
resource = 'firewall_policy'
def add_known_arguments(self, parser):
common_add_known_arguments(parser)
add_common_args(parser)
parser.add_argument(
'--name',
help=_('Name for the firewall policy.'))
utils.add_boolean_argument(
parser, '--shared',
help=_('Update the sharing status of the policy. '
'(True means shared)'))
utils.add_boolean_argument(
parser, '--audited',
help=_('Update the audit status of the policy. '
'(True means auditing is enabled)'))
def args2body(self, parsed_args):
return common_args2body(self.get_client(), parsed_args)
return parse_common_args(self.get_client(), parsed_args)
class DeleteFirewallPolicy(neutronv20.DeleteCommand):
@@ -168,7 +179,7 @@ class FirewallPolicyInsertRule(neutronv20.UpdateCommand):
self.add_known_arguments(parser)
return parser
def run(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
body = self.args2body(parsed_args)
_id = neutronv20.find_resourceid_by_name_or_id(neutron_client,
@@ -206,7 +217,7 @@ class FirewallPolicyRemoveRule(neutronv20.UpdateCommand):
self.add_known_arguments(parser)
return parser
def run(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
body = self.args2body(parsed_args)
_id = neutronv20.find_resourceid_by_name_or_id(neutron_client,

View File

@@ -21,6 +21,62 @@ from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronv20
def _add_common_args(parser, is_create=True):
"""If is_create is True, protocol and action become mandatory arguments.
CreateCommand = is_create : True
UpdateCommand = is_create : False
"""
parser.add_argument(
'--name',
help=_('Name for the firewall rule.'))
parser.add_argument(
'--description',
help=_('Description for the firewall rule.'))
parser.add_argument(
'--source-ip-address',
help=_('Source IP address or subnet.'))
parser.add_argument(
'--destination-ip-address',
help=_('Destination IP address or subnet.'))
parser.add_argument(
'--source-port',
help=_('Source port (integer in [1, 65535] or range in a:b).'))
parser.add_argument(
'--destination-port',
help=_('Destination port (integer in [1, 65535] or range in '
'a:b).'))
utils.add_boolean_argument(
parser, '--enabled', dest='enabled',
help=_('Whether to enable or disable this rule.'))
parser.add_argument(
'--protocol', choices=['tcp', 'udp', 'icmp', 'any'],
required=is_create,
type=utils.convert_to_lowercase,
help=_('Protocol for the firewall rule.'))
parser.add_argument(
'--action',
required=is_create,
type=utils.convert_to_lowercase,
choices=['allow', 'deny', 'reject'],
help=_('Action for the firewall rule.'))
def common_args2body(parsed_args):
body = {}
neutronv20.update_dict(parsed_args, body,
['name', 'description', 'shared', 'tenant_id',
'source_ip_address', 'destination_ip_address',
'source_port', 'destination_port', 'action',
'enabled', 'ip_version'])
protocol = parsed_args.protocol
if protocol:
if protocol == 'any':
protocol = None
body['protocol'] = protocol
return body
class ListFirewallRule(neutronv20.ListCommand):
"""List firewall rules that belong to a given tenant."""
@@ -69,56 +125,19 @@ class CreateFirewallRule(neutronv20.CreateCommand):
resource = 'firewall_rule'
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help=_('Name for the firewall rule.'))
parser.add_argument(
'--description',
help=_('Description for the firewall rule.'))
parser.add_argument(
'--shared',
dest='shared',
action='store_true',
help=_('Set shared to True (default is False).'),
help=_('Set shared flag for the firewall rule.'),
default=argparse.SUPPRESS)
_add_common_args(parser)
parser.add_argument(
'--source-ip-address',
help=_('Source IP address or subnet.'))
parser.add_argument(
'--destination-ip-address',
help=_('Destination IP address or subnet.'))
parser.add_argument(
'--source-port',
help=_('Source port (integer in [1, 65535] or range in a:b).'))
parser.add_argument(
'--destination-port',
help=_('Destination port (integer in [1, 65535] or range in '
'a:b).'))
utils.add_boolean_argument(
parser, '--enabled', dest='enabled',
help=_('Whether to enable or disable this rule.'))
parser.add_argument(
'--protocol', choices=['tcp', 'udp', 'icmp', 'any'],
required=True,
help=_('Protocol for the firewall rule.'))
parser.add_argument(
'--action',
required=True,
choices=['allow', 'deny', 'reject'],
help=_('Action for the firewall rule.'))
'--ip-version',
type=int, choices=[4, 6], default=4,
help=_('IP version for the firewall rule (default is 4).'))
def args2body(self, parsed_args):
body = {}
neutronv20.update_dict(parsed_args, body,
['name', 'description', 'shared', 'protocol',
'source_ip_address', 'destination_ip_address',
'source_port', 'destination_port',
'action', 'enabled', 'tenant_id'])
protocol = parsed_args.protocol
if protocol == 'any':
protocol = None
body['protocol'] = protocol
return {self.resource: body}
return {self.resource: common_args2body(parsed_args)}
class UpdateFirewallRule(neutronv20.UpdateCommand):
@@ -127,19 +146,20 @@ class UpdateFirewallRule(neutronv20.UpdateCommand):
resource = 'firewall_rule'
def add_known_arguments(self, parser):
utils.add_boolean_argument(
parser,
'--shared',
dest='shared',
help=_('Update the shared flag for the firewall rule.'),
default=argparse.SUPPRESS)
parser.add_argument(
'--protocol', choices=['tcp', 'udp', 'icmp', 'any'],
required=False,
help=_('Protocol for the firewall rule.'))
'--ip-version',
type=int, choices=[4, 6],
help=_('Update IP version for the firewall rule.'))
_add_common_args(parser, is_create=False)
def args2body(self, parsed_args):
body = {}
protocol = parsed_args.protocol
if protocol:
if protocol == 'any':
protocol = None
body['protocol'] = protocol
return {self.resource: body}
return {self.resource: common_args2body(parsed_args)}
class DeleteFirewallRule(neutronv20.DeleteCommand):

View File

@@ -124,7 +124,7 @@ class AssociateHealthMonitor(neutronV20.NeutronCommand):
help=_('ID of the pool to be associated with the health monitor.'))
return parser
def run(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
body = {'health_monitor': {'id': parsed_args.health_monitor_id}}
pool_id = neutronV20.find_resourceid_by_name_or_id(
@@ -150,7 +150,7 @@ class DisassociateHealthMonitor(neutronV20.NeutronCommand):
help=_('ID of the pool to be associated with the health monitor.'))
return parser
def run(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
pool_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'pool', parsed_args.pool_id)

View File

@@ -107,8 +107,7 @@ class RetrievePoolStats(neutronV20.ShowCommand):
resource = 'pool'
def get_data(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
pool_id = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'pool', parsed_args.id)

View File

@@ -0,0 +1,155 @@
# Copyright 2016 Radware LTD.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
def _get_listener_id(client, listener_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client, 'listener', listener_id_or_name)
def _get_pool_id(client, pool_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client, 'pool', pool_id_or_name, cmd_resource='lbaas_pool')
def _add_common_args(parser, is_create=True):
parser.add_argument(
'--name',
help=_('Name of the policy.'))
parser.add_argument(
'--description',
help=_('Description of the policy.'))
parser.add_argument(
'--action',
required=is_create,
metavar='ACTION',
type=utils.convert_to_uppercase,
choices=['REJECT', 'REDIRECT_TO_POOL', 'REDIRECT_TO_URL'],
help=_('Action type of the policy.'))
parser.add_argument(
'--redirect-pool',
help=_('ID or name of the pool for REDIRECT_TO_POOL action type.'))
parser.add_argument(
'--redirect-url',
help=_('URL for REDIRECT_TO_URL action type. '
'This should be a valid URL string.'))
parser.add_argument(
'--position',
type=int,
help=_('L7 policy position in ordered policies list. '
'This must be an integer starting from 1. '
'Not specifying the position will place the policy '
'at the tail of existing policies list.'))
def _common_args2body(client, parsed_args, is_create=True):
if parsed_args.redirect_url:
if parsed_args.action != 'REDIRECT_TO_URL':
raise exceptions.CommandError(_('Action must be REDIRECT_TO_URL'))
if parsed_args.redirect_pool:
if parsed_args.action != 'REDIRECT_TO_POOL':
raise exceptions.CommandError(_('Action must be REDIRECT_TO_POOL'))
parsed_args.redirect_pool_id = _get_pool_id(
client, parsed_args.redirect_pool)
if (parsed_args.action == 'REDIRECT_TO_URL' and
not parsed_args.redirect_url):
raise exceptions.CommandError(_('Redirect URL must be specified'))
if (parsed_args.action == 'REDIRECT_TO_POOL' and
not parsed_args.redirect_pool):
raise exceptions.CommandError(_('Redirect pool must be specified'))
attributes = ['name', 'description',
'action', 'redirect_pool_id', 'redirect_url',
'position', 'admin_state_up']
if is_create:
parsed_args.listener_id = _get_listener_id(
client, parsed_args.listener)
attributes.extend(['listener_id', 'tenant_id'])
body = {}
neutronV20.update_dict(parsed_args, body, attributes)
return {'l7policy': body}
class ListL7Policy(neutronV20.ListCommand):
"""LBaaS v2 List L7 policies that belong to a given listener."""
resource = 'l7policy'
shadow_resource = 'lbaas_l7policy'
pagination_support = True
sorting_support = True
list_columns = [
'id', 'name', 'action', 'redirect_pool_id', 'redirect_url',
'position', 'admin_state_up', 'status'
]
class ShowL7Policy(neutronV20.ShowCommand):
"""LBaaS v2 Show information of a given L7 policy."""
resource = 'l7policy'
shadow_resource = 'lbaas_l7policy'
class CreateL7Policy(neutronV20.CreateCommand):
"""LBaaS v2 Create L7 policy."""
resource = 'l7policy'
shadow_resource = 'lbaas_l7policy'
def add_known_arguments(self, parser):
_add_common_args(parser)
parser.add_argument(
'--admin-state-down',
dest='admin_state_up',
action='store_false',
help=_('Set admin state up to false.'))
parser.add_argument(
'--listener',
required=True,
metavar='LISTENER',
help=_('ID or name of the listener this policy belongs to.'))
def args2body(self, parsed_args):
return _common_args2body(self.get_client(), parsed_args)
class UpdateL7Policy(neutronV20.UpdateCommand):
"""LBaaS v2 Update a given L7 policy."""
resource = 'l7policy'
shadow_resource = 'lbaas_l7policy'
def add_known_arguments(self, parser):
_add_common_args(parser, is_create=False)
utils.add_boolean_argument(
parser, '--admin-state-up',
help=_('Specify the administrative state of the policy'
' (True meaning "Up").'))
def args2body(self, parsed_args):
return _common_args2body(self.get_client(), parsed_args, False)
class DeleteL7Policy(neutronV20.DeleteCommand):
"""LBaaS v2 Delete a given L7 policy."""
resource = 'l7policy'
shadow_resource = 'lbaas_l7policy'

View File

@@ -0,0 +1,148 @@
# Copyright 2016 Radware LTD.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from neutronclient._i18n import _
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
def _get_policy_id(client, policy_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client, 'l7policy', policy_id_or_name,
cmd_resource='lbaas_l7policy')
class LbaasL7RuleMixin(object):
def set_extra_attrs(self, parsed_args):
self.parent_id = _get_policy_id(self.get_client(),
parsed_args.l7policy)
def add_known_arguments(self, parser):
parser.add_argument(
'l7policy', metavar='L7POLICY',
help=_('ID or name of L7 policy this rule belongs to.'))
def _add_common_args(parser, is_create=True):
parser.add_argument(
'--type',
required=is_create,
type=utils.convert_to_uppercase,
choices=['HOST_NAME', 'PATH', 'FILE_TYPE', 'HEADER', 'COOKIE'],
help=_('Rule type.'))
parser.add_argument(
'--compare-type',
required=is_create,
type=utils.convert_to_uppercase,
choices=['REGEX', 'STARTS_WITH', 'ENDS_WITH',
'CONTAINS', 'EQUAL_TO'],
help=_('Rule compare type.'))
parser.add_argument(
'--invert-compare',
dest='invert',
action='store_true',
help=_('Invert the compare type.'))
parser.add_argument(
'--key',
help=_('Key to compare.'
' Relevant for HEADER and COOKIE types only.'))
parser.add_argument(
'--value',
required=is_create,
help=_('Value to compare.'))
def _common_args2body(client, parsed_args, is_create=True):
attributes = ['type', 'compare_type',
'invert', 'key', 'value', 'admin_state_up']
if is_create:
attributes.append('tenant_id')
body = {}
neutronV20.update_dict(parsed_args, body, attributes)
return {'rule': body}
class ListL7Rule(LbaasL7RuleMixin, neutronV20.ListCommand):
"""LBaaS v2 List L7 rules that belong to a given L7 policy."""
resource = 'rule'
shadow_resource = 'lbaas_l7rule'
pagination_support = True
sorting_support = True
list_columns = [
'id', 'type', 'compare_type', 'invert', 'key', 'value',
'admin_state_up', 'status'
]
def take_action(self, parsed_args):
self.parent_id = _get_policy_id(self.get_client(),
parsed_args.l7policy)
self.values_specs.append('--l7policy_id=%s' % self.parent_id)
return super(ListL7Rule, self).take_action(parsed_args)
class ShowL7Rule(LbaasL7RuleMixin, neutronV20.ShowCommand):
"""LBaaS v2 Show information of a given rule."""
resource = 'rule'
shadow_resource = 'lbaas_l7rule'
class CreateL7Rule(LbaasL7RuleMixin, neutronV20.CreateCommand):
"""LBaaS v2 Create L7 rule."""
resource = 'rule'
shadow_resource = 'lbaas_l7rule'
def add_known_arguments(self, parser):
super(CreateL7Rule, self).add_known_arguments(parser)
_add_common_args(parser)
parser.add_argument(
'--admin-state-down',
dest='admin_state_up',
action='store_false',
help=_('Set admin state up to false'))
def args2body(self, parsed_args):
return _common_args2body(self.get_client(), parsed_args)
class UpdateL7Rule(LbaasL7RuleMixin, neutronV20.UpdateCommand):
"""LBaaS v2 Update a given L7 rule."""
resource = 'rule'
shadow_resource = 'lbaas_l7rule'
def add_known_arguments(self, parser):
super(UpdateL7Rule, self).add_known_arguments(parser)
_add_common_args(parser, False)
utils.add_boolean_argument(
parser, '--admin-state-up',
help=_('Specify the administrative state of the rule'
' (True meaning "Up").'))
def args2body(self, parsed_args):
return _common_args2body(self.get_client(), parsed_args, False)
class DeleteL7Rule(LbaasL7RuleMixin, neutronV20.DeleteCommand):
"""LBaaS v2 Delete a given L7 rule."""
resource = 'rule'
shadow_resource = 'lbaas_l7rule'

View File

@@ -16,18 +16,27 @@
#
from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
def _get_loadbalancer_id(client, lb_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client,
'loadbalancer',
lb_id_or_name,
client, 'loadbalancer', lb_id_or_name,
cmd_resource='lbaas_loadbalancer')
def _get_pool(client, pool_id_or_name):
return neutronV20.find_resource_by_name_or_id(
client, 'pool', pool_id_or_name, cmd_resource='lbaas_pool')
def _get_pool_id(client, pool_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client, 'pool', pool_id_or_name, cmd_resource='lbaas_pool')
class ListListener(neutronV20.ListCommand):
"""LBaaS v2 List listeners that belong to a given tenant."""
@@ -64,7 +73,8 @@ class CreateListener(neutronV20.CreateCommand):
help=_('Description of the listener.'))
parser.add_argument(
'--name',
help=_('The name of the listener.'))
help=_('The name of the listener. At least one of --default-pool '
'or --loadbalancer must be specified.'))
parser.add_argument(
'--default-tls-container-ref',
dest='default_tls_container_ref',
@@ -75,9 +85,11 @@ class CreateListener(neutronV20.CreateCommand):
dest='sni_container_refs',
nargs='+',
help=_('List of TLS container references for SNI.'))
parser.add_argument(
'--default-pool',
help=_('Default pool for the listener.'))
parser.add_argument(
'--loadbalancer',
required=True,
metavar='LOADBALANCER',
help=_('ID or name of the load balancer.'))
parser.add_argument(
@@ -93,22 +105,29 @@ class CreateListener(neutronV20.CreateCommand):
help=_('Protocol port for the listener.'))
def args2body(self, parsed_args):
resource = {
'protocol': parsed_args.protocol,
'protocol_port': parsed_args.protocol_port,
'admin_state_up': parsed_args.admin_state
}
if not parsed_args.loadbalancer and not parsed_args.default_pool:
message = _('Either --default-pool or --loadbalancer must be '
'specified.')
raise exceptions.CommandError(message)
if parsed_args.loadbalancer:
parsed_args.loadbalancer = _get_loadbalancer_id(
self.get_client(),
parsed_args.loadbalancer)
body = {'loadbalancer_id': parsed_args.loadbalancer,
'protocol': parsed_args.protocol,
'protocol_port': parsed_args.protocol_port,
'admin_state_up': parsed_args.admin_state}
loadbalancer_id = _get_loadbalancer_id(
self.get_client(), parsed_args.loadbalancer)
resource['loadbalancer_id'] = loadbalancer_id
if parsed_args.default_pool:
default_pool_id = _get_pool_id(
self.get_client(), parsed_args.default_pool)
resource['default_pool_id'] = default_pool_id
neutronV20.update_dict(parsed_args, body,
neutronV20.update_dict(parsed_args, resource,
['connection_limit', 'description',
'loadbalancer_id', 'name',
'default_tls_container_ref',
'sni_container_refs',
'tenant_id'])
return {self.resource: body}
'name', 'default_tls_container_ref',
'sni_container_refs', 'tenant_id'])
return {self.resource: resource}
class UpdateListener(neutronV20.UpdateCommand):

View File

@@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from oslo_serialization import jsonutils
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronV20
@@ -100,8 +101,7 @@ class RetrieveLoadBalancerStats(neutronV20.ShowCommand):
resource = 'loadbalancer'
def get_data(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
neutron_client.format = parsed_args.request_format
loadbalancer_id = neutronV20.find_resourceid_by_name_or_id(
@@ -128,3 +128,31 @@ class RetrieveLoadBalancerStats(neutronV20.ShowCommand):
# here covert the data dict to the 1-1 vector format below:
# [(field1, field2, field3, ...), (value1, value2, value3, ...)]
return list(zip(*sorted(stats.items())))
class RetrieveLoadBalancerStatus(neutronV20.NeutronCommand):
"""Retrieve status for a given loadbalancer.
The only output is a formatted JSON tree, and the table format
does not support this type of data.
"""
resource = 'loadbalancer'
def get_parser(self, prog_name):
parser = super(RetrieveLoadBalancerStatus, self).get_parser(prog_name)
parser.add_argument(
self.resource, metavar=self.resource.upper(),
help=_('ID or name of %s to show.') % self.resource)
return parser
def take_action(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
neutron_client = self.get_client()
lb_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, self.resource, parsed_args.loadbalancer)
params = {}
data = neutron_client.retrieve_loadbalancer_status(lb_id, **params)
res = data['statuses']
if 'statuses' in data:
print(jsonutils.dumps(res, indent=4))

View File

@@ -49,10 +49,10 @@ class ListMember(LbaasMemberMixin, neutronV20.ListCommand):
pagination_support = True
sorting_support = True
def get_data(self, parsed_args):
def take_action(self, parsed_args):
self.parent_id = _get_pool_id(self.get_client(), parsed_args.pool)
self.values_specs.append('--pool_id=%s' % self.parent_id)
return super(ListMember, self).get_data(parsed_args)
return super(ListMember, self).take_action(parsed_args)
class ShowMember(LbaasMemberMixin, neutronV20.ShowCommand):

View File

@@ -1,6 +1,7 @@
# Copyright 2013 Mirantis Inc.
# Copyright 2014 Blue Box Group, Inc.
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# Copyright 2015 Blue Box, an IBM Company
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -17,10 +18,27 @@
#
from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
def _get_loadbalancer_id(client, lb_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client, 'loadbalancer', lb_id_or_name,
cmd_resource='lbaas_loadbalancer')
def _get_listener(client, listener_id_or_name):
return neutronV20.find_resource_by_name_or_id(
client, 'listener', listener_id_or_name)
def _get_listener_id(client, listener_id_or_name):
return neutronV20.find_resourceid_by_name_or_id(
client, 'listener', listener_id_or_name)
class ListPool(neutronV20.ListCommand):
"""LBaaS v2 List pools that belong to a given tenant."""
@@ -64,20 +82,28 @@ class CreatePool(neutronV20.CreateCommand):
parser.add_argument(
'--session-persistence',
metavar='type=TYPE[,cookie_name=COOKIE_NAME]',
type=utils.str2dict_type(required_keys=['type'],
optional_keys=['cookie_name']),
help=_('The type of session persistence to use and associated '
'cookie name'))
parser.add_argument(
'--name', help=_('The name of the pool.'))
parser.add_argument(
'--listener',
help=_('Listener whose default-pool should be set to this pool. '
'At least one of --listener or --loadbalancer must be '
'specified.'))
parser.add_argument(
'--loadbalancer',
help=_('Loadbalancer with which this pool should be associated. '
'At least one of --listener or --loadbalancer must be '
'specified.'))
parser.add_argument(
'--lb-algorithm',
required=True,
choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'],
help=_('The algorithm used to distribute load between the members '
'of the pool.'))
parser.add_argument(
'--listener',
required=True,
help=_('The listener to associate with the pool'))
parser.add_argument(
'--protocol',
required=True,
@@ -86,19 +112,29 @@ class CreatePool(neutronV20.CreateCommand):
help=_('Protocol for balancing.'))
def args2body(self, parsed_args):
if parsed_args.session_persistence:
parsed_args.session_persistence = utils.str2dict(
parsed_args.session_persistence)
_listener_id = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'listener', parsed_args.listener)
body = {'admin_state_up': parsed_args.admin_state,
'protocol': parsed_args.protocol,
'lb_algorithm': parsed_args.lb_algorithm,
'listener_id': _listener_id}
neutronV20.update_dict(parsed_args, body,
resource = {
'admin_state_up': parsed_args.admin_state,
'protocol': parsed_args.protocol,
'lb_algorithm': parsed_args.lb_algorithm
}
if not parsed_args.listener and not parsed_args.loadbalancer:
message = _('At least one of --listener or --loadbalancer must be '
'specified.')
raise exceptions.CommandError(message)
if parsed_args.listener:
listener_id = _get_listener_id(
self.get_client(),
parsed_args.listener)
resource['listener_id'] = listener_id
if parsed_args.loadbalancer:
loadbalancer_id = _get_loadbalancer_id(
self.get_client(),
parsed_args.loadbalancer)
resource['loadbalancer_id'] = loadbalancer_id
neutronV20.update_dict(parsed_args, resource,
['description', 'name',
'session_persistence', 'tenant_id'])
return {self.resource: body}
return {self.resource: resource}
class UpdatePool(neutronV20.UpdateCommand):

View File

@@ -21,6 +21,7 @@ from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.neutron.v2_0 import availability_zone
from neutronclient.neutron.v2_0 import dns
from neutronclient.neutron.v2_0.qos import policy as qos_policy
@@ -44,6 +45,44 @@ class ListNetwork(neutronV20.ListCommand):
pagination_support = True
sorting_support = True
filter_attrs = [
'tenant_id',
'name',
'admin_state_up',
{'name': 'status',
'help': _("Filter %s according to their operation status."
"(For example: ACTIVE, ERROR etc)"),
'boolean': False,
'argparse_kwargs': {'type': utils.convert_to_uppercase}},
{'name': 'shared',
'help': _('Filter and list the networks which are shared.'),
'boolean': True},
{'name': 'router:external',
'help': _('Filter and list the networks which are external.'),
'boolean': True},
{'name': 'tags',
'help': _("Filter and list %s which has all given tags. "
"Multiple tags can be set like --tags <tag[,tag...]>"),
'boolean': False,
'argparse_kwargs': {'metavar': 'TAG'}},
{'name': 'tags_any',
'help': _("Filter and list %s which has any given tags. "
"Multiple tags can be set like --tags-any <tag[,tag...]>"),
'boolean': False,
'argparse_kwargs': {'metavar': 'TAG'}},
{'name': 'not_tags',
'help': _("Filter and list %s which does not have all given tags. "
"Multiple tags can be set like --not-tags <tag[,tag...]>"),
'boolean': False,
'argparse_kwargs': {'metavar': 'TAG'}},
{'name': 'not_tags_any',
'help': _("Filter and list %s which does not have any given tags. "
"Multiple tags can be set like --not-tags-any "
"<tag[,tag...]>"),
'boolean': False,
'argparse_kwargs': {'metavar': 'TAG'}},
]
def extend_list(self, data, parsed_args):
"""Add subnet information to a network list."""
neutron_client = self.get_client()
@@ -148,6 +187,7 @@ class CreateNetwork(neutronV20.CreateCommand, qos_policy.CreateQosPolicyMixin):
self.add_arguments_qos_policy(parser)
availability_zone.add_az_hint_argument(parser, self.resource)
dns.add_dns_argument_create(parser, self.resource, 'domain')
def args2body(self, parsed_args):
body = {'name': parsed_args.name,
@@ -161,6 +201,7 @@ class CreateNetwork(neutronV20.CreateCommand, qos_policy.CreateQosPolicyMixin):
self.args2body_qos_policy(parsed_args, body)
availability_zone.args2body_az_hint(parsed_args, body)
dns.args2body_dns_create(parsed_args, body, 'domain')
return {'network': body}
@@ -178,8 +219,10 @@ class UpdateNetwork(neutronV20.UpdateCommand, qos_policy.UpdateQosPolicyMixin):
def add_known_arguments(self, parser):
self.add_arguments_qos_policy(parser)
dns.add_dns_argument_update(parser, self.resource, 'domain')
def args2body(self, parsed_args):
body = {}
self.args2body_qos_policy(parsed_args, body)
dns.args2body_dns_update(parsed_args, body, 'domain')
return {'network': body}

View File

@@ -0,0 +1,73 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from cliff import show
import six
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronV20
class ListIpAvailability(neutronV20.ListCommand):
"""List IP usage of networks"""
resource = 'network_ip_availability'
resource_plural = 'network_ip_availabilities'
list_columns = ['network_id', 'network_name', 'total_ips', 'used_ips']
paginations_support = True
sorting_support = True
filter_attrs = [
{'name': 'ip_version',
'help': _('Returns IP availability for the network subnets '
'with a given IP version. Default: 4'),
'argparse_kwargs': {'type': int,
'choices': [4, 6],
'default': 4}
},
{'name': 'network_id',
'help': _('Returns IP availability for the network '
'matching a given network ID.')},
{'name': 'network_name',
'help': _('Returns IP availability for the network '
'matching a given name.')},
{'name': 'tenant_id',
'help': _('Returns IP availability for the networks '
'with a given tenant ID.')},
]
class ShowIpAvailability(neutronV20.NeutronCommand, show.ShowOne):
"""Show IP usage of specific network"""
resource = 'network_ip_availability'
def get_parser(self, prog_name):
parser = super(ShowIpAvailability, self).get_parser(prog_name)
parser.add_argument(
'network_id', metavar='NETWORK',
help=_('ID or name of network to look up.'))
return parser
def take_action(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
neutron_client = self.get_client()
_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'network', parsed_args.network_id)
data = neutron_client.show_network_ip_availability(_id)
self.format_output_data(data)
resource = data[self.resource]
if self.resource in data:
return zip(*sorted(six.iteritems(resource)))
else:
return None

View File

@@ -234,8 +234,7 @@ class NetworkGatewayInterfaceCommand(neutronV20.NeutronCommand):
class ConnectNetworkGateway(NetworkGatewayInterfaceCommand):
"""Add an internal network interface to a router."""
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
(gateway_id, network_id) = self.retrieve_ids(neutron_client,
parsed_args)
@@ -252,8 +251,7 @@ class ConnectNetworkGateway(NetworkGatewayInterfaceCommand):
class DisconnectNetworkGateway(NetworkGatewayInterfaceCommand):
"""Remove a network from a network gateway."""
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
(gateway_id, network_id) = self.retrieve_ids(neutron_client,
parsed_args)

View File

@@ -22,6 +22,7 @@ from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.neutron.v2_0 import dns
from neutronclient.neutron.v2_0.qos import policy as qos_policy
@@ -46,6 +47,7 @@ def _add_updatable_args(parser):
parser.add_argument(
'--fixed-ip', metavar='subnet_id=SUBNET,ip_address=IP_ADDR',
action='append',
type=utils.str2dict_type(optional_keys=['subnet_id', 'ip_address']),
help=_('Desired IP and/or subnet for this port: '
'subnet_id=<name_or_id>,ip_address=<ip>. '
'You can repeat this option.'))
@@ -73,13 +75,12 @@ def _updatable_args2body(parsed_args, body, client):
ips = []
if parsed_args.fixed_ip:
for ip_spec in parsed_args.fixed_ip:
ip_dict = utils.str2dict(ip_spec)
if 'subnet_id' in ip_dict:
subnet_name_id = ip_dict['subnet_id']
if 'subnet_id' in ip_spec:
subnet_name_id = ip_spec['subnet_id']
_subnet_id = neutronV20.find_resourceid_by_name_or_id(
client, 'subnet', subnet_name_id)
ip_dict['subnet_id'] = _subnet_id
ips.append(ip_dict)
ip_spec['subnet_id'] = _subnet_id
ips.append(ip_spec)
if ips:
body['fixed_ips'] = ips
@@ -107,16 +108,16 @@ class ListRouterPort(neutronV20.ListCommand):
def get_parser(self, prog_name):
parser = super(ListRouterPort, self).get_parser(prog_name)
parser.add_argument(
'id', metavar='router',
'id', metavar='ROUTER',
help=_('ID or name of router to look up.'))
return parser
def get_data(self, parsed_args):
def take_action(self, parsed_args):
neutron_client = self.get_client()
_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'router', parsed_args.id)
self.values_specs.append('--device_id=%s' % _id)
return super(ListRouterPort, self).get_data(parsed_args)
return super(ListRouterPort, self).take_action(parsed_args)
class ShowPort(neutronV20.ShowCommand):
@@ -158,6 +159,9 @@ class UpdateExtraDhcpOptMixin(object):
default=[],
action='append',
dest='extra_dhcp_opts',
type=utils.str2dict_type(
required_keys=['opt_name'],
optional_keys=['opt_value', 'ip_version']),
help=_('Extra dhcp options to be assigned to this port: '
'opt_name=<dhcp_option_name>,opt_value=<value>,'
'ip_version={4,6}. You can repeat this option.'))
@@ -174,7 +178,7 @@ class UpdateExtraDhcpOptMixin(object):
"ip_version={4,6}. "
"You can repeat this option.")
for opt in parsed_args.extra_dhcp_opts:
opt_ele.update(utils.str2dict(opt))
opt_ele.update(opt)
if ('opt_name' in opt_ele and
('opt_value' in opt_ele or 'ip_version' in opt_ele)):
if opt_ele.get('opt_value') == 'null':
@@ -199,7 +203,9 @@ class UpdatePortAllowedAddressPair(object):
default=[],
action='append',
dest='allowed_address_pairs',
type=utils.str2dict,
type=utils.str2dict_type(
required_keys=['ip_address'],
optional_keys=['mac_address']),
help=_('Allowed address pair associated with the port.'
'You can repeat this option.'))
group_aap.add_argument(
@@ -263,6 +269,7 @@ class CreatePort(neutronV20.CreateCommand, UpdatePortSecGroupMixin,
parser.add_argument(
'network_id', metavar='NETWORK',
help=_('Network ID or name this port belongs to.'))
dns.add_dns_argument_create(parser, self.resource, 'name')
def args2body(self, parsed_args):
client = self.get_client()
@@ -283,6 +290,7 @@ class CreatePort(neutronV20.CreateCommand, UpdatePortSecGroupMixin,
self.args2body_extradhcpopt(parsed_args, body)
self.args2body_qos_policy(parsed_args, body)
self.args2body_allowedaddresspairs(parsed_args, body)
dns.args2body_dns_create(parsed_args, body, 'name')
return {'port': body}
@@ -314,6 +322,7 @@ class UpdatePort(neutronV20.UpdateCommand, UpdatePortSecGroupMixin,
self.add_arguments_extradhcpopt(parser)
self.add_arguments_qos_policy(parser)
self.add_arguments_allowedaddresspairs(parser)
dns.add_dns_argument_update(parser, self.resource, 'name')
def args2body(self, parsed_args):
body = {}
@@ -326,5 +335,6 @@ class UpdatePort(neutronV20.UpdateCommand, UpdatePortSecGroupMixin,
self.args2body_extradhcpopt(parsed_args, body)
self.args2body_qos_policy(parsed_args, body)
self.args2body_allowedaddresspairs(parsed_args, body)
dns.args2body_dns_update(parsed_args, body, 'name')
return {'port': body}

View File

@@ -0,0 +1,147 @@
# Copyright 2016 Cisco Systems
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronV20
class Purge(neutronV20.NeutronCommand):
def _pluralize(self, string):
return string + 's'
def _get_resources(self, neutron_client, resource_types, tenant_id):
resources = []
for resource_type in resource_types:
resources.append([])
resource_type_plural = self._pluralize(resource_type)
opts = {'fields': ['id', 'tenant_id']}
if resource_type_plural == 'ports':
opts['fields'].append('device_id')
opts['fields'].append('device_owner')
function = getattr(neutron_client, 'list_%s' %
resource_type_plural)
if callable(function):
returned_resources = function(**opts).get(resource_type_plural,
[])
for resource in returned_resources:
if resource['tenant_id'] == tenant_id:
index = resource_types.index(resource_type)
resources[index].append(resource)
self.total_resources += 1
return resources
def _delete_resource(self, neutron_client, resource_type, resource):
resource_id = resource['id']
if resource_type == 'port':
if resource.get('device_owner', '') == 'network:router_interface':
body = {'port_id': resource_id}
neutron_client.remove_interface_router(resource['device_id'],
body)
return
function = getattr(neutron_client, 'delete_%s' % resource_type)
if callable(function):
function(resource_id)
def _purge_resources(self, neutron_client, resource_types,
tenant_resources):
deleted = {}
failed = {}
failures = False
for resources in tenant_resources:
index = tenant_resources.index(resources)
resource_type = resource_types[index]
failed[resource_type] = 0
deleted[resource_type] = 0
for resource in resources:
try:
self._delete_resource(neutron_client, resource_type,
resource)
deleted[resource_type] += 1
self.deleted_resources += 1
except Exception:
failures = True
failed[resource_type] += 1
self.total_resources -= 1
percent_complete = 100
if self.total_resources > 0:
percent_complete = (self.deleted_resources /
float(self.total_resources)) * 100
sys.stdout.write("\rPurging resources: %d%% complete." %
percent_complete)
sys.stdout.flush()
return (deleted, failed, failures)
def _build_message(self, deleted, failed, failures):
msg = ''
deleted_msg = []
for resource, value in deleted.items():
if value:
if not msg:
msg = 'Deleted'
if not value == 1:
resource = self._pluralize(resource)
deleted_msg.append(" %d %s" % (value, resource))
if deleted_msg:
msg += ','.join(deleted_msg)
failed_msg = []
if failures:
if msg:
msg += '. '
msg += 'The following resources could not be deleted:'
for resource, value in failed.items():
if value:
if not value == 1:
resource = self._pluralize(resource)
failed_msg.append(" %d %s" % (value, resource))
msg += ','.join(failed_msg)
if msg:
msg += '.'
else:
msg = _('Tenant has no supported resources.')
return msg
def get_parser(self, prog_name):
parser = super(Purge, self).get_parser(prog_name)
parser.add_argument(
'tenant', metavar='TENANT',
help=_('ID of Tenant owning the resources to be deleted.'))
return parser
def take_action(self, parsed_args):
neutron_client = self.get_client()
self.any_failures = False
# A list of the types of resources supported in the order in which
# they should be deleted.
resource_types = ['floatingip', 'port', 'router',
'network', 'security_group']
deleted = {}
failed = {}
self.total_resources = 0
self.deleted_resources = 0
resources = self._get_resources(neutron_client, resource_types,
parsed_args.tenant)
deleted, failed, failures = self._purge_resources(neutron_client,
resource_types,
resources)
print('\n%s' % self._build_message(deleted, failed, failures))

View File

@@ -37,7 +37,6 @@ def get_tenant_id(args, client):
class DeleteQuota(neutronV20.NeutronCommand):
"""Delete defined quotas of a given tenant."""
api = 'network'
resource = 'quota'
def get_parser(self, prog_name):
@@ -53,8 +52,7 @@ class DeleteQuota(neutronV20.NeutronCommand):
help=argparse.SUPPRESS, nargs='?')
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
tenant_id = get_tenant_id(parsed_args, neutron_client)
obj_deleter = getattr(neutron_client,
@@ -70,15 +68,13 @@ class DeleteQuota(neutronV20.NeutronCommand):
class ListQuota(neutronV20.NeutronCommand, lister.Lister):
"""List quotas of all tenants who have non-default quota values."""
api = 'network'
resource = 'quota'
def get_parser(self, prog_name):
parser = super(ListQuota, self).get_parser(prog_name)
return parser
def get_data(self, parsed_args):
self.log.debug('get_data(%s)', parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
search_opts = {}
self.log.debug('search options: %s', search_opts)
@@ -98,7 +94,6 @@ class ShowQuota(neutronV20.NeutronCommand, show.ShowOne):
"""Show quotas of a given tenant.
"""
api = 'network'
resource = "quota"
def get_parser(self, prog_name):
@@ -117,8 +112,7 @@ class ShowQuota(neutronV20.NeutronCommand, show.ShowOne):
help=argparse.SUPPRESS, nargs='?')
return parser
def get_data(self, parsed_args):
self.log.debug('get_data(%s)', parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
tenant_id = get_tenant_id(parsed_args, neutron_client)
params = {}
@@ -216,8 +210,7 @@ class UpdateQuota(neutronV20.NeutronCommand, show.ShowOne):
getattr(parsed_args, resource))
return {self.resource: quota}
def get_data(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_extra_values = neutronV20.parse_args_to_dict(self.values_specs)
neutronV20._merge_args(self, parsed_args, _extra_values,

View File

@@ -16,20 +16,31 @@
from neutronclient._i18n import _
from neutronclient.neutron import v2_0 as neutronV20
# key=object_type: value={key=resource, value=cmd_resource}
RBAC_OBJECTS = {'network': {'network': 'network'},
'qos-policy': {'policy': 'qos_policy'}}
def get_rbac_object_id(client, obj_type, obj_id_or_name):
if obj_type == 'network':
obj_id = neutronV20.find_resourceid_by_name_or_id(client,
'network',
obj_id_or_name)
return obj_id
def _get_cmd_resource(obj_type):
resource = list(RBAC_OBJECTS[obj_type])[0]
cmd_resource = RBAC_OBJECTS[obj_type][resource]
return resource, cmd_resource
def get_rbac_obj_params(client, obj_type, obj_id_or_name):
resource, cmd_resource = _get_cmd_resource(obj_type)
obj_id = neutronV20.find_resourceid_by_name_or_id(
client=client, resource=resource, name_or_id=obj_id_or_name,
cmd_resource=cmd_resource)
return obj_id, cmd_resource
class ListRBACPolicy(neutronV20.ListCommand):
"""List RBAC policies that belong to a given tenant."""
resource = 'rbac_policy'
list_columns = ['id', 'object_id']
list_columns = ['id', 'object_type', 'object_id']
pagination_support = True
sorting_support = True
allow_names = False
@@ -53,7 +64,7 @@ class CreateRBACPolicy(neutronV20.CreateCommand):
metavar='RBAC_OBJECT',
help=_('ID or name of the RBAC object.'))
parser.add_argument(
'--type', choices=['network'],
'--type', choices=RBAC_OBJECTS.keys(),
required=True,
help=_('Type of the object that RBAC policy affects.'))
parser.add_argument(
@@ -67,11 +78,13 @@ class CreateRBACPolicy(neutronV20.CreateCommand):
def args2body(self, parsed_args):
neutron_client = self.get_client()
_object_id = get_rbac_object_id(neutron_client, parsed_args.type,
parsed_args.name)
neutron_client.format = parsed_args.request_format
_object_id, _object_type = get_rbac_obj_params(neutron_client,
parsed_args.type,
parsed_args.name)
body = {
'object_id': _object_id,
'object_type': parsed_args.type,
'object_type': _object_type,
'target_tenant': parsed_args.target_tenant,
'action': parsed_args.action,
}

View File

@@ -114,7 +114,8 @@ class UpdateRouter(neutronV20.UpdateCommand):
routes_group = parser.add_mutually_exclusive_group()
routes_group.add_argument(
'--route', metavar='destination=CIDR,nexthop=IP_ADDR',
action='append', dest='routes', type=utils.str2dict,
action='append', dest='routes',
type=utils.str2dict_type(required_keys=['destination', 'nexthop']),
help=_('Route to associate with the router.'
' You can repeat this option.'))
routes_group.add_argument(
@@ -138,7 +139,6 @@ class UpdateRouter(neutronV20.UpdateCommand):
class RouterInterfaceCommand(neutronV20.NeutronCommand):
"""Based class to Add/Remove router interface."""
api = 'network'
resource = 'router'
def call_api(self, neutron_client, router_id, body):
@@ -161,8 +161,7 @@ class RouterInterfaceCommand(neutronV20.NeutronCommand):
'subnet.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
if '=' in parsed_args.interface:
@@ -211,7 +210,6 @@ class RemoveInterfaceRouter(RouterInterfaceCommand):
class SetGatewayRouter(neutronV20.NeutronCommand):
"""Set the external network gateway for a router."""
api = 'network'
resource = 'router'
def get_parser(self, prog_name):
@@ -226,14 +224,18 @@ class SetGatewayRouter(neutronV20.NeutronCommand):
'--disable-snat', action='store_true',
help=_('Disable source NAT on the router gateway.'))
parser.add_argument(
'--fixed-ip', action='append',
'--fixed-ip', metavar='subnet_id=SUBNET,ip_address=IP_ADDR',
action='append',
type=utils.str2dict_type(optional_keys=['subnet_id',
'ip_address']),
help=_('Desired IP and/or subnet on external network: '
'subnet_id=<name_or_id>,ip_address=<ip>. '
'You can specify both of subnet_id and ip_address or '
'specify one of them as well. '
'You can repeat this option.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_router_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, self.resource, parsed_args.router)
@@ -245,13 +247,12 @@ class SetGatewayRouter(neutronV20.NeutronCommand):
if parsed_args.fixed_ip:
ips = []
for ip_spec in parsed_args.fixed_ip:
ip_dict = utils.str2dict(ip_spec)
subnet_name_id = ip_dict.get('subnet_id')
subnet_name_id = ip_spec.get('subnet_id')
if subnet_name_id:
subnet_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'subnet', subnet_name_id)
ip_dict['subnet_id'] = subnet_id
ips.append(ip_dict)
ip_spec['subnet_id'] = subnet_id
ips.append(ip_spec)
router_dict['external_fixed_ips'] = ips
neutron_client.add_gateway_router(_router_id, router_dict)
print(_('Set gateway for router %s') % parsed_args.router,
@@ -261,7 +262,6 @@ class SetGatewayRouter(neutronV20.NeutronCommand):
class RemoveGatewayRouter(neutronV20.NeutronCommand):
"""Remove an external network gateway from a router."""
api = 'network'
resource = 'router'
def get_parser(self, prog_name):
@@ -271,8 +271,7 @@ class RemoveGatewayRouter(neutronV20.NeutronCommand):
help=_('ID or name of the router.'))
return parser
def run(self, parsed_args):
self.log.debug('run(%s)' % parsed_args)
def take_action(self, parsed_args):
neutron_client = self.get_client()
_router_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, self.resource, parsed_args.router)

View File

@@ -62,16 +62,19 @@ def add_updatable_arguments(parser):
help=_('No distribution of gateway.'))
parser.add_argument(
'--allocation-pool', metavar='start=IP_ADDR,end=IP_ADDR',
action='append', dest='allocation_pools', type=utils.str2dict,
action='append', dest='allocation_pools',
type=utils.str2dict_type(required_keys=['start', 'end']),
help=_('Allocation pool IP addresses for this subnet '
'(This option can be repeated).'))
parser.add_argument(
'--allocation_pool',
action='append', dest='allocation_pools', type=utils.str2dict,
action='append', dest='allocation_pools',
type=utils.str2dict_type(required_keys=['start', 'end']),
help=argparse.SUPPRESS)
parser.add_argument(
'--host-route', metavar='destination=CIDR,nexthop=IP_ADDR',
action='append', dest='host_routes', type=utils.str2dict,
action='append', dest='host_routes',
type=utils.str2dict_type(required_keys=['destination', 'nexthop']),
help=_('Additional route (This option can be repeated).'))
parser.add_argument(
'--dns-nameserver', metavar='DNS_NAMESERVER',
@@ -187,6 +190,10 @@ class CreateSubnet(neutronV20.CreateCommand):
'--subnetpool', metavar='SUBNETPOOL',
help=_('ID or name of subnetpool from which this subnet '
'will obtain a CIDR.'))
parser.add_argument(
'--use-default-subnetpool',
action='store_true',
help=_('Use default subnetpool for ip_version, if it exists.'))
parser.add_argument(
'--prefixlen', metavar='PREFIX_LENGTH',
help=_('Prefix length for subnet allocation from subnetpool.'))
@@ -199,6 +206,8 @@ class CreateSubnet(neutronV20.CreateCommand):
if parsed_args.prefixlen:
body['prefixlen'] = parsed_args.prefixlen
ip_version = parsed_args.ip_version
if parsed_args.use_default_subnetpool:
body['use_default_subnetpool'] = True
if parsed_args.subnetpool:
if parsed_args.subnetpool == 'None':
_subnetpool_id = None

View File

@@ -15,9 +15,17 @@
#
from neutronclient._i18n import _
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
def _format_prefixes(subnetpool):
try:
return '\n'.join(pool for pool in subnetpool['prefixes'])
except (TypeError, KeyError):
return subnetpool['prefixes']
def add_updatable_arguments(parser):
parser.add_argument(
'--min-prefixlen', type=int,
@@ -32,20 +40,25 @@ def add_updatable_arguments(parser):
'--pool-prefix',
action='append', dest='prefixes',
help=_('Subnetpool prefixes (This option can be repeated).'))
utils.add_boolean_argument(
parser, '--is-default',
help=_('Specify whether this should be the default subnetpool '
'(True meaning default).'))
def updatable_args2body(parsed_args, body, for_create=True):
neutronV20.update_dict(parsed_args, body,
['name', 'prefixes', 'default_prefixlen',
'min_prefixlen', 'max_prefixlen'])
'min_prefixlen', 'max_prefixlen', 'is_default'])
class ListSubnetPool(neutronV20.ListCommand):
"""List subnetpools that belong to a given tenant."""
_formatters = {'prefixes': _format_prefixes, }
resource = 'subnetpool'
list_columns = ['id', 'name', 'prefixes',
'default_prefixlen', 'address_scope_id']
'default_prefixlen', 'address_scope_id', 'is_default']
pagination_support = True
sorting_support = True
@@ -69,6 +82,7 @@ class CreateSubnetPool(neutronV20.CreateCommand):
help=_('Set the subnetpool as shared.'))
parser.add_argument(
'name',
metavar='NAME',
help=_('Name of subnetpool to create.'))
parser.add_argument(
'--address-scope',

View File

@@ -0,0 +1,105 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutronclient._i18n import _
from neutronclient.common import exceptions
from neutronclient.neutron import v2_0 as neutronv20
# List of resources can be set tag
TAG_RESOURCES = ['network']
def _convert_resource_args(client, parsed_args):
resource_type = neutronv20._get_resource_plural(
parsed_args.resource_type, client)
resource_id = neutronv20.find_resourceid_by_name_or_id(
client, parsed_args.resource_type, parsed_args.resource)
return resource_type, resource_id
def _add_common_arguments(parser):
parser.add_argument('--resource-type',
choices=TAG_RESOURCES,
dest='resource_type',
required=True,
help=_('Resource Type.'))
parser.add_argument('--resource',
required=True,
help=_('Resource name or ID.'))
class AddTag(neutronv20.NeutronCommand):
"""Add a tag into the resource."""
def get_parser(self, prog_name):
parser = super(AddTag, self).get_parser(prog_name)
_add_common_arguments(parser)
parser.add_argument('--tag',
required=True,
help=_('Tag to be added.'))
return parser
def take_action(self, parsed_args):
client = self.get_client()
resource_type, resource_id = _convert_resource_args(client,
parsed_args)
client.add_tag(resource_type, resource_id, parsed_args.tag)
class ReplaceTag(neutronv20.NeutronCommand):
"""Replace all tags on the resource."""
def get_parser(self, prog_name):
parser = super(ReplaceTag, self).get_parser(prog_name)
_add_common_arguments(parser)
parser.add_argument('--tag',
metavar='TAG',
action='append',
dest='tags',
required=True,
help=_('Tag (This option can be repeated).'))
return parser
def take_action(self, parsed_args):
client = self.get_client()
resource_type, resource_id = _convert_resource_args(client,
parsed_args)
body = {'tags': parsed_args.tags}
client.replace_tag(resource_type, resource_id, body)
class RemoveTag(neutronv20.NeutronCommand):
"""Remove a tag on the resource."""
def get_parser(self, prog_name):
parser = super(RemoveTag, self).get_parser(prog_name)
_add_common_arguments(parser)
tag_opt = parser.add_mutually_exclusive_group()
tag_opt.add_argument('--all',
action='store_true',
help=_('Remove all tags on the resource.'))
tag_opt.add_argument('--tag',
help=_('Tag to be removed.'))
return parser
def take_action(self, parsed_args):
if not parsed_args.all and not parsed_args.tag:
raise exceptions.CommandError(
_("--all or --tag must be specified"))
client = self.get_client()
resource_type, resource_id = _convert_resource_args(client,
parsed_args)
if parsed_args.all:
client.remove_tag_all(resource_type, resource_id)
else:
client.remove_tag(resource_type, resource_id, parsed_args.tag)

View File

@@ -35,6 +35,7 @@ class ShowIKEPolicy(neutronv20.ShowCommand):
"""Show information of a given IKE policy."""
resource = 'ikepolicy'
help_resource = 'IKE policy'
class CreateIKEPolicy(neutronv20.CreateCommand):
@@ -70,7 +71,7 @@ class CreateIKEPolicy(neutronv20.CreateCommand):
parser.add_argument(
'--lifetime',
metavar="units=UNITS,value=VALUE",
type=utils.str2dict,
type=utils.str2dict_type(optional_keys=['units', 'value']),
help=vpn_utils.lifetime_help("IKE"))
parser.add_argument(
'name', metavar='NAME',
@@ -93,12 +94,13 @@ class UpdateIKEPolicy(neutronv20.UpdateCommand):
"""Update a given IKE policy."""
resource = 'ikepolicy'
help_resource = 'IKE policy'
def add_known_arguments(self, parser):
parser.add_argument(
'--lifetime',
metavar="units=UNITS,value=VALUE",
type=utils.str2dict,
type=utils.str2dict_type(optional_keys=['units', 'value']),
help=vpn_utils.lifetime_help("IKE"))
def args2body(self, parsed_args):
@@ -114,3 +116,4 @@ class DeleteIKEPolicy(neutronv20.DeleteCommand):
"""Delete a given IKE policy."""
resource = 'ikepolicy'
help_resource = 'IKE policy'

View File

@@ -46,6 +46,7 @@ class ShowIPsecSiteConnection(neutronv20.ShowCommand):
"""Show information of a given IPsec site connection."""
resource = 'ipsec_site_connection'
help_resource = 'IPsec site connection'
class IPsecSiteConnectionMixin(object):
@@ -54,7 +55,8 @@ class IPsecSiteConnectionMixin(object):
parser.add_argument(
'--dpd',
metavar="action=ACTION,interval=INTERVAL,timeout=TIMEOUT",
type=utils.str2dict,
type=utils.str2dict_type(
optional_keys=['action', 'interval', 'timeout']),
help=vpn_utils.dpd_help("IPsec connection."))
parser.add_argument(
'--local-ep-group',
@@ -196,9 +198,11 @@ class UpdateIPsecSiteConnection(IPsecSiteConnectionMixin,
"""Update a given IPsec site connection."""
resource = 'ipsec_site_connection'
help_resource = 'IPsec site connection'
class DeleteIPsecSiteConnection(neutronv20.DeleteCommand):
"""Delete a given IPsec site connection."""
resource = 'ipsec_site_connection'
help_resource = 'IPsec site connection'

View File

@@ -35,6 +35,7 @@ class ShowIPsecPolicy(neutronv20.ShowCommand):
"""Show information of a given IPsec policy."""
resource = 'ipsecpolicy'
help_resource = 'IPsec policy'
class CreateIPsecPolicy(neutronv20.CreateCommand):
@@ -69,7 +70,7 @@ class CreateIPsecPolicy(neutronv20.CreateCommand):
parser.add_argument(
'--lifetime',
metavar="units=UNITS,value=VALUE",
type=utils.str2dict,
type=utils.str2dict_type(optional_keys=['units', 'value']),
help=vpn_utils.lifetime_help("IPsec"))
parser.add_argument(
'name', metavar='NAME',
@@ -92,12 +93,13 @@ class UpdateIPsecPolicy(neutronv20.UpdateCommand):
"""Update a given IPsec policy."""
resource = 'ipsecpolicy'
help_resource = 'IPsec policy'
def add_known_arguments(self, parser):
parser.add_argument(
'--lifetime',
metavar="units=UNITS,value=VALUE",
type=utils.str2dict,
type=utils.str2dict_type(optional_keys=['units', 'value']),
help=vpn_utils.lifetime_help("IPsec"))
def args2body(self, parsed_args):
@@ -113,3 +115,4 @@ class DeleteIPsecPolicy(neutronv20.DeleteCommand):
"""Delete a given IPsec policy."""
resource = 'ipsecpolicy'
help_resource = 'IPsec policy'

View File

@@ -34,6 +34,7 @@ class ShowVPNService(neutronv20.ShowCommand):
"""Show information of a given VPN service."""
resource = 'vpnservice'
help_resource = 'VPN service'
class CreateVPNService(neutronv20.CreateCommand):
@@ -83,9 +84,11 @@ class UpdateVPNService(neutronv20.UpdateCommand):
"""Update a given VPN service."""
resource = 'vpnservice'
help_resource = 'VPN service'
class DeleteVPNService(neutronv20.DeleteCommand):
"""Delete a given VPN service."""
resource = 'vpnservice'
help_resource = 'VPN service'

View File

@@ -32,18 +32,22 @@ import os_client_config
from oslo_utils import encodeutils
from cliff import app
from cliff import command
from cliff import commandmanager
from neutronclient._i18n import _
from neutronclient.common import clientmanager
from neutronclient.common import command as openstack_command
from neutronclient.common import exceptions as exc
from neutronclient.common import extension as client_extension
from neutronclient.common import utils
from neutronclient.neutron.v2_0 import address_scope
from neutronclient.neutron.v2_0 import agent
from neutronclient.neutron.v2_0 import agentscheduler
from neutronclient.neutron.v2_0 import auto_allocated_topology
from neutronclient.neutron.v2_0 import availability_zone
from neutronclient.neutron.v2_0.bgp import dragentscheduler as bgp_drsched
from neutronclient.neutron.v2_0.bgp import peer as bgp_peer
from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker
from neutronclient.neutron.v2_0 import extension
from neutronclient.neutron.v2_0.flavor import flavor
from neutronclient.neutron.v2_0.flavor import flavor_profile
@@ -55,6 +59,8 @@ from neutronclient.neutron.v2_0.lb import healthmonitor as lb_healthmonitor
from neutronclient.neutron.v2_0.lb import member as lb_member
from neutronclient.neutron.v2_0.lb import pool as lb_pool
from neutronclient.neutron.v2_0.lb.v2 import healthmonitor as lbaas_healthmon
from neutronclient.neutron.v2_0.lb.v2 import l7policy as lbaas_l7policy
from neutronclient.neutron.v2_0.lb.v2 import l7rule as lbaas_l7rule
from neutronclient.neutron.v2_0.lb.v2 import listener as lbaas_listener
from neutronclient.neutron.v2_0.lb.v2 import loadbalancer as lbaas_loadbalancer
from neutronclient.neutron.v2_0.lb.v2 import member as lbaas_member
@@ -62,9 +68,11 @@ from neutronclient.neutron.v2_0.lb.v2 import pool as lbaas_pool
from neutronclient.neutron.v2_0.lb import vip as lb_vip
from neutronclient.neutron.v2_0 import metering
from neutronclient.neutron.v2_0 import network
from neutronclient.neutron.v2_0 import network_ip_availability
from neutronclient.neutron.v2_0.nsx import networkgateway
from neutronclient.neutron.v2_0.nsx import qos_queue
from neutronclient.neutron.v2_0 import port
from neutronclient.neutron.v2_0 import purge
from neutronclient.neutron.v2_0.qos import bandwidth_limit_rule
from neutronclient.neutron.v2_0.qos import policy as qos_policy
from neutronclient.neutron.v2_0.qos import rule as qos_rule
@@ -75,6 +83,7 @@ from neutronclient.neutron.v2_0 import securitygroup
from neutronclient.neutron.v2_0 import servicetype
from neutronclient.neutron.v2_0 import subnet
from neutronclient.neutron.v2_0 import subnetpool
from neutronclient.neutron.v2_0 import tag
from neutronclient.neutron.v2_0.vpn import endpoint_group
from neutronclient.neutron.v2_0.vpn import ikepolicy
from neutronclient.neutron.v2_0.vpn import ipsec_site_connection
@@ -140,9 +149,12 @@ def check_non_negative_int(value):
return value
class BashCompletionCommand(openstack_command.OpenStackCommand):
class BashCompletionCommand(command.Command):
"""Prints all of the commands and options for bash-completion."""
resource = "bash_completion"
def take_action(self, parsed_args):
pass
COMMAND_V2 = {
'bash-completion': BashCompletionCommand,
@@ -167,6 +179,7 @@ COMMAND_V2 = {
'port-create': port.CreatePort,
'port-delete': port.DeletePort,
'port-update': port.UpdatePort,
'purge': purge.Purge,
'quota-list': quota.ListQuota,
'quota-show': quota.ShowQuota,
'quota-delete': quota.DeleteQuota,
@@ -204,11 +217,22 @@ COMMAND_V2 = {
'lbaas-loadbalancer-update': lbaas_loadbalancer.UpdateLoadBalancer,
'lbaas-loadbalancer-delete': lbaas_loadbalancer.DeleteLoadBalancer,
'lbaas-loadbalancer-stats': lbaas_loadbalancer.RetrieveLoadBalancerStats,
'lbaas-loadbalancer-status': lbaas_loadbalancer.RetrieveLoadBalancerStatus,
'lbaas-listener-list': lbaas_listener.ListListener,
'lbaas-listener-show': lbaas_listener.ShowListener,
'lbaas-listener-create': lbaas_listener.CreateListener,
'lbaas-listener-update': lbaas_listener.UpdateListener,
'lbaas-listener-delete': lbaas_listener.DeleteListener,
'lbaas-l7policy-list': lbaas_l7policy.ListL7Policy,
'lbaas-l7policy-show': lbaas_l7policy.ShowL7Policy,
'lbaas-l7policy-create': lbaas_l7policy.CreateL7Policy,
'lbaas-l7policy-update': lbaas_l7policy.UpdateL7Policy,
'lbaas-l7policy-delete': lbaas_l7policy.DeleteL7Policy,
'lbaas-l7rule-list': lbaas_l7rule.ListL7Rule,
'lbaas-l7rule-show': lbaas_l7rule.ShowL7Rule,
'lbaas-l7rule-create': lbaas_l7rule.CreateL7Rule,
'lbaas-l7rule-update': lbaas_l7rule.UpdateL7Rule,
'lbaas-l7rule-delete': lbaas_l7rule.DeleteL7Rule,
'lbaas-pool-list': lbaas_pool.ListPool,
'lbaas-pool-show': lbaas_pool.ShowPool,
'lbaas-pool-create': lbaas_pool.CreatePool,
@@ -388,6 +412,42 @@ COMMAND_V2 = {
'flavor-profile-delete': flavor_profile.DeleteFlavorProfile,
'flavor-profile-update': flavor_profile.UpdateFlavorProfile,
'availability-zone-list': availability_zone.ListAvailabilityZone,
'auto-allocated-topology-show': (
auto_allocated_topology.ShowAutoAllocatedTopology),
'bgp-dragent-speaker-add': (
bgp_drsched.AddBGPSpeakerToDRAgent
),
'bgp-dragent-speaker-remove': (
bgp_drsched.RemoveBGPSpeakerFromDRAgent
),
'bgp-speaker-list-on-dragent': (
bgp_drsched.ListBGPSpeakersOnDRAgent
),
'bgp-dragent-list-hosting-speaker': (
bgp_drsched.ListDRAgentsHostingBGPSpeaker
),
'bgp-speaker-list': bgp_speaker.ListSpeakers,
'bgp-speaker-advertiseroute-list': (
bgp_speaker.ListRoutesAdvertisedBySpeaker
),
'bgp-speaker-show': bgp_speaker.ShowSpeaker,
'bgp-speaker-create': bgp_speaker.CreateSpeaker,
'bgp-speaker-update': bgp_speaker.UpdateSpeaker,
'bgp-speaker-delete': bgp_speaker.DeleteSpeaker,
'bgp-speaker-peer-add': bgp_speaker.AddPeerToSpeaker,
'bgp-speaker-peer-remove': bgp_speaker.RemovePeerFromSpeaker,
'bgp-speaker-network-add': bgp_speaker.AddNetworkToSpeaker,
'bgp-speaker-network-remove': bgp_speaker.RemoveNetworkFromSpeaker,
'bgp-peer-list': bgp_peer.ListPeers,
'bgp-peer-show': bgp_peer.ShowPeer,
'bgp-peer-create': bgp_peer.CreatePeer,
'bgp-peer-update': bgp_peer.UpdatePeer,
'bgp-peer-delete': bgp_peer.DeletePeer,
'net-ip-availability-list': network_ip_availability.ListIpAvailability,
'net-ip-availability-show': network_ip_availability.ShowIpAvailability,
'tag-add': tag.AddTag,
'tag-replace': tag.ReplaceTag,
'tag-remove': tag.RemoveTag,
}
COMMANDS = {'2.0': COMMAND_V2}
@@ -727,9 +787,9 @@ class NeutronShell(app.App):
options = set()
for option, _action in self.parser._option_string_actions.items():
options.add(option)
for command_name, command in self.command_manager:
commands.add(command_name)
cmd_factory = command.load()
for _name, _command in self.command_manager:
commands.add(_name)
cmd_factory = _command.load()
cmd = cmd_factory(self, None)
cmd_parser = cmd.get_parser('')
for option, _action in cmd_parser._option_string_actions.items():
@@ -838,11 +898,22 @@ class NeutronShell(app.App):
cloud=self.options.os_cloud, argparse=self.options,
network_api_version=self.api_version)
verify, cert = cloud_config.get_requests_verify_args()
auth = cloud_config.get_auth()
auth_session = session.Session(
auth=auth, verify=verify, cert=cert,
timeout=self.options.http_timeout)
# TODO(singhj): Remove dependancy on HTTPClient
# for the case of token-endpoint authentication
# When using token-endpoint authentication legacy
# HTTPClient will be used, otherwise SessionClient
# will be used.
if self.options.os_token and self.options.os_url:
auth = None
auth_session = None
else:
auth = cloud_config.get_auth()
auth_session = session.Session(
auth=auth, verify=verify, cert=cert,
timeout=self.options.http_timeout)
interface = self.options.os_endpoint_type or self.endpoint_type
if interface.endswith('URL'):
@@ -851,6 +922,8 @@ class NeutronShell(app.App):
retries=self.options.retries,
raise_errors=False,
session=auth_session,
url=self.options.os_url,
token=self.options.os_token,
region_name=cloud_config.get_region_name(),
api_version=cloud_config.get_api_version('network'),
service_type=cloud_config.get_service_type('network'),

View File

@@ -0,0 +1,172 @@
# Copyright 2016 Cisco Systems
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutronclient.tests.functional import base
from tempest_lib import exceptions
class PurgeNeutronClientCLITest(base.ClientTestBase):
def _safe_cleanup(self, delete_command):
try:
self.neutron(delete_command)
except exceptions.CommandFailed:
# This resource was already purged successfully
pass
def _create_subnet(self, name, tenant_id, cidr):
params = ('%(name)s --name %(name)s --tenant-id %(tenant)s '
'%(cidr)s' % {'name': name,
'tenant': tenant_id,
'cidr': cidr})
subnet = self.parser.listing(self.neutron('subnet-create',
params=params))
for row in subnet:
if row['Field'] == 'id':
return row['Value']
def _create_router(self, name, tenant_id):
params = ('%(name)s --tenant_id %(tenant)s' % {'name': name,
'tenant': tenant_id})
router = self.parser.listing(self.neutron('router-create',
params=params))
for row in router:
if row['Field'] == 'id':
return row['Value']
def _create_floatingip(self, network, tenant_id):
params = ('%(network)s --tenant-id %(tenant)s' %
{'network': network, 'tenant': tenant_id})
floatingip = self.parser.listing(self.neutron('floatingip-create',
params=params))
for row in floatingip:
if row['Field'] == 'id':
return row['Value']
def _create_resources(self, name, tenant_id, shared_tenant_id=None):
# If no shared_tenant_id is provided, create the resources for the
# current tenant to test that they will be deleted when not in use.
if not shared_tenant_id:
shared_tenant_id = tenant_id
self.neutron('net-create',
params=('%(name)s --router:external True '
'--tenant-id %(tenant)s' % {'name': name,
'tenant': tenant_id}))
self.addCleanup(self._safe_cleanup, 'net-delete %s' % name)
self.neutron('net-create',
params=('%(name)s-shared --shared '
'--tenant-id %(tenant)s' %
{'name': name, 'tenant': shared_tenant_id}))
self.addCleanup(self._safe_cleanup,
'net-delete %s-shared' % name)
subnet = self._create_subnet(name, tenant_id, '192.168.71.0/24')
self.addCleanup(self._safe_cleanup, 'subnet-delete %s' % name)
subnet = self._create_subnet('%s-shared' % name, tenant_id,
'192.168.81.0/24')
self.addCleanup(self._safe_cleanup, 'subnet-delete %s-shared' % name)
router = self._create_router(name, tenant_id)
self.addCleanup(self._safe_cleanup, 'router-delete %s' % name)
self.neutron('router-interface-add',
params=('%(router)s %(subnet)s '
'--tenant-id %(tenant)s' % {'router': router,
'subnet': subnet,
'tenant': tenant_id}))
self.neutron('port-create',
params=('%(name)s --name %(name)s '
'--tenant-id %(tenant)s' % {'name': name,
'tenant': tenant_id}))
self.addCleanup(self._safe_cleanup, 'port-delete %s' % name)
self.neutron('port-create',
params=('%(name)s-shared --name %(name)s-shared '
'--tenant-id %(tenant)s' % {'name': name,
'tenant': tenant_id}))
self.addCleanup(self._safe_cleanup, 'port-delete %s-shared' % name)
self.neutron('security-group-create',
params=('%(name)s --tenant-id %(tenant)s' %
{'name': name, 'tenant': tenant_id}))
self.addCleanup(self._safe_cleanup, 'security-group-delete %s' % name)
floatingip = self._create_floatingip(name, tenant_id)
self.addCleanup(self._safe_cleanup, ('floatingip-delete '
'%s' % floatingip))
return floatingip
def _verify_deletion(self, resources, resource_type):
purged = True
no_purge_purged = True
for row in resources:
if resource_type == 'port' and row.get('id', None):
port = self.parser.listing(self.neutron('port-show',
params=row['id']))
port_dict = {}
for row in port:
port_dict[row['Field']] = row['Value']
if port_dict['device_owner'] == 'network:router_interface':
if port_dict['tenant_id'] == 'purge-tenant':
purged = False
elif port_dict['tenant_id'] == 'no-purge-tenant':
no_purge_purged = False
if not purged or not no_purge_purged:
self.addCleanup(self.neutron,
('router-interface-delete %(router)s '
'port=%(port)s' %
{'router': port_dict['device_id'],
'port': port_dict['id']}))
if (row.get('name') == 'purge-me' or
row.get('id') == self.purge_floatingip):
purged = False
elif ('no-purge' in row.get('name', '') or
row.get('id') == self.no_purge_floatingip):
no_purge_purged = False
if not purged:
self.fail('%s not deleted by neutron purge' % resource_type)
if no_purge_purged:
self.fail('%s owned by another tenant incorrectly deleted '
'by neutron purge' % resource_type)
def test_purge(self):
self.purge_floatingip = self._create_resources('purge-me',
'purge-tenant')
self.no_purge_floatingip = self._create_resources('no-purge',
'no-purge-tenant',
'purge-tenant')
purge_output = self.neutron('purge', params='purge-tenant').strip()
if not purge_output:
self.fail('Purge command did not return feedback')
networks = self.parser.listing(self.neutron('net-list'))
subnets = self.parser.listing(self.neutron('subnet-list'))
routers = self.parser.listing(self.neutron('router-list'))
ports = self.parser.listing(self.neutron('port-list'))
floatingips = self.parser.listing(self.neutron('floatingip-list'))
self._verify_deletion(networks, 'network')
self._verify_deletion(subnets, 'subnet')
self._verify_deletion(ports, 'port')
self._verify_deletion(routers, 'router')
self._verify_deletion(floatingips, 'floatingip')

View File

View File

@@ -0,0 +1,66 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient.neutron.v2_0.bgp import dragentscheduler as bgp_drsched
from neutronclient.tests.unit import test_cli20
from neutronclient.tests.unit import test_cli20_agentschedulers as test_as
BGP_DRAGENT_ID = 'bgp_dragent_id1'
BGP_SPEAKER = 'bgp_speaker_id1'
class CLITestV20DRAgentScheduler(test_as.CLITestV20AgentScheduler):
def test_add_bgp_speaker_to_dragent(self):
resource = 'agent'
cmd = bgp_drsched.AddBGPSpeakerToDRAgent(
test_cli20.MyApp(sys.stdout), None)
args = (BGP_DRAGENT_ID, BGP_SPEAKER)
body = {'bgp_speaker_id': BGP_SPEAKER}
result = {'bgp_speaker_id': 'bgp_speaker_id', }
self._test_add_to_agent(resource, cmd, args,
self.client.BGP_DRINSTANCES,
body, result)
def test_remove_bgp_speaker_from_dragent(self):
resource = 'agent'
cmd = bgp_drsched.RemoveBGPSpeakerFromDRAgent(
test_cli20.MyApp(sys.stdout), None)
args = (BGP_DRAGENT_ID, BGP_SPEAKER)
self._test_remove_from_agent(resource, cmd, args,
self.client.BGP_DRINSTANCES)
def test_list_bgp_speakers_on_dragent(self):
resources = 'bgp_speakers'
cmd = bgp_drsched.ListBGPSpeakersOnDRAgent(
test_cli20.MyApp(sys.stdout), None)
path = ((self.client.agent_path + self.client.BGP_DRINSTANCES) %
BGP_DRAGENT_ID)
self._test_list_resources(resources, cmd, base_args=[BGP_DRAGENT_ID],
path=path)
def test_list_dragents_hosting_bgp_speaker(self):
resources = 'agent'
cmd = bgp_drsched.ListDRAgentsHostingBGPSpeaker(
test_cli20.MyApp(sys.stdout), None)
path = ((self.client.bgp_speaker_path + self.client.BGP_DRAGENTS) %
BGP_DRAGENT_ID)
contents = {self.id_field: 'myid1', 'alive': True}
self._test_list_resources(resources, cmd, base_args=[BGP_DRAGENT_ID],
path=path, response_contents=contents)

View File

@@ -0,0 +1,223 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.bgp import peer as bgp_peer
from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker
from neutronclient.tests.unit import test_cli20
class CLITestV20BGPPeerJSON(test_cli20.CLITestV20Base):
non_admin_status_resources = ['bgp_peer']
def test_create_bgp_peer_with_mandatory_params(self):
# Create BGP peer with mandatory params.
resource = 'bgp_peer'
cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
peerip = '1.1.1.1'
remote_asnum = '1'
args = [name,
'--peer-ip', peerip,
'--remote-as', remote_asnum, ]
position_names = ['name', 'peer_ip', 'remote_as',
'auth_type']
position_values = [name, peerip, remote_asnum, 'none']
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_bgp_peer_with_all_params(self):
# Create BGP peer with all params.
resource = 'bgp_peer'
cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
peerip = '1.1.1.1'
remote_asnum = '65535'
authType = 'md5'
password = 'abc'
args = [name,
'--peer-ip', peerip,
'--remote-as', remote_asnum,
'--auth-type', authType,
'--password', password]
position_names = ['name', 'peer_ip', 'remote_as',
'auth_type', 'password']
position_values = [name, peerip, remote_asnum, authType, password]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_bgp_peer_with_invalid_min_remote_asnum(self):
# Create BGP peer with invalid minimum remote-asnum.
resource = 'bgp_peer'
cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
peerip = '1.1.1.1'
remote_asnum = '0'
args = [name,
'--peer-ip', peerip,
'--remote-as', remote_asnum, ]
position_names = ['name', 'peer_ip', 'remote_as', ]
position_values = [name, peerip, remote_asnum, ]
exc = self.assertRaises(exceptions.CommandError,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
self.assertEqual('remote-as "0" should be an integer [%s:%s].' %
(bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM),
str(exc))
def test_create_bgp_peer_with_invalid_max_remote_asnum(self):
# Create BGP peer with invalid maximum remote-asnum.
resource = 'bgp_peer'
cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
peerip = '1.1.1.1'
remote_asnum = '65536'
args = [name,
'--peer-ip', peerip,
'--remote-as', remote_asnum, ]
position_names = ['name', 'peer_ip', 'remote_as',
'auth_type', 'password']
position_values = [name, peerip, remote_asnum, 'none', '']
exc = self.assertRaises(exceptions.CommandError,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
self.assertEqual('remote-as "65536" should be an integer [%s:%s].' %
(bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM),
str(exc))
def test_create_authenticated_bgp_peer_without_authtype(self):
# Create authenticated BGP peer without auth-type.
resource = 'bgp_peer'
cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
peerip = '1.1.1.1'
remote_asnum = '2048'
password = 'abc'
args = [name,
'--peer-ip', peerip,
'--remote-as', remote_asnum,
'--password', password]
position_names = ['name', 'peer_ip', 'remote_as', 'password']
position_values = [name, peerip, remote_asnum, password]
exc = self.assertRaises(exceptions.CommandError,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
self.assertEqual('Must provide auth-type if password is specified.',
str(exc))
def test_create_authenticated_bgp_peer_without_password(self):
# Create authenticated BGP peer without password.
resource = 'bgp_peer'
cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
peerip = '1.1.1.1'
remote_asnum = '2048'
authType = 'md5'
args = [name,
'--peer-ip', peerip,
'--remote-as', remote_asnum,
'--auth-type', authType]
position_names = ['name', 'peer_ip', 'remote_as', 'auth-type']
position_values = [name, peerip, remote_asnum, authType]
exc = self.assertRaises(exceptions.CommandError,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
self.assertEqual('Must provide password if auth-type is specified.',
str(exc))
def test_update_bgp_peer(self):
# Update BGP peer:
# myid --advertise-tenant-networks True
# --advertise-floating-ip-host-routes False
resource = 'bgp_peer'
cmd = bgp_peer.UpdatePeer(test_cli20.MyApp(sys.stdout),
None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--name', 'new-name',
'--password', 'abc'],
{'name': 'new-name', 'password': 'abc'})
def test_update_bgp_peer_exception(self):
# Update BGP peer: myid.
resource = 'bgp_peer'
cmd = bgp_peer.UpdatePeer(test_cli20.MyApp(sys.stdout),
None)
self.assertRaises(exceptions.CommandError,
self._test_update_resource,
resource, cmd, 'myid', ['myid'], {})
def test_list_bgp_peer(self):
# List all BGP peers.
resources = "bgp_peers"
cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd, True)
# TODO(Vikram): Add test_list_bgp_peer_pagination
def test_list_bgp_peer_sort(self):
# sorted list: bgp-peer-list --sort-key name --sort-key id
# --sort-key asc --sort-key desc
resources = "bgp_peers"
cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_list_bgp_peer_limit(self):
# size (1000) limited list: bgp-peer-list -P.
resources = "bgp_peers"
cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd, page_size=1000)
def test_show_bgp_peer(self):
# Show BGP peer: --fields id --fields name myid.
resource = 'bgp_peer'
cmd = bgp_peer.ShowPeer(test_cli20.MyApp(sys.stdout),
None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args,
['id', 'name'])
def test_delete_bgp_peer(self):
# Delete BGP peer: bgp_peer_id.
resource = 'bgp_peer'
cmd = bgp_peer.DeletePeer(test_cli20.MyApp(sys.stdout),
None)
myid = 'myid'
args = [myid]
self._test_delete_resource(resource, cmd, myid, args)

View File

@@ -0,0 +1,267 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from mox3 import mox
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker
from neutronclient.tests.unit import test_cli20
class CLITestV20BGPSpeakerJSON(test_cli20.CLITestV20Base):
non_admin_status_resources = ['bgp_speaker']
def test_create_bgp_speaker_with_minimal_options(self):
# Create BGP Speaker with mandatory params.
resource = 'bgp_speaker'
cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
local_asnum = '1'
args = [name, '--local-as', local_asnum, ]
position_names = ['name', 'local_as', 'ip_version']
position_values = [name, local_asnum, 4]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_ipv4_bgp_speaker_with_all_params(self):
# Create BGP Speaker with all params.
resource = 'bgp_speaker'
cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
local_asnum = '1'
args = [name,
'--local-as', local_asnum,
'--ip-version', '4',
'--advertise-floating-ip-host-routes', 'True',
'--advertise-tenant-networks', 'True']
position_names = ['name', 'local_as', 'ip_version',
'advertise_floating_ip_host_routes',
'advertise_tenant_networks']
position_values = [name, local_asnum, 4, 'True', 'True']
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_ipv6_bgp_speaker_with_all_params(self):
# Create BGP Speaker with all params.
resource = 'bgp_speaker'
cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
local_asnum = '65535'
args = [name,
'--local-as', local_asnum,
'--ip-version', '6',
'--advertise-floating-ip-host-routes', 'True',
'--advertise-tenant-networks', 'True']
position_names = ['name', 'local_as', 'ip_version',
'advertise_floating_ip_host_routes',
'advertise_tenant_networks']
position_values = [name, local_asnum, 6, 'True', 'True']
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_bgp_speaker_with_invalid_min_local_asnum(self):
# Create BGP Speaker with invalid minimum local-asnum.
resource = 'bgp_speaker'
cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
local_asnum = '0'
args = [name,
'--local-as', local_asnum]
position_names = ['name', 'local_as']
position_values = [name, local_asnum]
exc = self.assertRaises(exceptions.CommandError,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
self.assertEqual('local-as "0" should be an integer [%s:%s].' %
(bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM),
str(exc))
def test_create_bgp_speaker_with_invalid_max_local_asnum(self):
# Create BGP Speaker with invalid maximum local-asnum.
resource = 'bgp_speaker'
cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
my_id = 'my-id'
local_asnum = '65536'
args = [name,
'--local-as', local_asnum]
position_names = ['name', 'local_as', ]
position_values = [name, local_asnum, ]
exc = self.assertRaises(exceptions.CommandError,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
self.assertEqual('local-as "65536" should be an integer [%s:%s].' %
(bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM),
str(exc))
def test_update_bgp_speaker(self):
# Update BGP Speaker:
# myid --advertise-tenant-networks True
# --advertise-floating-ip-host-routes False
resource = 'bgp_speaker'
cmd = bgp_speaker.UpdateSpeaker(test_cli20.MyApp(sys.stdout),
None)
self._test_update_resource(resource, cmd, 'myid',
['myid',
'--name', 'new-name',
'--advertise-tenant-networks', 'True',
'--advertise-floating-ip-host-routes',
'False'],
{'name': 'new-name',
'advertise_tenant_networks': 'True',
'advertise_floating_ip_host_routes':
'False'})
def test_update_bgp_speaker_exception(self):
# Update BGP Speaker: myid.
resource = 'bgp_speaker'
cmd = bgp_speaker.UpdateSpeaker(test_cli20.MyApp(sys.stdout),
None)
self.assertRaises(exceptions.CommandError,
self._test_update_resource,
resource, cmd, 'myid', ['myid'], {})
def test_list_bgp_speaker(self):
# List all BGP Speakers.
resources = "bgp_speakers"
cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd, True)
def test_list_bgp_speaker_pagination(self):
# List all BGP Speakers with pagination support.
cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout),
None)
self.mox.StubOutWithMock(bgp_speaker.ListSpeakers,
"extend_list")
bgp_speaker.ListSpeakers.extend_list(mox.IsA(list),
mox.IgnoreArg())
self._test_list_resources_with_pagination("bgp_speakers",
cmd)
self.mox.VerifyAll()
self.mox.UnsetStubs()
def test_list_bgp_speaker_sort(self):
# sorted list: bgp-speaker-list --sort-key name --sort-key id
# --sort-key asc --sort-key desc
resources = "bgp_speakers"
cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_list_bgp_speaker_limit(self):
# size (1000) limited list: bgp-speaker-list -P.
resources = "bgp_speakers"
cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd, page_size=1000)
def test_show_bgp_speaker(self):
# Show BGP Speaker: --fields id --fields name myid.
resource = 'bgp_speaker'
cmd = bgp_speaker.ShowSpeaker(test_cli20.MyApp(sys.stdout),
None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args,
['id', 'name'])
def test_delete_bgp_speaker(self):
# Delete BGP Speaker: bgp_speaker_id.
resource = 'bgp_speaker'
cmd = bgp_speaker.DeleteSpeaker(test_cli20.MyApp(sys.stdout),
None)
myid = 'myid'
args = [myid]
self._test_delete_resource(resource, cmd, myid, args)
def _test_add_remove_peer(self, action, cmd, args):
"""Add or Remove BGP Peer to/from a BGP Speaker."""
resource = 'bgp_speaker'
subcmd = '%s_bgp_peer' % action
body = {'bgp_peer_id': 'peerid'}
if action == 'add':
retval = {'bgp_peer': 'peerid'}
else:
retval = None
self._test_update_resource_action(resource, cmd, 'myid',
subcmd, args, body, retval)
def test_add_peer_to_bgp_speaker(self):
# Add peer to BGP speaker: myid peer_id=peerid
cmd = bgp_speaker.AddPeerToSpeaker(test_cli20.MyApp(sys.stdout),
None)
args = ['myid', 'peerid']
self._test_add_remove_peer('add', cmd, args)
def test_remove_peer_from_bgp_speaker(self):
# Remove peer from BGP speaker: myid peer_id=peerid
cmd = bgp_speaker.RemovePeerFromSpeaker(test_cli20.MyApp(sys.stdout),
None)
args = ['myid', 'peerid']
self._test_add_remove_peer('remove', cmd, args)
def _test_add_remove_network(self, action, cmd, args):
# Add or Remove network to/from a BGP Speaker.
resource = 'bgp_speaker'
subcmd = '%s_gateway_network' % action
body = {'network_id': 'netid'}
if action == 'add':
retval = {'network': 'netid'}
else:
retval = None
self._test_update_resource_action(resource, cmd, 'myid',
subcmd, args, body, retval)
def test_add_network_to_bgp_speaker(self):
# Add peer to BGP speaker: myid network_id=netid
cmd = bgp_speaker.AddNetworkToSpeaker(test_cli20.MyApp(sys.stdout),
None)
args = ['myid', 'netid']
self._test_add_remove_network('add', cmd, args)
def test_remove_network_from_bgp_speaker(self):
# Remove network from BGP speaker: myid network_id=netid
cmd = bgp_speaker.RemoveNetworkFromSpeaker(
test_cli20.MyApp(sys.stdout), None)
args = ['myid', 'netid']
self._test_add_remove_network('remove', cmd, args)
def test_list_routes_advertised_by_a_bgp_speaker(self):
# Retrieve advertised route list
resources = 'advertised_routes'
cmd = bgp_speaker.ListRoutesAdvertisedBySpeaker(
test_cli20.MyApp(sys.stdout), None)
bs_id = 'bgp_speaker_id1'
path = ((self.client.bgp_speaker_path + '/get_advertised_routes') %
bs_id)
self._test_list_resources(resources, cmd, base_args=[bs_id],
path=path)

View File

@@ -160,3 +160,11 @@ class CLITestV20FirewallJSON(test_cli20.CLITestV20Base):
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)
def test_update_firewall_admin_state(self):
# firewall-update myid --admin-state-up True.
resource = 'firewall'
cmd = firewall.UpdateFirewall(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--admin-state-up', 'True'],
{'admin_state_up': 'True'})

View File

@@ -213,3 +213,14 @@ class CLITestV20FirewallPolicyJSON(test_cli20.CLITestV20Base):
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
def test_update_firewall_policy_name_shared_audited(self):
# firewall-policy-update myid --name newname2 --shared --audited
resource = 'firewall_policy'
cmd = firewallpolicy.UpdateFirewallPolicy(test_cli20.MyApp(sys.stdout),
None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--name', 'newname2',
'--shared', 'True', '--audited', 'True'],
{'name': 'newname2',
'shared': 'True', 'audited': 'True'})

View File

@@ -32,6 +32,7 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
my_id = 'myid'
protocol = 'tcp'
action = 'allow'
ip_version = 4
args = ['--tenant-id', tenant_id,
'--admin-state-up',
'--protocol', protocol,
@@ -42,7 +43,8 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
protocol=protocol, action=action,
enabled=enabled, tenant_id=tenant_id)
enabled=enabled, tenant_id=tenant_id,
ip_version=ip_version)
def test_create_enabled_firewall_rule_with_mandatory_params_lcase(self):
self._test_create_firewall_rule_with_mandatory_params(enabled='true')
@@ -56,7 +58,9 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
def test_create_disabled_firewall_rule_with_mandatory_params(self):
self._test_create_firewall_rule_with_mandatory_params(enabled='False')
def _setup_create_firewall_rule_with_all_params(self, protocol='tcp'):
def _setup_create_firewall_rule_with_all_params(
self, protocol='tcp', protocol_cli=None,
action='allow', action_cli=None, ip_version='4'):
# firewall-rule-create with all params set.
resource = 'firewall_rule'
cmd = firewallrule.CreateFirewallRule(test_cli20.MyApp(sys.stdout),
@@ -67,18 +71,18 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
destination_ip = '192.168.2.0/24'
source_port = '0:65535'
destination_port = '0:65535'
action = 'allow'
tenant_id = 'my-tenant'
my_id = 'myid'
enabled = 'True'
args = ['--description', description,
'--shared',
'--protocol', protocol,
'--protocol', protocol_cli or protocol,
'--ip-version', ip_version,
'--source-ip-address', source_ip,
'--destination-ip-address', destination_ip,
'--source-port', source_port,
'--destination-port', destination_port,
'--action', action,
'--action', action_cli or action,
'--enabled', enabled,
'--admin-state-up',
'--tenant-id', tenant_id]
@@ -86,16 +90,29 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
position_values = []
if protocol == 'any':
protocol = None
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
description=description, shared=True,
protocol=protocol,
source_ip_address=source_ip,
destination_ip_address=destination_ip,
source_port=source_port,
destination_port=destination_port,
action=action, enabled='True',
tenant_id=tenant_id)
if ip_version == '4' or ip_version == '6':
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
description=description, shared=True,
protocol=protocol,
ip_version=int(ip_version),
source_ip_address=source_ip,
destination_ip_address=destination_ip,
source_port=source_port,
destination_port=destination_port,
action=action, enabled='True',
tenant_id=tenant_id)
else:
self.assertRaises(SystemExit, self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values,
ip_version=int(ip_version),
source_ip_address=source_ip,
destination_ip_address=destination_ip,
source_port=source_port,
destination_port=destination_port,
action=action, enabled='True',
tenant_id=tenant_id)
def test_create_firewall_rule_with_all_params(self):
self._setup_create_firewall_rule_with_all_params()
@@ -103,6 +120,22 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
def test_create_firewall_rule_with_proto_any(self):
self._setup_create_firewall_rule_with_all_params(protocol='any')
def test_create_firewall_rule_with_IP_version_6(self):
self._setup_create_firewall_rule_with_all_params(ip_version='6')
def test_create_firewall_rule_with_invalid_IP_version(self):
self._setup_create_firewall_rule_with_all_params(ip_version='5')
def test_create_firewall_rule_with_proto_action_upper_capitalized(self):
for protocol in ('TCP', 'Tcp', 'ANY', 'AnY'):
self._setup_create_firewall_rule_with_all_params(
protocol=protocol.lower(),
protocol_cli=protocol)
for action in ('Allow', 'DENY', 'reject'):
self._setup_create_firewall_rule_with_all_params(
action=action.lower(),
action_cli=action)
def test_list_firewall_rules(self):
# firewall-rule-list.
resources = "firewall_rules"
@@ -160,15 +193,54 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base):
['myid', '--name', 'newname'],
{'name': 'newname', })
def test_update_firewall_rule_protocol(self):
# firewall-rule-update myid --protocol any.
resource = 'firewall_rule'
cmd = firewallrule.UpdateFirewallRule(test_cli20.MyApp(sys.stdout),
None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--protocol', 'any'],
{'protocol': None, })
# firewall-rule-update myid --description any
self._test_update_resource(resource, cmd, 'myid',
['myid', '--description', 'any'],
{'description': 'any', })
# firewall-rule-update myid --source_ip_address 192.192.192.192
self._test_update_resource(resource, cmd, 'myid',
['myid', '--source_ip_address',
'192.192.192.192'],
{'source_ip_address': '192.192.192.192', })
# firewall-rule-update myid --source_port 32767
self._test_update_resource(resource, cmd, 'myid',
['myid', '--source_port', '32767'],
{'source_port': '32767', })
# firewall-rule-update myid --destination_ip_address 0.1.0.1
self._test_update_resource(resource, cmd, 'myid',
['myid', '--destination_ip_address',
'0.1.0.1'],
{'destination_ip_address': '0.1.0.1', })
# firewall-rule-update myid --destination_port 65432
self._test_update_resource(resource, cmd, 'myid',
['myid', '--destination_port',
'65432'],
{'destination_port': '65432', })
# firewall-rule-update myid --enabled False
self._test_update_resource(resource, cmd, 'myid',
['myid', '--enabled', 'False'],
{'enabled': 'False', })
# firewall-rule-update myid --action reject
self._test_update_resource(resource, cmd, 'myid',
['myid', '--action', 'reject'],
{'action': 'reject', })
# firewall-rule-update myid --shared false
self._test_update_resource(resource, cmd, 'myid',
['myid', '--shared', 'false'],
{'shared': 'false', })
def test_delete_firewall_rule(self):
# firewall-rule-delete my-id.
resource = 'firewall_rule'

View File

@@ -0,0 +1,260 @@
# Copyright 2016 Radware LTD.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.lb.v2 import l7policy
from neutronclient.tests.unit import test_cli20
"""Structure for mapping cli and api arguments
The structure maps cli arguments and a list of its
api argument name, default cli value and default api value.
It helps to make tests more general for different argument types.
"""
args_conf = {
'name': ['name', 'test_policy', 'test_policy'],
'description': ['description', 'test policy', 'test policy'],
'listener': ['listener_id', 'test_listener', 'test_listener'],
'admin-state-up': ['admin_state_up', True, True],
'admin-state-down': ['admin_state_up', None, False],
'action': ['action', 'REJECT', 'REJECT'],
'redirect-url': ['redirect_url', 'http://url', 'http://url'],
'redirect-pool': ['redirect_pool_id', 'test_pool', 'test_pool'],
'position': ['position', '1', 1]}
class CLITestV20LbL7PolicyJSON(test_cli20.CLITestV20Base):
def _get_test_args(self, *args, **kwargs):
"""Function for generically building testing arguments"""
cli_args = []
api_args = {}
for arg in args:
cli_args.append('--' + arg)
if not args_conf[arg][1]:
pass
elif arg in kwargs:
cli_args.append(str(kwargs[arg]))
else:
cli_args.append(args_conf[arg][1])
if arg in kwargs:
api_args[args_conf[arg][0]] = kwargs[arg]
else:
api_args[args_conf[arg][0]] = args_conf[arg][2]
return cli_args, api_args
def _test_create_policy(self, *args, **kwargs):
resource = 'l7policy'
cmd_resource = 'lbaas_l7policy'
cmd = l7policy.CreateL7Policy(test_cli20.MyApp(sys.stdout), None)
cli_args, api_args = self._get_test_args(*args, **kwargs)
position_names = list(api_args.keys())
position_values = list(api_args.values())
self._test_create_resource(resource, cmd, None, 'test_id',
cli_args, position_names, position_values,
cmd_resource=cmd_resource)
def _test_update_policy(self, *args, **kwargs):
resource = 'l7policy'
cmd_resource = 'lbaas_l7policy'
cmd = l7policy.UpdateL7Policy(test_cli20.MyApp(sys.stdout), None)
cli_args, api_args = self._get_test_args(*args, **kwargs)
cli_args.append('test_id')
self._test_update_resource(resource, cmd, 'test_id',
cli_args, api_args,
cmd_resource=cmd_resource)
def test_create_policy_with_mandatory_params(self):
# lbaas-l7policy-create with mandatory params only.
self._test_create_policy('action', 'listener')
def test_create_policy_with_all_params(self):
# lbaas-l7policy-create REJECT policy.
self._test_create_policy('name', 'description',
'action', 'listener',
'position')
def test_create_disabled_policy(self):
# lbaas-l7policy-create disabled REJECT policy.
self._test_create_policy('action', 'listener', 'admin-state-down')
def test_create_url_redirect_policy(self):
# lbaas-l7policy-create REDIRECT_TO_URL policy.
self._test_create_policy('name', 'description',
'action', 'listener',
'redirect-url',
action='REDIRECT_TO_URL')
def test_create_url_redirect_policy_no_url(self):
# lbaas-l7policy-create REDIRECT_TO_URL policy without url argument.
self.assertRaises(exceptions.CommandError,
self._test_create_policy,
'name', 'description',
'action', 'listener',
action='REDIRECT_TO_URL')
def test_create_pool_redirect_policy(self):
# lbaas-l7policy-create REDIRECT_TO_POOL policy.
self._test_create_policy('name', 'description',
'action', 'listener',
'redirect-pool',
action='REDIRECT_TO_POOL')
def test_create_pool_redirect_policy_no_pool(self):
# lbaas-l7policy-create REDIRECT_TO_POOL policy without pool argument.
self.assertRaises(exceptions.CommandError,
self._test_create_policy,
'name', 'description',
'action', 'listener',
action='REDIRECT_TO_POOL')
def test_create_reject_policy_with_url(self):
# lbaas-l7policy-create REJECT policy while specifying url argument.
self.assertRaises(exceptions.CommandError,
self._test_create_policy,
'action', 'listener',
'redirect-url')
def test_create_reject_policy_with_pool(self):
# lbaas-l7policy-create REJECT policy while specifying pool argument.
self.assertRaises(exceptions.CommandError,
self._test_create_policy,
'action', 'listener',
'redirect-pool')
def test_create_pool_redirect_policy_with_url(self):
# lbaas-l7policy-create REDIRECT_TO_POOL policy with url argument.
self.assertRaises(exceptions.CommandError,
self._test_create_policy,
'action', 'listener',
'redirect-pool', 'redirect-url',
action='REDIRECT_TO_POOL')
def test_create_url_redirect_policy_with_pool(self):
# lbaas-l7policy-create REDIRECT_TO_URL policy with pool argument.
self.assertRaises(exceptions.CommandError,
self._test_create_policy,
'action', 'listener',
'redirect-pool', 'redirect-url',
action='REDIRECT_TO_URL')
def test_list_policies(self):
# lbaas-l7policy-list.
resources = 'l7policies'
cmd_resources = 'lbaas_l7policies'
cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True,
cmd_resources=cmd_resources)
def test_list_policies_pagination(self):
# lbaas-l7policy-list with pagination.
resources = 'l7policies'
cmd_resources = 'lbaas_l7policies'
cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(
resources, cmd, cmd_resources=cmd_resources)
def test_list_policies_sort(self):
# lbaas-l7policy-list --sort-key id --sort-key asc.
resources = 'l7policies'
cmd_resources = 'lbaas_l7policies'
cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(
resources, cmd, True, cmd_resources=cmd_resources)
def test_list_policies_limit(self):
# lbaas-l7policy-list -P.
resources = 'l7policies'
cmd_resources = 'lbaas_l7policies'
cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(
resources, cmd, page_size=1000, cmd_resources=cmd_resources)
def test_show_policy_id(self):
# lbaas-l7policy-show test_id.
resource = 'l7policy'
cmd_resource = 'lbaas_l7policy'
cmd = l7policy.ShowL7Policy(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'test_id', self.test_id]
self._test_show_resource(
resource, cmd, self.test_id, args,
['test_id'], cmd_resource=cmd_resource)
def test_show_policy_id_name(self):
# lbaas-l7policy-show.
resource = 'l7policy'
cmd_resource = 'lbaas_l7policy'
cmd = l7policy.ShowL7Policy(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'test_id', '--fields', 'name', self.test_id]
self._test_show_resource(
resource, cmd, self.test_id, args,
['test_id', 'name'], cmd_resource=cmd_resource)
def test_disable_policy(self):
# lbaas-l7policy-update test_id --admin-state-up False.
self._test_update_policy('admin-state-up',
**{'admin-state-up': 'False'})
def test_update_policy_name_and_description(self):
# lbaas-l7policy-update test_id --name other --description other_desc.
self._test_update_policy('name', 'description',
name='name',
description='other desc')
def test_update_pool_redirect_policy(self):
# lbaas-l7policy-update test_id --action REDIRECT_TO_POOL
# --redirect-pool id.
self._test_update_policy('action', 'redirect-pool',
**{'action': 'REDIRECT_TO_POOL',
'redirect-pool': 'id'})
def test_update_url_redirect_policy(self):
# lbaas-l7policy-update test_id --action REDIRECT_TO_URL
# --redirect-url http://other_url.
self._test_update_policy('action', 'redirect-url',
**{'action': 'REDIRECT_TO_URL',
'redirect-url': 'http://other_url'})
def test_update_policy_position(self):
# lbaas-l7policy-update test_id --position 2.
self._test_update_policy('position',
position=2)
def test_delete_policy(self):
# lbaas-l7policy-delete test_id.
resource = 'l7policy'
cmd_resource = 'lbaas_l7policy'
cmd = l7policy.DeleteL7Policy(test_cli20.MyApp(sys.stdout), None)
test_id = 'test_id'
args = [test_id]
self._test_delete_resource(resource, cmd, test_id, args,
cmd_resource=cmd_resource)

View File

@@ -0,0 +1,210 @@
# Copyright 2016 Radware LTD.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient.neutron.v2_0.lb.v2 import l7rule
from neutronclient.tests.unit import test_cli20
"""Structure for mapping cli and api arguments
The structure maps cli arguments and a list of its
api argument name, default cli value and default api value.
It helps to make tests more general for different argument types.
"""
args_conf = {
'admin-state-up': ['admin_state_up', True, True],
'admin-state-down': ['admin_state_up', None, False],
'type': ['type', 'HOST_NAME', 'HOST_NAME'],
'compare-type': ['compare_type', 'EQUAL_TO', 'EQUAL_TO'],
'invert-compare': ['invert', None, True],
'key': ['key', 'key', 'key'],
'value': ['value', 'value', 'value']}
class CLITestV20LbL7RuleJSON(test_cli20.CLITestV20Base):
def _get_test_args(self, *args, **kwargs):
"""Function for generically building testing arguments"""
cli_args = []
api_args = {}
for arg in args:
cli_args.append('--' + arg)
if not args_conf[arg][1]:
pass
elif arg in kwargs:
cli_args.append(str(kwargs[arg]))
else:
cli_args.append(args_conf[arg][1])
if arg in kwargs:
api_args[args_conf[arg][0]] = kwargs[arg]
else:
api_args[args_conf[arg][0]] = args_conf[arg][2]
if 'invert' not in api_args:
api_args['invert'] = False
return cli_args, api_args
def _test_create_rule(self, *args, **kwargs):
resource = 'rule'
cmd_resource = 'lbaas_l7rule'
cmd = l7rule.CreateL7Rule(test_cli20.MyApp(sys.stdout), None)
cli_args, api_args = self._get_test_args(*args, **kwargs)
position_names = list(api_args.keys())
position_values = list(api_args.values())
cli_args.append('test_policy')
self._test_create_resource(resource, cmd, None, 'test_id',
cli_args, position_names, position_values,
cmd_resource=cmd_resource,
parent_id='test_policy')
def _test_update_rule(self, *args, **kwargs):
resource = 'rule'
cmd_resource = 'lbaas_l7rule'
cmd = l7rule.UpdateL7Rule(test_cli20.MyApp(sys.stdout), None)
cli_args, api_args = self._get_test_args(*args, **kwargs)
cli_args.append('test_id')
cli_args.append('test_policy')
self._test_update_resource(resource, cmd, 'test_id',
cli_args, api_args,
cmd_resource=cmd_resource,
parent_id='test_policy')
def test_create_rule_with_mandatory_params(self):
# lbaas-l7rule-create with mandatory params only.
self._test_create_rule('type', 'compare-type',
'value')
def test_create_disabled_rule(self):
# lbaas-l7rule-create disabled rule.
self._test_create_rule('type', 'compare-type',
'value', 'admin-state-down')
def test_create_rule_with_all_params(self):
# lbaas-l7rule-create with all params set.
self._test_create_rule('type', 'compare-type',
'invert-compare', 'key', 'value',
type='HEADER', compare_type='CONTAINS',
key='other_key', value='other_value')
def test_create_rule_with_inverted_compare(self):
# lbaas-l7rule-create with invertted compare type.
self._test_create_rule('type', 'compare-type',
'invert-compare', 'value')
def test_list_rules(self):
# lbaas-l7rule-list.
resources = 'rules'
cmd_resources = 'lbaas_l7rules'
cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None)
policy_id = 'policy_id'
self._test_list_resources(resources, cmd, True,
base_args=[policy_id],
cmd_resources=cmd_resources,
parent_id=policy_id,
query="l7policy_id=%s" % policy_id)
def test_list_rules_pagination(self):
# lbaas-l7rule-list with pagination.
resources = 'rules'
cmd_resources = 'lbaas_l7rules'
cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None)
policy_id = 'policy_id'
self._test_list_resources_with_pagination(
resources, cmd, base_args=[policy_id],
cmd_resources=cmd_resources, parent_id=policy_id,
query="l7policy_id=%s" % policy_id)
def test_list_rules_sort(self):
# lbaas-l7rule-list --sort-key id --sort-key asc.
resources = 'rules'
cmd_resources = 'lbaas_l7rules'
cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None)
policy_id = 'policy_id'
self._test_list_resources(
resources, cmd, True, base_args=[policy_id],
cmd_resources=cmd_resources, parent_id=policy_id,
query="l7policy_id=%s" % policy_id)
def test_list_rules_limit(self):
# lbaas-l7rule-list -P.
resources = 'rules'
cmd_resources = 'lbaas_l7rules'
cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None)
policy_id = 'policy_id'
self._test_list_resources(resources, cmd, page_size=1000,
base_args=[policy_id],
cmd_resources=cmd_resources,
parent_id=policy_id,
query="l7policy_id=%s" % policy_id)
def test_show_rule_id(self):
# lbaas-l7rule-show test_id.
resource = 'rule'
cmd_resource = 'lbaas_l7rule'
policy_id = 'policy_id'
cmd = l7rule.ShowL7Rule(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id, policy_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'],
cmd_resource=cmd_resource,
parent_id=policy_id)
def test_update_rule_type(self):
# lbaas-l7rule-update test_id --type HEADER test_policy
self._test_update_rule('type', type='HEADER')
def test_update_rule_compare_type(self):
# lbaas-l7rule-update test_id --compare-type CONTAINS test_policy.
self._test_update_rule('compare-type',
**{'compare-type': 'CONTAINS'})
def test_update_rule_inverted_compare_type(self):
# lbaas-l7rule-update test_id --invert-compare test_policy.
self._test_update_rule('invert-compare')
def test_update_rule_key_value(self):
# lbaas-l7rule-update test_id --key other --value other test_policy.
self._test_update_rule('key', 'value',
key='other', value='other')
def test_delete_rule(self):
# lbaas-l7rule-delete test_id policy_id.
resource = 'rule'
cmd_resource = 'lbaas_l7rule'
policy_id = 'policy_id'
test_id = 'test_id'
cmd = l7rule.DeleteL7Rule(test_cli20.MyApp(sys.stdout), None)
args = [test_id, policy_id]
self._test_delete_resource(resource, cmd, test_id, args,
cmd_resource=cmd_resource,
parent_id=policy_id)

View File

@@ -16,14 +16,15 @@
import sys
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.lb.v2 import listener
from neutronclient.tests.unit import test_cli20
class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base):
def test_create_listener_with_mandatory_params(self):
# lbaas-listener-create with mandatory params only.
def test_create_listener_with_loadbalancer(self):
# lbaas-listener-create with --loadbalancer
resource = 'listener'
cmd_resource = 'lbaas_listener'
cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None)
@@ -40,6 +41,41 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base):
position_names, position_values,
cmd_resource=cmd_resource)
def test_create_listener_with_default_pool(self):
# lbaas-listener-create with --default-pool and no --loadbalancer.
resource = 'listener'
cmd_resource = 'lbaas_listener'
cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
default_pool_id = 'default-pool'
protocol = 'TCP'
protocol_port = '80'
args = ['--protocol', protocol, '--protocol-port', protocol_port,
'--default-pool', default_pool_id]
position_names = ['protocol', 'protocol_port', 'default_pool_id']
position_values = [protocol, protocol_port, default_pool_id,
True]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
def test_create_listener_with_no_loadbalancer_or_default_pool(self):
# lbaas-listener-create without --default-pool or --loadbalancer.
resource = 'listener'
cmd_resource = 'lbaas_listener'
cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
protocol = 'TCP'
protocol_port = '80'
args = ['--protocol', protocol, '--protocol-port', protocol_port]
position_names = ['protocol', 'protocol_port']
position_values = [protocol, protocol_port, True]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource,
no_api_call=True,
expected_exception=exceptions.CommandError)
def test_create_listener_with_all_params(self):
# lbaas-listener-create with all params set.
resource = 'listener'
@@ -47,6 +83,7 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base):
cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
loadbalancer = 'loadbalancer'
default_pool_id = 'default-pool'
protocol = 'TCP'
protocol_port = '80'
connection_limit = 10
@@ -54,14 +91,17 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base):
args = ['--admin-state-down',
'--protocol', protocol, '--protocol-port', protocol_port,
'--loadbalancer', loadbalancer,
'--default-pool', default_pool_id,
'--default-tls-container-ref', def_tls_cont_ref,
'--sni-container-refs', '1111', '2222', '3333',
'--connection-limit', '10']
position_names = ['admin_state_up',
'protocol', 'protocol_port', 'loadbalancer_id',
'default_pool_id',
'default_tls_container_ref', 'sni_container_refs',
'connection_limit']
position_values = [False, protocol, protocol_port, loadbalancer,
default_pool_id,
def_tls_cont_ref, ['1111', '2222', '3333'],
connection_limit]
self._test_create_resource(resource, cmd, '', my_id, args,

View File

@@ -166,3 +166,40 @@ class CLITestV20LbLoadBalancerJSON(test_cli20.CLITestV20Base):
self.assertIn('1234', _str)
self.assertIn('bytes_out', _str)
self.assertIn('4321', _str)
def test_get_loadbalancer_statuses(self):
# lbaas-loadbalancer-status test_id.
resource = 'loadbalancer'
cmd = lb.RetrieveLoadBalancerStatus(test_cli20.MyApp(sys.stdout), None)
my_id = self.test_id
args = [my_id]
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
expected_res = {'statuses': {'operating_status': 'ONLINE',
'provisioning_status': 'ACTIVE'}}
resstr = self.client.serialize(expected_res)
path = getattr(self.client, "lbaas_loadbalancer_path_status")
return_tup = (test_cli20.MyResp(200), resstr)
self.client.httpclient.request(
test_cli20.end_url(path % my_id), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(return_tup)
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("test_" + resource)
parsed_args = cmd_parser.parse_args(args)
cmd.run(parsed_args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
self.assertIn('operating_status', _str)
self.assertIn('ONLINE', _str)
self.assertIn('provisioning_status', _str)
self.assertIn('ACTIVE', _str)

View File

@@ -16,14 +16,15 @@
import sys
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.lb.v2 import pool
from neutronclient.tests.unit import test_cli20
class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base):
def test_create_pool_with_mandatory_params(self):
# lbaas-pool-create with mandatory params only.
def test_create_pool_with_listener(self):
# lbaas-pool-create with listener
resource = 'pool'
cmd_resource = 'lbaas_pool'
cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None)
@@ -40,6 +41,41 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base):
position_names, position_values,
cmd_resource=cmd_resource)
def test_create_pool_with_loadbalancer_no_listener(self):
"""lbaas-pool-create with loadbalancer, no listener."""
resource = 'pool'
cmd_resource = 'lbaas_pool'
cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
lb_algorithm = 'ROUND_ROBIN'
loadbalancer = 'loadbalancer'
protocol = 'TCP'
args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol,
'--loadbalancer', loadbalancer]
position_names = ['admin_state_up', 'lb_algorithm', 'protocol',
'loadbalancer_id']
position_values = [True, lb_algorithm, protocol, loadbalancer]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
def test_create_pool_with_no_listener_or_loadbalancer(self):
"""lbaas-pool-create with no listener or loadbalancer."""
resource = 'pool'
cmd_resource = 'lbaas_pool'
cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
lb_algorithm = 'ROUND_ROBIN'
protocol = 'TCP'
args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol]
position_names = ['admin_state_up', 'lb_algorithm', 'protocol']
position_values = [True, lb_algorithm, protocol]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource,
no_api_call=True,
expected_exception=exceptions.CommandError)
def test_create_pool_with_all_params(self):
# lbaas-pool-create with all params set.
resource = 'pool'
@@ -48,6 +84,7 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base):
my_id = 'my-id'
lb_algorithm = 'ROUND_ROBIN'
listener = 'listener'
loadbalancer = 'loadbalancer'
protocol = 'TCP'
description = 'description'
session_persistence_str = 'type=APP_COOKIE,cookie_name=1234'
@@ -57,12 +94,13 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base):
args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol,
'--description', description, '--session-persistence',
session_persistence_str, '--admin-state-down', '--name', name,
'--listener', listener]
'--listener', listener, '--loadbalancer', loadbalancer]
position_names = ['lb_algorithm', 'protocol', 'description',
'session_persistence', 'admin_state_up', 'name',
'listener_id']
'listener_id', 'loadbalancer_id']
position_values = [lb_algorithm, protocol, description,
session_persistence, False, name, listener]
session_persistence, False, name, listener,
loadbalancer]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)

View File

@@ -0,0 +1,55 @@
# Copyright 2016 IBM
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient.neutron.v2_0 import auto_allocated_topology as aat
from neutronclient.tests.unit import test_cli20
class TestAutoAllocatedTopologyJSON(test_cli20.CLITestV20Base):
def test_show_auto_allocated_topology_arg(self):
resource = 'auto_allocated_topology'
cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None)
args = ['--tenant-id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args)
def test_show_auto_allocated_topology_posarg(self):
resource = 'auto_allocated_topology'
cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None)
args = ['some-tenant']
self._test_show_resource(resource, cmd, "some-tenant", args)
def test_show_auto_allocated_topology_no_arg(self):
resource = 'auto_allocated_topology'
cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None)
args = []
self._test_show_resource(resource, cmd, "None", args)
def test_show_auto_allocated_topology_dry_run_as_tenant(self):
resource = 'auto_allocated_topology'
cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None)
args = ['--dry-run']
self._test_show_resource(resource, cmd, "None", args,
fields=('dry-run',))
def test_show_auto_allocated_topology_dry_run_as_admin(self):
resource = 'auto_allocated_topology'
cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None)
args = ['--dry-run', 'some-tenant']
self._test_show_resource(resource, cmd, "some-tenant", args,
fields=('dry-run',))

View File

@@ -37,6 +37,7 @@ API_VERSION = "2.0"
FORMAT = 'json'
TOKEN = 'testtoken'
ENDURL = 'localurl'
REQUEST_ID = 'test_request_id'
@contextlib.contextmanager
@@ -65,7 +66,7 @@ class FakeStdout(object):
return result
class MyResp(object):
class MyResp(requests.Response):
def __init__(self, status_code, headers=None, reason=None):
self.status_code = status_code
self.headers = headers or {}
@@ -557,6 +558,81 @@ class CLITestV20Base(base.BaseTestCase):
self.assertIn(myid, _str)
class TestListCommand(neutronV2_0.ListCommand):
resource = 'test_resource'
filter_attrs = [
'name',
'admin_state_up',
{'name': 'foo', 'help': 'non-boolean attribute foo'},
{'name': 'bar', 'help': 'boolean attribute bar',
'boolean': True},
{'name': 'baz', 'help': 'integer attribute baz',
'argparse_kwargs': {'choices': ['baz1', 'baz2']}},
]
class ListCommandTestCase(CLITestV20Base):
def setUp(self):
super(ListCommandTestCase, self).setUp()
self.client.extend_list('test_resources', '/test_resources', None)
setattr(self.client, 'test_resources_path', '/test_resources')
def _test_list_resources_filter_params(self, base_args='', query=''):
resources = 'test_resources'
cmd = TestListCommand(MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd,
base_args=base_args.split(),
query=query)
def _test_list_resources_with_arg_error(self, base_args=''):
self.addCleanup(self.mox.UnsetStubs)
resources = 'test_resources'
cmd = TestListCommand(MyApp(sys.stdout), None)
# argparse parse error leads to SystemExit
self.assertRaises(SystemExit,
self._test_list_resources,
resources, cmd,
base_args=base_args.split())
def test_list_resources_without_filter(self):
self._test_list_resources_filter_params()
def test_list_resources_use_default_filter(self):
self._test_list_resources_filter_params(
base_args='--name val1 --admin-state-up False',
query='name=val1&admin_state_up=False')
def test_list_resources_use_custom_filter(self):
self._test_list_resources_filter_params(
base_args='--foo FOO --bar True',
query='foo=FOO&bar=True')
def test_list_resources_boolean_check_default_filter(self):
self._test_list_resources_filter_params(
base_args='--admin-state-up True', query='admin_state_up=True')
self._test_list_resources_filter_params(
base_args='--admin-state-up False', query='admin_state_up=False')
self._test_list_resources_with_arg_error(
base_args='--admin-state-up non-true-false')
def test_list_resources_boolean_check_custom_filter(self):
self._test_list_resources_filter_params(
base_args='--bar True', query='bar=True')
self._test_list_resources_filter_params(
base_args='--bar False', query='bar=False')
self._test_list_resources_with_arg_error(
base_args='--bar non-true-false')
def test_list_resources_argparse_kwargs(self):
self._test_list_resources_filter_params(
base_args='--baz baz1', query='baz=baz1')
self._test_list_resources_filter_params(
base_args='--baz baz2', query='baz=baz2')
self._test_list_resources_with_arg_error(
base_args='--bar non-choice')
class ClientV2TestJson(CLITestV20Base):
def test_do_request_unicode(self):
self.mox.StubOutWithMock(self.client.httpclient, "request")
@@ -573,41 +649,46 @@ class ClientV2TestJson(CLITestV20Base):
self.client.httpclient.auth_token = encodeutils.safe_encode(
unicode_text)
expected_auth_token = encodeutils.safe_encode(unicode_text)
resp_headers = {'x-openstack-request-id': REQUEST_ID}
self.client.httpclient.request(
end_url(expected_action, query=expect_query, format=self.format),
'PUT', body=expect_body,
headers=mox.ContainsKeyValue(
'X-Auth-Token',
expected_auth_token)).AndReturn((MyResp(200), expect_body))
expected_auth_token)).AndReturn((MyResp(200, resp_headers),
expect_body))
self.mox.ReplayAll()
res_body = self.client.do_request('PUT', action, body=body,
params=params)
result = self.client.do_request('PUT', action, body=body,
params=params)
self.mox.VerifyAll()
self.mox.UnsetStubs()
# test response with unicode
self.assertEqual(body, res_body)
self.assertEqual(body, result)
def test_do_request_error_without_response_body(self):
self.mox.StubOutWithMock(self.client.httpclient, "request")
params = {'test': 'value'}
expect_query = six.moves.urllib.parse.urlencode(params)
self.client.httpclient.auth_token = 'token'
resp_headers = {'x-openstack-request-id': REQUEST_ID}
self.client.httpclient.request(
MyUrlComparator(end_url(
'/test', query=expect_query, format=self.format), self.client),
'PUT', body='',
headers=mox.ContainsKeyValue('X-Auth-Token', 'token')
).AndReturn((MyResp(400, reason='An error'), ''))
).AndReturn((MyResp(400, headers=resp_headers, reason='An error'), ''))
self.mox.ReplayAll()
error = self.assertRaises(exceptions.NeutronClientException,
self.client.do_request, 'PUT', '/test',
body='', params=params)
self.assertEqual("An error", str(error))
expected_error = "An error\nNeutron server returns " \
"request_ids: %s" % [REQUEST_ID]
self.assertEqual(expected_error, str(error))
self.mox.VerifyAll()
self.mox.UnsetStubs()
@@ -622,22 +703,129 @@ class ClientV2TestJson(CLITestV20Base):
else:
self.fail('Expected exception NOT raised')
def test_do_request_request_ids(self):
self.mox.StubOutWithMock(self.client.httpclient, "request")
params = {'test': 'value'}
expect_query = six.moves.urllib.parse.urlencode(params)
self.client.httpclient.auth_token = 'token'
body = params
expect_body = self.client.serialize(body)
resp_headers = {'x-openstack-request-id': REQUEST_ID}
self.client.httpclient.request(
MyUrlComparator(end_url(
'/test', query=expect_query,
format=self.format), self.client),
'PUT', body=expect_body,
headers=mox.ContainsKeyValue('X-Auth-Token', 'token')
).AndReturn((MyResp(200, resp_headers), expect_body))
self.mox.ReplayAll()
result = self.client.do_request('PUT', '/test', body=body,
params=params)
self.mox.VerifyAll()
self.mox.UnsetStubs()
self.assertEqual(body, result)
self.assertEqual([REQUEST_ID], result.request_ids)
def test_list_request_ids_with_retrieve_all_true(self):
self.mox.StubOutWithMock(self.client.httpclient, "request")
path = '/test'
resources = 'tests'
fake_query = "marker=myid2&limit=2"
reses1 = {resources: [{'id': 'myid1', },
{'id': 'myid2', }],
'%s_links' % resources: [{'href': end_url(path, fake_query),
'rel': 'next'}]}
reses2 = {resources: [{'id': 'myid3', },
{'id': 'myid4', }]}
resstr1 = self.client.serialize(reses1)
resstr2 = self.client.serialize(reses2)
resp_headers = {'x-openstack-request-id': REQUEST_ID}
self.client.httpclient.request(
end_url(path, "", format=self.format), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
resstr1))
self.client.httpclient.request(
MyUrlComparator(end_url(path, fake_query, format=self.format),
self.client), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
resstr2))
self.mox.ReplayAll()
result = self.client.list(resources, path)
self.mox.VerifyAll()
self.mox.UnsetStubs()
self.assertEqual([REQUEST_ID, REQUEST_ID], result.request_ids)
def test_list_request_ids_with_retrieve_all_false(self):
self.mox.StubOutWithMock(self.client.httpclient, "request")
path = '/test'
resources = 'tests'
fake_query = "marker=myid2&limit=2"
reses1 = {resources: [{'id': 'myid1', },
{'id': 'myid2', }],
'%s_links' % resources: [{'href': end_url(path, fake_query),
'rel': 'next'}]}
reses2 = {resources: [{'id': 'myid3', },
{'id': 'myid4', }]}
resstr1 = self.client.serialize(reses1)
resstr2 = self.client.serialize(reses2)
resp_headers = {'x-openstack-request-id': REQUEST_ID}
self.client.httpclient.request(
end_url(path, "", format=self.format), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
resstr1))
self.client.httpclient.request(
MyUrlComparator(end_url(path, fake_query, format=self.format),
self.client), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
resstr2))
self.mox.ReplayAll()
result = self.client.list(resources, path, retrieve_all=False)
next(result)
self.assertEqual([REQUEST_ID], result.request_ids)
next(result)
self.assertEqual([REQUEST_ID, REQUEST_ID], result.request_ids)
self.mox.VerifyAll()
self.mox.UnsetStubs()
class CLITestV20ExceptionHandler(CLITestV20Base):
def _test_exception_handler_v20(
self, expected_exception, status_code, expected_msg,
error_type=None, error_msg=None, error_detail=None,
error_content=None):
request_id=None, error_content=None):
resp = MyResp(status_code, {'x-openstack-request-id': request_id})
if request_id is not None:
expected_msg += "\nNeutron server returns " \
"request_ids: %s" % [request_id]
if error_content is None:
error_content = {'NeutronError': {'type': error_type,
'message': error_msg,
'detail': error_detail}}
expected_content = self.client._convert_into_with_meta(error_content,
resp)
e = self.assertRaises(expected_exception,
client.exception_handler_v20,
status_code, error_content)
status_code, expected_content)
self.assertEqual(status_code, e.status_code)
self.assertEqual(expected_exception.__name__,
e.__class__.__name__)
if expected_msg is None:
if error_detail:
@@ -651,7 +839,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
'fake-network-uuid. The IP address fake-ip is in use.')
self._test_exception_handler_v20(
exceptions.IpAddressInUseClient, 409, err_msg,
'IpAddressInUse', err_msg, '')
'IpAddressInUse', err_msg, '', REQUEST_ID)
def test_exception_handler_v20_neutron_known_error(self):
known_error_map = [
@@ -677,7 +865,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
self._test_exception_handler_v20(
client_exc, status_code,
error_msg + '\n' + error_detail,
server_exc, error_msg, error_detail)
server_exc, error_msg, error_detail, REQUEST_ID)
def test_exception_handler_v20_neutron_known_error_without_detail(self):
error_msg = 'Network not found'
@@ -685,7 +873,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
self._test_exception_handler_v20(
exceptions.NetworkNotFoundClient, 404,
error_msg,
'NetworkNotFound', error_msg, error_detail)
'NetworkNotFound', error_msg, error_detail, REQUEST_ID)
def test_exception_handler_v20_unknown_error_to_per_code_exception(self):
for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items():
@@ -694,7 +882,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
self._test_exception_handler_v20(
client_exc, status_code,
error_msg + '\n' + error_detail,
'UnknownError', error_msg, error_detail)
'UnknownError', error_msg, error_detail, [REQUEST_ID])
def test_exception_handler_v20_neutron_unknown_status_code(self):
error_msg = 'Unknown error'
@@ -702,36 +890,44 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
self._test_exception_handler_v20(
exceptions.NeutronClientException, 501,
error_msg + '\n' + error_detail,
'UnknownError', error_msg, error_detail)
'UnknownError', error_msg, error_detail, REQUEST_ID)
def test_exception_handler_v20_bad_neutron_error(self):
error_content = {'NeutronError': {'unknown_key': 'UNKNOWN'}}
self._test_exception_handler_v20(
exceptions.NeutronClientException, 500,
expected_msg={'unknown_key': 'UNKNOWN'},
error_content=error_content)
for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items():
error_content = {'NeutronError': {'unknown_key': 'UNKNOWN'}}
self._test_exception_handler_v20(
client_exc, status_code,
expected_msg="{'unknown_key': 'UNKNOWN'}",
error_content=error_content,
request_id=REQUEST_ID)
def test_exception_handler_v20_error_dict_contains_message(self):
error_content = {'message': 'This is an error message'}
self._test_exception_handler_v20(
exceptions.NeutronClientException, 500,
expected_msg='This is an error message',
error_content=error_content)
for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items():
self._test_exception_handler_v20(
client_exc, status_code,
expected_msg='This is an error message',
error_content=error_content,
request_id=REQUEST_ID)
def test_exception_handler_v20_error_dict_not_contain_message(self):
# 599 is not contained in HTTP_EXCEPTION_MAP.
error_content = {'error': 'This is an error message'}
expected_msg = '%s-%s' % (500, error_content)
expected_msg = '%s-%s' % (599, error_content)
self._test_exception_handler_v20(
exceptions.NeutronClientException, 500,
exceptions.NeutronClientException, 599,
expected_msg=expected_msg,
request_id=None,
error_content=error_content)
def test_exception_handler_v20_default_fallback(self):
# 599 is not contained in HTTP_EXCEPTION_MAP.
error_content = 'This is an error message'
expected_msg = '%s-%s' % (500, error_content)
expected_msg = '%s-%s' % (599, error_content)
self._test_exception_handler_v20(
exceptions.NeutronClientException, 500,
exceptions.NeutronClientException, 599,
expected_msg=expected_msg,
request_id=None,
error_content=error_content)
def test_exception_status(self):
@@ -767,3 +963,60 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
self.assertIsNotNone(error.status_code)
self.mox.VerifyAll()
self.mox.UnsetStubs()
class DictWithMetaTest(base.BaseTestCase):
def test_dict_with_meta(self):
body = {'test': 'value'}
resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
obj = client._DictWithMeta(body, resp)
self.assertEqual(body, obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class TupleWithMetaTest(base.BaseTestCase):
def test_tuple_with_meta(self):
body = ('test', 'value')
resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
obj = client._TupleWithMeta(body, resp)
self.assertEqual(body, obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class StrWithMetaTest(base.BaseTestCase):
def test_str_with_meta(self):
body = "test_string"
resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
obj = client._StrWithMeta(body, resp)
self.assertEqual(body, obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class GeneratorWithMetaTest(base.BaseTestCase):
body = {'test': 'value'}
resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
def _pagination(self, collection, path, **params):
obj = client._DictWithMeta(self.body, self.resp)
yield obj
def test_generator(self):
obj = client._GeneratorWithMeta(self._pagination, 'test_collection',
'test_path', test_args='test_args')
self.assertEqual(self.body, next(obj))
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)

View File

@@ -30,19 +30,46 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base):
def setUp(self):
super(CLITestV20AddressScopeJSON, self).setUp(plurals={'tags': 'tag'})
def test_create_address_scope_with_minimum_option(self):
# Create address_scope: foo-address-scope with minimum option.
def test_create_address_scope_with_minimum_option_ipv4(self):
"""Create address_scope: foo-address-scope with minimum option."""
resource = 'address_scope'
cmd = address_scope.CreateAddressScope(
test_cli20.MyApp(sys.stdout), None)
name = 'foo-address-scope'
myid = 'myid'
args = [name]
position_names = ['name']
position_values = [name]
args = [name, '4']
position_names = ['name', 'ip_version']
position_values = [name, 4]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_address_scope_with_minimum_option_ipv6(self):
"""Create address_scope: foo-address-scope with minimum option."""
resource = 'address_scope'
cmd = address_scope.CreateAddressScope(
test_cli20.MyApp(sys.stdout), None)
name = 'foo-address-scope'
myid = 'myid'
args = [name, '6']
position_names = ['name', 'ip_version']
position_values = [name, 6]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_address_scope_with_minimum_option_bad_ip_version(self):
"""Create address_scope: foo-address-scope with minimum option."""
resource = 'address_scope'
cmd = address_scope.CreateAddressScope(
test_cli20.MyApp(sys.stdout), None)
name = 'foo-address-scope'
myid = 'myid'
args = [name, '5']
position_names = ['name', 'ip_version']
position_values = [name, 5]
self.assertRaises(SystemExit, self._test_create_resource,
resource, cmd, name, myid, args, position_names,
position_values)
def test_create_address_scope_with_all_option(self):
# Create address_scope: foo-address-scope with all options.
resource = 'address_scope'
@@ -50,9 +77,9 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base):
test_cli20.MyApp(sys.stdout), None)
name = 'foo-address-scope'
myid = 'myid'
args = [name, '--shared']
position_names = ['name', 'shared']
position_values = [name, True]
args = [name, '4', '--shared']
position_names = ['name', 'ip_version', 'shared']
position_values = [name, 4, True]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
@@ -62,10 +89,11 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base):
cmd = address_scope.CreateAddressScope(
test_cli20.MyApp(sys.stdout), None)
name = u'\u7f51\u7edc'
ip_version = u'4'
myid = 'myid'
args = [name]
position_names = ['name']
position_values = [name]
args = [name, ip_version]
position_names = ['name', 'ip_version']
position_values = [name, 4]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
@@ -115,7 +143,7 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base):
cmd = address_scope.ListAddressScope(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_key=["name", "id", "ip_version"],
sort_dir=["asc", "desc"])
def test_list_address_scope_limit(self):
@@ -130,9 +158,10 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base):
resource = 'address_scope'
cmd = address_scope.ShowAddressScope(
test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
args = ['--fields', 'id', '--fields', 'name', self.test_id,
'--fields', 'ip_version', '6']
self._test_show_resource(resource, cmd, self.test_id, args,
['id', 'name'])
['id', 'name', 'ip_version'])
def test_delete_address_scope(self):
# Delete address_scope: address_scope_id.

View File

@@ -117,6 +117,21 @@ class CLITestV20FloatingIpsJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_floatingip_with_dns_name_and_dns_domain(self):
# Create floatingip: fip1 with dns name and dns domain.
resource = 'floatingip'
cmd = fip.CreateFloatingIP(test_cli20.MyApp(sys.stdout), None)
name = 'fip1'
myid = 'myid'
dns_name_name = 'my-floatingip'
dns_domain_name = 'my-domain.org.'
args = [name, '--dns-name', dns_name_name, '--dns-domain',
dns_domain_name]
position_names = ['floating_network_id', 'dns_name', 'dns_domain']
position_values = [name, dns_name_name, dns_domain_name]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_list_floatingips(self):
# list floatingips: -D.
resources = 'floatingips'

View File

@@ -163,6 +163,19 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_network_with_dns_domain(self):
# Create net: --dns-domain my-domain.org.
resource = 'network'
cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None)
name = 'myname'
myid = 'myid'
dns_domain_name = 'my-domain.org.'
args = [name, '--dns-domain', dns_domain_name]
position_names = ['name', 'dns_domain']
position_values = [name, dns_domain_name]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_list_nets_empty_with_column(self):
resources = "networks"
cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None)
@@ -196,13 +209,15 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base):
def _test_list_networks(self, cmd, detail=False, tags=(),
fields_1=(), fields_2=(), page_size=None,
sort_key=(), sort_dir=()):
sort_key=(), sort_dir=(), base_args=None,
query=''):
resources = "networks"
self.mox.StubOutWithMock(network.ListNetwork, "extend_list")
network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg())
self._test_list_resources(resources, cmd, detail, tags,
fields_1, fields_2, page_size=page_size,
sort_key=sort_key, sort_dir=sort_dir)
sort_key=sort_key, sort_dir=sort_dir,
base_args=base_args, query=query)
def test_list_nets_pagination(self):
cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None)
@@ -288,7 +303,7 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base):
args = []
cmd_parser = cmd.get_parser('list_networks')
parsed_args = cmd_parser.parse_args(args)
result = cmd.get_data(parsed_args)
result = cmd.take_action(parsed_args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_result = [x for x in result[1]]
@@ -526,6 +541,22 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base):
['myid', '--no-qos-policy'],
{'qos_policy_id': None, })
def test_update_network_with_dns_domain(self):
# Update net: myid --dns-domain my-domain.org.
resource = 'network'
cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--dns-domain', 'my-domain.org.'],
{'dns_domain': 'my-domain.org.', })
def test_update_network_with_no_dns_domain(self):
# Update net: myid --no-dns-domain
resource = 'network'
cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--no-dns-domain'],
{'dns_domain': "", })
def test_show_network(self):
# Show net: --fields id --fields name myid.
resource = 'network'
@@ -611,3 +642,9 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base):
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response)
self._test_extend_list(mox_calls)
def test_list_shared_networks(self):
# list nets : --shared False
cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None)
self._test_list_networks(cmd, base_args='--shared False'.split(),
query='shared=False')

View File

@@ -0,0 +1,54 @@
# 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 sys
from neutronclient.neutron.v2_0 import network_ip_availability
from neutronclient.tests.unit import test_cli20
class CLITestV20NetworkIPAvailability(test_cli20.CLITestV20Base):
id_field = 'network_id'
def _test_list_network_ip_availability(self, args, query):
resources = "network_ip_availabilities"
cmd = network_ip_availability.ListIpAvailability(test_cli20.MyApp
(sys.stdout), None)
self._test_list_resources(resources, cmd,
base_args=args,
query=query)
def test_list_network_ip_availability(self):
self._test_list_network_ip_availability(args=None,
query='ip_version=4')
def test_list_network_ip_availability_ipv6(self):
self._test_list_network_ip_availability(
args=['--ip-version', '6'], query='ip_version=6')
def test_list_network_ip_availability_net_id_and_ipv4(self):
self._test_list_network_ip_availability(
args=['--ip-version', '4', '--network-id', 'myid'],
query='ip_version=4&network_id=myid')
def test_list_network_ip_availability_net_name_and_tenant_id(self):
self._test_list_network_ip_availability(
args=['--network-name', 'foo', '--tenant-id', 'mytenant'],
query='network_name=foo&tenant_id=mytenant&ip_version=4')
def test_show_network_ip_availability(self):
resource = "network_ip_availability"
cmd = network_ip_availability.ShowIpAvailability(
test_cli20.MyApp(sys.stdout), None)
self._test_show_resource(resource, cmd, self.test_id,
args=[self.test_id])

View File

@@ -338,6 +338,20 @@ class CLITestV20PortJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_port_with_dns_name(self):
# Create port: --dns-name my-port.
resource = 'port'
cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None)
name = 'myname'
myid = 'myid'
netid = 'netid'
dns_name_name = 'my-port'
args = [netid, '--dns-name', dns_name_name]
position_names = ['network_id', 'dns_name']
position_values = [netid, dns_name_name]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_port_with_allowed_address_pair_ipaddr(self):
# Create port:
# --allowed-address-pair ip_address=addr0
@@ -589,13 +603,14 @@ class CLITestV20PortJSON(test_cli20.CLITestV20Base):
resource = 'port'
cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None)
myid = 'myid'
net_id = 'net_id'
subnet_id = 'subnet_id'
ip_addr = '123.123.123.123'
args = [myid,
'--fixed-ip', "network_id=%(net_id)s,ip_address=%(ip_addr)s" %
{'net_id': net_id,
'--fixed-ip',
"subnet_id=%(subnet_id)s,ip_address=%(ip_addr)s" %
{'subnet_id': subnet_id,
'ip_addr': ip_addr}]
updated_fields = {"fixed_ips": [{'network_id': net_id,
updated_fields = {"fixed_ips": [{'subnet_id': subnet_id,
'ip_address': ip_addr}]}
self._test_update_resource(resource, cmd, myid, args, updated_fields)
@@ -648,6 +663,22 @@ class CLITestV20PortJSON(test_cli20.CLITestV20Base):
['myid', '--no-qos-policy'],
{'qos_policy_id': None, })
def test_update_port_with_dns_name(self):
# Update port: myid --dns-name my-port.
resource = 'port'
cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--dns-name', 'my-port'],
{'dns_name': 'my-port', })
def test_update_port_with_no_dns_name(self):
# Update port: myid --no-dns-name
resource = 'port'
cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--no-dns-name'],
{'dns_name': "", })
def test_delete_extra_dhcp_opts_from_port(self):
resource = 'port'
myid = 'myid'

View File

@@ -0,0 +1,100 @@
# Copyright 2016 Cisco Systems
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from neutronclient.neutron.v2_0 import purge
from neutronclient.tests.unit import test_cli20
class CLITestV20Purge(test_cli20.CLITestV20Base):
def setUp(self):
super(CLITestV20Purge, self).setUp()
self.resource_types = ['floatingip', 'port', 'router',
'network', 'security_group']
def _generate_resources_dict(self, value=0):
resources_dict = {}
resources_dict['true'] = value
for resource_type in self.resource_types:
resources_dict[resource_type] = value
return resources_dict
def _verify_suffix(self, resources, message):
for resource, value in resources.items():
if value > 0:
suffix = list('%(value)d %(resource)s' %
{'value': value, 'resource': resource})
if value != 1:
suffix.append('s')
suffix = ''.join(suffix)
self.assertIn(suffix, message)
else:
self.assertNotIn(resource, message)
def _verify_message(self, message, deleted, failed):
message = message.split('.')
success_prefix = "Deleted "
failure_prefix = "The following resources could not be deleted: "
if not deleted['true']:
for msg in message:
self.assertNotIn(success_prefix, msg)
message = message[0]
if not failed['true']:
expected = 'Tenant has no supported resources'
self.assertEqual(expected, message)
else:
self.assertIn(failure_prefix, message)
self._verify_suffix(failed, message)
else:
resources_deleted = message[0]
self.assertIn(success_prefix, resources_deleted)
self._verify_suffix(deleted, resources_deleted)
if failed['true']:
resources_failed = message[1]
self.assertIn(failure_prefix, resources_failed)
self._verify_suffix(failed, resources_failed)
else:
for msg in message:
self.assertNotIn(failure_prefix, msg)
def _verify_result(self, my_purge, deleted, failed):
message = my_purge._build_message(deleted, failed, failed['true'])
self._verify_message(message, deleted, failed)
def test_build_message(self):
my_purge = purge.Purge(test_cli20.MyApp(sys.stdout), None)
# Verify message when tenant has no supported resources
deleted = self._generate_resources_dict()
failed = self._generate_resources_dict()
self._verify_result(my_purge, deleted, failed)
# Verify message when tenant has supported resources,
# and they are all deleteable
deleted = self._generate_resources_dict(1)
self._verify_result(my_purge, deleted, failed)
# Verify message when tenant has supported resources,
# and some are not deleteable
failed = self._generate_resources_dict(1)
self._verify_result(my_purge, deleted, failed)
# Verify message when tenant has supported resources,
# and all are not deleteable
deleted = self._generate_resources_dict()
self._verify_result(my_purge, deleted, failed)

View File

@@ -15,41 +15,54 @@
import sys
import testscenarios
from neutronclient.neutron.v2_0 import rbac
from neutronclient.tests.unit import test_cli20
load_tests = testscenarios.load_tests_apply_scenarios
class CLITestV20RBACJSON(test_cli20.CLITestV20Base):
class CLITestV20RBACBaseJSON(test_cli20.CLITestV20Base):
non_admin_status_resources = ['rbac_policy']
scenarios = [
('network rbac objects',
{'object_type_name': 'network', 'object_type_val': 'network'}),
('qos policy rbac objects',
{'object_type_name': 'qos-policy', 'object_type_val': 'qos_policy'}),
]
def test_create_rbac_policy_with_mandatory_params(self):
# Create rbac: rbac_object --type network --action access_as_shared
# Create rbac: rbac_object --type <object_type_name> --action
# access_as_shared
resource = 'rbac_policy'
cmd = rbac.CreateRBACPolicy(test_cli20.MyApp(sys.stdout), None)
name = 'rbac_object'
myid = 'myid'
args = [name, '--type', 'network',
args = [name, '--type', self.object_type_name,
'--action', 'access_as_shared']
position_names = ['object_id', 'object_type',
'target_tenant', 'action']
position_values = [name, 'network', None, 'access_as_shared']
position_values = [name, self.object_type_val, None,
'access_as_shared']
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_rbac_policy_with_all_params(self):
# Create rbac: rbac_object --type network --target-tenant tenant_id
# --action access_as_external
# Create rbac: rbac_object --type <object_type_name>
# --target-tenant tenant_id --action access_as_external
resource = 'rbac_policy'
cmd = rbac.CreateRBACPolicy(test_cli20.MyApp(sys.stdout), None)
name = 'rbac_object'
myid = 'myid'
args = [name, '--type', 'network',
args = [name, '--type', self.object_type_name,
'--target-tenant', 'tenant_id',
'--action', 'access_as_external']
position_names = ['object_id', 'object_type',
'target_tenant', 'action']
position_values = [name, 'network', 'tenant_id', 'access_as_external']
position_values = [name, self.object_type_val, 'tenant_id',
'access_as_external']
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
@@ -59,12 +72,13 @@ class CLITestV20RBACJSON(test_cli20.CLITestV20Base):
cmd = rbac.CreateRBACPolicy(test_cli20.MyApp(sys.stdout), None)
name = u'\u7f51\u7edc'
myid = 'myid'
args = [name, '--type', 'network',
args = [name, '--type', self.object_type_name,
'--target-tenant', 'tenant_id',
'--action', 'access_as_external']
position_names = ['object_id', 'object_type',
'target_tenant', 'action']
position_values = [name, 'network', 'tenant_id', 'access_as_external']
position_values = [name, self.object_type_val, 'tenant_id',
'access_as_external']
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)

View File

@@ -383,6 +383,21 @@ class CLITestV20RouterJSON(test_cli20.CLITestV20Base):
{"subnet_id": "mysubnet"}]}}
)
def test_set_gateway_external_ip_and_subnet(self):
# set external gateway for router: myid externalid --fixed-ip ...
resource = 'router'
cmd = router.SetGatewayRouter(test_cli20.MyApp(sys.stdout), None)
args = ['myid', 'externalid', '--fixed-ip',
'ip_address=10.0.0.2,subnet_id=mysubnet']
self._test_update_resource(resource, cmd, 'myid',
args,
{"external_gateway_info":
{"network_id": "externalid",
"external_fixed_ips": [
{"subnet_id": "mysubnet",
"ip_address": "10.0.0.2"}]}}
)
def test_remove_gateway(self):
# Remove external gateway from router: externalid.
resource = 'router'

View File

@@ -407,7 +407,7 @@ class CLITestV20SecurityGroupsJSON(test_cli20.CLITestV20Base):
cmd_parser = cmd.get_parser('list_security_group_rules')
parsed_args = cmd_parser.parse_args(args)
result = cmd.get_data(parsed_args)
result = cmd.take_action(parsed_args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
# Check columns

View File

@@ -290,6 +290,25 @@ class CLITestV20SubnetJSON(test_cli20.CLITestV20Base):
position_names, position_values,
tenant_id='tenantid')
def test_create_subnet_with_use_default_subnetpool(self):
# Create subnet: --tenant-id tenantid --use-default-subnetpool \
# netid cidr.
resource = 'subnet'
cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None)
name = 'myname'
myid = 'myid'
netid = 'netid'
cidr = 'prefixvalue'
args = ['--tenant_id', 'tenantid',
'--use-default-subnetpool',
netid, cidr]
position_names = ['ip_version', 'use_default_subnetpool', 'network_id',
'cidr']
position_values = [4, True, netid, cidr]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values,
tenant_id='tenantid')
def test_create_subnet_with_disable_dhcp(self):
# Create subnet: --tenant-id tenantid --disable-dhcp netid cidr.
resource = 'subnet'

View File

@@ -63,6 +63,26 @@ class CLITestV20SubnetPoolJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_subnetpool(self, default='false'):
# Create subnetpool: myname.
resource = 'subnetpool'
cmd = subnetpool.CreateSubnetPool(test_cli20.MyApp(sys.stdout), None)
name = 'myname'
myid = 'myid'
min_prefixlen = 30
prefix1 = '10.11.12.0/24'
prefix2 = '12.11.13.0/24'
args = [name, '--min-prefixlen', str(min_prefixlen),
'--pool-prefix', prefix1, '--pool-prefix', prefix2,
'--is-default', default]
position_names = ['name', 'min_prefixlen', 'prefixes', 'is_default']
position_values = [name, min_prefixlen, [prefix1, prefix2], default]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_subnetpool_default(self):
self.test_create_subnetpool(default='true')
def test_create_subnetpool_with_unicode(self):
# Create subnetpool: u'\u7f51\u7edc'.
resource = 'subnetpool'

View File

@@ -0,0 +1,128 @@
# 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 sys
from mox3 import mox
from neutronclient.common import exceptions
from neutronclient.neutron import v2_0 as neutronV2_0
from neutronclient.neutron.v2_0 import network
from neutronclient.neutron.v2_0 import tag
from neutronclient import shell
from neutronclient.tests.unit import test_cli20
class CLITestV20Tag(test_cli20.CLITestV20Base):
def _test_tag_operation(self, cmd, path, method, args, prog_name,
body=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if body:
body = test_cli20.MyComparator(body, self.client)
self.client.httpclient.request(
test_cli20.MyUrlComparator(
test_cli20.end_url(path, format=self.format), self.client),
method, body=body,
headers=mox.ContainsKeyValue(
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(
(test_cli20.MyResp(204), None))
self.mox.ReplayAll()
cmd_parser = cmd.get_parser(prog_name)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
def _test_tags_query(self, cmd, resources, args, query):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
path = getattr(self.client, resources + "_path")
res = {resources: [{'id': 'myid'}]}
resstr = self.client.serialize(res)
self.client.httpclient.request(
test_cli20.MyUrlComparator(
test_cli20.end_url(path, query, format=self.format),
self.client),
'GET', body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(
(test_cli20.MyResp(200), resstr))
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("list_networks")
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
self.assertIn('myid', _str)
def _make_tag_path(self, resource, resource_id, tag):
path = getattr(self.client, "tag_path")
resource_plural = neutronV2_0._get_resource_plural(resource,
self.client)
return path % (resource_plural, resource_id, tag)
def _make_tags_path(self, resource, resource_id):
path = getattr(self.client, "tags_path")
resource_plural = neutronV2_0._get_resource_plural(resource,
self.client)
return path % (resource_plural, resource_id)
def test_add_tag(self):
cmd = tag.AddTag(test_cli20.MyApp(sys.stdout), None)
path = self._make_tag_path('network', 'myid', 'red')
args = ['--resource-type', 'network', '--resource', 'myid',
'--tag', 'red']
self._test_tag_operation(cmd, path, 'PUT', args, "tag-add")
def test_replace_tag(self):
cmd = tag.ReplaceTag(test_cli20.MyApp(sys.stdout), None)
path = self._make_tags_path('network', 'myid')
args = ['--resource-type', 'network', '--resource', 'myid',
'--tag', 'red', '--tag', 'blue']
body = {'tags': ['red', 'blue']}
self._test_tag_operation(cmd, path, 'PUT', args, "tag-replace",
body=body)
def test_remove_tag(self):
cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None)
path = self._make_tag_path('network', 'myid', 'red')
args = ['--resource-type', 'network', '--resource', 'myid',
'--tag', 'red']
self._test_tag_operation(cmd, path, 'DELETE', args, "tag-remove")
def test_remove_tag_all(self):
cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None)
path = self._make_tags_path('network', 'myid')
args = ['--resource-type', 'network', '--resource', 'myid',
'--all']
self._test_tag_operation(cmd, path, 'DELETE', args, "tag-remove")
def test_no_tag_nor_all(self):
cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None)
path = self._make_tags_path('network', 'myid')
args = ['--resource-type', 'network', '--resource', 'myid']
self.assertRaises(exceptions.CommandError, self._test_tag_operation,
cmd, path, 'DELETE', args, "tag-remove")
def test_tags_query(self):
# This test examines that '-' in the tag related filters
# is not converted to '_'.
resources = 'networks'
cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None)
self.mox.StubOutWithMock(network.ListNetwork, "extend_list")
network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg())
args = ['--not-tags', 'red,blue', '--tags-any', 'green',
'--not-tags-any', 'black']
query = "not-tags=red,blue&tags-any=green&not-tags-any=black"
self._test_tags_query(cmd, resources, args, query)

View File

@@ -104,6 +104,11 @@ class CLITestV20ExtensionJSON(test_cli20.CLITestV20Base):
cmd = fox_sockets.FoxInSocketsList(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True)
def test_list_fox_pagination(self):
resources = 'fox_sockets'
cmd = fox_sockets.FoxInSocketsList(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(resources, cmd)
def test_show_fox_socket(self):
# Show fox_socket: --fields id --fields name myid.
resource = 'fox_socket'

View File

@@ -144,7 +144,7 @@ class CLITestNameorID(testtools.TestCase):
try:
neutronV20.find_resourceid_by_name_or_id(
self.client, 'network', name)
except exceptions.NeutronClientException as ex:
except exceptions.NotFound as ex:
self.assertIn('Unable to find', ex.message)
self.assertEqual(404, ex.status_code)
@@ -193,6 +193,6 @@ class CLITestNameorID(testtools.TestCase):
try:
neutronV20.find_resourceid_by_name_or_id(
self.client, 'security_group', name, project)
except exceptions.NeutronClientException as ex:
except exceptions.NotFound as ex:
self.assertIn('Unable to find', ex.message)
self.assertEqual(404, ex.status_code)

View File

@@ -49,6 +49,36 @@ class TestUtils(testtools.TestCase):
self.assertRaises(argparse.ArgumentTypeError,
utils.str2dict, input_str)
def test_str2dict_optional_keys(self):
self.assertDictEqual({'key1': 'value1'},
utils.str2dict('key1=value1',
optional_keys=['key1', 'key2']))
self.assertDictEqual({'key1': 'value1', 'key2': 'value2'},
utils.str2dict('key1=value1,key2=value2',
optional_keys=['key1', 'key2']))
e = self.assertRaises(argparse.ArgumentTypeError,
utils.str2dict,
'key1=value1,key2=value2,key3=value3',
optional_keys=['key1', 'key2'])
self.assertEqual("Invalid key(s) 'key3' specified. "
"Valid key(s): 'key1, key2'.",
str(e))
def test_str2dict_required_keys(self):
self.assertDictEqual(
{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'},
utils.str2dict('key1=value1,key2=value2,key3=value3',
required_keys=['key1', 'key2'],
optional_keys=['key3']))
self.assertDictEqual(
{'key1': 'value1', 'key2': 'value2'},
utils.str2dict('key1=value1,key2=value2',
required_keys=['key1', 'key2']))
e = self.assertRaises(argparse.ArgumentTypeError,
utils.str2dict, 'key1=value1',
required_keys=['key1', 'key2'])
self.assertEqual("Required key(s) 'key2' not specified.", str(e))
def test_get_dict_item_properties(self):
item = {'name': 'test_name', 'id': 'test_id'}
fields = ('name', 'id')

View File

@@ -16,6 +16,7 @@
import sys
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.vpn import ikepolicy
from neutronclient.tests.unit import test_cli20
@@ -101,7 +102,7 @@ class CLITestV20VpnIkePolicyJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def _test_lifetime_values(self, lifetime):
def _test_lifetime_values(self, lifetime, expected_exc=None):
resource = 'ikepolicy'
cmd = ikepolicy.CreateIKEPolicy(test_cli20.MyApp(sys.stdout), None)
name = 'ikepolicy1'
@@ -134,16 +135,17 @@ class CLITestV20VpnIkePolicyJSON(test_cli20.CLITestV20Base):
auth_algorithm, encryption_algorithm,
phase1_negotiation_mode, ike_version, pfs,
tenant_id]
try:
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
except Exception:
return
self.fail("IKEPolicy Lifetime Error")
if not expected_exc:
expected_exc = exceptions.CommandError
self.assertRaises(
expected_exc,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_ikepolicy_with_invalid_lifetime_keys(self):
lifetime = 'uts=seconds,val=20000'
self._test_lifetime_values(lifetime)
self._test_lifetime_values(lifetime, expected_exc=SystemExit)
def test_create_ikepolicy_with_invalid_lifetime_value(self):
lifetime = 'units=seconds,value=-1'

View File

@@ -180,7 +180,7 @@ class CLITestV20IPsecSiteConnectionJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, None, my_id, args,
position_names, position_values)
def _test_create_failure(self, additional_args=None):
def _test_create_failure(self, additional_args=None, expected_exc=None):
# Helper to test failure of IPSec site-to-site creation failure.
resource = 'ipsec_site_connection'
cmd = ipsec_site_connection.CreateIPsecSiteConnection(
@@ -215,7 +215,9 @@ class CLITestV20IPsecSiteConnectionJSON(test_cli20.CLITestV20Base):
position_values = [tenant_id, admin_state, peer_address, peer_id, psk,
mtu, initiator, None, None, vpnservice_id,
ikepolicy_id, ipsecpolicy_id]
self.assertRaises(exceptions.CommandError,
if not expected_exc:
expected_exc = exceptions.CommandError
self.assertRaises(expected_exc,
self._test_create_resource,
resource, cmd, None, my_id, args,
position_names, position_values)
@@ -227,7 +229,7 @@ class CLITestV20IPsecSiteConnectionJSON(test_cli20.CLITestV20Base):
def test_fail_create_with_invalid_dpd_keys(self):
bad_dpd_key = ['--dpd', 'act=restart,interval=30,time=120']
self._test_create_failure(bad_dpd_key)
self._test_create_failure(bad_dpd_key, SystemExit)
def test_fail_create_with_invalid_dpd_values(self):
bad_dpd_values = ['--dpd', 'action=hold,interval=30,timeout=-1']

View File

@@ -16,6 +16,7 @@
import sys
from neutronclient.common import exceptions
from neutronclient.neutron.v2_0.vpn import ipsecpolicy
from neutronclient.tests.unit import test_cli20
@@ -98,7 +99,7 @@ class CLITestV20VpnIpsecPolicyJSON(test_cli20.CLITestV20Base):
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
def _test_lifetime_values(self, lifetime):
def _test_lifetime_values(self, lifetime, expected_exc=None):
resource = 'ipsecpolicy'
cmd = ipsecpolicy.CreateIPsecPolicy(test_cli20.MyApp(sys.stdout), None)
name = 'ipsecpolicy1'
@@ -131,19 +132,24 @@ class CLITestV20VpnIpsecPolicyJSON(test_cli20.CLITestV20Base):
auth_algorithm, encryption_algorithm,
phase1_negotiation_mode, ike_version, pfs,
tenant_id]
try:
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values)
except Exception:
return
self.fail("IPsecPolicy Lifetime Error")
if not expected_exc:
expected_exc = exceptions.CommandError
self.assertRaises(
expected_exc,
self._test_create_resource,
resource, cmd, name, my_id, args,
position_names, position_values)
def test_create_ipsecpolicy_with_invalid_lifetime_keys(self):
lifetime = 'uts=seconds,val=20000'
self._test_lifetime_values(lifetime, SystemExit)
def test_create_ipsecpolicy_with_invalid_lifetime_units(self):
lifetime = 'units=minutes,value=600'
self._test_lifetime_values(lifetime)
def test_create_ipsecpolicy_with_invalide_lifetime_values(self):
lifetime = 'units=minutes,value=0'
def test_create_ipsecpolicy_with_invalid_lifetime_value(self):
lifetime = 'units=seconds,value=0'
self._test_lifetime_values(lifetime)
def test_list_ipsecpolicy(self):

View File

@@ -22,6 +22,7 @@ import time
import requests
import six.moves.urllib.parse as urlparse
from six import string_types
from neutronclient._i18n import _
from neutronclient import client
@@ -44,10 +45,11 @@ def exception_handler_v20(status_code, error_content):
:param error_content: deserialized body of error response
"""
error_dict = None
request_ids = error_content.request_ids
if isinstance(error_content, dict):
error_dict = error_content.get('NeutronError')
# Find real error type
bad_neutron_error_flag = False
client_exc = None
if error_dict:
# If Neutron key is found, it will definitely contain
# a 'message' and 'type' keys?
@@ -56,39 +58,34 @@ def exception_handler_v20(status_code, error_content):
error_message = error_dict['message']
if error_dict['detail']:
error_message += "\n" + error_dict['detail']
except Exception:
bad_neutron_error_flag = True
if not bad_neutron_error_flag:
# If corresponding exception is defined, use it.
client_exc = getattr(exceptions, '%sClient' % error_type, None)
# Otherwise look up per status-code client exception
if not client_exc:
client_exc = exceptions.HTTP_EXCEPTION_MAP.get(status_code)
if client_exc:
raise client_exc(message=error_message,
status_code=status_code)
else:
raise exceptions.NeutronClientException(
status_code=status_code, message=error_message)
else:
raise exceptions.NeutronClientException(status_code=status_code,
message=error_dict)
except Exception:
error_message = "%s" % error_dict
else:
message = None
error_message = None
if isinstance(error_content, dict):
message = error_content.get('message')
if message:
raise exceptions.NeutronClientException(status_code=status_code,
message=message)
error_message = error_content.get('message')
if not error_message:
# If we end up here the exception was not a neutron error
error_message = "%s-%s" % (status_code, error_content)
# If we end up here the exception was not a neutron error
msg = "%s-%s" % (status_code, error_content)
raise exceptions.NeutronClientException(status_code=status_code,
message=msg)
# If an exception corresponding to the error type is not found,
# look up per status-code client exception.
if not client_exc:
client_exc = exceptions.HTTP_EXCEPTION_MAP.get(status_code)
# If there is no exception per status-code,
# Use NeutronClientException as fallback.
if not client_exc:
client_exc = exceptions.NeutronClientException
raise client_exc(message=error_message,
status_code=status_code,
request_ids=request_ids)
class APIParamsCall(object):
"""A Decorator to support formating and tenant overriding and filters."""
"""A Decorator to support formatting and tenant overriding and filters."""
def __init__(self, function):
self.function = function
@@ -103,6 +100,99 @@ class APIParamsCall(object):
return with_params
class _RequestIdMixin(object):
"""Wrapper class to expose x-openstack-request-id to the caller."""
def _request_ids_setup(self):
self._request_ids = []
@property
def request_ids(self):
return self._request_ids
def _append_request_ids(self, resp):
"""Add request_ids as an attribute to the object
:param resp: Response object or list of Response objects
"""
if isinstance(resp, list):
# Add list of request_ids if response is of type list.
for resp_obj in resp:
self._append_request_id(resp_obj)
elif resp is not None:
# Add request_ids if response contains single object.
self._append_request_id(resp)
def _append_request_id(self, resp):
if isinstance(resp, requests.Response):
# Extract 'x-openstack-request-id' from headers if
# response is a Response object.
request_id = resp.headers.get('x-openstack-request-id')
else:
# If resp is of type string.
request_id = resp
if request_id:
self._request_ids.append(request_id)
class _DictWithMeta(dict, _RequestIdMixin):
def __init__(self, values, resp):
super(_DictWithMeta, self).__init__(values)
self._request_ids_setup()
self._append_request_ids(resp)
class _TupleWithMeta(tuple, _RequestIdMixin):
def __new__(cls, values, resp):
return super(_TupleWithMeta, cls).__new__(cls, values)
def __init__(self, values, resp):
self._request_ids_setup()
self._append_request_ids(resp)
class _StrWithMeta(str, _RequestIdMixin):
def __new__(cls, value, resp):
return super(_StrWithMeta, cls).__new__(cls, value)
def __init__(self, values, resp):
self._request_ids_setup()
self._append_request_ids(resp)
class _GeneratorWithMeta(_RequestIdMixin):
def __init__(self, paginate_func, collection, path, **params):
self.paginate_func = paginate_func
self.collection = collection
self.path = path
self.params = params
self.generator = None
self._request_ids_setup()
def _paginate(self):
for r in self.paginate_func(
self.collection, self.path, **self.params):
yield r, r.request_ids
def __iter__(self):
return self
# Python 3 compatibility
def __next__(self):
return self.next()
def next(self):
if not self.generator:
self.generator = self._paginate()
try:
obj, req_id = next(self.generator)
self._append_request_ids(req_id)
except StopIteration:
raise StopIteration()
return obj
class ClientBase(object):
"""Client for the OpenStack Neutron v2.0 API.
@@ -168,7 +258,7 @@ class ClientBase(object):
self.action_prefix = "/v%s" % (self.version)
self.retry_interval = 1
def _handle_fault_response(self, status_code, response_body):
def _handle_fault_response(self, status_code, response_body, resp):
# Create exception with HTTP status code and message
_logger.debug("Error message: %s", response_body)
# Add deserialized error message to exception arguments
@@ -178,14 +268,15 @@ class ClientBase(object):
# If unable to deserialized body it is probably not a
# Neutron error
des_error_body = {'message': response_body}
error_body = self._convert_into_with_meta(des_error_body, resp)
# Raise the appropriate exception
exception_handler_v20(status_code, des_error_body)
exception_handler_v20(status_code, error_body)
def do_request(self, method, action, body=None, headers=None, params=None):
# Add format and tenant_id
action += ".%s" % self.format
action = self.action_prefix + action
if type(params) is dict and params:
if isinstance(params, dict) and params:
params = utils.safe_encode_dict(params)
action += '?' + urlparse.urlencode(params, doseq=1)
@@ -199,11 +290,12 @@ class ClientBase(object):
requests.codes.created,
requests.codes.accepted,
requests.codes.no_content):
return self.deserialize(replybody, status_code)
data = self.deserialize(replybody, status_code)
return self._convert_into_with_meta(data, resp)
else:
if not replybody:
replybody = resp.reason
self._handle_fault_response(status_code, replybody)
self._handle_fault_response(status_code, replybody, resp)
def get_auth_info(self):
return self.httpclient.get_auth_info()
@@ -216,7 +308,7 @@ class ClientBase(object):
"""
if data is None:
return None
elif type(data) is dict:
elif isinstance(data, dict):
return serializer.Serializer().serialize(data)
else:
raise Exception(_("Unable to serialize object of type = '%s'") %
@@ -277,11 +369,14 @@ class ClientBase(object):
def list(self, collection, path, retrieve_all=True, **params):
if retrieve_all:
res = []
request_ids = []
for r in self._pagination(collection, path, **params):
res.extend(r[collection])
return {collection: res}
request_ids.extend(r.request_ids)
return _DictWithMeta({collection: res}, request_ids)
else:
return self._pagination(collection, path, **params)
return _GeneratorWithMeta(self._pagination, collection,
path, **params)
def _pagination(self, collection, path, **params):
if params.get('page_reverse', False):
@@ -303,6 +398,15 @@ class ClientBase(object):
except KeyError:
break
def _convert_into_with_meta(self, item, resp):
if item:
if isinstance(item, dict):
return _DictWithMeta(item, resp)
elif isinstance(item, string_types):
return _StrWithMeta(item, resp)
else:
return _TupleWithMeta((), resp)
class Client(ClientBase):
@@ -342,8 +446,13 @@ class Client(ClientBase):
lbaas_loadbalancers_path = "/lbaas/loadbalancers"
lbaas_loadbalancer_path = "/lbaas/loadbalancers/%s"
lbaas_loadbalancer_path_stats = "/lbaas/loadbalancers/%s/stats"
lbaas_loadbalancer_path_status = "/lbaas/loadbalancers/%s/statuses"
lbaas_listeners_path = "/lbaas/listeners"
lbaas_listener_path = "/lbaas/listeners/%s"
lbaas_l7policies_path = "/lbaas/l7policies"
lbaas_l7policy_path = lbaas_l7policies_path + "/%s"
lbaas_l7rules_path = lbaas_l7policy_path + "/rules"
lbaas_l7rule_path = lbaas_l7rules_path + "/%s"
lbaas_pools_path = "/lbaas/pools"
lbaas_pool_path = "/lbaas/pools/%s"
lbaas_healthmonitors_path = "/lbaas/healthmonitors"
@@ -408,6 +517,19 @@ class Client(ClientBase):
flavor_profile_bindings_path = flavor_path + service_profiles_path
flavor_profile_binding_path = flavor_path + service_profile_path
availability_zones_path = "/availability_zones"
auto_allocated_topology_path = "/auto-allocated-topology/%s"
BGP_DRINSTANCES = "/bgp-drinstances"
BGP_DRINSTANCE = "/bgp-drinstance/%s"
BGP_DRAGENTS = "/bgp-dragents"
BGP_DRAGENT = "/bgp-dragents/%s"
bgp_speakers_path = "/bgp-speakers"
bgp_speaker_path = "/bgp-speakers/%s"
bgp_peers_path = "/bgp-peers"
bgp_peer_path = "/bgp-peers/%s"
network_ip_availabilities_path = '/network-ip-availabilities'
network_ip_availability_path = '/network-ip-availabilities/%s'
tags_path = "/%s/%s/tags"
tag_path = "/%s/%s/tags/%s"
# API has no way to report plurals, so we have to hard code them
EXTED_PLURALS = {'routers': 'router',
@@ -434,6 +556,9 @@ class Client(ClientBase):
'metering_label_rules': 'metering_label_rule',
'loadbalancers': 'loadbalancer',
'listeners': 'listener',
'l7rules': 'l7rule',
'l7policies': 'l7policy',
'lbaas_l7policies': 'lbaas_l7policy',
'lbaas_pools': 'lbaas_pool',
'lbaas_healthmonitors': 'lbaas_healthmonitor',
'lbaas_members': 'lbaas_member',
@@ -443,33 +568,37 @@ class Client(ClientBase):
'qos_policies': 'qos_policy',
'policies': 'policy',
'bandwidth_limit_rules': 'bandwidth_limit_rule',
'rules': 'rule',
'rule_types': 'rule_type',
'flavors': 'flavor',
'bgp_speakers': 'bgp_speaker',
'bgp_peers': 'bgp_peer',
'network_ip_availabilities': 'network_ip_availability',
}
@APIParamsCall
def list_ext(self, path, **_params):
"""Client extension hook for lists."""
return self.get(path, params=_params)
def list_ext(self, collection, path, retrieve_all, **_params):
"""Client extension hook for list."""
return self.list(collection, path, retrieve_all, **_params)
@APIParamsCall
def show_ext(self, path, id, **_params):
"""Client extension hook for shows."""
"""Client extension hook for show."""
return self.get(path % id, params=_params)
@APIParamsCall
def create_ext(self, path, body=None):
"""Client extension hook for creates."""
"""Client extension hook for create."""
return self.post(path, body=body)
@APIParamsCall
def update_ext(self, path, id, body=None):
"""Client extension hook for updates."""
"""Client extension hook for update."""
return self.put(path % id, body=body)
@APIParamsCall
def delete_ext(self, path, id):
"""Client extension hook for deletes."""
"""Client extension hook for delete."""
return self.delete(path % id)
@APIParamsCall
@@ -499,24 +628,24 @@ class Client(ClientBase):
@APIParamsCall
def list_extensions(self, **_params):
"""Fetch a list of all exts on server side."""
"""Fetch a list of all extensions on server side."""
return self.get(self.extensions_path, params=_params)
@APIParamsCall
def show_extension(self, ext_alias, **_params):
"""Fetch a list of all exts on server side."""
"""Fetches information of a certain extension."""
return self.get(self.extension_path % ext_alias, params=_params)
@APIParamsCall
def list_ports(self, retrieve_all=True, **_params):
"""Fetches a list of all networks for a tenant."""
"""Fetches a list of all ports for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('ports', self.ports_path, retrieve_all,
**_params)
@APIParamsCall
def show_port(self, port, **_params):
"""Fetches information of a certain network."""
"""Fetches information of a certain port."""
return self.get(self.port_path % (port), params=_params)
@APIParamsCall
@@ -563,7 +692,7 @@ class Client(ClientBase):
@APIParamsCall
def list_subnets(self, retrieve_all=True, **_params):
"""Fetches a list of all networks for a tenant."""
"""Fetches a list of all subnets for a tenant."""
return self.list('subnets', self.subnets_path, retrieve_all,
**_params)
@@ -944,6 +1073,12 @@ class Client(ClientBase):
return self.get(self.lbaas_loadbalancer_path_stats % (loadbalancer),
params=_params)
@APIParamsCall
def retrieve_loadbalancer_status(self, loadbalancer, **_params):
"""Retrieves status for a certain load balancer."""
return self.get(self.lbaas_loadbalancer_path_status % (loadbalancer),
params=_params)
@APIParamsCall
def list_listeners(self, retrieve_all=True, **_params):
"""Fetches a list of all lbaas_listeners for a tenant."""
@@ -972,6 +1107,62 @@ class Client(ClientBase):
"""Deletes the specified lbaas_listener."""
return self.delete(self.lbaas_listener_path % (lbaas_listener))
@APIParamsCall
def list_lbaas_l7policies(self, retrieve_all=True, **_params):
"""Fetches a list of all L7 policies for a listener."""
return self.list('l7policies', self.lbaas_l7policies_path,
retrieve_all, **_params)
@APIParamsCall
def show_lbaas_l7policy(self, l7policy, **_params):
"""Fetches information of a certain listener's L7 policy."""
return self.get(self.lbaas_l7policy_path % l7policy,
params=_params)
@APIParamsCall
def create_lbaas_l7policy(self, body=None):
"""Creates L7 policy for a certain listener."""
return self.post(self.lbaas_l7policies_path, body=body)
@APIParamsCall
def update_lbaas_l7policy(self, l7policy, body=None):
"""Updates L7 policy."""
return self.put(self.lbaas_l7policy_path % l7policy,
body=body)
@APIParamsCall
def delete_lbaas_l7policy(self, l7policy):
"""Deletes the specified L7 policy."""
return self.delete(self.lbaas_l7policy_path % l7policy)
@APIParamsCall
def list_lbaas_l7rules(self, l7policy, retrieve_all=True, **_params):
"""Fetches a list of all rules for L7 policy."""
return self.list('rules', self.lbaas_l7rules_path % l7policy,
retrieve_all, **_params)
@APIParamsCall
def show_lbaas_l7rule(self, l7rule, l7policy, **_params):
"""Fetches information of a certain L7 policy's rule."""
return self.get(self.lbaas_l7rule_path % (l7policy, l7rule),
params=_params)
@APIParamsCall
def create_lbaas_l7rule(self, l7policy, body=None):
"""Creates rule for a certain L7 policy."""
return self.post(self.lbaas_l7rules_path % l7policy, body=body)
@APIParamsCall
def update_lbaas_l7rule(self, l7rule, l7policy, body=None):
"""Updates L7 rule."""
return self.put(self.lbaas_l7rule_path % (l7policy, l7rule),
body=body)
@APIParamsCall
def delete_lbaas_l7rule(self, l7rule, l7policy):
"""Deletes the specified L7 rule."""
return self.delete(self.lbaas_l7rule_path % (l7policy, l7rule))
@APIParamsCall
def list_lbaas_pools(self, retrieve_all=True, **_params):
"""Fetches a list of all lbaas_pools for a tenant."""
@@ -1338,6 +1529,30 @@ class Client(ClientBase):
return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent,
body=body)
@APIParamsCall
def list_dragents_hosting_bgp_speaker(self, bgp_speaker, **_params):
"""Fetches a list of Dynamic Routing agents hosting a BGP speaker."""
return self.get((self.bgp_speaker_path + self.BGP_DRAGENTS)
% bgp_speaker, params=_params)
@APIParamsCall
def add_bgp_speaker_to_dragent(self, bgp_dragent, body):
"""Adds a BGP speaker to Dynamic Routing agent."""
return self.post((self.agent_path + self.BGP_DRINSTANCES)
% bgp_dragent, body=body)
@APIParamsCall
def remove_bgp_speaker_from_dragent(self, bgp_dragent, bgpspeaker_id):
"""Removes a BGP speaker from Dynamic Routing agent."""
return self.delete((self.agent_path + self.BGP_DRINSTANCES + "/%s")
% (bgp_dragent, bgpspeaker_id))
@APIParamsCall
def list_bgp_speaker_on_dragent(self, bgp_dragent, **_params):
"""Fetches a list of BGP speakers hosted by Dynamic Routing agent."""
return self.get((self.agent_path + self.BGP_DRINSTANCES)
% bgp_dragent, params=_params)
@APIParamsCall
def list_firewall_rules(self, retrieve_all=True, **_params):
"""Fetches a list of all firewall rules for a tenant."""
@@ -1411,7 +1626,7 @@ class Client(ClientBase):
@APIParamsCall
def list_firewalls(self, retrieve_all=True, **_params):
"""Fetches a list of all firewals for a tenant."""
"""Fetches a list of all firewalls for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('firewalls', self.firewalls_path, retrieve_all,
@@ -1686,6 +1901,133 @@ class Client(ClientBase):
return self.list('availability_zones', self.availability_zones_path,
retrieve_all, **_params)
@APIParamsCall
def get_auto_allocated_topology(self, tenant_id, **_params):
"""Fetch information about a tenant's auto-allocated topology."""
return self.get(
self.auto_allocated_topology_path % tenant_id,
params=_params)
def validate_auto_allocated_topology_requirements(self, tenant_id):
"""Validate requirements for getting an auto-allocated topology."""
return self.get_auto_allocated_topology(tenant_id, fields=['dry-run'])
@APIParamsCall
def list_bgp_speakers(self, retrieve_all=True, **_params):
"""Fetches a list of all BGP speakers for a tenant."""
return self.list('bgp_speakers', self.bgp_speakers_path, retrieve_all,
**_params)
@APIParamsCall
def show_bgp_speaker(self, bgp_speaker_id, **_params):
"""Fetches information of a certain BGP speaker."""
return self.get(self.bgp_speaker_path % (bgp_speaker_id),
params=_params)
@APIParamsCall
def create_bgp_speaker(self, body=None):
"""Creates a new BGP speaker."""
return self.post(self.bgp_speakers_path, body=body)
@APIParamsCall
def update_bgp_speaker(self, bgp_speaker_id, body=None):
"""Update a BGP speaker."""
return self.put(self.bgp_speaker_path % bgp_speaker_id, body=body)
@APIParamsCall
def delete_bgp_speaker(self, speaker_id):
"""Deletes the specified BGP speaker."""
return self.delete(self.bgp_speaker_path % (speaker_id))
@APIParamsCall
def add_peer_to_bgp_speaker(self, speaker_id, body=None):
"""Adds a peer to BGP speaker."""
return self.put((self.bgp_speaker_path % speaker_id) +
"/add_bgp_peer", body=body)
@APIParamsCall
def remove_peer_from_bgp_speaker(self, speaker_id, body=None):
"""Removes a peer from BGP speaker."""
return self.put((self.bgp_speaker_path % speaker_id) +
"/remove_bgp_peer", body=body)
@APIParamsCall
def add_network_to_bgp_speaker(self, speaker_id, body=None):
"""Adds a network to BGP speaker."""
return self.put((self.bgp_speaker_path % speaker_id) +
"/add_gateway_network", body=body)
@APIParamsCall
def remove_network_from_bgp_speaker(self, speaker_id, body=None):
"""Removes a network from BGP speaker."""
return self.put((self.bgp_speaker_path % speaker_id) +
"/remove_gateway_network", body=body)
@APIParamsCall
def list_route_advertised_from_bgp_speaker(self, speaker_id, **_params):
"""Fetches a list of all routes advertised by BGP speaker."""
return self.get((self.bgp_speaker_path % speaker_id) +
"/get_advertised_routes", params=_params)
@APIParamsCall
def list_bgp_peers(self, **_params):
"""Fetches a list of all BGP peers."""
return self.get(self.bgp_peers_path, params=_params)
@APIParamsCall
def show_bgp_peer(self, peer_id, **_params):
"""Fetches information of a certain BGP peer."""
return self.get(self.bgp_peer_path % peer_id,
params=_params)
@APIParamsCall
def create_bgp_peer(self, body=None):
"""Create a new BGP peer."""
return self.post(self.bgp_peers_path, body=body)
@APIParamsCall
def update_bgp_peer(self, bgp_peer_id, body=None):
"""Update a BGP peer."""
return self.put(self.bgp_peer_path % bgp_peer_id, body=body)
@APIParamsCall
def delete_bgp_peer(self, peer_id):
"""Deletes the specified BGP peer."""
return self.delete(self.bgp_peer_path % peer_id)
@APIParamsCall
def list_network_ip_availabilities(self, retrieve_all=True, **_params):
"""Fetches IP availibility information for all networks"""
return self.list('network_ip_availabilities',
self.network_ip_availabilities_path,
retrieve_all, **_params)
@APIParamsCall
def show_network_ip_availability(self, network, **_params):
"""Fetches IP availability information for a specified network"""
return self.get(self.network_ip_availability_path % (network),
params=_params)
@APIParamsCall
def add_tag(self, resource_type, resource_id, tag, **_params):
"""Add a tag on the resource."""
return self.put(self.tag_path % (resource_type, resource_id, tag))
@APIParamsCall
def replace_tag(self, resource_type, resource_id, body, **_params):
"""Replace tags on the resource."""
return self.put(self.tags_path % (resource_type, resource_id), body)
@APIParamsCall
def remove_tag(self, resource_type, resource_id, tag, **_params):
"""Remove a tag on the resource."""
return self.delete(self.tag_path % (resource_type, resource_id, tag))
@APIParamsCall
def remove_tag_all(self, resource_type, resource_id, **_params):
"""Remove all tags on the resource."""
return self.delete(self.tags_path % (resource_type, resource_id))
def __init__(self, **kwargs):
"""Initialize a new client for the Neutron v2.0 API."""
super(Client, self).__init__(**kwargs)
@@ -1701,11 +2043,13 @@ class Client(ClientBase):
setattr(self, "show_%s" % resource_singular, fn)
def extend_list(self, resource_plural, path, parent_resource):
def _fx(**_params):
return self.list_ext(path, **_params)
def _fx(retrieve_all=True, **_params):
return self.list_ext(resource_plural, path,
retrieve_all, **_params)
def _parent_fx(parent_id, **_params):
return self.list_ext(path % parent_id, **_params)
def _parent_fx(parent_id, retrieve_all=True, **_params):
return self.list_ext(resource_plural, path % parent_id,
retrieve_all, **_params)
fn = _fx if not parent_resource else _parent_fx
setattr(self, "list_%s" % resource_plural, fn)

View File

@@ -0,0 +1,9 @@
---
features:
- |
CLI support for the "get-me-a-network" feature, which simplifies
the process for launching an instance with basic network
connectivity.
* The ``auto-allocated-topology-show`` command provides the
network of the automatically allocated topology for a tenant.

View File

@@ -0,0 +1,8 @@
---
features:
- |
CLI support for Layer 7 content policies and rules.
* L7 policies can be defined for listeners along
with the ability to set L7 policy order.
* Multiple rules can be created for an L7 policy.

View File

@@ -0,0 +1,7 @@
---
features:
- |
CLI support for load balancer status tree.
* The ``lbaas-loadbalancer-status`` command provides the status
tree of a specific load balancer.

View File

@@ -0,0 +1,16 @@
---
features:
- |
New command 'neutron purge <tenant_id>' will delete all
supported resources owned by the given tenant, provided
that the user has sufficient authorization and the
resources in question are not shared, in use, or
otherwise undeletable.
Supported resources are:
* Networks
* Subnets
* Routers
* Ports
* Floating IPs
* Security Groups

View File

@@ -0,0 +1,8 @@
---
features:
- |
CLI support for QoS policy RBAC.
* The ``rbac-create`` command include a --type qos-policy
option.
* The ``rbac-list`` command output includes a new 'type' column.

View File

@@ -0,0 +1,8 @@
---
features:
- |
CLI support for Neutron-LBaaS v2 shared pools added.
* Pools can be created independently from listeners.
* Listeners can share the same default_pool.
* Makes Layer 7 switching support much more useful.

View File

@@ -0,0 +1,16 @@
---
features:
- |
CLI support for tag.
* The ``tag-add`` command sets a tag on the network resource. It also
includes ``--resource-type``, ``--resource`` and ``--tag`` options.
* The ``tag-replace`` command replaces tags on the network resource. It
also includes ``--resource-type``, ``--resource`` and ``--tag``
options. More than one ``--tag`` options can be set.
* The ``tag-remove`` command removes tags on the network resource. It also
includes ``--resource-type``, ``--resource``, ``--tag`` and ``--all``
options. The ``--all`` option allow to remove all tags on the network
resource.
* The ``net-list`` command includes ``--tags``, ``--tags-any``,
``--not-tags`` and ``--not-tags-any`` options.

View File

@@ -0,0 +1,5 @@
---
features:
- |
CLI support for the BGP dynamic routing functionality will help
advertising neutron fixed-ips and dvr host routes via BGP.

View File

@@ -0,0 +1,9 @@
---
features:
- |
CLI support for default subnetpools.
* The ``subnetpool-list`` and ``subnetpool-show`` command output includes
the ``is_default`` field.
* The ``subnetpool-create`` and ``subnetpool-update`` commands include a
``--is-default`` option.

View File

@@ -0,0 +1,5 @@
---
critical:
- Fix a critical bug that when lazy translation is enabled
NeutronClientException raises a TypeError
(`bug 1552760 <https://bugs.launchpad.net/heat/+bug/1552760>`_).

View File

@@ -0,0 +1,6 @@
---
fixes:
- Fix `bug 1450414 <https://bugs.launchpad.net/python-neutronclient/+bug/1450414>`_
that authentication with via ``--os-token`` and ``--os-url`` options
(or corresponding environment variables) does not work
after keystone v3 API support.

View File

@@ -0,0 +1,9 @@
---
features:
- |
CLI support for network IP availability
* The ``net-ip-availability-list`` command provides a list of IP
usage statistics for all networks.
* The ``net-ip-availability-show`` command provides IP usage stats
for a specific network.

View File

@@ -16,6 +16,7 @@ upgrade:
- Cisco-specific neutron client commands have been removed.
These commands are ported to networking-cisco.
- py26 support has been dropped.
- py33 support has been dropped.
fixes:
- Name is no longer looked up on RBAC policies,
RBAC policies have no name field so the name query to

View File

@@ -0,0 +1,3 @@
---
features:
- Neutron client returns 'x-openstack-request-id'.

View File

@@ -10,6 +10,7 @@ Old Release Notes
* made the publicURL the default endpoint instead of adminURL
* add ability to update security group name (requires 2013.2-Havana or later)
* add flake8 and pbr support for testing and building
* add ability to retrieve a specific load balancer's status tree
2.2.0
-----

View File

@@ -1,18 +1,17 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.6
argparse
cliff>=1.15.0 # Apache-2.0
debtcollector>=0.3.0 # Apache-2.0
iso8601>=0.1.9
netaddr!=0.7.16,>=0.7.12
oslo.i18n>=1.5.0 # Apache-2.0
pbr>=1.6 # Apache-2.0
cliff!=1.16.0,>=1.15.0 # Apache-2.0
debtcollector>=1.2.0 # Apache-2.0
iso8601>=0.1.9 # MIT
netaddr!=0.7.16,>=0.7.12 # BSD
oslo.i18n>=2.1.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.utils>=3.2.0 # Apache-2.0
os-client-config>=1.13.1
keystoneauth1>=2.1.0
requests!=2.9.0,>=2.8.1
simplejson>=2.2.0
six>=1.9.0
Babel>=1.3
oslo.utils>=3.5.0 # Apache-2.0
os-client-config>=1.13.1 # Apache-2.0
keystoneauth1>=2.1.0 # Apache-2.0
requests!=2.9.0,>=2.8.1 # Apache-2.0
simplejson>=2.2.0 # MIT
six>=1.9.0 # MIT
Babel>=1.3 # BSD

View File

@@ -38,3 +38,17 @@ source-dir = doc/source
[wheel]
universal = 1
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = neutronclient/locale/neutronclient.pot
[compile_catalog]
directory = neutronclient/locale
domain = neutronclient
[update_catalog]
domain = neutronclient
output_dir = neutronclient/locale
input_file = neutronclient/locale/neutronclient.pot

View File

@@ -3,17 +3,18 @@
# process, which may cause wedges in the gate later.
hacking<0.11,>=0.10.0
coverage>=3.6
discover
fixtures>=1.3.1
mox3>=0.7.0
mock>=1.2
coverage>=3.6 # Apache-2.0
discover # BSD
fixtures>=1.3.1 # Apache-2.0/BSD
mox3>=0.7.0 # Apache-2.0
mock>=1.2 # BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
python-subunit>=0.0.18
python-subunit>=0.0.18 # Apache-2.0/BSD
reno>=0.1.1 # Apache2
requests-mock>=0.7.0 # Apache-2.0
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
testrepository>=0.0.18
testtools>=1.4.0
tempest-lib>=0.12.0
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
testscenarios>=0.4 # Apache-2.0/BSD
tempest-lib>=0.14.0 # Apache-2.0

View File

@@ -13,7 +13,14 @@ usedevelop = True
install_command = pip install -U {opts} {packages}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --testr-args='{posargs}'
# Delete bytecodes from normal directories before running tests.
# Note that bytecodes in dot directories will not be deleted
# to keep bytecodes of python modules installed into virtualenvs.
commands = sh -c "find . -type d -name '.?*' -prune -o \
\( -type d -name '__pycache__' -o -type f -name '*.py[co]' \) \
-print0 | xargs -0 rm -rf"
python setup.py testr --testr-args='{posargs}'
whitelist_externals = sh
[testenv:pep8]
commands = flake8