diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 000000000..94e40afbb
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,7 @@
+[run]
+branch = True
+source = neutronclient
+omit = neutronclient/tests/*
+
+[report]
+ignore_errors = True
diff --git a/.gitignore b/.gitignore
index 2fc28f418..abdf7b022 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,22 @@
*.pyc
*.DS_Store
+*.egg
+*.sw?
+AUTHORS
+ChangeLog
build/*
build-stamp
-.coverage
cover/*
-python_quantumclient.egg-info/*
-quantum/vcsversion.py
+doc/build/
+doc/source/api/
+releasenotes/build/
+python_neutronclient.egg-info/*
+neutron/vcsversion.py
+neutronclient/versioninfo
run_tests.err.log
run_tests.log
-.venv/
+.autogenerated
+.coverage
+.stestr/
.tox/
+.venv/
diff --git a/.gitreview b/.gitreview
index 40817febd..faf5d1f5b 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
[gerrit]
-host=review.openstack.org
+host=review.opendev.org
port=29418
-project=openstack/python-quantumclient.git
+project=openstack/python-neutronclient.git
diff --git a/.pylintrc b/.pylintrc
index bd677fd6a..7e4a0716c 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -20,7 +20,7 @@ variable-rgx=[a-z_][a-z0-9_]{0,30}$
argument-rgx=[a-z_][a-z0-9_]{1,30}$
# Method names should be at least 3 characters long
-# and be lowecased with underscores
+# and be lowercased with underscores
method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$
# Module names matching quantum-* are ok (files in bin/)
diff --git a/.stestr.conf b/.stestr.conf
new file mode 100644
index 000000000..875bb1461
--- /dev/null
+++ b/.stestr.conf
@@ -0,0 +1,3 @@
+[DEFAULT]
+test_path=${OS_TEST_PATH:-./neutronclient/tests/unit}
+top_dir=./
diff --git a/.zuul.yaml b/.zuul.yaml
new file mode 100644
index 000000000..7c8822186
--- /dev/null
+++ b/.zuul.yaml
@@ -0,0 +1,39 @@
+- project:
+ templates:
+ - openstack-cover-jobs
+ - openstack-python3-jobs
+ - publish-openstack-docs-pti
+ - check-requirements
+ - lib-forward-testing-python3
+ - release-notes-jobs-python3
+ - openstackclient-plugin-jobs
+ experimental:
+ jobs:
+ - neutronclient-grenade-neutron-lib:
+ irrelevant-files:
+ - ^(test-|)requirements.txt$
+ - ^setup.cfg$
+
+- job:
+ name: neutronclient-grenade-neutron-lib
+ parent: grenade
+ description: |
+ neutron-lib grenade job.
+ The version of this job on the current branch is py3 based,
+ while any branch before ussuri needs to use the py2 version,
+ which is defined in openstack-zuul-jobs with the old name
+ (legacy-grenade-dsvm-neutron-libs).
+ Users of this job needs to pay attention of the version used.
+ Former names for this job were:
+ * legacy-grenade-dsvm-neutron-libs
+ * neutron-lib-grenade-dsvm
+ required-projects:
+ - openstack/keystoneauth
+ - openstack/neutron
+ - openstack/neutron-lib
+ - openstack/python-cinderclient
+ - openstack/python-glanceclient
+ - openstack/python-ironicclient
+ - openstack/python-keystoneclient
+ - openstack/python-neutronclient
+ - openstack/python-novaclient
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 000000000..b726f1d6b
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,16 @@
+If you would like to contribute to the development of OpenStack,
+you must follow the steps documented at:
+
+ http://docs.openstack.org/infra/manual/developers.html#development-workflow
+
+Once those steps have been completed, changes to OpenStack
+should be submitted for review via the Gerrit tool, following
+the workflow documented at:
+
+ http://docs.openstack.org/infra/manual/developers.html#development-workflow
+
+Pull requests submitted through GitHub will be ignored.
+
+Bugs should be filed on Launchpad, not GitHub:
+
+ https://bugs.launchpad.net/python-neutronclient
diff --git a/HACKING.rst b/HACKING.rst
index d99a07f9a..7230971af 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,187 +1,26 @@
-QuantumClient Style Commandments
+Neutron Style Commandments
================================
-- Step 1: Read http://www.python.org/dev/peps/pep-0008/
-- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
-- Step 3: Read on
-
-
-General
--------
-- Put two newlines between top-level code (funcs, classes, etc)
-- Put one newline between methods in classes and anywhere else
-- Do not write "except:", use "except Exception:" at the very least
-- Include your name with TODOs as in "#TODO(termie)"
-- Do not shadow a built-in or reserved word. Example::
-
- def list():
- return [1, 2, 3]
-
- mylist = list() # BAD, shadows `list` built-in
-
- class Foo(object):
- def list(self):
- return [1, 2, 3]
-
- mylist = Foo().list() # OKAY, does not shadow built-in
-
-
-Imports
--------
-- Do not make relative imports
-- Order your imports by the full module path
-- Organize your imports according to the following template
-
-Example::
-
- # vim: tabstop=4 shiftwidth=4 softtabstop=4
- {{stdlib imports in human alphabetical order}}
- \n
- {{third-party lib imports in human alphabetical order}}
- \n
- {{quantum imports in human alphabetical order}}
- \n
- \n
- {{begin your code}}
-
-
-Human Alphabetical Order Examples
----------------------------------
-Example::
-
- import httplib
- import logging
- import random
- import StringIO
- import time
- import unittest
-
- import eventlet
- import webob.exc
-
- import quantum.api.networks
- from quantum.api import ports
- from quantum.db import models
- from quantum.extensions import multiport
- import quantum.manager
- from quantum import service
-
-
-Docstrings
-----------
-Example::
-
- """A one line docstring looks like this and ends in a period."""
-
-
- """A multiline docstring has a one-line summary, less than 80 characters.
-
- Then a new paragraph after a newline that explains in more detail any
- general information about the function, class or method. Example usages
- are also great to have here if it is a complex class for function.
-
- When writing the docstring for a class, an extra line should be placed
- after the closing quotations. For more in-depth explanations for these
- decisions see http://www.python.org/dev/peps/pep-0257/
-
- If you are going to describe parameters and return values, use Sphinx, the
- appropriate syntax is as follows.
-
- :param foo: the foo parameter
- :param bar: the bar parameter
- :returns: return_type -- description of the return value
- :returns: description of the return value
- :raises: AttributeError, KeyError
- """
-
-
-Dictionaries/Lists
-------------------
-If a dictionary (dict) or list object is longer than 80 characters, its items
-should be split with newlines. Embedded iterables should have their items
-indented. Additionally, the last item in the dictionary should have a trailing
-comma. This increases readability and simplifies future diffs.
-
-Example::
-
- my_dictionary = {
- "image": {
- "name": "Just a Snapshot",
- "size": 2749573,
- "properties": {
- "user_id": 12,
- "arch": "x86_64",
- },
- "things": [
- "thing_one",
- "thing_two",
- ],
- "status": "ACTIVE",
- },
- }
-
-
-Calling Methods
----------------
-Calls to methods 80 characters or longer should format each argument with
-newlines. This is not a requirement, but a guideline::
-
- unnecessarily_long_function_name('string one',
- 'string two',
- kwarg1=constants.ACTIVE,
- kwarg2=['a', 'b', 'c'])
-
-
-Rather than constructing parameters inline, it is better to break things up::
-
- list_of_strings = [
- 'what_a_long_string',
- 'not as long',
- ]
-
- dict_of_numbers = {
- 'one': 1,
- 'two': 2,
- 'twenty four': 24,
- }
-
- object_one.call_a_method('string three',
- 'string four',
- kwarg1=list_of_strings,
- kwarg2=dict_of_numbers)
-
-
-Internationalization (i18n) Strings
------------------------------------
-In order to support multiple languages, we have a mechanism to support
-automatic translations of exception and log strings.
-
-Example::
-
- msg = _("An error occurred")
- raise HTTPBadRequest(explanation=msg)
-
-If you have a variable to place within the string, first internationalize the
-template string then do the replacement.
-
-Example::
-
- msg = _("Missing parameter: %s") % ("flavor",)
- LOG.error(msg)
-
-If you have multiple variables to place in the string, use keyword parameters.
-This helps our translators reorder parameters when needed.
-
-Example::
-
- msg = _("The server with id %(s_id)s has no key %(m_key)s")
- LOG.error(msg % {"s_id": "1234", "m_key": "imageId"})
-
-
-Creating Unit Tests
--------------------
-For every new feature, unit tests should be created that both test and
-(implicitly) document the usage of said feature. If submitting a patch for a
-bug that had no unit test, a new passing unit test should be added. If a
-submitted bug fix does have a unit test, be sure to add a new one that fails
-without the patch and passes with the patch.
+- Step 1: Read the OpenStack Style Commandments
+ https://docs.openstack.org/hacking/latest/
+- Step 2: Read on
+
+
+Running Tests
+-------------
+The testing system is based on a combination of tox and testr. The canonical
+approach to running tests is to simply run the command `tox`. This will
+create virtual environments, populate them with depenedencies and run all of
+the tests that OpenStack CI systems run. Behind the scenes, tox is running
+`testr run --parallel`, but is set up such that you can supply any additional
+testr arguments that are needed to tox. For example, you can run:
+`tox -- --analyze-isolation` to cause tox to tell testr to add
+--analyze-isolation to its argument list.
+
+It is also possible to run the tests inside of a virtual environment
+you have created, or it is possible that you have all of the dependencies
+installed locally already. In this case, you can interact with the testr
+command directly. Running `testr run` will run the entire test suite. `testr
+run --parallel` will run it in parallel (this is the default incantation tox
+uses.) More information about testr can be found at:
+http://wiki.openstack.org/testr
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 94058d438..000000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,6 +0,0 @@
-include tox.ini
-include LICENSE README HACKING.rst
-include version.py
-include tools/*
-include quantumclient/tests/*
-include quantumclient/tests/unit/*
diff --git a/README b/README
deleted file mode 100644
index 20b7a8978..000000000
--- a/README
+++ /dev/null
@@ -1 +0,0 @@
-This is the client API library for Quantum.
diff --git a/README.rst b/README.rst
new file mode 100644
index 000000000..eaac15c2c
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,43 @@
+========================
+Team and repository tags
+========================
+
+.. image:: https://governance.openstack.org/tc/badges/python-neutronclient.svg
+ :target: https://governance.openstack.org/tc/reference/tags/index.html
+
+.. Change things from this point on
+
+Python bindings to the Neutron API
+==================================
+
+.. image:: https://img.shields.io/pypi/v/python-neutronclient.svg
+ :target: https://pypi.org/project/python-neutronclient/
+ :alt: Latest Version
+
+This is a client library for Neutron built on the Neutron API. It
+provides a Python API (the ``neutronclient`` module).
+
+.. note:: This project has been deprecated. The CLI code has been deleted
+ and is not accessible anymore. The Python bindings are still in use by
+ other projects but no new features will be added to this project.
+ All projects under Openstack governance migrating to use OpenstackSDK.
+ Any new feature should be proposed to OpenStack SDK and OpenStack
+ Client.
+
+* License: Apache License, Version 2.0
+* `PyPi`_ - package installation
+* `Online Documentation`_
+* `Launchpad project`_ - release management
+* `Blueprints`_ - feature specifications
+* `Bugs`_ - issue tracking
+* `Source`_
+* `Developer's Guide`_
+
+.. _PyPi: https://pypi.org/project/python-neutronclient
+.. _Online Documentation: https://docs.openstack.org/python-neutronclient/latest/
+.. _Launchpad project: https://launchpad.net/python-neutronclient
+.. _Blueprints: https://blueprints.launchpad.net/python-neutronclient
+.. _Bugs: https://bugs.launchpad.net/python-neutronclient
+.. _Source: https://opendev.org/openstack/python-neutronclient
+.. _Developer's Guide: http://docs.openstack.org/infra/manual/developers.html
+.. _Release Notes: https://docs.openstack.org/releasenotes/python-neutronclient
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 000000000..718a2395a
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,7 @@
+# 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.
+openstackdocstheme>=2.2.0 # Apache-2.0
+reno>=3.1.0 # Apache-2.0
+sphinx>=2.0.0,!=2.1.0 # BSD
+cliff>=3.4.0 # Apache-2.0
diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst
new file mode 100644
index 000000000..4b1f4720a
--- /dev/null
+++ b/doc/source/cli/index.rst
@@ -0,0 +1,55 @@
+..
+ 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.
+
+
+ Convention for heading levels in Neutron devref:
+ ======= Heading 0 (reserved for the title in a document)
+ ------- Heading 1
+ ~~~~~~~ Heading 2
+ +++++++ Heading 3
+ ''''''' Heading 4
+ (Avoid deeper levels because they do not render well.)
+
+=========
+Using CLI
+=========
+
+There is `OpenStackClient (OSC)
+`__
+which support the Networking API
+
+OpenStackClient
+---------------
+
+OpenStackClient provides
+`the basic network commands `__
+and python-neutronclient provides :doc:`extensions `
+(aka OSC plugins) for advanced networking services.
+
+.. toctree::
+ :maxdepth: 1
+
+ Basic network commands
+ Network commands for advanced networking services
+ Mapping Guide from neutron CLI
+
+neutron CLI
+-----------
+
+.. warning::
+
+ neutron CLI is removed. Use openstack CLI instead. See `openstack CLI command list
+ `__
+ and :doc:`its extensions for advanced networking services `.
+ The command mapping from neutron CLI to openstack CLI is available
+ `here `__.
diff --git a/doc/source/cli/osc/v2/bgp-dynamic-routing.rst b/doc/source/cli/osc/v2/bgp-dynamic-routing.rst
new file mode 100644
index 000000000..c86bb45ee
--- /dev/null
+++ b/doc/source/cli/osc/v2/bgp-dynamic-routing.rst
@@ -0,0 +1,47 @@
+===================
+BGP Dynamic Routing
+===================
+
+BGP dynamic routing enables announcement of project subnet prefixes
+via BGP. Admins create BGP speakers and BGP peers. BGP peers can be
+associated with BGP speakers, thereby enabling peering sessions with
+operator infrastructure. BGP speakers can be associated with networks,
+which controls which routes are announced to peers.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker create
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker delete
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker list
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker set
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker show
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker add network
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker remove network
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker add peer
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker remove peer
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp speaker list advertised routes
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp peer *
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgp dragent *
diff --git a/doc/source/cli/osc/v2/network-log.rst b/doc/source/cli/osc/v2/network-log.rst
new file mode 100644
index 000000000..df3a32729
--- /dev/null
+++ b/doc/source/cli/osc/v2/network-log.rst
@@ -0,0 +1,15 @@
+===========
+network log
+===========
+
+A **network log** is a container to group security groups or ports for logging.
+Specified resources can be logged via these event (``ALL``, ``ACCEPT`` or
+``DROP``).
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: network loggable resources list
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: network log *
diff --git a/doc/source/cli/osc/v2/networking-sfc.rst b/doc/source/cli/osc/v2/networking-sfc.rst
new file mode 100644
index 000000000..4f4242d74
--- /dev/null
+++ b/doc/source/cli/osc/v2/networking-sfc.rst
@@ -0,0 +1,39 @@
+==============
+networking sfc
+==============
+
+**Service Function Chaining** is a mechanism for overriding the basic destination based forwarding
+that is typical of IP networks. Service Function Chains consist of an ordered sequence of
+Service Functions (SFs). SFs are virtual machines (or potentially physical devices) that perform a
+network function such as firewall, content cache, packet inspection, or any other function that
+requires processing of packets in a flow from point A to point B even though the SFs are not
+literally between point A and B from a routing table perspective.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc flow classifier *
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port chain *
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port pair create
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port pair delete
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port pair list
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port pair set
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port pair show
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc port pair group *
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: sfc service graph *
diff --git a/doc/source/cli/osc/v2/subnet-onboard.rst b/doc/source/cli/osc/v2/subnet-onboard.rst
new file mode 100644
index 000000000..006970372
--- /dev/null
+++ b/doc/source/cli/osc/v2/subnet-onboard.rst
@@ -0,0 +1,17 @@
+=======================
+network onboard subnets
+=======================
+
+**network onboard subnets** enables a subnet to be adopted or
+"onboarded" into an existing subnet pool. The CIDR of the subnet
+is checked for uniqueness across any applicable address scopes
+and all subnets allocated from the target subnet pool. Once
+onboarded, the subnet CIDR is added to the prefix list of the
+subnet pool and the subnet appears as though it has been allocated
+from the subnet pool. The subnet also begins participating in the
+applicable address scope if the subnet pool belongs to one.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: network onboard subnets
diff --git a/doc/source/cli/osc/v2/vpn-endpoint-group.rst b/doc/source/cli/osc/v2/vpn-endpoint-group.rst
new file mode 100644
index 000000000..9581d8b4a
--- /dev/null
+++ b/doc/source/cli/osc/v2/vpn-endpoint-group.rst
@@ -0,0 +1,11 @@
+==================
+VPN Endpoint Group
+==================
+
+The **Endpoint Group** is used to configure multiple local and remote subnets
+in vpnservice object.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: vpn endpoint group *
diff --git a/doc/source/cli/osc/v2/vpn-ike-policy.rst b/doc/source/cli/osc/v2/vpn-ike-policy.rst
new file mode 100644
index 000000000..3e38a5c7c
--- /dev/null
+++ b/doc/source/cli/osc/v2/vpn-ike-policy.rst
@@ -0,0 +1,12 @@
+==============
+VPN IKE Policy
+==============
+
+The **IKE Policy** is used for phases one and two negotiation of the
+VPN connection. You can specify both the authentication and encryption
+algorithms for connections.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: vpn ike policy *
diff --git a/doc/source/cli/osc/v2/vpn-ipsec-policy.rst b/doc/source/cli/osc/v2/vpn-ipsec-policy.rst
new file mode 100644
index 000000000..c8a5f3beb
--- /dev/null
+++ b/doc/source/cli/osc/v2/vpn-ipsec-policy.rst
@@ -0,0 +1,11 @@
+================
+VPN IPsec Policy
+================
+
+The **IPsec Policy** specifies the authentication and encryption algorithms
+and encapsulation mode to use for the established VPN connection.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: vpn ipsec policy *
diff --git a/doc/source/cli/osc/v2/vpn-ipsec-site-connection.rst b/doc/source/cli/osc/v2/vpn-ipsec-site-connection.rst
new file mode 100644
index 000000000..0244e73ee
--- /dev/null
+++ b/doc/source/cli/osc/v2/vpn-ipsec-site-connection.rst
@@ -0,0 +1,10 @@
+=========================
+VPN IPsec Site Connection
+=========================
+
+Creates a site-to-site **IPsec Site Connection** for a VPN service.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: vpn ipsec site connection *
diff --git a/doc/source/cli/osc/v2/vpn-service.rst b/doc/source/cli/osc/v2/vpn-service.rst
new file mode 100644
index 000000000..b412555c8
--- /dev/null
+++ b/doc/source/cli/osc/v2/vpn-service.rst
@@ -0,0 +1,11 @@
+===========
+VPN Service
+===========
+
+The **VPN Service** is associated with a router. After you
+create the service, it can contain multiple VPN connections.
+
+Network v2
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: vpn service *
diff --git a/doc/source/cli/osc_plugins.rst b/doc/source/cli/osc_plugins.rst
new file mode 100644
index 000000000..0cd7e26ba
--- /dev/null
+++ b/doc/source/cli/osc_plugins.rst
@@ -0,0 +1,37 @@
+..
+ 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.
+
+
+ Convention for heading levels in Neutron devref:
+ ======= Heading 0 (reserved for the title in a document)
+ ------- Heading 1
+ ~~~~~~~ Heading 2
+ +++++++ Heading 3
+ ''''''' Heading 4
+ (Avoid deeper levels because they do not render well.)
+
+Advanced Network Commands in OpenStack Client
+=============================================
+
+The following list covers the extended commands for advanced network
+services available in ``openstack`` command.
+
+These commands can be referenced by doing ``openstack help`` and
+the detail of individual command can be referred by
+``openstack help ``.
+
+.. toctree::
+ :glob:
+ :maxdepth: 2
+
+ osc/v2/*
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 000000000..70fd0de2f
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+
+# -- General configuration ---------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'reno.sphinxext',
+ 'openstackdocstheme',
+ 'cliff.sphinxext',
+]
+
+# openstackdocstheme options
+openstackdocs_repo_name = 'openstack/python-neutronclient'
+openstackdocs_pdf_link = True
+openstackdocs_bug_project = 'python-neutronclient'
+openstackdocs_bug_tag = 'doc'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+copyright = 'OpenStack Foundation'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output ---------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'openstackdocs'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'neutronclientdoc'
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_documents = [
+ ('index', 'doc-python-neutronclient.tex',
+ 'python-neutronclient Documentation',
+ 'Neutron Contributors', 'manual'),
+]
+
+# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
+latex_use_xindy = False
+
+latex_domain_indices = False
+
+latex_elements = {
+ 'makeindex': '',
+ 'printindex': '',
+ 'preamble': r'\setcounter{tocdepth}{5}',
+}
+
+# -- Options for cliff.sphinxext plugin ---------------------------------------
+
+autoprogram_cliff_application = 'openstack'
diff --git a/doc/source/contributor/index.rst b/doc/source/contributor/index.rst
new file mode 100644
index 000000000..fae3f6b82
--- /dev/null
+++ b/doc/source/contributor/index.rst
@@ -0,0 +1,34 @@
+..
+ 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.
+
+
+ Convention for heading levels in Neutron devref:
+ ======= Heading 0 (reserved for the title in a document)
+ ------- Heading 1
+ ~~~~~~~ Heading 2
+ +++++++ Heading 3
+ ''''''' Heading 4
+ (Avoid deeper levels because they do not render well.)
+
+=================
+Contributor Guide
+=================
+
+In the Contributor Guide, you will find information on neutronclient's
+lower level programming details or APIs as well as the transition to
+OpenStack client.
+
+.. toctree::
+ :maxdepth: 2
+
+ transition_to_osc
diff --git a/doc/source/contributor/transition_to_osc.rst b/doc/source/contributor/transition_to_osc.rst
new file mode 100644
index 000000000..9bb952902
--- /dev/null
+++ b/doc/source/contributor/transition_to_osc.rst
@@ -0,0 +1,222 @@
+..
+ 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.
+
+
+ Convention for heading levels in Neutron devref:
+ ======= Heading 0 (reserved for the title in a document)
+ ------- Heading 1
+ ~~~~~~~ Heading 2
+ +++++++ Heading 3
+ ''''''' Heading 4
+ (Avoid deeper levels because they do not render well.)
+
+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) `_
+and the `OpenStack Python SDK `_.
+This transition is being guided by the
+`Deprecate individual CLIs in favour of OSC `_
+OpenStack spec. See the `Neutron RFE `_,
+`OSC neutron support etherpad `_ and
+details below for the overall progress of this transition.
+
+Overview
+--------
+
+This transition will result in the neutron client's ``neutron`` CLI being
+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 `_
+``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 `_
+before the ``neutron`` CLI is removed. Such users will maintain their OSC plugin
+commands within their own project and will be responsible for deprecating and
+removing their ``neutron`` CLI extension.
+
+Transition Steps
+----------------
+
+1. **Done:** OSC adds OpenStack Python SDK as a dependency. See the following
+ patch set: https://review.opendev.org/#/c/138745/
+
+2. **Done:** OSC switches its networking support for the
+ `network `_
+ command object to use the OpenStack Python SDK instead of the neutron
+ client's Python library. See the following patch set:
+ https://review.opendev.org/#/c/253348/
+
+3. **Done:** OSC removes its python-neutronclient dependency.
+ See the following patch set: https://review.opendev.org/#/c/255545/
+
+4. **In Progress:** OpenStack Python SDK releases version 1.0 to guarantee
+ backwards compatibility of its networking support and OSC updates
+ its dependencies to include OpenStack Python SDK version 1.0 or later.
+ See the following blueprint: https://blueprints.launchpad.net/python-openstackclient/+spec/network-command-sdk-support
+
+5. **Done:** OSC switches its networking support for the
+ `ip floating `_,
+ `ip floating pool `_,
+ `ip fixed `_,
+ `security group `_, and
+ `security group rule `_
+ command objects to use the OpenStack Python SDK instead of the nova
+ client's Python library when neutron is enabled. When nova network
+ is enabled, then the nova client's Python library will continue to
+ be used. See the following OSC bugs:
+
+ * **Done** `Floating IP CRUD `_
+
+ * **Done** `Port CRUD `_
+
+ * **Done** `Security Group CRUD `_
+
+ * **Done** `Security Group Rule CRUD `_
+
+6. **Done** 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 possibly the
+ OpenStack Python SDK. 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. **Done** Deprecate the ``neutron`` CLI. Running the CLI after
+ it has been `deprecated `_
+ will issue a warning message:
+ ``neutron CLI is deprecated and will be removed in the Z cycle. Use openstack CLI instead.``
+ In addition, no new features will be added to the CLI, though fixes to
+ the CLI will be assessed on a case by case basis.
+
+8. **Done** Remove the ``neutron`` CLI after two deprecation cycles
+ once the criteria below have been met.
+
+ * The networking support provide by the ``openstack`` CLI is functionally
+ equivalent to the ``neutron`` CLI and it contains sufficient functional
+ and unit test coverage.
+
+ * `Neutron Stadium `_
+ projects, Neutron documentation and `DevStack `_
+ use ``openstack`` CLI instead of ``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.
+
+Developer Guide
+---------------
+The ``neutron`` CLI tool is now removed and all new CLI changes should be done
+in the ``OpenStackClient (OSC)`` and, if needed, also in the ``OpenStack SDK``.
+
+**Where does my CLI belong?**
+
+If you are developing an API in any of the `neutron repos `_
+the client-side support must be generally located in either the openstackclient or neutronclient
+repos. Whether the actual code goes into one or the other repo it depends on the nature of the
+feature, its maturity level, and/or the depth of feedback required during the development.
+
+The table below provides an idea of what goes where. Generally speaking, we consider Core anything
+that is L2 and L3 related or that it has been located in the neutron repo for quite sometime, e.g.
+QoS or Metering, or that it is available in each neutron deployment irrespective of its configuration
+(e.g. auto-allocated-topology). Any client feature that falls into this categorization will need to
+be contributed in OSC. Any other that does not, will need to go into neutronclient, assuming that
+its server-side is located in a neutron controlled repo. This is a general guideline, when in doubt,
+please reach out to a member of the neutron core team for clarifications.
+
++---------------------------+-------------------+-------------------------------------------------+
+| Networking Commands | OSC Plugin | OpenStack Project for ``openstack`` Commands |
++===========================+===================+=================================================+
+| Core | No | python-openstackclient |
++---------------------------+-------------------+-------------------------------------------------+
+| Extension | Yes | python-neutronclient |
+| (i.e. neutron stadium) | | (``neutronclient/osc/v2/``) |
++---------------------------+-------------------+-------------------------------------------------+
+| Other | Yes | Applicable project owning networking resource |
++---------------------------+-------------------+-------------------------------------------------+
+
+When a repo stops being under neutron governance, its client-side counterpart will have to go through
+deprecation. Bear in mind that for grandfathered extensions like FWaaS v1, VPNaaS, and LBaaS v1, this
+is not required as the neutronclient is already deprecated on its own.
+
+**Which Python library do I change?**
+
++-------------------------------------------------+-----------------------------------------------+
+| OpenStack Project for ``openstack`` Commands | Python Library to Change |
++=================================================+===============================================+
+| python-openstackclient | openstacksdk |
++-------------------------------------------------+-----------------------------------------------+
+| Other | Applicable project owning network resource |
++-------------------------------------------------+-----------------------------------------------+
+
+
+**Important:** The actual name of the command object and/or action in OSC may differ
+from those used by neutron in order to follow the OSC command structure and to avoid
+name conflicts. The `network` prefix must be used to avoid name conflicts if the
+command object name is highly likely to have an ambiguous meaning. Developers should
+get new command objects and actions approved by the OSC team before proceeding with the
+implementation.
+
+The "Core" group includes network resources that provide core ``neutron`` project
+features (e.g. network, subnet, port, etc.) and not advanced features in the
+``neutron`` project (e.g. trunk, etc.) or advanced services in separate projects
+(FWaaS, LBaaS, VPNaaS, dynamic routing, etc.).
+The "Other" group applies projects other than the core ``neutron`` project.
+Contact the neutron PTL or core team with questions on network resource classification.
+
+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 `_
+file.
+
+When adding an ``openstack`` networking command to python-openstackclient,
+you can optionally propose an
+`OSC command spec `_
+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 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 command to change.
+
+Developer References
+--------------------
+
+* See `OSC neutron support etherpad `_
+ to determine if an ``openstack`` command is in progress.
+* See `OSC command list `_
+ to determine if an ``openstack`` command exists.
+* See `OSC command spec list `_
+ to determine if an ``openstack`` command spec exists.
+* See `OSC plugin command list `_
+ to determine if an ``openstack`` plugin command exists.
+* See `OSC command structure `_
+ to determine the current ``openstack`` command objects, plugin objects and actions.
+* See `OSC human interface guide `_
+ for guidance on creating new OSC command interfaces.
+* See `OSC plugin `_
+ 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
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 000000000..c36d482ed
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,65 @@
+..
+ 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.
+
+ Convention for heading levels in Neutron devref:
+ ======= Heading 0 (reserved for the title in a document)
+ ------- Heading 1
+ ~~~~~~~ Heading 2
+ +++++++ Heading 3
+ ''''''' Heading 4
+ (Avoid deeper levels because they do not render well.)
+
+==================================
+python-neutronclient documentation
+==================================
+
+This is a client for OpenStack Networking API. It provides
+:doc:`Python API bindings ` (the neutronclient module).
+
+There is
+`OpenStack Client (OSC) `__.
+CLI which support the Networking API.
+
+User Documentation
+------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ cli/index
+ reference/index
+
+Contributor Guide
+-----------------
+
+In the :doc:`Contributor Guide `, you will find
+information on neutronclient's lower level programming details or APIs
+as well as the transition to OpenStack client.
+
+.. toctree::
+ :maxdepth: 2
+
+ contributor/index
+
+.. note::
+
+ neutron CLI has been deprecated from Ocata release.
+ We do not add, change and drop any existing commands any more.
+ We only accept changes on OSC plugin, neutronclient python bindings
+ and bug fixes on the deprecated CLI (``neutron`` command).
+
+History
+-------
+
+Release notes is available at
+http://docs.openstack.org/releasenotes/python-neutronclient/.
diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst
new file mode 100644
index 000000000..5678d6578
--- /dev/null
+++ b/doc/source/reference/index.rst
@@ -0,0 +1,95 @@
+..
+ 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.
+
+
+ Convention for heading levels in Neutron devref:
+ ======= Heading 0 (reserved for the title in a document)
+ ------- Heading 1
+ ~~~~~~~ Heading 2
+ +++++++ Heading 3
+ ''''''' Heading 4
+ (Avoid deeper levels because they do not render well.)
+
+neutronclient Python API
+========================
+
+Basic Usage
+-----------
+
+First create a client instance using a keystoneauth Session. For more
+information on this keystoneauth API, see `Using Sessions`_.
+
+.. _Using Sessions: https://docs.openstack.org/keystoneauth/latest/using-sessions.html
+
+.. code-block:: python
+
+ from keystoneauth1 import identity
+ from keystoneauth1 import session
+ from neutronclient.v2_0 import client
+ username='username'
+ password='password'
+ project_name='demo'
+ project_domain_id='default'
+ user_domain_id='default'
+ auth_url='http://auth.example.com:5000/v3'
+ auth = identity.Password(auth_url=auth_url,
+ username=username,
+ password=password,
+ project_name=project_name,
+ project_domain_id=project_domain_id,
+ user_domain_id=user_domain_id)
+ sess = session.Session(auth=auth)
+ neutron = client.Client(session=sess)
+
+If you are using Identity v2.0 API (DEPRECATED), create an auth plugin using
+the appropriate parameters and `keystoneauth1.identity` will handle Identity
+API version discovery. Then you can create a Session and a Neutronclient just
+like the previous example.
+
+.. code-block:: python
+
+ auth = identity.Password(auth_url=auth_url,
+ username=username,
+ password=password,
+ project_name=project_name)
+ # create a Session and a Neutronclient
+
+Now you can call various methods on the client instance.
+
+.. code-block:: python
+
+ network = {'name': 'mynetwork', 'admin_state_up': True}
+ neutron.create_network({'network':network})
+ networks = neutron.list_networks(name='mynetwork')
+ print networks
+ network_id = networks['networks'][0]['id']
+ neutron.delete_network(network_id)
+
+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')
+
+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']
diff --git a/quantumclient/tests/__init__.py b/neutronclient/__init__.py
similarity index 100%
rename from quantumclient/tests/__init__.py
rename to neutronclient/__init__.py
diff --git a/neutronclient/_i18n.py b/neutronclient/_i18n.py
new file mode 100644
index 000000000..50ac3c5bf
--- /dev/null
+++ b/neutronclient/_i18n.py
@@ -0,0 +1,31 @@
+# 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 oslo_i18n
+
+
+DOMAIN = 'neutronclient'
+
+_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
+
+# The translation function using the well-known name "_"
+_ = _translators.primary
+
+# The contextual translation function using the name "_C"
+_C = _translators.contextual_form
+
+# The plural translation function using the name "_P"
+_P = _translators.plural_form
+
+
+def get_available_languages():
+ return oslo_i18n.get_available_languages(DOMAIN)
diff --git a/neutronclient/client.py b/neutronclient/client.py
new file mode 100644
index 000000000..e25f575f3
--- /dev/null
+++ b/neutronclient/client.py
@@ -0,0 +1,439 @@
+# 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.
+#
+
+import logging
+import os
+
+import debtcollector.renames
+from keystoneauth1 import access
+from keystoneauth1 import adapter
+from oslo_serialization import jsonutils
+from oslo_utils import importutils
+import requests
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+
+osprofiler_web = importutils.try_import("osprofiler.web")
+
+_logger = logging.getLogger(__name__)
+
+if os.environ.get('NEUTRONCLIENT_DEBUG'):
+ ch = logging.StreamHandler()
+ _logger.setLevel(logging.DEBUG)
+ _logger.addHandler(ch)
+ _requests_log_level = logging.DEBUG
+else:
+ _requests_log_level = logging.WARNING
+
+logging.getLogger("requests").setLevel(_requests_log_level)
+MAX_URI_LEN = 8192
+USER_AGENT = 'python-neutronclient'
+REQ_ID_HEADER = 'X-OpenStack-Request-ID'
+
+
+class HTTPClient(object):
+ """Handles the REST calls and responses, include authn."""
+
+ CONTENT_TYPE = 'application/json'
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_name', 'project_name', replace=True)
+ def __init__(self, username=None, user_id=None,
+ project_name=None, project_id=None,
+ password=None, auth_url=None,
+ token=None, region_name=None, timeout=None,
+ endpoint_url=None, insecure=False,
+ endpoint_type='publicURL',
+ auth_strategy='keystone', ca_cert=None, cert=None,
+ log_credentials=False, service_type='network',
+ global_request_id=None, **kwargs):
+
+ self.username = username
+ self.user_id = user_id
+ self.project_name = project_name
+ self.project_id = project_id
+ self.password = password
+ self.auth_url = auth_url.rstrip('/') if auth_url else None
+ self.service_type = service_type
+ self.endpoint_type = endpoint_type
+ self.region_name = region_name
+ self.timeout = timeout
+ self.auth_token = token
+ self.auth_tenant_id = None
+ self.auth_user_id = None
+ self.endpoint_url = endpoint_url
+ self.auth_strategy = auth_strategy
+ self.log_credentials = log_credentials
+ self.global_request_id = global_request_id
+ self.cert = cert
+ if insecure:
+ self.verify_cert = False
+ else:
+ self.verify_cert = ca_cert if ca_cert else True
+
+ def _cs_request(self, *args, **kwargs):
+ kargs = {}
+ kargs.setdefault('headers', kwargs.get('headers', {}))
+ kargs['headers']['User-Agent'] = USER_AGENT
+
+ if 'body' in kwargs:
+ kargs['body'] = kwargs['body']
+
+ if self.log_credentials:
+ log_kargs = kargs
+ else:
+ log_kargs = self._strip_credentials(kargs)
+
+ utils.http_log_req(_logger, args, log_kargs)
+ try:
+ resp, body = self.request(*args, **kargs)
+ except requests.exceptions.SSLError as e:
+ raise exceptions.SslCertificateValidationError(reason=str(e))
+ except Exception as e:
+ # Wrap the low-level connection error (socket timeout, redirect
+ # limit, decompression error, etc) into our custom high-level
+ # connection exception (it is excepted in the upper layers of code)
+ _logger.debug("throwing ConnectionFailed : %s", e)
+ raise exceptions.ConnectionFailed(reason=str(e))
+ utils.http_log_resp(_logger, resp, body)
+
+ # log request-id for each api call
+ request_id = resp.headers.get('x-openstack-request-id')
+ if request_id:
+ _logger.debug('%(method)s call to neutron for '
+ '%(url)s used request id '
+ '%(response_request_id)s',
+ {'method': resp.request.method,
+ 'url': resp.url,
+ 'response_request_id': request_id})
+
+ if resp.status_code == 401:
+ raise exceptions.Unauthorized(message=body)
+ return resp, body
+
+ def _strip_credentials(self, kwargs):
+ if kwargs.get('body') and self.password:
+ log_kwargs = kwargs.copy()
+ log_kwargs['body'] = kwargs['body'].replace(self.password,
+ 'REDACTED')
+ return log_kwargs
+ else:
+ return kwargs
+
+ def authenticate_and_fetch_endpoint_url(self):
+ if not self.auth_token:
+ self.authenticate()
+ elif not self.endpoint_url:
+ self.endpoint_url = self._get_endpoint_url()
+
+ def request(self, url, method, body=None, headers=None, **kwargs):
+ """Request without authentication."""
+
+ content_type = kwargs.pop('content_type', None) or 'application/json'
+ headers = headers or {}
+ headers.setdefault('Accept', content_type)
+
+ if body:
+ headers.setdefault('Content-Type', content_type)
+
+ if self.global_request_id:
+ headers.setdefault(REQ_ID_HEADER, self.global_request_id)
+
+ headers['User-Agent'] = USER_AGENT
+ # NOTE(dbelova): osprofiler_web.get_trace_id_headers does not add any
+ # headers in case if osprofiler is not initialized.
+ if osprofiler_web:
+ headers.update(osprofiler_web.get_trace_id_headers())
+
+ resp = requests.request(
+ method,
+ url,
+ data=body,
+ headers=headers,
+ verify=self.verify_cert,
+ cert=self.cert,
+ timeout=self.timeout,
+ **kwargs)
+
+ return resp, resp.text
+
+ def _check_uri_length(self, action):
+ uri_len = len(self.endpoint_url) + len(action)
+ if uri_len > MAX_URI_LEN:
+ raise exceptions.RequestURITooLong(
+ excess=uri_len - MAX_URI_LEN)
+
+ def do_request(self, url, method, **kwargs):
+ # Ensure client always has correct uri - do not guesstimate anything
+ self.authenticate_and_fetch_endpoint_url()
+ self._check_uri_length(url)
+
+ # Perform the request once. If we get a 401 back then it
+ # might be because the auth token expired, so try to
+ # re-authenticate and try again. If it still fails, bail.
+ try:
+ kwargs['headers'] = kwargs.get('headers') or {}
+ if self.auth_token is None:
+ self.auth_token = ""
+ kwargs['headers']['X-Auth-Token'] = self.auth_token
+ resp, body = self._cs_request(self.endpoint_url + url, method,
+ **kwargs)
+ return resp, body
+ except exceptions.Unauthorized:
+ self.authenticate()
+ kwargs['headers'] = kwargs.get('headers') or {}
+ kwargs['headers']['X-Auth-Token'] = self.auth_token
+ resp, body = self._cs_request(
+ self.endpoint_url + url, method, **kwargs)
+ return resp, body
+
+ def _extract_service_catalog(self, body):
+ """Set the client's service catalog from the response data."""
+ self.auth_ref = access.create(body=body)
+ self.service_catalog = self.auth_ref.service_catalog
+ self.auth_token = self.auth_ref.auth_token
+ self.auth_tenant_id = self.auth_ref.tenant_id
+ self.auth_user_id = self.auth_ref.user_id
+
+ if not self.endpoint_url:
+ self.endpoint_url = self.service_catalog.url_for(
+ region_name=self.region_name,
+ service_type=self.service_type,
+ interface=self.endpoint_type)
+
+ def _authenticate_keystone(self):
+ if self.user_id:
+ creds = {'userId': self.user_id,
+ 'password': self.password}
+ else:
+ creds = {'username': self.username,
+ 'password': self.password}
+
+ if self.project_id:
+ body = {'auth': {'passwordCredentials': creds,
+ 'tenantId': self.project_id, }, }
+ else:
+ body = {'auth': {'passwordCredentials': creds,
+ 'tenantName': self.project_name, }, }
+
+ if self.auth_url is None:
+ raise exceptions.NoAuthURLProvided()
+
+ token_url = self.auth_url + "/tokens"
+ resp, resp_body = self._cs_request(token_url, "POST",
+ body=jsonutils.dumps(body),
+ content_type="application/json",
+ allow_redirects=True)
+ if resp.status_code != 200:
+ raise exceptions.Unauthorized(message=resp_body)
+ if resp_body:
+ try:
+ resp_body = jsonutils.loads(resp_body)
+ except ValueError:
+ pass
+ else:
+ resp_body = None
+ self._extract_service_catalog(resp_body)
+
+ def _authenticate_noauth(self):
+ if not self.endpoint_url:
+ message = _('For "noauth" authentication strategy, the endpoint '
+ 'must be specified either in the constructor or '
+ 'using --os-url')
+ raise exceptions.Unauthorized(message=message)
+
+ def authenticate(self):
+ if self.auth_strategy == 'keystone':
+ self._authenticate_keystone()
+ elif self.auth_strategy == 'noauth':
+ self._authenticate_noauth()
+ else:
+ err_msg = _('Unknown auth strategy: %s') % self.auth_strategy
+ raise exceptions.Unauthorized(message=err_msg)
+
+ def _get_endpoint_url(self):
+ if self.auth_url is None:
+ raise exceptions.NoAuthURLProvided()
+
+ url = self.auth_url + '/tokens/%s/endpoints' % self.auth_token
+ try:
+ resp, body = self._cs_request(url, "GET")
+ except exceptions.Unauthorized:
+ # rollback to authenticate() to handle case when neutron client
+ # is initialized just before the token is expired
+ self.authenticate()
+ return self.endpoint_url
+
+ body = jsonutils.loads(body)
+ for endpoint in body.get('endpoints', []):
+ if (endpoint['type'] == 'network' and
+ endpoint.get('region') == self.region_name):
+ if self.endpoint_type not in endpoint:
+ raise exceptions.EndpointTypeNotFound(
+ type_=self.endpoint_type)
+ return endpoint[self.endpoint_type]
+
+ raise exceptions.EndpointNotFound()
+
+ def get_auth_info(self):
+ return {'auth_token': self.auth_token,
+ 'auth_tenant_id': self.auth_tenant_id,
+ 'auth_user_id': self.auth_user_id,
+ 'endpoint_url': self.endpoint_url}
+
+ def get_auth_ref(self):
+ return getattr(self, 'auth_ref', None)
+
+
+class SessionClient(adapter.Adapter):
+
+ def request(self, *args, **kwargs):
+ kwargs.setdefault('authenticated', False)
+ kwargs.setdefault('raise_exc', False)
+
+ content_type = kwargs.pop('content_type', None) or 'application/json'
+
+ headers = kwargs.get('headers') or {}
+ headers.setdefault('Accept', content_type)
+
+ # NOTE(dbelova): osprofiler_web.get_trace_id_headers does not add any
+ # headers in case if osprofiler is not initialized.
+ if osprofiler_web:
+ headers.update(osprofiler_web.get_trace_id_headers())
+
+ try:
+ kwargs.setdefault('data', kwargs.pop('body'))
+ except KeyError:
+ pass
+
+ if kwargs.get('data'):
+ headers.setdefault('Content-Type', content_type)
+
+ kwargs['headers'] = headers
+ resp = super(SessionClient, self).request(*args, **kwargs)
+ return resp, resp.text
+
+ def _check_uri_length(self, url):
+ uri_len = len(self.endpoint_url) + len(url)
+ if uri_len > MAX_URI_LEN:
+ raise exceptions.RequestURITooLong(
+ excess=uri_len - MAX_URI_LEN)
+
+ def do_request(self, url, method, **kwargs):
+ kwargs.setdefault('authenticated', True)
+ self._check_uri_length(url)
+ return self.request(url, method, **kwargs)
+
+ @property
+ def endpoint_url(self):
+ # NOTE(jamielennox): This is used purely by the CLI and should be
+ # removed when the CLI gets smarter.
+ return self.get_endpoint()
+
+ @property
+ def auth_token(self):
+ # NOTE(jamielennox): This is used purely by the CLI and should be
+ # removed when the CLI gets smarter.
+ return self.get_token()
+
+ def authenticate(self):
+ # NOTE(jamielennox): This is used purely by the CLI and should be
+ # removed when the CLI gets smarter.
+ self.get_token()
+
+ def get_auth_info(self):
+ auth_info = {'auth_token': self.auth_token,
+ 'endpoint_url': self.endpoint_url}
+
+ # NOTE(jamielennox): This is the best we can do here. It will work
+ # with identity plugins which is the primary case but we should
+ # deprecate it's usage as much as possible.
+ try:
+ get_access = (self.auth or self.session.auth).get_access
+ except AttributeError:
+ pass
+ else:
+ auth_ref = get_access(self.session)
+
+ auth_info['auth_tenant_id'] = auth_ref.project_id
+ auth_info['auth_user_id'] = auth_ref.user_id
+
+ return auth_info
+
+ def get_auth_ref(self):
+ return self.session.auth.get_auth_ref(self.session)
+
+
+# FIXME(bklei): Should refactor this to use kwargs and only
+# explicitly list arguments that are not None.
+@debtcollector.renames.renamed_kwarg('tenant_id', 'project_id', replace=True)
+@debtcollector.renames.renamed_kwarg(
+ 'tenant_name', 'project_name', replace=True)
+def construct_http_client(username=None,
+ user_id=None,
+ project_name=None,
+ project_id=None,
+ password=None,
+ auth_url=None,
+ token=None,
+ region_name=None,
+ timeout=None,
+ endpoint_url=None,
+ insecure=False,
+ endpoint_type='public',
+ log_credentials=None,
+ auth_strategy='keystone',
+ ca_cert=None,
+ cert=None,
+ service_type='network',
+ session=None,
+ global_request_id=None,
+ **kwargs):
+
+ if session:
+ kwargs.setdefault('user_agent', USER_AGENT)
+ kwargs.setdefault('interface', endpoint_type)
+ return SessionClient(session=session,
+ service_type=service_type,
+ region_name=region_name,
+ global_request_id=global_request_id,
+ **kwargs)
+ else:
+ # FIXME(bklei): username and password are now optional. Need
+ # to test that they were provided in this mode. Should also
+ # refactor to use kwargs.
+ return HTTPClient(username=username,
+ password=password,
+ project_id=project_id,
+ project_name=project_name,
+ user_id=user_id,
+ auth_url=auth_url,
+ token=token,
+ endpoint_url=endpoint_url,
+ insecure=insecure,
+ timeout=timeout,
+ region_name=region_name,
+ endpoint_type=endpoint_type,
+ service_type=service_type,
+ ca_cert=ca_cert,
+ cert=cert,
+ log_credentials=log_credentials,
+ auth_strategy=auth_strategy,
+ global_request_id=global_request_id)
diff --git a/quantumclient/tests/unit/__init__.py b/neutronclient/common/__init__.py
similarity index 100%
rename from quantumclient/tests/unit/__init__.py
rename to neutronclient/common/__init__.py
diff --git a/neutronclient/common/clientmanager.py b/neutronclient/common/clientmanager.py
new file mode 100644
index 000000000..56d3d3c12
--- /dev/null
+++ b/neutronclient/common/clientmanager.py
@@ -0,0 +1,120 @@
+# 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.
+#
+
+"""Manage access to the clients, including authenticating when needed.
+"""
+
+import debtcollector.renames
+
+from neutronclient import client
+from neutronclient.neutron import client as neutron_client
+
+
+class ClientCache(object):
+ """Descriptor class for caching created client handles."""
+
+ def __init__(self, factory):
+ self.factory = factory
+ self._handle = None
+
+ def __get__(self, instance, owner):
+ # Tell the ClientManager to login to keystone
+ if self._handle is None:
+ self._handle = self.factory(instance)
+ return self._handle
+
+
+class ClientManager(object):
+ """Manages access to API clients, including authentication."""
+ neutron = ClientCache(neutron_client.make_client)
+ # Provide support for old quantum commands (for example
+ # in stable versions)
+ quantum = neutron
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_name', 'project_name', replace=True)
+ def __init__(self, token=None, url=None,
+ auth_url=None,
+ endpoint_type=None,
+ project_name=None,
+ project_id=None,
+ username=None,
+ user_id=None,
+ password=None,
+ region_name=None,
+ api_version=None,
+ auth_strategy=None,
+ insecure=False,
+ ca_cert=None,
+ log_credentials=False,
+ service_type=None,
+ service_name=None,
+ timeout=None,
+ retries=0,
+ raise_errors=True,
+ session=None,
+ auth=None,
+ ):
+ self._token = token
+ self._url = url
+ self._auth_url = auth_url
+ self._service_type = service_type
+ self._service_name = service_name
+ self._endpoint_type = endpoint_type
+ self._project_name = project_name
+ self._project_id = project_id
+ self._username = username
+ self._user_id = user_id
+ self._password = password
+ self._region_name = region_name
+ self._api_version = api_version
+ self._service_catalog = None
+ self._auth_strategy = auth_strategy
+ self._insecure = insecure
+ self._ca_cert = ca_cert
+ self._log_credentials = log_credentials
+ self._timeout = timeout
+ self._retries = retries
+ self._raise_errors = raise_errors
+ self._session = session
+ self._auth = auth
+ return
+
+ def initialize(self):
+ if not self._url:
+ httpclient = client.construct_http_client(
+ username=self._username,
+ user_id=self._user_id,
+ project_name=self._project_name,
+ project_id=self._project_id,
+ password=self._password,
+ region_name=self._region_name,
+ auth_url=self._auth_url,
+ service_type=self._service_type,
+ service_name=self._service_name,
+ endpoint_type=self._endpoint_type,
+ insecure=self._insecure,
+ ca_cert=self._ca_cert,
+ timeout=self._timeout,
+ session=self._session,
+ auth=self._auth,
+ log_credentials=self._log_credentials)
+ httpclient.authenticate()
+ # Populate other password flow attributes
+ self._token = httpclient.auth_token
+ self._url = httpclient.endpoint_url
diff --git a/neutronclient/common/constants.py b/neutronclient/common/constants.py
new file mode 100644
index 000000000..ccb22cd80
--- /dev/null
+++ b/neutronclient/common/constants.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2012 OpenStack Foundation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+TYPE_BOOL = "bool"
+TYPE_INT = "int"
+TYPE_LONG = "long"
+TYPE_FLOAT = "float"
+TYPE_LIST = "list"
+TYPE_DICT = "dict"
+
+PLURALS = {'networks': 'network',
+ 'ports': 'port',
+ 'subnets': 'subnet',
+ 'subnetpools': 'subnetpool',
+ 'dns_nameservers': 'dns_nameserver',
+ 'host_routes': 'host_route',
+ 'allocation_pools': 'allocation_pool',
+ 'fixed_ips': 'fixed_ip',
+ 'extensions': 'extension'}
diff --git a/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py
new file mode 100644
index 000000000..443f78155
--- /dev/null
+++ b/neutronclient/common/exceptions.py
@@ -0,0 +1,260 @@
+# Copyright 2011 VMware, Inc
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_utils import encodeutils
+
+from neutronclient._i18n import _
+
+"""
+Neutron base exception handling.
+
+Exceptions are classified into three categories:
+* Exceptions corresponding to exceptions from neutron server:
+ This type of exceptions should inherit one of exceptions
+ in HTTP_EXCEPTION_MAP.
+* Exceptions from client library:
+ This type of exceptions should inherit NeutronClientException.
+* Exceptions from CLI code:
+ This type of exceptions should inherit NeutronCLIError.
+"""
+
+
+# NOTE: This method is defined here to avoid
+# an import loop between common.utils and this module.
+def _safe_decode_dict(kwargs):
+ for k, v in kwargs.items():
+ kwargs[k] = encodeutils.safe_decode(v)
+ return kwargs
+
+
+class NeutronException(Exception):
+ """Base Neutron Exception.
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+ """
+ message = _("An unknown exception occurred.")
+
+ def __init__(self, message=None, **kwargs):
+ if message:
+ self.message = message
+ try:
+ self._error_string = self.message % _safe_decode_dict(kwargs)
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+
+ def __str__(self):
+ return self._error_string
+
+
+class NeutronClientException(NeutronException):
+ """Base exception which exceptions from Neutron are mapped into.
+
+ NOTE: on the client side, we use different exception types in order
+ to allow client library users to handle server exceptions in try...except
+ blocks. The actual error message is the one generated on the server side.
+ """
+
+ 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)
+
+
+# Base exceptions from Neutron
+
+class BadRequest(NeutronClientException):
+ status_code = 400
+
+
+class Unauthorized(NeutronClientException):
+ status_code = 401
+ message = _("Unauthorized: bad credentials.")
+
+
+class Forbidden(NeutronClientException):
+ status_code = 403
+ message = _("Forbidden: your credentials don't give you access to this "
+ "resource.")
+
+
+class NotFound(NeutronClientException):
+ status_code = 404
+
+
+class Conflict(NeutronClientException):
+ status_code = 409
+
+
+class InternalServerError(NeutronClientException):
+ status_code = 500
+
+
+class ServiceUnavailable(NeutronClientException):
+ status_code = 503
+
+
+HTTP_EXCEPTION_MAP = {
+ 400: BadRequest,
+ 401: Unauthorized,
+ 403: Forbidden,
+ 404: NotFound,
+ 409: Conflict,
+ 500: InternalServerError,
+ 503: ServiceUnavailable,
+}
+
+
+# Exceptions mapped to Neutron server exceptions
+# These are defined if a user of client library needs specific exception.
+# Exception name should be + 'Client'
+# e.g., NetworkNotFound -> NetworkNotFoundClient
+
+class NetworkNotFoundClient(NotFound):
+ pass
+
+
+class PortNotFoundClient(NotFound):
+ pass
+
+
+class StateInvalidClient(BadRequest):
+ pass
+
+
+class NetworkInUseClient(Conflict):
+ pass
+
+
+class PortInUseClient(Conflict):
+ pass
+
+
+class IpAddressInUseClient(Conflict):
+ pass
+
+
+class IpAddressAlreadyAllocatedClient(Conflict):
+ pass
+
+
+class InvalidIpForNetworkClient(BadRequest):
+ pass
+
+
+class InvalidIpForSubnetClient(BadRequest):
+ pass
+
+
+class OverQuotaClient(Conflict):
+ pass
+
+
+class IpAddressGenerationFailureClient(Conflict):
+ pass
+
+
+class MacAddressInUseClient(Conflict):
+ pass
+
+
+class HostNotCompatibleWithFixedIpsClient(Conflict):
+ pass
+
+
+class ExternalIpAddressExhaustedClient(BadRequest):
+ pass
+
+
+# Exceptions from client library
+
+class NoAuthURLProvided(Unauthorized):
+ message = _("auth_url was not provided to the Neutron client")
+
+
+class EndpointNotFound(NeutronClientException):
+ message = _("Could not find Service or Region in Service Catalog.")
+
+
+class EndpointTypeNotFound(NeutronClientException):
+ message = _("Could not find endpoint type %(type_)s in Service Catalog.")
+
+
+class AmbiguousEndpoints(NeutronClientException):
+ message = _("Found more than one matching endpoint in Service Catalog: "
+ "%(matching_endpoints)")
+
+
+class RequestURITooLong(NeutronClientException):
+ """Raised when a request fails with HTTP error 414."""
+
+ def __init__(self, **kwargs):
+ self.excess = kwargs.get('excess', 0)
+ super(RequestURITooLong, self).__init__(**kwargs)
+
+
+class ConnectionFailed(NeutronClientException):
+ message = _("Connection to neutron failed: %(reason)s")
+
+
+class SslCertificateValidationError(NeutronClientException):
+ message = _("SSL certificate validation has failed: %(reason)s")
+
+
+class MalformedResponseBody(NeutronClientException):
+ message = _("Malformed response body: %(reason)s")
+
+
+class InvalidContentType(NeutronClientException):
+ message = _("Invalid content type %(content_type)s.")
+
+
+# Command line exceptions
+
+class NeutronCLIError(NeutronException):
+ """Exception raised when command line parsing fails."""
+ pass
+
+
+class CommandError(NeutronCLIError):
+ pass
+
+
+class UnsupportedVersion(NeutronCLIError):
+ """Indicates usage of an unsupported API version
+
+ Indicates that the user is trying to use an unsupported version of
+ the API.
+ """
+ pass
+
+
+class NeutronClientNoUniqueMatch(NeutronCLIError):
+ message = _("Multiple %(resource)s matches found for name '%(name)s',"
+ " use an ID to be more specific.")
diff --git a/neutronclient/common/extension.py b/neutronclient/common/extension.py
new file mode 100644
index 000000000..00a035d6c
--- /dev/null
+++ b/neutronclient/common/extension.py
@@ -0,0 +1,85 @@
+# Copyright 2015 Rackspace Hosting Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+from stevedore import extension
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _discover_via_entry_points():
+ emgr = extension.ExtensionManager('neutronclient.extension',
+ invoke_on_load=False)
+ return ((ext.name, ext.plugin) for ext in emgr)
+
+
+class NeutronClientExtension(neutronV20.NeutronCommand):
+ pagination_support = False
+ _formatters = {}
+ sorting_support = False
+
+
+class ClientExtensionShow(NeutronClientExtension, neutronV20.ShowCommand):
+ 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).take_action(parsed_args)
+
+
+class ClientExtensionList(NeutronClientExtension, neutronV20.ListCommand):
+ 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).take_action(parsed_args)
+
+
+class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand):
+ 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).take_action(parsed_args)
+
+
+class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand):
+ 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).take_action(parsed_args)
+
+
+class ClientExtensionUpdate(NeutronClientExtension, neutronV20.UpdateCommand):
+ 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).take_action(parsed_args)
diff --git a/neutronclient/common/serializer.py b/neutronclient/common/serializer.py
new file mode 100644
index 000000000..d4c8bbbb6
--- /dev/null
+++ b/neutronclient/common/serializer.py
@@ -0,0 +1,120 @@
+# Copyright 2013 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 oslo_serialization import jsonutils
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions as exception
+
+
+class ActionDispatcher(object):
+ """Maps method name to local methods through action name."""
+
+ def dispatch(self, *args, **kwargs):
+ """Find and call local method."""
+ action = kwargs.pop('action', 'default')
+ action_method = getattr(self, str(action), self.default)
+ return action_method(*args, **kwargs)
+
+ def default(self, data):
+ raise NotImplementedError()
+
+
+class DictSerializer(ActionDispatcher):
+ """Default request body serialization."""
+
+ def serialize(self, data, action='default'):
+ return self.dispatch(data, action=action)
+
+ def default(self, data):
+ return ""
+
+
+class JSONDictSerializer(DictSerializer):
+ """Default JSON request body serialization."""
+
+ def default(self, data):
+ def sanitizer(obj):
+ return str(obj)
+ return jsonutils.dumps(data, default=sanitizer)
+
+
+class TextDeserializer(ActionDispatcher):
+ """Default request body deserialization."""
+
+ def deserialize(self, datastring, action='default'):
+ return self.dispatch(datastring, action=action)
+
+ def default(self, datastring):
+ return {}
+
+
+class JSONDeserializer(TextDeserializer):
+
+ def _from_json(self, datastring):
+ try:
+ return jsonutils.loads(datastring)
+ except ValueError:
+ msg = _("Cannot understand JSON")
+ raise exception.MalformedResponseBody(reason=msg)
+
+ def default(self, datastring):
+ return {'body': self._from_json(datastring)}
+
+
+# NOTE(maru): this class is duplicated from neutron.wsgi
+class Serializer(object):
+ """Serializes and deserializes dictionaries to certain MIME types."""
+
+ def __init__(self, metadata=None):
+ """Create a serializer based on the given WSGI environment.
+
+ 'metadata' is an optional dict mapping MIME types to information
+ needed to serialize a dictionary to that type.
+
+ """
+ self.metadata = metadata or {}
+
+ def _get_serialize_handler(self, content_type):
+ handlers = {
+ 'application/json': JSONDictSerializer(),
+ }
+
+ try:
+ return handlers[content_type]
+ except Exception:
+ raise exception.InvalidContentType(content_type=content_type)
+
+ def serialize(self, data):
+ """Serialize a dictionary into the specified content type."""
+ return self._get_serialize_handler("application/json").serialize(data)
+
+ def deserialize(self, datastring):
+ """Deserialize a string to a dictionary.
+
+ The string must be in the format of a supported MIME type.
+ """
+ return self.get_deserialize_handler("application/json").deserialize(
+ datastring)
+
+ def get_deserialize_handler(self, content_type):
+ handlers = {
+ 'application/json': JSONDeserializer(),
+ }
+
+ try:
+ return handlers[content_type]
+ except Exception:
+ raise exception.InvalidContentType(content_type=content_type)
diff --git a/neutronclient/common/utils.py b/neutronclient/common/utils.py
new file mode 100644
index 000000000..93f92fd19
--- /dev/null
+++ b/neutronclient/common/utils.py
@@ -0,0 +1,237 @@
+# Copyright 2011, VMware, Inc.
+#
+# 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.
+#
+# Borrowed from nova code base, more utilities will be added/borrowed as and
+# when needed.
+
+"""Utilities and helper functions."""
+
+import argparse
+import functools
+import hashlib
+import logging
+import os
+
+from oslo_utils import encodeutils
+from oslo_utils import importutils
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+
+SENSITIVE_HEADERS = ('X-Auth-Token',)
+
+
+def env(*vars, **kwargs):
+ """Returns the first environment variable set.
+
+ If none are non-empty, defaults to '' or keyword arg default.
+ """
+ for v in vars:
+ value = os.environ.get(v)
+ if value:
+ return value
+ return kwargs.get('default', '')
+
+
+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.
+
+ :param api_name: the name of the API, e.g. 'compute', 'image', etc
+ :param version: the requested API version
+ :param version_map: a dict of client classes keyed by version
+ :rtype: a client class for the requested API version
+ """
+ try:
+ client_path = version_map[str(version)]
+ except (KeyError, ValueError):
+ msg = _("Invalid %(api_name)s client version '%(version)s'. must be "
+ "one of: %(map_keys)s")
+ msg = msg % {'api_name': api_name, 'version': version,
+ 'map_keys': ', '.join(version_map.keys())}
+ raise exceptions.UnsupportedVersion(msg)
+
+ return importutils.import_class(client_path)
+
+
+def get_item_properties(item, fields, mixed_case_fields=(), formatters=None):
+ """Return a tuple containing the item properties.
+
+ :param item: a single item resource (e.g. Server, Tenant, etc)
+ :param fields: tuple of strings with the desired field names
+ :param mixed_case_fields: tuple of field names to preserve case
+ :param formatters: dictionary mapping field names to callables
+ to format the values
+ """
+ if formatters is None:
+ formatters = {}
+
+ row = []
+
+ for field in fields:
+ if field in formatters:
+ row.append(formatters[field](item))
+ else:
+ if field in mixed_case_fields:
+ field_name = field.replace(' ', '_')
+ else:
+ field_name = field.lower().replace(' ', '_')
+ if not hasattr(item, field_name) and isinstance(item, dict):
+ data = item[field_name]
+ else:
+ data = getattr(item, field_name, '')
+ if data is None:
+ data = ''
+ row.append(data)
+ return tuple(row)
+
+
+def str2bool(strbool):
+ if strbool is None:
+ return None
+ return strbool.lower() == 'true'
+
+
+def str2dict(strdict, required_keys=None, optional_keys=None):
+ """Convert key1=value1,key2=value2,... string into dictionary.
+
+ :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:
+ i = 0
+ kvlist = []
+ for kv in strdict.split(','):
+ if '=' in kv:
+ kvlist.append(kv)
+ i += 1
+ elif i == 0:
+ msg = _("missing value for key '%s'")
+ raise argparse.ArgumentTypeError(msg % kv)
+ else:
+ kvlist[i - 1] = "%s,%s" % (kvlist[i - 1], kv)
+ for kv in kvlist:
+ key, sep, value = kv.partition('=')
+ if not sep:
+ 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
+
+ string_parts = ['curl -i']
+ for element in args:
+ if element in ('GET', 'POST', 'DELETE', 'PUT'):
+ string_parts.append(' -X %s' % element)
+ else:
+ string_parts.append(' %s' % element)
+
+ for (key, value) in kwargs['headers'].items():
+ if key in SENSITIVE_HEADERS:
+ v = value.encode('utf-8')
+ h = hashlib.sha256(v)
+ d = h.hexdigest()
+ value = "{SHA256}%s" % d
+ header = ' -H "%s: %s"' % (key, value)
+ string_parts.append(header)
+
+ if 'body' in kwargs and kwargs['body']:
+ string_parts.append(" -d '%s'" % (kwargs['body']))
+ req = encodeutils.safe_encode("".join(string_parts))
+ _logger.debug("REQ: %s", req)
+
+
+def http_log_resp(_logger, resp, body):
+ if not _logger.isEnabledFor(logging.DEBUG):
+ return
+ _logger.debug("RESP: %(code)s %(headers)s %(body)s",
+ {'code': resp.status_code,
+ 'headers': resp.headers,
+ 'body': body})
+
+
+def _safe_encode_without_obj(data):
+ if isinstance(data, str):
+ return encodeutils.safe_encode(data)
+ return data
+
+
+def safe_encode_list(data):
+ return list(map(_safe_encode_without_obj, data))
+
+
+def safe_encode_dict(data):
+ def _encode_item(item):
+ k, v = item
+ if isinstance(v, list):
+ return (k, safe_encode_list(v))
+ elif isinstance(v, dict):
+ return (k, safe_encode_dict(v))
+ return (k, _safe_encode_without_obj(v))
+
+ return dict(list(map(_encode_item, data.items())))
+
+
+def add_boolean_argument(parser, name, **kwargs):
+ for keyword in ('metavar', 'choices'):
+ kwargs.pop(keyword, None)
+ default = kwargs.pop('default', argparse.SUPPRESS)
+ parser.add_argument(
+ name,
+ metavar='{True,False}',
+ choices=['True', 'true', 'False', 'false'],
+ default=default,
+ **kwargs)
diff --git a/neutronclient/common/validators.py b/neutronclient/common/validators.py
new file mode 100644
index 000000000..831d68e8f
--- /dev/null
+++ b/neutronclient/common/validators.py
@@ -0,0 +1,69 @@
+# Copyright 2014 NEC Corporation
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import netaddr
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+
+
+def validate_int_range(parsed_args, attr_name, min_value=None, max_value=None):
+ val = getattr(parsed_args, attr_name, None)
+ if val is None:
+ return
+ try:
+ if not isinstance(val, int):
+ int_val = int(val, 0)
+ else:
+ int_val = val
+ if ((min_value is None or min_value <= int_val) and
+ (max_value is None or int_val <= max_value)):
+ return
+ except (ValueError, TypeError):
+ pass
+
+ if min_value is not None and max_value is not None:
+ msg = (_('%(attr_name)s "%(val)s" should be an integer '
+ '[%(min)i:%(max)i].') %
+ {'attr_name': attr_name.replace('_', '-'),
+ 'val': val, 'min': min_value, 'max': max_value})
+ elif min_value is not None:
+ msg = (_('%(attr_name)s "%(val)s" should be an integer '
+ 'greater than or equal to %(min)i.') %
+ {'attr_name': attr_name.replace('_', '-'),
+ 'val': val, 'min': min_value})
+ elif max_value is not None:
+ msg = (_('%(attr_name)s "%(val)s" should be an integer '
+ 'smaller than or equal to %(max)i.') %
+ {'attr_name': attr_name.replace('_', '-'),
+ 'val': val, 'max': max_value})
+ else:
+ msg = (_('%(attr_name)s "%(val)s" should be an integer.') %
+ {'attr_name': attr_name.replace('_', '-'),
+ 'val': val})
+
+ raise exceptions.CommandError(msg)
+
+
+def validate_ip_subnet(parsed_args, attr_name):
+ val = getattr(parsed_args, attr_name)
+ if not val:
+ return
+ try:
+ netaddr.IPNetwork(val)
+ except (netaddr.AddrFormatError, ValueError):
+ raise exceptions.CommandError(
+ (_('%(attr_name)s "%(val)s" is not a valid CIDR.') %
+ {'attr_name': attr_name.replace('_', '-'), 'val': val}))
diff --git a/neutronclient/neutron/__init__.py b/neutronclient/neutron/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/client.py b/neutronclient/neutron/client.py
new file mode 100644
index 000000000..d859d815d
--- /dev/null
+++ b/neutronclient/neutron/client.py
@@ -0,0 +1,65 @@
+# 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 neutronclient.common import utils
+
+
+API_NAME = 'network'
+API_VERSIONS = {
+ '2.0': 'neutronclient.v2_0.client.Client',
+ '2': 'neutronclient.v2_0.client.Client',
+}
+
+
+def make_client(instance):
+ """Returns an neutron client."""
+ neutron_client = utils.get_client_class(
+ API_NAME,
+ instance._api_version,
+ API_VERSIONS,
+ )
+ instance.initialize()
+ url = instance._url
+ url = url.rstrip("/")
+ client = neutron_client(username=instance._username,
+ project_name=instance._project_name,
+ password=instance._password,
+ region_name=instance._region_name,
+ auth_url=instance._auth_url,
+ endpoint_url=url,
+ endpoint_type=instance._endpoint_type,
+ token=instance._token,
+ auth_strategy=instance._auth_strategy,
+ insecure=instance._insecure,
+ ca_cert=instance._ca_cert,
+ retries=instance._retries,
+ raise_errors=instance._raise_errors,
+ session=instance._session,
+ auth=instance._auth)
+ return client
+
+
+def Client(api_version, *args, **kwargs):
+ """Return an neutron client.
+
+ @param api_version: only 2.0 is supported now
+ """
+ neutron_client = utils.get_client_class(
+ API_NAME,
+ api_version,
+ API_VERSIONS,
+ )
+ return neutron_client(*args, **kwargs)
diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py
new file mode 100644
index 000000000..849a93d9e
--- /dev/null
+++ b/neutronclient/neutron/v2_0/__init__.py
@@ -0,0 +1,826 @@
+# 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.
+#
+
+import abc
+import argparse
+import functools
+import logging
+
+from cliff import command
+from cliff import lister
+from cliff import show
+from oslo_serialization import jsonutils
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+
+HYPHEN_OPTS = ['tags_any', 'not_tags', 'not_tags_any']
+
+
+def find_resource_by_id(client, resource, resource_id, cmd_resource=None,
+ parent_id=None, fields=None):
+ return client.find_resource_by_id(resource, resource_id, cmd_resource,
+ parent_id, fields)
+
+
+def find_resourceid_by_id(client, resource, resource_id, cmd_resource=None,
+ parent_id=None):
+ return find_resource_by_id(client, resource, resource_id, cmd_resource,
+ parent_id, fields='id')['id']
+
+
+def find_resource_by_name_or_id(client, resource, name_or_id,
+ project_id=None, cmd_resource=None,
+ parent_id=None, fields=None):
+ return client.find_resource(resource, name_or_id, project_id,
+ cmd_resource, parent_id, fields)
+
+
+def find_resourceid_by_name_or_id(client, resource, name_or_id,
+ project_id=None, cmd_resource=None,
+ parent_id=None):
+ return find_resource_by_name_or_id(client, resource, name_or_id,
+ project_id, cmd_resource,
+ parent_id, fields='id')['id']
+
+
+def add_show_list_common_argument(parser):
+ parser.add_argument(
+ '-D', '--show-details',
+ help=_('Show detailed information.'),
+ action='store_true',
+ default=False, )
+ parser.add_argument(
+ '--show_details',
+ action='store_true',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--fields',
+ help=argparse.SUPPRESS,
+ action='append',
+ default=[])
+ parser.add_argument(
+ '-F', '--field',
+ dest='fields', metavar='FIELD',
+ help=_('Specify the field(s) to be returned by server. You can '
+ 'repeat this option.'),
+ action='append',
+ default=[])
+
+
+def add_pagination_argument(parser):
+ parser.add_argument(
+ '-P', '--page-size',
+ dest='page_size', metavar='SIZE', type=int,
+ help=_("Specify retrieve unit of each request, then split one request "
+ "to several requests."),
+ default=None)
+
+
+def add_sorting_argument(parser):
+ parser.add_argument(
+ '--sort-key',
+ dest='sort_key', metavar='FIELD',
+ action='append',
+ help=_("Sorts the list by the specified fields in the specified "
+ "directions. You can repeat this option, but you must "
+ "specify an equal number of sort_dir and sort_key values. "
+ "Extra sort_dir options are ignored. Missing sort_dir options "
+ "use the default asc value."),
+ default=[])
+ parser.add_argument(
+ '--sort-dir',
+ dest='sort_dir', metavar='{asc,desc}',
+ help=_("Sorts the list in the specified direction. You can repeat "
+ "this option."),
+ action='append',
+ default=[],
+ choices=['asc', 'desc'])
+
+
+def is_number(s):
+ try:
+ float(s) # for int, long and float
+ except ValueError:
+ try:
+ complex(s) # for complex
+ except ValueError:
+ return False
+
+ return True
+
+
+def _process_previous_argument(current_arg, _value_number, current_type_str,
+ _list_flag, _values_specs, _clear_flag,
+ values_specs):
+ if current_arg is not None:
+ if _value_number == 0 and (current_type_str or _list_flag):
+ # This kind of argument should have value
+ raise exceptions.CommandError(
+ _("Invalid values_specs %s") % ' '.join(values_specs))
+ if _value_number > 1 or _list_flag or current_type_str == 'list':
+ current_arg.update({'nargs': '+'})
+ elif _value_number == 0:
+ if _clear_flag:
+ # if we have action=clear, we use argument's default
+ # value None for argument
+ _values_specs.pop()
+ else:
+ # We assume non value argument as bool one
+ current_arg.update({'action': 'store_true'})
+
+
+def parse_args_to_dict(values_specs):
+ """It is used to analyze the extra command options to command.
+
+ Besides known options and arguments, our commands also support user to
+ put more options to the end of command line. For example,
+ list_nets -- --tag x y --key1 value1, where '-- --tag x y --key1 value1'
+ is extra options to our list_nets. This feature can support V2.0 API's
+ fields selection and filters. For example, to list networks which has name
+ 'test4', we can have list_nets -- --name=test4.
+
+ value spec is: --key type=int|bool|... value. Type is one of Python
+ built-in types. By default, type is string. The key without value is
+ a bool option. Key with two values will be a list option.
+ """
+
+ # values_specs for example: '-- --tag x y --key1 type=int value1'
+ # -- is a pseudo argument
+ values_specs_copy = values_specs[:]
+ if values_specs_copy and values_specs_copy[0] == '--':
+ del values_specs_copy[0]
+ # converted ArgumentParser arguments for each of the options
+ _options = {}
+ # the argument part for current option in _options
+ current_arg = None
+ # the string after remove meta info in values_specs
+ # for example, '--tag x y --key1 value1'
+ _values_specs = []
+ # record the count of values for an option
+ # for example: for '--tag x y', it is 2, while for '--key1 value1', it is 1
+ _value_number = 0
+ # list=true
+ _list_flag = False
+ # action=clear
+ _clear_flag = False
+ # the current item in values_specs
+ current_item = None
+ # the str after 'type='
+ current_type_str = None
+ # dict of allowed types
+ allowed_type_dict = {
+ 'bool': utils.str2bool,
+ 'dict': utils.str2dict,
+ 'int': int,
+ 'str': str,
+ }
+
+ for _item in values_specs_copy:
+ if _item.startswith('--'):
+ # Deal with previous argument if any
+ _process_previous_argument(
+ current_arg, _value_number, current_type_str,
+ _list_flag, _values_specs, _clear_flag, values_specs)
+
+ # Init variables for current argument
+ current_item = _item
+ _list_flag = False
+ _clear_flag = False
+ current_type_str = None
+ if "=" in _item:
+ _value_number = 1
+ _item = _item.split('=')[0]
+ else:
+ _value_number = 0
+ if _item in _options:
+ raise exceptions.CommandError(
+ _("Duplicated options %s") % ' '.join(values_specs))
+ else:
+ _options.update({_item: {}})
+ current_arg = _options[_item]
+ _item = current_item
+ elif _item.startswith('type='):
+ if current_arg is None:
+ raise exceptions.CommandError(
+ _("Invalid values_specs %s") % ' '.join(values_specs))
+ if 'type' not in current_arg:
+ current_type_str = _item.split('=', 2)[1]
+ if current_type_str in allowed_type_dict:
+ current_arg['type'] = allowed_type_dict[current_type_str]
+ continue
+ else:
+ raise exceptions.CommandError(
+ _("Invalid value_specs {valspec}: type {curtypestr}"
+ " is not supported").format(
+ valspec=' '.join(values_specs),
+ curtypestr=current_type_str))
+
+ elif _item == 'list=true':
+ _list_flag = True
+ continue
+ elif _item == 'action=clear':
+ _clear_flag = True
+ continue
+
+ if not _item.startswith('--'):
+ # All others are value items
+ # Make sure '--' occurs first and allow minus value
+ if (not current_item or '=' in current_item or
+ _item.startswith('-') and not is_number(_item)):
+ raise exceptions.CommandError(
+ _("Invalid values_specs %s") % ' '.join(values_specs))
+ _value_number += 1
+
+ if _item.startswith('---'):
+ raise exceptions.CommandError(
+ _("Invalid values_specs %s") % ' '.join(values_specs))
+ _values_specs.append(_item)
+
+ # Deal with last one argument
+ _process_previous_argument(
+ current_arg, _value_number, current_type_str,
+ _list_flag, _values_specs, _clear_flag, values_specs)
+
+ # Populate the parser with arguments
+ _parser = argparse.ArgumentParser(add_help=False)
+ for opt, optspec in _options.items():
+ _parser.add_argument(opt, **optspec)
+ _args = _parser.parse_args(_values_specs)
+
+ result_dict = {}
+ for opt in _options.keys():
+ _opt = opt.split('--', 2)[1]
+ _opt = _opt.replace('-', '_')
+ _value = getattr(_args, _opt)
+ result_dict.update({_opt: _value})
+ return result_dict
+
+
+def _merge_args(qCmd, parsed_args, _extra_values, value_specs):
+ """Merge arguments from _extra_values into parsed_args.
+
+ If an argument value are provided in both and it is a list,
+ the values in _extra_values will be merged into parsed_args.
+
+ @param parsed_args: the parsed args from known options
+ @param _extra_values: the other parsed arguments in unknown parts
+ @param values_specs: the unparsed unknown parts
+ """
+ temp_values = _extra_values.copy()
+ for key, value in temp_values.items():
+ if hasattr(parsed_args, key):
+ arg_value = getattr(parsed_args, key)
+ if arg_value is not None and value is not None:
+ if isinstance(arg_value, list):
+ if value and isinstance(value, list):
+ if (not arg_value or
+ isinstance(arg_value[0], type(value[0]))):
+ arg_value.extend(value)
+ _extra_values.pop(key)
+
+
+def update_dict(obj, dict, attributes):
+ """Update dict with fields from obj.attributes.
+
+ :param obj: the object updated into dict
+ :param dict: the result dictionary
+ :param attributes: a list of attributes belonging to obj
+ """
+ for attribute in attributes:
+ if hasattr(obj, attribute) and getattr(obj, attribute) is not None:
+ dict[attribute] = getattr(obj, attribute)
+
+
+# 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):
+ def __new__(cls, name, bases, cls_dict):
+ if 'log' not in cls_dict:
+ cls_dict['log'] = logging.getLogger(
+ cls_dict['__module__'] + '.' + name)
+ return super(NeutronCommandMeta, cls).__new__(cls,
+ name, bases, cls_dict)
+
+
+class NeutronCommand(command.Command, metaclass=NeutronCommandMeta):
+
+ values_specs = []
+ json_indent = None
+ resource = None
+ shadow_resource = None
+ parent_id = None
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)', parsed_args)
+ return super(NeutronCommand, self).run(parsed_args)
+
+ @property
+ def cmd_resource(self):
+ if self.shadow_resource:
+ return self.shadow_resource
+ return self.resource
+
+ def get_client(self):
+ return self.app.client_manager.neutron
+
+ def get_parser(self, prog_name):
+ parser = super(NeutronCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ '--request-format',
+ help=argparse.SUPPRESS,
+ default='json',
+ choices=['json', ], )
+ parser.add_argument(
+ '--request_format',
+ choices=['json', ],
+ help=argparse.SUPPRESS)
+
+ return parser
+
+ def cleanup_output_data(self, data):
+ pass
+
+ def format_output_data(self, data):
+ # Modify data to make it more readable
+ if self.resource in data:
+ for k, v in data[self.resource].items():
+ if isinstance(v, list):
+ value = '\n'.join(jsonutils.dumps(
+ i, indent=self.json_indent) if isinstance(i, dict)
+ else str(i) for i in v)
+ data[self.resource][k] = value
+ elif isinstance(v, dict):
+ value = jsonutils.dumps(v, indent=self.json_indent)
+ data[self.resource][k] = value
+ elif v is None:
+ data[self.resource][k] = ''
+
+ def add_known_arguments(self, parser):
+ pass
+
+ def set_extra_attrs(self, parsed_args):
+ pass
+
+ def args2body(self, parsed_args):
+ return {}
+
+
+class CreateCommand(NeutronCommand, show.ShowOne):
+ """Create a resource for a given tenant."""
+
+ log = None
+
+ def get_parser(self, prog_name):
+ parser = super(CreateCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='TENANT_ID',
+ help=_('The owner tenant ID.'), )
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ self.add_known_arguments(parser)
+ return parser
+
+ 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)
+ _merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ body = self.args2body(parsed_args)
+ body[self.resource].update(_extra_values)
+ obj_creator = getattr(neutron_client,
+ "create_%s" % self.cmd_resource)
+ if self.parent_id:
+ data = obj_creator(self.parent_id, body)
+ else:
+ data = obj_creator(body)
+ self.cleanup_output_data(data)
+ if parsed_args.formatter == 'table':
+ self.format_output_data(data)
+ info = self.resource in data and data[self.resource] or None
+ if info:
+ if parsed_args.formatter == 'table':
+ print(_('Created a new %s:') % self.resource,
+ file=self.app.stdout)
+ else:
+ info = {'': ''}
+ return zip(*sorted(info.items()))
+
+
+class UpdateCommand(NeutronCommand):
+ """Update resource's information."""
+
+ log = None
+ allow_names = True
+ help_resource = None
+
+ def get_parser(self, prog_name):
+ parser = super(UpdateCommand, self).get_parser(prog_name)
+ if self.allow_names:
+ 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.help_resource)
+ self.add_known_arguments(parser)
+ return parser
+
+ 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)
+ _merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ body = self.args2body(parsed_args)
+ if self.resource in body:
+ body[self.resource].update(_extra_values)
+ else:
+ body[self.resource] = _extra_values
+ if not body[self.resource]:
+ raise exceptions.CommandError(
+ _("Must specify new values to update %s") %
+ self.cmd_resource)
+ if self.allow_names:
+ _id = find_resourceid_by_name_or_id(
+ neutron_client, self.resource, parsed_args.id,
+ cmd_resource=self.cmd_resource, parent_id=self.parent_id)
+ else:
+ _id = find_resourceid_by_id(
+ neutron_client, self.resource, parsed_args.id,
+ self.cmd_resource, self.parent_id)
+ obj_updater = getattr(neutron_client,
+ "update_%s" % self.cmd_resource)
+ if self.parent_id:
+ obj_updater(_id, self.parent_id, body)
+ else:
+ obj_updater(_id, body)
+ print((_('Updated %(resource)s: %(id)s') %
+ {'id': parsed_args.id, 'resource': self.resource}),
+ file=self.app.stdout)
+ return
+
+
+class DeleteCommand(NeutronCommand):
+ """Delete a given resource."""
+
+ log = None
+ allow_names = True
+ help_resource = None
+ bulk_delete = True
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteCommand, self).get_parser(prog_name)
+ if not self.help_resource:
+ self.help_resource = self.resource
+ if self.allow_names:
+ help_str = _('ID(s) or name(s) of %s to delete.')
+ else:
+ help_str = _('ID(s) of %s to delete.')
+ parser.add_argument(
+ 'id', metavar=self.resource.upper(),
+ nargs='+' if self.bulk_delete else 1,
+ help=help_str % self.help_resource)
+ self.add_known_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ self.set_extra_attrs(parsed_args)
+ neutron_client = self.get_client()
+ obj_deleter = getattr(neutron_client,
+ "delete_%s" % self.cmd_resource)
+
+ if self.bulk_delete:
+ self._bulk_delete(obj_deleter, neutron_client, parsed_args.id)
+ else:
+ self.delete_item(obj_deleter, neutron_client, parsed_args.id)
+ print((_('Deleted %(resource)s: %(id)s')
+ % {'id': parsed_args.id,
+ 'resource': self.resource}),
+ file=self.app.stdout)
+ return
+
+ def _bulk_delete(self, obj_deleter, neutron_client, parsed_args_ids):
+ successful_delete = []
+ non_existent = []
+ multiple_ids = []
+ for item_id in parsed_args_ids:
+ try:
+ self.delete_item(obj_deleter, neutron_client, item_id)
+ successful_delete.append(item_id)
+ except exceptions.NotFound:
+ non_existent.append(item_id)
+ except exceptions.NeutronClientNoUniqueMatch:
+ multiple_ids.append(item_id)
+ if successful_delete:
+ print((_('Deleted %(resource)s(s): %(id)s'))
+ % {'id': ", ".join(successful_delete),
+ 'resource': self.cmd_resource},
+ file=self.app.stdout)
+ if non_existent or multiple_ids:
+ err_msgs = []
+ if non_existent:
+ err_msgs.append((_("Unable to find %(resource)s(s) with id(s) "
+ "'%(id)s'.") %
+ {'resource': self.cmd_resource,
+ 'id': ", ".join(non_existent)}))
+ if multiple_ids:
+ err_msgs.append((_("Multiple %(resource)s(s) matches found "
+ "for name(s) '%(id)s'. Please use an ID "
+ "to be more specific.") %
+ {'resource': self.cmd_resource,
+ 'id': ", ".join(multiple_ids)}))
+ raise exceptions.NeutronCLIError(message='\n'.join(err_msgs))
+
+ def delete_item(self, obj_deleter, neutron_client, item_id):
+ if self.allow_names:
+ params = {'cmd_resource': self.cmd_resource,
+ 'parent_id': self.parent_id}
+ _id = find_resourceid_by_name_or_id(neutron_client,
+ self.resource,
+ item_id,
+ **params)
+ else:
+ _id = item_id
+
+ if self.parent_id:
+ obj_deleter(_id, self.parent_id)
+ else:
+ obj_deleter(_id)
+ return
+
+
+class ListCommand(NeutronCommand, lister.Lister):
+ """List resources that belong to a given tenant."""
+
+ 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)
+ add_show_list_common_argument(parser)
+ if self.pagination_support:
+ add_pagination_argument(parser)
+ 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
+ if parsed_args.fields:
+ 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):
+ resource_plural = neutron_client.get_resource_plural(self.cmd_resource)
+ obj_lister = getattr(neutron_client, "list_%s" % resource_plural)
+ if self.parent_id:
+ data = obj_lister(self.parent_id, **search_opts)
+ else:
+ data = obj_lister(**search_opts)
+ return data
+
+ def retrieve_list(self, parsed_args):
+ """Retrieve a list of resources from Neutron server."""
+ neutron_client = self.get_client()
+ _extra_values = parse_args_to_dict(self.values_specs)
+ _merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ search_opts = self.args2search_opts(parsed_args)
+ search_opts.update(_extra_values)
+ if self.pagination_support:
+ page_size = parsed_args.page_size
+ if page_size:
+ search_opts.update({'limit': page_size})
+ if self.sorting_support:
+ keys = parsed_args.sort_key
+ if keys:
+ search_opts.update({'sort_key': keys})
+ dirs = parsed_args.sort_dir
+ len_diff = len(keys) - len(dirs)
+ if len_diff > 0:
+ dirs += ['asc'] * len_diff
+ elif len_diff < 0:
+ dirs = dirs[:len(keys)]
+ if dirs:
+ search_opts.update({'sort_dir': dirs})
+ data = self.call_server(neutron_client, search_opts, parsed_args)
+ collection = neutron_client.get_resource_plural(self.resource)
+ return data.get(collection, [])
+
+ def extend_list(self, data, parsed_args):
+ """Update a retrieved list.
+
+ This method provides a way to modify an original list returned from
+ the neutron server. For example, you can add subnet cidr information
+ to a network list.
+ """
+ pass
+
+ def setup_columns(self, info, parsed_args):
+ _columns = len(info) > 0 and sorted(info[0].keys()) or []
+ if not _columns:
+ # clean the parsed_args.columns so that cliff will not break
+ parsed_args.columns = []
+ elif parsed_args.columns:
+ _columns = [x for x in parsed_args.columns if x in _columns]
+ elif self.list_columns:
+ # if no -c(s) by user and list_columns, we use columns in
+ # both list_columns and returned resource.
+ # Also Keep their order the same as in list_columns
+ _columns = self._setup_columns_with_tenant_id(self.list_columns,
+ _columns)
+
+ if parsed_args.formatter == 'table':
+ formatters = self._formatters
+ elif (parsed_args.formatter == 'csv' and
+ hasattr(self, '_formatters_csv')):
+ formatters = self._formatters_csv
+ else:
+ # For other formatters, we use raw value returned from neutron
+ formatters = {}
+
+ return (_columns, (utils.get_item_properties(
+ s, _columns, formatters=formatters, )
+ for s in info), )
+
+ def _setup_columns_with_tenant_id(self, display_columns, avail_columns):
+ _columns = [x for x in display_columns if x in avail_columns]
+ if 'tenant_id' in display_columns:
+ return _columns
+ if 'tenant_id' not in avail_columns:
+ return _columns
+ if not self.is_admin_role():
+ return _columns
+ try:
+ pos_id = _columns.index('id')
+ except ValueError:
+ pos_id = 0
+ try:
+ pos_name = _columns.index('name')
+ except ValueError:
+ pos_name = 0
+ _columns.insert(max(pos_id, pos_name) + 1, 'tenant_id')
+ return _columns
+
+ def is_admin_role(self):
+ client = self.get_client()
+ auth_ref = client.httpclient.get_auth_ref()
+ if not auth_ref:
+ return False
+ return 'admin' in auth_ref.role_names
+
+ def take_action(self, parsed_args):
+ self.set_extra_attrs(parsed_args)
+ data = self.retrieve_list(parsed_args)
+ self.extend_list(data, parsed_args)
+ return self.setup_columns(data, parsed_args)
+
+
+class ShowCommand(NeutronCommand, show.ShowOne):
+ """Show information of a given resource."""
+
+ log = None
+ allow_names = True
+ help_resource = None
+
+ def get_parser(self, prog_name):
+ parser = super(ShowCommand, self).get_parser(prog_name)
+ add_show_list_common_argument(parser)
+ if self.allow_names:
+ 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.help_resource)
+ self.add_known_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ self.set_extra_attrs(parsed_args)
+ neutron_client = self.get_client()
+
+ params = {}
+ if parsed_args.show_details:
+ params = {'verbose': 'True'}
+ if parsed_args.fields:
+ params = {'fields': parsed_args.fields}
+ if self.allow_names:
+ _id = find_resourceid_by_name_or_id(neutron_client,
+ self.resource,
+ parsed_args.id,
+ cmd_resource=self.cmd_resource,
+ parent_id=self.parent_id)
+ else:
+ _id = parsed_args.id
+
+ obj_shower = getattr(neutron_client, "show_%s" % self.cmd_resource)
+ if self.parent_id:
+ data = obj_shower(_id, self.parent_id, **params)
+ else:
+ data = obj_shower(_id, **params)
+ self.cleanup_output_data(data)
+ if parsed_args.formatter == 'table':
+ self.format_output_data(data)
+ resource = data[self.resource]
+ if self.resource in data:
+ return zip(*sorted(resource.items()))
+ else:
+ return None
diff --git a/neutronclient/neutron/v2_0/address_scope.py b/neutronclient/neutron/v2_0/address_scope.py
new file mode 100644
index 000000000..73eba80f5
--- /dev/null
+++ b/neutronclient/neutron/v2_0/address_scope.py
@@ -0,0 +1,89 @@
+# Copyright 2015 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 utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListAddressScope(neutronV20.ListCommand):
+ """List address scopes that belong to a given tenant."""
+
+ resource = 'address_scope'
+ list_columns = ['id', 'name', 'ip_version']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowAddressScope(neutronV20.ShowCommand):
+ """Show information about an address scope."""
+
+ resource = 'address_scope'
+
+
+class CreateAddressScope(neutronV20.CreateCommand):
+ """Create an address scope for a given tenant."""
+
+ resource = 'address_scope'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ 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,
+ 'ip_version': parsed_args.ip_version}
+ if parsed_args.shared:
+ body['shared'] = True
+ neutronV20.update_dict(parsed_args, body, ['tenant_id'])
+ return {self.resource: body}
+
+
+class DeleteAddressScope(neutronV20.DeleteCommand):
+ """Delete an address scope."""
+
+ resource = 'address_scope'
+
+
+class UpdateAddressScope(neutronV20.UpdateCommand):
+ """Update an address scope."""
+
+ resource = 'address_scope'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument('--name',
+ help=_('Updated name of the address scope.'))
+ utils.add_boolean_argument(
+ parser, '--shared',
+ help=_('Set sharing of address scope. '
+ '(True means shared)'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body, ['name', 'shared'])
+ return {self.resource: body}
diff --git a/neutronclient/neutron/v2_0/agent.py b/neutronclient/neutron/v2_0/agent.py
new file mode 100644
index 000000000..36167ff66
--- /dev/null
+++ b/neutronclient/neutron/v2_0/agent.py
@@ -0,0 +1,78 @@
+# Copyright 2013 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 neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_timestamp(component):
+ try:
+ return component['heartbeat_timestamp'].split(".", 2)[0]
+ except (TypeError, KeyError):
+ return ''
+
+
+class ListAgent(neutronV20.ListCommand):
+ """List agents."""
+
+ resource = 'agent'
+ list_columns = ['id', 'agent_type', 'host', 'availability_zone', 'alive',
+ 'admin_state_up', 'binary']
+ _formatters = {'heartbeat_timestamp': _format_timestamp}
+ sorting_support = True
+
+ def extend_list(self, data, parsed_args):
+ for agent in data:
+ if 'alive' in agent:
+ agent['alive'] = ":-)" if agent['alive'] else 'xxx'
+
+
+class ShowAgent(neutronV20.ShowCommand):
+ """Show information of a given agent."""
+
+ resource = 'agent'
+ allow_names = False
+ json_indent = 5
+
+
+class DeleteAgent(neutronV20.DeleteCommand):
+ """Delete a given agent."""
+
+ resource = 'agent'
+ allow_names = False
+
+
+class UpdateAgent(neutronV20.UpdateCommand):
+ """Updates the admin status and description for a specified agent."""
+
+ resource = 'agent'
+ allow_names = False
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state',
+ action='store_false',
+ help=_('Set admin state up of the agent to false.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description for the agent.'))
+
+ def args2body(self, parsed_args):
+ body = {'admin_state_up': parsed_args.admin_state}
+ neutronV20.update_dict(parsed_args, body,
+ ['description'])
+ return {self.resource: body}
diff --git a/neutronclient/neutron/v2_0/agentscheduler.py b/neutronclient/neutron/v2_0/agentscheduler.py
new file mode 100644
index 000000000..e92b10be1
--- /dev/null
+++ b/neutronclient/neutron/v2_0/agentscheduler.py
@@ -0,0 +1,341 @@
+# Copyright 2013 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 neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.neutron.v2_0 import network
+from neutronclient.neutron.v2_0 import router
+
+
+PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
+
+
+class AddNetworkToDhcpAgent(neutronV20.NeutronCommand):
+ """Add a network to a DHCP agent."""
+
+ def get_parser(self, prog_name):
+ 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 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)
+ neutron_client.add_network_to_dhcp_agent(parsed_args.dhcp_agent,
+ {'network_id': _net_id})
+ print(_('Added network %s to DHCP agent') % parsed_args.network,
+ file=self.app.stdout)
+
+
+class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand):
+ """Remove a network from a DHCP agent."""
+
+ def get_parser(self, prog_name):
+ 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 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)
+ neutron_client.remove_network_from_dhcp_agent(
+ parsed_args.dhcp_agent, _net_id)
+ print(_('Removed network %s from DHCP agent') % parsed_args.network,
+ file=self.app.stdout)
+
+
+class ListNetworksOnDhcpAgent(network.ListNetwork):
+ """List the networks on a DHCP agent."""
+
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworksOnDhcpAgent,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'dhcp_agent',
+ metavar='DHCP_AGENT',
+ help=_('ID of the DHCP agent.'))
+ return parser
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ data = neutron_client.list_networks_on_dhcp_agent(
+ parsed_args.dhcp_agent, **search_opts)
+ return data
+
+
+class ListDhcpAgentsHostingNetwork(neutronV20.ListCommand):
+ """List DHCP agents hosting a network."""
+
+ resource = 'agent'
+ _formatters = {}
+ list_columns = ['id', 'host', 'admin_state_up', 'alive']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListDhcpAgentsHostingNetwork,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'network',
+ metavar='NETWORK',
+ help=_('Network to query.'))
+ 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):
+ _id = neutronV20.find_resourceid_by_name_or_id(neutron_client,
+ 'network',
+ parsed_args.network)
+ search_opts['network'] = _id
+ data = neutron_client.list_dhcp_agent_hosting_networks(**search_opts)
+ return data
+
+
+class AddRouterToL3Agent(neutronV20.NeutronCommand):
+ """Add a router to a L3 agent."""
+
+ def get_parser(self, prog_name):
+ 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 take_action(self, parsed_args):
+ neutron_client = self.get_client()
+ _id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'router', parsed_args.router)
+ neutron_client.add_router_to_l3_agent(parsed_args.l3_agent,
+ {'router_id': _id})
+ print(_('Added router %s to L3 agent') % parsed_args.router,
+ file=self.app.stdout)
+
+
+class RemoveRouterFromL3Agent(neutronV20.NeutronCommand):
+ """Remove a router from a L3 agent."""
+
+ def get_parser(self, prog_name):
+ 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 take_action(self, parsed_args):
+ neutron_client = self.get_client()
+ _id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'router', parsed_args.router)
+ neutron_client.remove_router_from_l3_agent(
+ parsed_args.l3_agent, _id)
+ print(_('Removed router %s from L3 agent') % parsed_args.router,
+ file=self.app.stdout)
+
+
+class ListRoutersOnL3Agent(neutronV20.ListCommand):
+ """List the routers on a L3 agent."""
+
+ _formatters = {'external_gateway_info':
+ router._format_external_gateway_info}
+ list_columns = ['id', 'name', 'external_gateway_info']
+ resource = 'router'
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListRoutersOnL3Agent,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'l3_agent',
+ metavar='L3_AGENT',
+ help=_('ID of the L3 agent to query.'))
+ return parser
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ data = neutron_client.list_routers_on_l3_agent(
+ parsed_args.l3_agent, **search_opts)
+ return data
+
+
+class ListL3AgentsHostingRouter(neutronV20.ListCommand):
+ """List L3 agents hosting a router."""
+
+ resource = 'agent'
+ _formatters = {}
+ list_columns = ['id', 'host', 'admin_state_up', 'alive']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListL3AgentsHostingRouter,
+ self).get_parser(prog_name)
+ parser.add_argument('router',
+ metavar='ROUTER',
+ help=_('Router to query.'))
+ return parser
+
+ def extend_list(self, data, parsed_args):
+ # Show the ha_state column only if the server responds with it,
+ # as some plugins do not support HA routers.
+ if any('ha_state' in agent for agent in data):
+ if 'ha_state' not in self.list_columns:
+ self.list_columns.append('ha_state')
+ for agent in data:
+ agent['alive'] = ":-)" if agent['alive'] else 'xxx'
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ _id = neutronV20.find_resourceid_by_name_or_id(neutron_client,
+ 'router',
+ parsed_args.router)
+ search_opts['router'] = _id
+ data = neutron_client.list_l3_agent_hosting_routers(**search_opts)
+ return data
+
+
+class ListPoolsOnLbaasAgent(neutronV20.ListCommand):
+ """List the pools on a loadbalancer agent."""
+
+ list_columns = ['id', 'name', 'lb_method', 'protocol',
+ 'admin_state_up', 'status']
+ resource = 'pool'
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ 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
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ data = neutron_client.list_pools_on_lbaas_agent(
+ parsed_args.lbaas_agent, **search_opts)
+ return data
+
+
+class GetLbaasAgentHostingPool(neutronV20.ListCommand):
+ """Get loadbalancer agent hosting a pool.
+
+ Deriving from ListCommand though server will return only one agent
+ to keep common output format for all agent schedulers
+ """
+
+ resource = 'agent'
+ list_columns = ['id', 'host', 'admin_state_up', 'alive']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(GetLbaasAgentHostingPool,
+ self).get_parser(prog_name)
+ parser.add_argument('pool',
+ metavar='POOL',
+ help=_('Pool to query.'))
+ 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):
+ _id = neutronV20.find_resourceid_by_name_or_id(neutron_client,
+ 'pool',
+ parsed_args.pool)
+ search_opts['pool'] = _id
+ agent = neutron_client.get_lbaas_agent_hosting_pool(**search_opts)
+ data = {'agents': [agent['agent']]}
+ return data
+
+
+class ListLoadBalancersOnLbaasAgent(neutronV20.ListCommand):
+ """List the loadbalancers on a loadbalancer v2 agent."""
+
+ list_columns = ['id', 'name', 'admin_state_up', 'provisioning_status']
+ resource = 'loadbalancer'
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListLoadBalancersOnLbaasAgent, self).get_parser(
+ prog_name)
+ parser.add_argument(
+ 'lbaas_agent',
+ metavar='LBAAS_AGENT',
+ help=_('ID of the loadbalancer agent to query.'))
+ return parser
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ data = neutron_client.list_loadbalancers_on_lbaas_agent(
+ parsed_args.lbaas_agent, **search_opts)
+ return data
+
+
+class GetLbaasAgentHostingLoadBalancer(neutronV20.ListCommand):
+ """Get lbaas v2 agent hosting a loadbalancer.
+
+ Deriving from ListCommand though server will return only one agent
+ to keep common output format for all agent schedulers
+ """
+
+ resource = 'agent'
+ list_columns = ['id', 'host', 'admin_state_up', 'alive']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(GetLbaasAgentHostingLoadBalancer,
+ self).get_parser(prog_name)
+ parser.add_argument('loadbalancer',
+ metavar='LOADBALANCER',
+ help=_('LoadBalancer to query.'))
+ 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):
+ _id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'loadbalancer', parsed_args.loadbalancer)
+ search_opts['loadbalancer'] = _id
+ agent = neutron_client.get_lbaas_agent_hosting_loadbalancer(
+ **search_opts)
+ data = {'agents': [agent['agent']]}
+ return data
diff --git a/neutronclient/neutron/v2_0/auto_allocated_topology.py b/neutronclient/neutron/v2_0/auto_allocated_topology.py
new file mode 100644
index 000000000..a24959e16
--- /dev/null
+++ b/neutronclient/neutron/v2_0/auto_allocated_topology.py
@@ -0,0 +1,112 @@
+# 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
+ # (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
+
+
+class DeleteAutoAllocatedTopology(v2_0.NeutronCommand):
+ """Delete the auto-allocated topology of a given tenant."""
+
+ resource = 'auto_allocated_topology'
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteAutoAllocatedTopology, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help=_('The owner tenant ID.'))
+ # Allow people to do
+ # neutron auto-allocated-topology-delete
+ # (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()
+ tenant_id = parsed_args.tenant_id or parsed_args.pos_tenant_id
+ client.delete_auto_allocated_topology(tenant_id)
+ # It tenant is None, let's be clear on what it means.
+ tenant_id = tenant_id or 'None (i.e. yours)'
+ print(_('Deleted topology for tenant %s.') % tenant_id,
+ file=self.app.stdout)
diff --git a/neutronclient/neutron/v2_0/availability_zone.py b/neutronclient/neutron/v2_0/availability_zone.py
new file mode 100644
index 000000000..2081d0990
--- /dev/null
+++ b/neutronclient/neutron/v2_0/availability_zone.py
@@ -0,0 +1,38 @@
+# 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.neutron import v2_0 as neutronv20
+
+
+def add_az_hint_argument(parser, resource):
+ parser.add_argument(
+ '--availability-zone-hint', metavar='AVAILABILITY_ZONE',
+ action='append', dest='availability_zone_hints',
+ help=_('Availability Zone for the %s '
+ '(requires availability zone extension, '
+ 'this option can be repeated).') % resource)
+
+
+def args2body_az_hint(parsed_args, resource):
+ if parsed_args.availability_zone_hints:
+ resource['availability_zone_hints'] = (
+ parsed_args.availability_zone_hints)
+
+
+class ListAvailabilityZone(neutronv20.ListCommand):
+ """List availability zones."""
+
+ resource = 'availability_zone'
+ list_columns = ['name', 'resource', 'state']
+ pagination_support = True
+ sorting_support = True
diff --git a/neutronclient/neutron/v2_0/bgp/__init__.py b/neutronclient/neutron/v2_0/bgp/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/bgp/dragentscheduler.py b/neutronclient/neutron/v2_0/bgp/dragentscheduler.py
new file mode 100644
index 000000000..d1e1de63f
--- /dev/null
+++ b/neutronclient/neutron/v2_0/bgp/dragentscheduler.py
@@ -0,0 +1,115 @@
+# 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.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
diff --git a/neutronclient/neutron/v2_0/bgp/peer.py b/neutronclient/neutron/v2_0/bgp/peer.py
new file mode 100644
index 000000000..8fefb660a
--- /dev/null
+++ b/neutronclient/neutron/v2_0/bgp/peer.py
@@ -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'
diff --git a/neutronclient/neutron/v2_0/bgp/speaker.py b/neutronclient/neutron/v2_0/bgp/speaker.py
new file mode 100644
index 000000000..34ce22eb8
--- /dev/null
+++ b/neutronclient/neutron/v2_0/bgp/speaker.py
@@ -0,0 +1,275 @@
+# 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 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 = 4294967295
+
+
+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
diff --git a/neutronclient/neutron/v2_0/contrib/__init__.py b/neutronclient/neutron/v2_0/contrib/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/contrib/_fox_sockets.py b/neutronclient/neutron/v2_0/contrib/_fox_sockets.py
new file mode 100644
index 000000000..1ff5ffd2b
--- /dev/null
+++ b/neutronclient/neutron/v2_0/contrib/_fox_sockets.py
@@ -0,0 +1,93 @@
+# Copyright 2015 Rackspace Hosting Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.common import extension
+
+
+def _add_updatable_args(parser):
+ parser.add_argument(
+ 'name',
+ help=_('Name of this fox socket.'))
+
+
+def _updatable_args2body(parsed_args, body, client):
+ if parsed_args.name:
+ body['name'] = parsed_args.name
+
+
+class FoxInSocket(extension.NeutronClientExtension):
+ """Define required variables for resource operations."""
+
+ resource = 'fox_socket'
+ resource_plural = '%ss' % resource
+ object_path = '/%s' % resource_plural
+ resource_path = '/%s/%%s' % resource_plural
+ versions = ['2.0']
+
+
+class FoxInSocketsList(extension.ClientExtensionList, FoxInSocket):
+ """List fox sockets."""
+
+ shell_command = 'fox-sockets-list'
+ list_columns = ['id', 'name']
+ pagination_support = True
+ sorting_support = True
+
+
+class FoxInSocketsCreate(extension.ClientExtensionCreate, FoxInSocket):
+ """Create a fox socket."""
+
+ shell_command = 'fox-sockets-create'
+ list_columns = ['id', 'name']
+
+ def add_known_arguments(self, parser):
+ _add_updatable_args(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ client = self.get_client()
+ _updatable_args2body(parsed_args, body, client)
+ return {'fox_socket': body}
+
+
+class FoxInSocketsUpdate(extension.ClientExtensionUpdate, FoxInSocket):
+ """Update a fox socket."""
+
+ shell_command = 'fox-sockets-update'
+ list_columns = ['id', 'name']
+
+ def add_known_arguments(self, parser):
+ # _add_updatable_args(parser)
+ parser.add_argument(
+ '--name',
+ help=_('Name of this fox socket.'))
+
+ def args2body(self, parsed_args):
+ body = {'name': parsed_args.name}
+ return {'fox_socket': body}
+
+
+class FoxInSocketsDelete(extension.ClientExtensionDelete, FoxInSocket):
+ """Delete a fox socket."""
+
+ shell_command = 'fox-sockets-delete'
+
+
+class FoxInSocketsShow(extension.ClientExtensionShow, FoxInSocket):
+ """Show a fox socket."""
+
+ shell_command = 'fox-sockets-show'
diff --git a/neutronclient/neutron/v2_0/dns.py b/neutronclient/neutron/v2_0/dns.py
new file mode 100644
index 000000000..0cf7ec0b4
--- /dev/null
+++ b/neutronclient/neutron/v2_0/dns.py
@@ -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] = ""
diff --git a/neutronclient/neutron/v2_0/extension.py b/neutronclient/neutron/v2_0/extension.py
new file mode 100644
index 000000000..24905d59d
--- /dev/null
+++ b/neutronclient/neutron/v2_0/extension.py
@@ -0,0 +1,31 @@
+# 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 neutronclient.neutron import v2_0 as cmd_base
+
+
+class ListExt(cmd_base.ListCommand):
+ """List all extensions."""
+
+ resource = 'extension'
+ list_columns = ['alias', 'name']
+
+
+class ShowExt(cmd_base.ShowCommand):
+ """Show information of a given resource."""
+
+ resource = "extension"
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/flavor/__init__.py b/neutronclient/neutron/v2_0/flavor/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/flavor/flavor.py b/neutronclient/neutron/v2_0/flavor/flavor.py
new file mode 100644
index 000000000..36a052ef2
--- /dev/null
+++ b/neutronclient/neutron/v2_0/flavor/flavor.py
@@ -0,0 +1,163 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# 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 neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListFlavor(neutronV20.ListCommand):
+ """List Neutron service flavors."""
+
+ resource = 'flavor'
+ list_columns = ['id', 'name', 'service_type', 'enabled']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowFlavor(neutronV20.ShowCommand):
+ """Show information about a given Neutron service flavor."""
+
+ resource = 'flavor'
+
+
+class CreateFlavor(neutronV20.CreateCommand):
+ """Create a Neutron service flavor."""
+
+ resource = 'flavor'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name',
+ metavar='NAME',
+ help=_('Name for the flavor.'))
+ parser.add_argument(
+ 'service_type',
+ metavar='SERVICE_TYPE',
+ help=_('Service type to which the flavor applies to: e.g. VPN. '
+ '(See service-provider-list for loaded examples.)'))
+ parser.add_argument(
+ '--description',
+ help=_('Description for the flavor.'))
+ utils.add_boolean_argument(
+ parser,
+ '--enabled',
+ default=argparse.SUPPRESS,
+ help=_('Sets enabled flag.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'description', 'service_type',
+ 'enabled'])
+ return {self.resource: body}
+
+
+class DeleteFlavor(neutronV20.DeleteCommand):
+ """Delete a given Neutron service flavor."""
+
+ resource = 'flavor'
+
+
+class UpdateFlavor(neutronV20.UpdateCommand):
+ """Update a Neutron service flavor."""
+
+ resource = 'flavor'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name for the flavor.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description for the flavor.'))
+ utils.add_boolean_argument(
+ parser,
+ '--enabled',
+ default=argparse.SUPPRESS,
+ help=_('Sets enabled flag.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'description', 'enabled'])
+ return {self.resource: body}
+
+
+class AssociateFlavor(neutronV20.NeutronCommand):
+ """Associate a Neutron service flavor with a flavor profile."""
+
+ resource = 'flavor'
+
+ def get_parser(self, prog_name):
+ parser = super(AssociateFlavor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flavor',
+ metavar='FLAVOR',
+ help=_('ID or name of the flavor to associate.'))
+ parser.add_argument(
+ 'flavor_profile',
+ metavar='FLAVOR_PROFILE',
+ help=_('ID of the flavor profile to be associated with the '
+ 'flavor.'))
+ return parser
+
+ 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)
+ service_profile_id = neutronV20.find_resourceid_by_id(
+ neutron_client, 'service_profile', parsed_args.flavor_profile)
+ body = {'service_profile': {'id': service_profile_id}}
+ neutron_client.associate_flavor(flavor_id, body)
+ print((_('Associated flavor %(flavor)s with '
+ 'flavor_profile %(profile)s') %
+ {'flavor': parsed_args.flavor,
+ 'profile': parsed_args.flavor_profile}),
+ file=self.app.stdout)
+
+
+class DisassociateFlavor(neutronV20.NeutronCommand):
+ """Disassociate a Neutron service flavor from a flavor profile."""
+
+ resource = 'flavor'
+
+ def get_parser(self, prog_name):
+ parser = super(DisassociateFlavor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flavor',
+ metavar='FLAVOR',
+ help=_('ID or name of the flavor to be disassociated.'))
+ parser.add_argument(
+ 'flavor_profile',
+ metavar='FLAVOR_PROFILE',
+ help=_('ID of the flavor profile to be disassociated from the '
+ 'flavor.'))
+ return parser
+
+ 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)
+ service_profile_id = neutronV20.find_resourceid_by_id(
+ neutron_client, 'service_profile', parsed_args.flavor_profile)
+ neutron_client.disassociate_flavor(flavor_id, service_profile_id)
+ print((_('Disassociated flavor %(flavor)s from '
+ 'flavor_profile %(profile)s') %
+ {'flavor': parsed_args.flavor,
+ 'profile': parsed_args.flavor_profile}),
+ file=self.app.stdout)
diff --git a/neutronclient/neutron/v2_0/flavor/flavor_profile.py b/neutronclient/neutron/v2_0/flavor/flavor_profile.py
new file mode 100644
index 000000000..894a8a67c
--- /dev/null
+++ b/neutronclient/neutron/v2_0/flavor/flavor_profile.py
@@ -0,0 +1,99 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# 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 neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListFlavorProfile(neutronV20.ListCommand):
+ """List Neutron service flavor profiles."""
+
+ resource = 'service_profile'
+ list_columns = ['id', 'description', 'enabled', 'metainfo']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowFlavorProfile(neutronV20.ShowCommand):
+ """Show information about a given Neutron service flavor profile."""
+
+ resource = 'service_profile'
+
+
+class CreateFlavorProfile(neutronV20.CreateCommand):
+ """Create a Neutron service flavor profile."""
+
+ resource = 'service_profile'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--description',
+ help=_('Description for the flavor profile.'))
+ parser.add_argument(
+ '--driver',
+ help=_('Python module path to driver.'))
+ parser.add_argument(
+ '--metainfo',
+ help=_('Metainfo for the flavor profile.'))
+ utils.add_boolean_argument(
+ parser,
+ '--enabled',
+ default=argparse.SUPPRESS,
+ help=_('Sets enabled flag.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body,
+ ['description', 'driver', 'enabled',
+ 'metainfo'])
+ return {self.resource: body}
+
+
+class DeleteFlavorProfile(neutronV20.DeleteCommand):
+ """Delete a given Neutron service flavor profile."""
+
+ resource = 'service_profile'
+
+
+class UpdateFlavorProfile(neutronV20.UpdateCommand):
+ """Update a given Neutron service flavor profile."""
+
+ resource = 'service_profile'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--description',
+ help=_('Description for the flavor profile.'))
+ parser.add_argument(
+ '--driver',
+ help=_('Python module path to driver.'))
+ parser.add_argument(
+ '--metainfo',
+ help=_('Metainfo for the flavor profile.'))
+ utils.add_boolean_argument(
+ parser,
+ '--enabled',
+ default=argparse.SUPPRESS,
+ help=_('Sets enabled flag.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body,
+ ['description', 'driver', 'enabled',
+ 'metainfo'])
+ return {self.resource: body}
diff --git a/neutronclient/neutron/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py
new file mode 100644
index 000000000..5f4ced40a
--- /dev/null
+++ b/neutronclient/neutron/v2_0/floatingip.py
@@ -0,0 +1,148 @@
+# 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.
+#
+
+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):
+ """List floating IPs that belong to a given tenant."""
+
+ resource = 'floatingip'
+ list_columns = ['id', 'fixed_ip_address', 'floating_ip_address',
+ 'port_id']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowFloatingIP(neutronV20.ShowCommand):
+ """Show information of a given floating IP."""
+
+ resource = 'floatingip'
+ allow_names = False
+
+
+class CreateFloatingIP(neutronV20.CreateCommand):
+ """Create a floating IP for a given tenant."""
+
+ resource = 'floatingip'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'floating_network_id', metavar='FLOATING_NETWORK',
+ help=_('ID or name of the network from which '
+ 'the floating IP is allocated.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the floating IP.'))
+ parser.add_argument(
+ '--port-id',
+ help=_('ID of the port to be associated with the floating IP.'))
+ parser.add_argument(
+ '--port_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--fixed-ip-address',
+ help=_('IP address on the port (only required if port has '
+ 'multiple IPs).'))
+ parser.add_argument(
+ '--fixed_ip_address',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--floating-ip-address',
+ help=_('IP address of the floating IP'))
+ parser.add_argument(
+ '--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(
+ self.get_client(), 'network', parsed_args.floating_network_id)
+ body = {'floating_network_id': _network_id}
+ neutronV20.update_dict(parsed_args, body,
+ ['port_id', 'tenant_id',
+ 'fixed_ip_address', 'description',
+ '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}
+
+
+class DeleteFloatingIP(neutronV20.DeleteCommand):
+ """Delete a given floating IP."""
+
+ resource = 'floatingip'
+ allow_names = False
+
+
+class AssociateFloatingIP(neutronV20.NeutronCommand):
+ """Create a mapping between a floating IP and a fixed IP."""
+
+ resource = 'floatingip'
+
+ def get_parser(self, prog_name):
+ parser = super(AssociateFloatingIP, self).get_parser(prog_name)
+ parser.add_argument(
+ 'floatingip_id', metavar='FLOATINGIP_ID',
+ help=_('ID of the floating IP to associate.'))
+ parser.add_argument(
+ 'port_id', metavar='PORT',
+ help=_('ID or name of the port to be associated with the '
+ 'floating IP.'))
+ parser.add_argument(
+ '--fixed-ip-address',
+ help=_('IP address on the port (only required if port has '
+ 'multiple IPs).'))
+ parser.add_argument(
+ '--fixed_ip_address',
+ help=argparse.SUPPRESS)
+ return parser
+
+ def take_action(self, parsed_args):
+ neutron_client = self.get_client()
+ update_dict = {}
+ neutronV20.update_dict(parsed_args, update_dict,
+ ['port_id', 'fixed_ip_address'])
+ neutron_client.update_floatingip(parsed_args.floatingip_id,
+ {'floatingip': update_dict})
+ print(_('Associated floating IP %s') % parsed_args.floatingip_id,
+ file=self.app.stdout)
+
+
+class DisassociateFloatingIP(neutronV20.NeutronCommand):
+ """Remove a mapping from a floating IP to a fixed IP."""
+
+ resource = 'floatingip'
+
+ def get_parser(self, prog_name):
+ parser = super(DisassociateFloatingIP, self).get_parser(prog_name)
+ parser.add_argument(
+ 'floatingip_id', metavar='FLOATINGIP_ID',
+ help=_('ID of the floating IP to disassociate.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ neutron_client = self.get_client()
+ neutron_client.update_floatingip(parsed_args.floatingip_id,
+ {'floatingip': {'port_id': None}})
+ print(_('Disassociated floating IP %s') % parsed_args.floatingip_id,
+ file=self.app.stdout)
diff --git a/neutronclient/neutron/v2_0/fw/__init__.py b/neutronclient/neutron/v2_0/fw/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/fw/firewall.py b/neutronclient/neutron/v2_0/fw/firewall.py
new file mode 100644
index 000000000..c04660a3b
--- /dev/null
+++ b/neutronclient/neutron/v2_0/fw/firewall.py
@@ -0,0 +1,128 @@
+# Copyright 2013 Big Switch Networks
+# 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 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=_('ID or name of the router associated with the firewall '
+ '(requires FWaaS router insertion extension to be enabled). '
+ '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."""
+
+ resource = 'firewall'
+ list_columns = ['id', 'name', 'firewall_policy_id']
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowFirewall(neutronv20.ShowCommand):
+ """Show information of a given firewall."""
+
+ resource = 'firewall'
+
+
+class CreateFirewall(neutronv20.CreateCommand):
+ """Create a firewall."""
+
+ resource = 'firewall'
+
+ def add_known_arguments(self, parser):
+ add_common_args(parser)
+ parser.add_argument(
+ 'policy', metavar='POLICY',
+ help=_('ID or name of the firewall policy '
+ 'associated to this firewall.'))
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state',
+ action='store_false',
+ help=_('Set admin state up to false.'))
+
+ def args2body(self, parsed_args):
+ 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}
+
+
+class UpdateFirewall(neutronv20.UpdateCommand):
+ """Update a given firewall."""
+
+ resource = 'firewall'
+
+ def add_known_arguments(self, parser):
+ add_common_args(parser)
+ parser.add_argument(
+ '--policy', metavar='POLICY',
+ help=_('ID or name of the firewall policy '
+ 'associated to this firewall.'))
+ 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):
+ 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):
+ """Delete a given firewall."""
+
+ resource = 'firewall'
diff --git a/neutronclient/neutron/v2_0/fw/firewallpolicy.py b/neutronclient/neutron/v2_0/fw/firewallpolicy.py
new file mode 100644
index 000000000..3d9331cd6
--- /dev/null
+++ b/neutronclient/neutron/v2_0/fw/firewallpolicy.py
@@ -0,0 +1,227 @@
+# Copyright 2013 Big Switch Networks
+# 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 neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronv20
+
+
+def _format_firewall_rules(firewall_policy):
+ try:
+ output = '[' + ',\n '.join([rule for rule in
+ firewall_policy['firewall_rules']]) + ']'
+ return output
+ except (TypeError, KeyError):
+ return ''
+
+
+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 parse_common_args(client, parsed_args):
+ if parsed_args.firewall_rules:
+ _firewall_rules = []
+ for f in parsed_args.firewall_rules:
+ _firewall_rules.append(
+ neutronv20.find_resourceid_by_name_or_id(
+ client, 'firewall_rule', f))
+ body = {'firewall_rules': _firewall_rules}
+ else:
+ body = {}
+ neutronv20.update_dict(parsed_args, body,
+ ['name', 'description', 'shared',
+ 'audited', 'tenant_id'])
+ return {'firewall_policy': body}
+
+
+class ListFirewallPolicy(neutronv20.ListCommand):
+ """List firewall policies that belong to a given tenant."""
+
+ resource = 'firewall_policy'
+ list_columns = ['id', 'name', 'firewall_rules']
+ _formatters = {'firewall_rules': _format_firewall_rules,
+ }
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowFirewallPolicy(neutronv20.ShowCommand):
+ """Show information of a given firewall policy."""
+
+ resource = 'firewall_policy'
+
+
+class CreateFirewallPolicy(neutronv20.CreateCommand):
+ """Create a firewall policy."""
+
+ resource = 'firewall_policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name',
+ metavar='NAME',
+ help=_('Name for the firewall policy.'))
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Create a shared policy.'),
+ default=argparse.SUPPRESS)
+ 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 parse_common_args(self.get_client(), parsed_args)
+
+
+class UpdateFirewallPolicy(neutronv20.UpdateCommand):
+ """Update a given firewall policy."""
+
+ resource = 'firewall_policy'
+
+ def add_known_arguments(self, 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 parse_common_args(self.get_client(), parsed_args)
+
+
+class DeleteFirewallPolicy(neutronv20.DeleteCommand):
+ """Delete a given firewall policy."""
+
+ resource = 'firewall_policy'
+
+
+class FirewallPolicyInsertRule(neutronv20.UpdateCommand):
+ """Insert a rule into a given firewall policy."""
+
+ resource = 'firewall_policy'
+
+ def call_api(self, neutron_client, firewall_policy_id, body):
+ return neutron_client.firewall_policy_insert_rule(firewall_policy_id,
+ body)
+
+ def args2body(self, parsed_args):
+ _rule = ''
+ if parsed_args.firewall_rule_id:
+ _rule = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'firewall_rule',
+ parsed_args.firewall_rule_id)
+ _insert_before = ''
+ if 'insert_before' in parsed_args:
+ if parsed_args.insert_before:
+ _insert_before = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'firewall_rule',
+ parsed_args.insert_before)
+ _insert_after = ''
+ if 'insert_after' in parsed_args:
+ if parsed_args.insert_after:
+ _insert_after = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'firewall_rule',
+ parsed_args.insert_after)
+ body = {'firewall_rule_id': _rule,
+ 'insert_before': _insert_before,
+ 'insert_after': _insert_after}
+ return body
+
+ def get_parser(self, prog_name):
+ parser = super(FirewallPolicyInsertRule, self).get_parser(prog_name)
+ parser.add_argument(
+ '--insert-before',
+ metavar='FIREWALL_RULE',
+ help=_('Insert before this rule.'))
+ parser.add_argument(
+ '--insert-after',
+ metavar='FIREWALL_RULE',
+ help=_('Insert after this rule.'))
+ parser.add_argument(
+ 'firewall_rule_id',
+ metavar='FIREWALL_RULE',
+ help=_('New rule to insert.'))
+ self.add_known_arguments(parser)
+ return parser
+
+ 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,
+ self.resource,
+ parsed_args.id)
+ self.call_api(neutron_client, _id, body)
+ print((_('Inserted firewall rule in firewall policy %(id)s') %
+ {'id': parsed_args.id}), file=self.app.stdout)
+
+
+class FirewallPolicyRemoveRule(neutronv20.UpdateCommand):
+ """Remove a rule from a given firewall policy."""
+
+ resource = 'firewall_policy'
+
+ def call_api(self, neutron_client, firewall_policy_id, body):
+ return neutron_client.firewall_policy_remove_rule(firewall_policy_id,
+ body)
+
+ def args2body(self, parsed_args):
+ _rule = ''
+ if parsed_args.firewall_rule_id:
+ _rule = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'firewall_rule',
+ parsed_args.firewall_rule_id)
+ body = {'firewall_rule_id': _rule}
+ return body
+
+ def get_parser(self, prog_name):
+ parser = super(FirewallPolicyRemoveRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'firewall_rule_id',
+ metavar='FIREWALL_RULE',
+ help=_('ID or name of the firewall rule to be removed '
+ 'from the policy.'))
+ self.add_known_arguments(parser)
+ return parser
+
+ 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,
+ self.resource,
+ parsed_args.id)
+ self.call_api(neutron_client, _id, body)
+ print((_('Removed firewall rule from firewall policy %(id)s') %
+ {'id': parsed_args.id}), file=self.app.stdout)
diff --git a/neutronclient/neutron/v2_0/fw/firewallrule.py b/neutronclient/neutron/v2_0/fw/firewallrule.py
new file mode 100644
index 000000000..df5db95d6
--- /dev/null
+++ b/neutronclient/neutron/v2_0/fw/firewallrule.py
@@ -0,0 +1,168 @@
+# Copyright 2013 Big Switch Networks
+# 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 neutronclient._i18n import _
+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."""
+
+ resource = 'firewall_rule'
+ list_columns = ['id', 'name', 'firewall_policy_id', 'summary', 'enabled']
+ pagination_support = True
+ sorting_support = True
+
+ def extend_list(self, data, parsed_args):
+ for d in data:
+ val = []
+ if d.get('protocol'):
+ protocol = d['protocol'].upper()
+ else:
+ protocol = 'no-protocol'
+ val.append(protocol)
+ if 'source_ip_address' in d and 'source_port' in d:
+ src = 'source: ' + str(d['source_ip_address']).lower()
+ src = src + '(' + str(d['source_port']).lower() + ')'
+ else:
+ src = 'source: none specified'
+ val.append(src)
+ if 'destination_ip_address' in d and 'destination_port' in d:
+ dst = 'dest: ' + str(d['destination_ip_address']).lower()
+ dst = dst + '(' + str(d['destination_port']).lower() + ')'
+ else:
+ dst = 'dest: none specified'
+ val.append(dst)
+ if 'action' in d:
+ action = d['action']
+ else:
+ action = 'no-action'
+ val.append(action)
+ d['summary'] = ',\n '.join(val)
+
+
+class ShowFirewallRule(neutronv20.ShowCommand):
+ """Show information of a given firewall rule."""
+
+ resource = 'firewall_rule'
+
+
+class CreateFirewallRule(neutronv20.CreateCommand):
+ """Create a firewall rule."""
+
+ resource = 'firewall_rule'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Set shared flag for the firewall rule.'),
+ default=argparse.SUPPRESS)
+ _add_common_args(parser)
+ parser.add_argument(
+ '--ip-version',
+ type=int, choices=[4, 6], default=4,
+ help=_('IP version for the firewall rule (default is 4).'))
+
+ def args2body(self, parsed_args):
+ return {self.resource: common_args2body(parsed_args)}
+
+
+class UpdateFirewallRule(neutronv20.UpdateCommand):
+ """Update a given firewall rule."""
+
+ 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(
+ '--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):
+ return {self.resource: common_args2body(parsed_args)}
+
+
+class DeleteFirewallRule(neutronv20.DeleteCommand):
+ """Delete a given firewall rule."""
+
+ resource = 'firewall_rule'
diff --git a/neutronclient/neutron/v2_0/lb/__init__.py b/neutronclient/neutron/v2_0/lb/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/lb/healthmonitor.py b/neutronclient/neutron/v2_0/lb/healthmonitor.py
new file mode 100644
index 000000000..33dfbf0bc
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/healthmonitor.py
@@ -0,0 +1,161 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListHealthMonitor(neutronV20.ListCommand):
+ """List health monitors that belong to a given tenant."""
+
+ resource = 'health_monitor'
+ list_columns = ['id', 'type', 'admin_state_up']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowHealthMonitor(neutronV20.ShowCommand):
+ """Show information of a given health monitor."""
+
+ resource = 'health_monitor'
+ allow_names = False
+
+
+class CreateHealthMonitor(neutronV20.CreateCommand):
+ """Create a health monitor."""
+
+ resource = 'health_monitor'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--expected-codes',
+ help=_('The list of HTTP status codes expected in '
+ 'response from the member to declare it healthy. This '
+ 'attribute can contain one value, '
+ 'or a list of values separated by comma, '
+ 'or a range of values (e.g. "200-299"). If this attribute '
+ 'is not specified, it defaults to "200".'))
+ parser.add_argument(
+ '--http-method',
+ help=_('The HTTP method used for requests by the monitor of type '
+ 'HTTP.'))
+ parser.add_argument(
+ '--url-path',
+ help=_('The HTTP path used in the HTTP request used by the monitor'
+ ' to test a member health. This must be a string '
+ 'beginning with a / (forward slash).'))
+ parser.add_argument(
+ '--delay',
+ required=True,
+ help=_('The time in seconds between sending probes to members.'))
+ parser.add_argument(
+ '--max-retries',
+ required=True,
+ help=_('Number of permissible connection failures before changing '
+ 'the member status to INACTIVE. [1..10]'))
+ parser.add_argument(
+ '--timeout',
+ required=True,
+ help=_('Maximum number of seconds for a monitor to wait for a '
+ 'connection to be established before it times out. The '
+ 'value must be less than the delay value.'))
+ parser.add_argument(
+ '--type',
+ required=True, choices=['PING', 'TCP', 'HTTP', 'HTTPS'],
+ help=_('One of the predefined health monitor types.'))
+
+ def args2body(self, parsed_args):
+ body = {'admin_state_up': parsed_args.admin_state,
+ 'delay': parsed_args.delay,
+ 'max_retries': parsed_args.max_retries,
+ 'timeout': parsed_args.timeout,
+ 'type': parsed_args.type}
+ neutronV20.update_dict(parsed_args, body,
+ ['expected_codes', 'http_method', 'url_path',
+ 'tenant_id'])
+ return {self.resource: body}
+
+
+class UpdateHealthMonitor(neutronV20.UpdateCommand):
+ """Update a given health monitor."""
+
+ resource = 'health_monitor'
+ allow_names = False
+
+
+class DeleteHealthMonitor(neutronV20.DeleteCommand):
+ """Delete a given health monitor."""
+
+ resource = 'health_monitor'
+ allow_names = False
+
+
+class AssociateHealthMonitor(neutronV20.NeutronCommand):
+ """Create a mapping between a health monitor and a pool."""
+
+ resource = 'health_monitor'
+
+ def get_parser(self, prog_name):
+ parser = super(AssociateHealthMonitor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'health_monitor_id', metavar='HEALTH_MONITOR_ID',
+ help=_('Health monitor to associate.'))
+ parser.add_argument(
+ 'pool_id', metavar='POOL',
+ help=_('ID of the pool to be associated with the health monitor.'))
+ return parser
+
+ 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(
+ neutron_client, 'pool', parsed_args.pool_id)
+ neutron_client.associate_health_monitor(pool_id, body)
+ print((_('Associated health monitor '
+ '%s') % parsed_args.health_monitor_id),
+ file=self.app.stdout)
+
+
+class DisassociateHealthMonitor(neutronV20.NeutronCommand):
+ """Remove a mapping from a health monitor to a pool."""
+
+ resource = 'health_monitor'
+
+ def get_parser(self, prog_name):
+ parser = super(DisassociateHealthMonitor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'health_monitor_id', metavar='HEALTH_MONITOR_ID',
+ help=_('Health monitor to disassociate.'))
+ parser.add_argument(
+ 'pool_id', metavar='POOL',
+ help=_('ID of the pool to be disassociated with the health '
+ 'monitor.'))
+ return parser
+
+ 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)
+ neutron_client.disassociate_health_monitor(pool_id,
+ parsed_args
+ .health_monitor_id)
+ print((_('Disassociated health monitor '
+ '%s') % parsed_args.health_monitor_id),
+ file=self.app.stdout)
diff --git a/neutronclient/neutron/v2_0/lb/member.py b/neutronclient/neutron/v2_0/lb/member.py
new file mode 100644
index 000000000..c90840cd1
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/member.py
@@ -0,0 +1,87 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListMember(neutronV20.ListCommand):
+ """List members that belong to a given tenant."""
+
+ resource = 'member'
+ list_columns = [
+ 'id', 'address', 'protocol_port', 'weight', 'admin_state_up', 'status'
+ ]
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowMember(neutronV20.ShowCommand):
+ """Show information of a given member."""
+
+ resource = 'member'
+ allow_names = False
+
+
+class CreateMember(neutronV20.CreateCommand):
+ """Create a member."""
+
+ resource = 'member'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--weight',
+ help=_('Weight of pool member in the pool (default:1, [0..256]).'))
+ parser.add_argument(
+ '--address',
+ required=True,
+ help=_('IP address of the pool member on the pool network.'))
+ parser.add_argument(
+ '--protocol-port',
+ required=True,
+ help=_('Port on which the pool member listens for requests or '
+ 'connections.'))
+ parser.add_argument(
+ 'pool_id', metavar='POOL',
+ help=_('ID or name of the pool this vip belongs to.'))
+
+ def args2body(self, parsed_args):
+ _pool_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'pool', parsed_args.pool_id)
+ body = {'pool_id': _pool_id,
+ 'admin_state_up': parsed_args.admin_state}
+ neutronV20.update_dict(
+ parsed_args,
+ body,
+ ['address', 'protocol_port', 'weight', 'tenant_id']
+ )
+ return {self.resource: body}
+
+
+class UpdateMember(neutronV20.UpdateCommand):
+ """Update a given member."""
+
+ resource = 'member'
+
+
+class DeleteMember(neutronV20.DeleteCommand):
+ """Delete a given member."""
+
+ resource = 'member'
diff --git a/neutronclient/neutron/v2_0/lb/pool.py b/neutronclient/neutron/v2_0/lb/pool.py
new file mode 100644
index 000000000..5684e8e6a
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/pool.py
@@ -0,0 +1,121 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_provider(pool):
+ return pool.get('provider') or 'N/A'
+
+
+class ListPool(neutronV20.ListCommand):
+ """List pools that belong to a given tenant."""
+
+ resource = 'pool'
+ list_columns = ['id', 'name', 'provider', 'lb_method', 'protocol',
+ 'admin_state_up', 'status']
+ _formatters = {'provider': _format_provider}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowPool(neutronV20.ShowCommand):
+ """Show information of a given pool."""
+
+ resource = 'pool'
+
+
+class CreatePool(neutronV20.CreateCommand):
+ """Create a pool."""
+
+ resource = 'pool'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the pool.'))
+ parser.add_argument(
+ '--lb-method',
+ 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(
+ '--name',
+ required=True,
+ help=_('The name of the pool.'))
+ parser.add_argument(
+ '--protocol',
+ required=True,
+ choices=['HTTP', 'HTTPS', 'TCP'],
+ help=_('Protocol for balancing.'))
+ parser.add_argument(
+ '--subnet-id', metavar='SUBNET',
+ required=True,
+ help=_('The subnet on which the members of the pool will be '
+ 'located.'))
+ parser.add_argument(
+ '--provider',
+ help=_('Provider name of the loadbalancer service.'))
+
+ def args2body(self, parsed_args):
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.subnet_id)
+ body = {'admin_state_up': parsed_args.admin_state,
+ 'subnet_id': _subnet_id}
+ neutronV20.update_dict(parsed_args, body,
+ ['description', 'lb_method', 'name',
+ 'protocol', 'tenant_id', 'provider'])
+ return {self.resource: body}
+
+
+class UpdatePool(neutronV20.UpdateCommand):
+ """Update a given pool."""
+
+ resource = 'pool'
+
+
+class DeletePool(neutronV20.DeleteCommand):
+ """Delete a given pool."""
+
+ resource = 'pool'
+
+
+class RetrievePoolStats(neutronV20.ShowCommand):
+ """Retrieve stats for a given pool."""
+
+ resource = 'pool'
+
+ 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)
+ params = {}
+ if parsed_args.fields:
+ params = {'fields': parsed_args.fields}
+
+ data = neutron_client.retrieve_pool_stats(pool_id, **params)
+ self.format_output_data(data)
+ stats = data['stats']
+ if 'stats' in data:
+ return zip(*sorted(stats.items()))
+ else:
+ return None
diff --git a/neutronclient/neutron/v2_0/lb/v2/__init__.py b/neutronclient/neutron/v2_0/lb/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py b/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py
new file mode 100644
index 000000000..34e72c842
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py
@@ -0,0 +1,143 @@
+# Copyright 2013 Mirantis Inc.
+# Copyright 2014 Blue Box Group, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+from neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _add_common_args(parser, is_create=True):
+ parser.add_argument(
+ '--delay',
+ required=is_create,
+ help=_('The time in seconds between sending probes to members.'))
+ parser.add_argument(
+ '--name',
+ help=_('Name of the health monitor.'))
+ parser.add_argument(
+ '--timeout',
+ required=is_create,
+ help=_('Maximum number of seconds for a monitor to wait for a '
+ 'connection to be established before it times out. The '
+ 'value must be less than the delay value.'))
+ parser.add_argument(
+ '--http-method',
+ type=utils.convert_to_uppercase,
+ help=_('The HTTP method used for requests by the monitor of type '
+ 'HTTP.'))
+ parser.add_argument(
+ '--url-path',
+ help=_('The HTTP path used in the HTTP request used by the monitor '
+ 'to test a member health. This must be a string '
+ 'beginning with a / (forward slash).'))
+ parser.add_argument(
+ '--max-retries',
+ required=is_create,
+ help=_('Number of permissible connection failures before changing '
+ 'the member status to INACTIVE. [1..10].'))
+ parser.add_argument(
+ '--expected-codes',
+ help=_('The list of HTTP status codes expected in '
+ 'response from the member to declare it healthy. This '
+ 'attribute can contain one value, '
+ 'or a list of values separated by comma, '
+ 'or a range of values (e.g. "200-299"). If this attribute '
+ 'is not specified, it defaults to "200".'))
+
+
+def _parse_common_args(body, parsed_args):
+ neutronV20.update_dict(parsed_args, body,
+ ['expected_codes', 'http_method', 'url_path',
+ 'timeout', 'name', 'delay', 'max_retries'])
+
+
+class ListHealthMonitor(neutronV20.ListCommand):
+ """LBaaS v2 List healthmonitors that belong to a given tenant."""
+
+ resource = 'healthmonitor'
+ shadow_resource = 'lbaas_healthmonitor'
+ list_columns = ['id', 'name', 'type', 'admin_state_up']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowHealthMonitor(neutronV20.ShowCommand):
+ """LBaaS v2 Show information of a given healthmonitor."""
+
+ resource = 'healthmonitor'
+ shadow_resource = 'lbaas_healthmonitor'
+
+
+class CreateHealthMonitor(neutronV20.CreateCommand):
+ """LBaaS v2 Create a healthmonitor."""
+
+ resource = 'healthmonitor'
+ shadow_resource = 'lbaas_healthmonitor'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser)
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--type',
+ required=True, choices=['PING', 'TCP', 'HTTP', 'HTTPS'],
+ help=_('One of the predefined health monitor types.'))
+ parser.add_argument(
+ '--pool', required=True,
+ help=_('ID or name of the pool that this healthmonitor will '
+ 'monitor.'))
+
+ def args2body(self, parsed_args):
+ pool_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'pool', parsed_args.pool,
+ cmd_resource='lbaas_pool')
+ body = {'admin_state_up': parsed_args.admin_state,
+ 'type': parsed_args.type,
+ 'pool_id': pool_id}
+ neutronV20.update_dict(parsed_args, body,
+ ['tenant_id'])
+ _parse_common_args(body, parsed_args)
+ return {self.resource: body}
+
+
+class UpdateHealthMonitor(neutronV20.UpdateCommand):
+ """LBaaS v2 Update a given healthmonitor."""
+
+ resource = 'healthmonitor'
+ shadow_resource = 'lbaas_healthmonitor'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser, is_create=False)
+ utils.add_boolean_argument(
+ parser, '--admin-state-up',
+ help=_('Update the administrative state of '
+ 'the health monitor (True meaning "Up").'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ _parse_common_args(body, parsed_args)
+ neutronV20.update_dict(parsed_args, body,
+ ['admin_state_up'])
+ return {self.resource: body}
+
+
+class DeleteHealthMonitor(neutronV20.DeleteCommand):
+ """LBaaS v2 Delete a given healthmonitor."""
+
+ resource = 'healthmonitor'
+ shadow_resource = 'lbaas_healthmonitor'
diff --git a/neutronclient/neutron/v2_0/lb/v2/l7policy.py b/neutronclient/neutron/v2_0/lb/v2/l7policy.py
new file mode 100644
index 000000000..26c331554
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/l7policy.py
@@ -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'
diff --git a/neutronclient/neutron/v2_0/lb/v2/l7rule.py b/neutronclient/neutron/v2_0/lb/v2/l7rule.py
new file mode 100644
index 000000000..3c468cabf
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/l7rule.py
@@ -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'
diff --git a/neutronclient/neutron/v2_0/lb/v2/listener.py b/neutronclient/neutron/v2_0/lb/v2/listener.py
new file mode 100644
index 000000000..481943e6b
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/listener.py
@@ -0,0 +1,169 @@
+# Copyright 2014 Blue Box Group, Inc.
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# 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_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_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')
+
+
+def _add_common_args(parser):
+ parser.add_argument(
+ '--description',
+ help=_('Description of the listener.'))
+ parser.add_argument(
+ '--connection-limit',
+ type=int,
+ help=_('The maximum number of connections per second allowed for '
+ 'the listener. Positive integer or -1 '
+ 'for unlimited (default).'))
+ parser.add_argument(
+ '--default-pool',
+ help=_('Default pool for the listener.'))
+
+
+def _parse_common_args(body, parsed_args, client):
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'description', 'connection_limit'])
+ if parsed_args.default_pool:
+ default_pool_id = _get_pool_id(
+ client, parsed_args.default_pool)
+ body['default_pool_id'] = default_pool_id
+
+
+class ListListener(neutronV20.ListCommand):
+ """LBaaS v2 List listeners that belong to a given tenant."""
+
+ resource = 'listener'
+ list_columns = ['id', 'default_pool_id', 'name', 'protocol',
+ 'protocol_port', 'admin_state_up', 'status']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowListener(neutronV20.ShowCommand):
+ """LBaaS v2 Show information of a given listener."""
+
+ resource = 'listener'
+
+
+class CreateListener(neutronV20.CreateCommand):
+ """LBaaS v2 Create a listener."""
+
+ resource = 'listener'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser)
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--name',
+ 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',
+ help=_('Default TLS container reference'
+ ' to retrieve TLS information.'))
+ parser.add_argument(
+ '--sni-container-refs',
+ dest='sni_container_refs',
+ nargs='+',
+ help=_('List of TLS container references for SNI.'))
+ parser.add_argument(
+ '--loadbalancer',
+ metavar='LOADBALANCER',
+ help=_('ID or name of the load balancer.'))
+ parser.add_argument(
+ '--protocol',
+ required=True,
+ choices=['TCP', 'HTTP', 'HTTPS', 'TERMINATED_HTTPS'],
+ type=utils.convert_to_uppercase,
+ help=_('Protocol for the listener.'))
+ parser.add_argument(
+ '--protocol-port',
+ dest='protocol_port', required=True,
+ metavar='PORT',
+ help=_('Protocol port for the listener.'))
+
+ def args2body(self, parsed_args):
+ if not parsed_args.loadbalancer and not parsed_args.default_pool:
+ message = _('Either --default-pool or --loadbalancer must be '
+ 'specified.')
+ raise exceptions.CommandError(message)
+ body = {
+ 'protocol': parsed_args.protocol,
+ 'protocol_port': parsed_args.protocol_port,
+ 'admin_state_up': parsed_args.admin_state
+ }
+ if parsed_args.loadbalancer:
+ loadbalancer_id = _get_loadbalancer_id(
+ self.get_client(), parsed_args.loadbalancer)
+ body['loadbalancer_id'] = loadbalancer_id
+
+ neutronV20.update_dict(parsed_args, body,
+ ['default_tls_container_ref',
+ 'sni_container_refs', 'tenant_id'])
+ _parse_common_args(body, parsed_args, self.get_client())
+ return {self.resource: body}
+
+
+class UpdateListener(neutronV20.UpdateCommand):
+ """LBaaS v2 Update a given listener."""
+
+ resource = 'listener'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser)
+ parser.add_argument(
+ '--name',
+ help=_('Name of the listener.'))
+ utils.add_boolean_argument(
+ parser, '--admin-state-up', dest='admin_state_up',
+ help=_('Specify the administrative state of the listener. '
+ '(True meaning "Up")'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body,
+ ['admin_state_up'])
+ _parse_common_args(body, parsed_args, self.get_client())
+ return {self.resource: body}
+
+
+class DeleteListener(neutronV20.DeleteCommand):
+ """LBaaS v2 Delete a given listener."""
+
+ resource = 'listener'
diff --git a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py
new file mode 100644
index 000000000..c40809fad
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py
@@ -0,0 +1,178 @@
+# Copyright 2014 Blue Box Group, Inc.
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# 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 oslo_serialization import jsonutils
+
+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(
+ '--description',
+ help=_('Description of the load balancer.'))
+ parser.add_argument(
+ '--name', metavar='NAME',
+ help=_('Name of the load balancer.'))
+
+
+def _parse_common_args(body, parsed_args):
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'description'])
+
+
+class ListLoadBalancer(neutronV20.ListCommand):
+ """LBaaS v2 List loadbalancers that belong to a given tenant."""
+
+ resource = 'loadbalancer'
+ list_columns = ['id', 'name', 'vip_address',
+ 'provisioning_status', 'provider']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowLoadBalancer(neutronV20.ShowCommand):
+ """LBaaS v2 Show information of a given loadbalancer."""
+
+ resource = 'loadbalancer'
+
+
+class CreateLoadBalancer(neutronV20.CreateCommand):
+ """LBaaS v2 Create a loadbalancer."""
+
+ resource = 'loadbalancer'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser)
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--provider',
+ help=_('Provider name of the load balancer service.'))
+ parser.add_argument(
+ '--flavor',
+ help=_('ID or name of the flavor.'))
+ parser.add_argument(
+ '--vip-address',
+ help=_('VIP address for the load balancer.'))
+ parser.add_argument(
+ 'vip_subnet', metavar='VIP_SUBNET',
+ help=_('Load balancer VIP subnet.'))
+
+ def args2body(self, parsed_args):
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.vip_subnet)
+ body = {'vip_subnet_id': _subnet_id,
+ 'admin_state_up': parsed_args.admin_state}
+ if parsed_args.flavor:
+ _flavor_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'flavor', parsed_args.flavor)
+ body['flavor_id'] = _flavor_id
+
+ neutronV20.update_dict(parsed_args, body,
+ ['provider', 'vip_address', 'tenant_id'])
+ _parse_common_args(body, parsed_args)
+ return {self.resource: body}
+
+
+class UpdateLoadBalancer(neutronV20.UpdateCommand):
+ """LBaaS v2 Update a given loadbalancer."""
+
+ resource = 'loadbalancer'
+
+ def add_known_arguments(self, parser):
+ utils.add_boolean_argument(
+ parser, '--admin-state-up',
+ help=_('Update the administrative state of '
+ 'the load balancer (True meaning "Up").'))
+ _add_common_args(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ _parse_common_args(body, parsed_args)
+ neutronV20.update_dict(parsed_args, body,
+ ['admin_state_up'])
+ return {self.resource: body}
+
+
+class DeleteLoadBalancer(neutronV20.DeleteCommand):
+ """LBaaS v2 Delete a given loadbalancer."""
+
+ resource = 'loadbalancer'
+
+
+class RetrieveLoadBalancerStats(neutronV20.ShowCommand):
+ """Retrieve stats for a given loadbalancer."""
+
+ resource = 'loadbalancer'
+
+ def take_action(self, parsed_args):
+ neutron_client = self.get_client()
+ loadbalancer_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'loadbalancer', parsed_args.id)
+ params = {}
+ if parsed_args.fields:
+ params = {'fields': parsed_args.fields}
+
+ data = neutron_client.retrieve_loadbalancer_stats(loadbalancer_id,
+ **params)
+ self.format_output_data(data)
+ stats = data['stats']
+ if 'stats' in data:
+ # To render the output table like:
+ # +--------------------+-------+
+ # | Field | Value |
+ # +--------------------+-------+
+ # | field1 | value1|
+ # | field2 | value2|
+ # | field3 | value3|
+ # | ... | ... |
+ # +--------------------+-------+
+ # it has two columns and the Filed column is alphabetical,
+ # here convert 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))
diff --git a/neutronclient/neutron/v2_0/lb/v2/member.py b/neutronclient/neutron/v2_0/lb/v2/member.py
new file mode 100644
index 000000000..ee3c090a9
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/member.py
@@ -0,0 +1,150 @@
+# Copyright 2013 Mirantis Inc.
+# Copyright 2014 Blue Box Group, Inc.
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# 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_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 LbaasMemberMixin(object):
+
+ def set_extra_attrs(self, parsed_args):
+ self.parent_id = _get_pool_id(self.get_client(), parsed_args.pool)
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'pool', metavar='POOL',
+ help=_('ID or name of the pool that this member belongs to.'))
+
+
+def _add_common_args(parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name of the member.'))
+ parser.add_argument(
+ '--weight',
+ help=_('Weight of the member in the pool (default:1, [0..256]).'))
+
+
+def _parse_common_args(body, parsed_args):
+ neutronV20.update_dict(parsed_args, body, ['weight', 'name'])
+
+
+class ListMember(LbaasMemberMixin, neutronV20.ListCommand):
+ """LBaaS v2 List members that belong to a given pool."""
+
+ resource = 'member'
+ shadow_resource = 'lbaas_member'
+ list_columns = [
+ 'id', 'name', 'address', 'protocol_port', 'weight',
+ 'subnet_id', 'admin_state_up', 'status'
+ ]
+ pagination_support = True
+ sorting_support = True
+
+ 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).take_action(parsed_args)
+
+
+class ShowMember(LbaasMemberMixin, neutronV20.ShowCommand):
+ """LBaaS v2 Show information of a given member."""
+
+ resource = 'member'
+ shadow_resource = 'lbaas_member'
+
+
+class CreateMember(neutronV20.CreateCommand):
+ """LBaaS v2 Create a member."""
+
+ resource = 'member'
+ shadow_resource = 'lbaas_member'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser)
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--subnet',
+ required=True,
+ help=_('Subnet ID or name for the member.'))
+ parser.add_argument(
+ '--address',
+ required=True,
+ help=_('IP address of the pool member in the pool.'))
+ parser.add_argument(
+ '--protocol-port',
+ required=True,
+ help=_('Port on which the pool member listens for requests or '
+ 'connections.'))
+ parser.add_argument(
+ 'pool', metavar='POOL',
+ help=_('ID or name of the pool that this member belongs to.'))
+
+ def args2body(self, parsed_args):
+ self.parent_id = _get_pool_id(self.get_client(), parsed_args.pool)
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.subnet)
+ body = {'subnet_id': _subnet_id,
+ 'admin_state_up': parsed_args.admin_state,
+ 'protocol_port': parsed_args.protocol_port,
+ 'address': parsed_args.address}
+ neutronV20.update_dict(parsed_args, body,
+ ['subnet_id', 'tenant_id'])
+ _parse_common_args(body, parsed_args)
+ return {self.resource: body}
+
+
+class UpdateMember(neutronV20.UpdateCommand):
+ """LBaaS v2 Update a given member."""
+
+ resource = 'member'
+ shadow_resource = 'lbaas_member'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'pool', metavar='POOL',
+ help=_('ID or name of the pool that this member belongs to.'))
+ utils.add_boolean_argument(
+ parser, '--admin-state-up',
+ help=_('Update the administrative state of '
+ 'the member (True meaning "Up").'))
+ _add_common_args(parser)
+
+ def args2body(self, parsed_args):
+ self.parent_id = _get_pool_id(self.get_client(), parsed_args.pool)
+ body = {}
+ if hasattr(parsed_args, "admin_state_up"):
+ body['admin_state_up'] = parsed_args.admin_state_up
+ _parse_common_args(body, parsed_args)
+ return {self.resource: body}
+
+
+class DeleteMember(LbaasMemberMixin, neutronV20.DeleteCommand):
+ """LBaaS v2 Delete a given member."""
+
+ resource = 'member'
+ shadow_resource = 'lbaas_member'
diff --git a/neutronclient/neutron/v2_0/lb/v2/pool.py b/neutronclient/neutron/v2_0/lb/v2/pool.py
new file mode 100644
index 000000000..0af383970
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/v2/pool.py
@@ -0,0 +1,189 @@
+# 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
+# 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_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)
+
+
+def _add_common_args(parser, is_create=True):
+ parser.add_argument(
+ '--description',
+ help=_('Description of the pool.'))
+ parser.add_argument(
+ '--name', help=_('The name of the pool.'))
+ parser.add_argument(
+ '--lb-algorithm',
+ required=is_create,
+ type=utils.convert_to_uppercase,
+ choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'],
+ help=_('The algorithm used to distribute load between the members '
+ 'of the pool.'))
+
+
+def _parse_common_args(parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args,
+ body, ['description', 'lb_algorithm', 'name'])
+ return body
+
+
+class ListPool(neutronV20.ListCommand):
+ """LBaaS v2 List pools that belong to a given tenant."""
+
+ resource = 'pool'
+ shadow_resource = 'lbaas_pool'
+ list_columns = ['id', 'name', 'lb_algorithm', 'protocol',
+ 'admin_state_up']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowPool(neutronV20.ShowCommand):
+ """LBaaS v2 Show information of a given pool."""
+
+ resource = 'pool'
+ shadow_resource = 'lbaas_pool'
+
+ def cleanup_output_data(self, data):
+ if 'members' not in data['pool']:
+ return []
+ member_info = []
+ for member in data['pool']['members']:
+ member_info.append(member['id'])
+ data['pool']['members'] = member_info
+
+
+class CreatePool(neutronV20.CreateCommand):
+ """LBaaS v2 Create a pool."""
+
+ resource = 'pool'
+ shadow_resource = 'lbaas_pool'
+
+ def add_known_arguments(self, parser):
+ _add_common_args(parser)
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ 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(
+ '--protocol',
+ type=utils.convert_to_uppercase,
+ required=True,
+ choices=['HTTP', 'HTTPS', 'TCP'],
+ help=_('Protocol for balancing.'))
+ 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.'))
+
+ def args2body(self, parsed_args):
+ 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)
+ body = _parse_common_args(parsed_args)
+ if parsed_args.listener:
+ listener_id = _get_listener_id(
+ self.get_client(),
+ parsed_args.listener)
+ body['listener_id'] = listener_id
+ if parsed_args.loadbalancer:
+ loadbalancer_id = _get_loadbalancer_id(
+ self.get_client(),
+ parsed_args.loadbalancer)
+ body['loadbalancer_id'] = loadbalancer_id
+ body['admin_state_up'] = parsed_args.admin_state
+ neutronV20.update_dict(parsed_args, body,
+ ['tenant_id', 'protocol',
+ 'session_persistence'])
+ return {self.resource: body}
+
+
+class UpdatePool(neutronV20.UpdateCommand):
+ """LBaaS v2 Update a given pool."""
+
+ resource = 'pool'
+ shadow_resource = 'lbaas_pool'
+
+ def add_known_arguments(self, parser):
+ utils.add_boolean_argument(
+ parser, '--admin-state-up',
+ help=_('Update the administrative state of '
+ 'the pool (True meaning "Up").'))
+ session_group = parser.add_mutually_exclusive_group()
+ session_group.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.'))
+ session_group.add_argument(
+ '--no-session-persistence',
+ action='store_true',
+ help=_('Clear session persistence for the pool.'))
+ _add_common_args(parser, False)
+
+ def args2body(self, parsed_args):
+ body = _parse_common_args(parsed_args)
+ if parsed_args.no_session_persistence:
+ body['session_persistence'] = None
+ elif parsed_args.session_persistence:
+ body['session_persistence'] = parsed_args.session_persistence
+ neutronV20.update_dict(parsed_args, body,
+ ['admin_state_up'])
+ return {self.resource: body}
+
+
+class DeletePool(neutronV20.DeleteCommand):
+ """LBaaS v2 Delete a given pool."""
+
+ resource = 'pool'
+ shadow_resource = 'lbaas_pool'
diff --git a/neutronclient/neutron/v2_0/lb/vip.py b/neutronclient/neutron/v2_0/lb/vip.py
new file mode 100644
index 000000000..e3ac8ee9d
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/vip.py
@@ -0,0 +1,104 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListVip(neutronV20.ListCommand):
+ """List vips that belong to a given tenant."""
+
+ resource = 'vip'
+ list_columns = ['id', 'name', 'algorithm', 'address', 'protocol',
+ 'admin_state_up', 'status']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowVip(neutronV20.ShowCommand):
+ """Show information of a given vip."""
+
+ resource = 'vip'
+
+
+class CreateVip(neutronV20.CreateCommand):
+ """Create a vip."""
+
+ resource = 'vip'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'pool_id', metavar='POOL',
+ help=_('ID or name of the pool to which this vip belongs.'))
+ parser.add_argument(
+ '--address',
+ help=_('IP address of the vip.'))
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--connection-limit',
+ help=_('The maximum number of connections per second allowed for '
+ 'the vip. Valid values: a positive integer or -1 '
+ 'for unlimited (default).'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the vip to be created.'))
+ parser.add_argument(
+ '--name',
+ required=True,
+ help=_('Name of the vip to be created.'))
+ parser.add_argument(
+ '--protocol-port',
+ required=True,
+ help=_('TCP port on which to listen for client traffic that is '
+ 'associated with the vip address.'))
+ parser.add_argument(
+ '--protocol',
+ required=True, choices=['TCP', 'HTTP', 'HTTPS'],
+ help=_('Protocol for balancing.'))
+ parser.add_argument(
+ '--subnet-id', metavar='SUBNET',
+ required=True,
+ help=_('The subnet on which to allocate the vip address.'))
+
+ def args2body(self, parsed_args):
+ _pool_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'pool', parsed_args.pool_id)
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.subnet_id)
+
+ body = {'pool_id': _pool_id,
+ 'admin_state_up': parsed_args.admin_state,
+ 'subnet_id': _subnet_id}
+ neutronV20.update_dict(parsed_args, body,
+ ['address', 'connection_limit', 'description',
+ 'name', 'protocol_port', 'protocol',
+ 'tenant_id'])
+ return {self.resource: body}
+
+
+class UpdateVip(neutronV20.UpdateCommand):
+ """Update a given vip."""
+
+ resource = 'vip'
+
+
+class DeleteVip(neutronV20.DeleteCommand):
+ """Delete a given vip."""
+
+ resource = 'vip'
diff --git a/neutronclient/neutron/v2_0/metering.py b/neutronclient/neutron/v2_0/metering.py
new file mode 100644
index 000000000..8f6d2174e
--- /dev/null
+++ b/neutronclient/neutron/v2_0/metering.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2013 eNovance SAS
+#
+# 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
+
+
+class ListMeteringLabel(neutronv20.ListCommand):
+ """List metering labels that belong to a given tenant."""
+
+ resource = 'metering_label'
+ list_columns = ['id', 'name', 'description', 'shared']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowMeteringLabel(neutronv20.ShowCommand):
+ """Show information of a given metering label."""
+
+ resource = 'metering_label'
+ allow_names = True
+
+
+class CreateMeteringLabel(neutronv20.CreateCommand):
+ """Create a metering label for a given tenant."""
+
+ resource = 'metering_label'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the metering label to be created.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the metering label to be created.'))
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Set the label as shared.'))
+
+ def args2body(self, parsed_args):
+ body = {'name': parsed_args.name}
+ neutronv20.update_dict(parsed_args, body,
+ ['tenant_id', 'description', 'shared'])
+ return {'metering_label': body}
+
+
+class DeleteMeteringLabel(neutronv20.DeleteCommand):
+ """Delete a given metering label."""
+
+ resource = 'metering_label'
+ allow_names = True
+
+
+class ListMeteringLabelRule(neutronv20.ListCommand):
+ """List metering labels that belong to a given label."""
+
+ resource = 'metering_label_rule'
+ list_columns = ['id', 'excluded', 'direction', 'remote_ip_prefix']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowMeteringLabelRule(neutronv20.ShowCommand):
+ """Show information of a given metering label rule."""
+
+ resource = 'metering_label_rule'
+ allow_names = False
+
+
+class CreateMeteringLabelRule(neutronv20.CreateCommand):
+ """Create a metering label rule for a given label."""
+
+ resource = 'metering_label_rule'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'label_id', metavar='LABEL',
+ help=_('ID or name of the label.'))
+ parser.add_argument(
+ 'remote_ip_prefix', metavar='REMOTE_IP_PREFIX',
+ help=_('CIDR to match on.'))
+ parser.add_argument(
+ '--direction',
+ default='ingress', choices=['ingress', 'egress'],
+ type=utils.convert_to_lowercase,
+ help=_('Direction of traffic, default: ingress.'))
+ parser.add_argument(
+ '--excluded',
+ action='store_true',
+ help=_('Exclude this CIDR from the label, default: not excluded.'))
+
+ def args2body(self, parsed_args):
+ neutron_client = self.get_client()
+ label_id = neutronv20.find_resourceid_by_name_or_id(
+ neutron_client, 'metering_label', parsed_args.label_id)
+
+ body = {'metering_label_id': label_id,
+ 'remote_ip_prefix': parsed_args.remote_ip_prefix}
+ neutronv20.update_dict(parsed_args, body,
+ ['direction', 'excluded'])
+ return {'metering_label_rule': body}
+
+
+class DeleteMeteringLabelRule(neutronv20.DeleteCommand):
+ """Delete a given metering label."""
+
+ resource = 'metering_label_rule'
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/network.py b/neutronclient/neutron/v2_0/network.py
new file mode 100644
index 000000000..6c68b623a
--- /dev/null
+++ b/neutronclient/neutron/v2_0/network.py
@@ -0,0 +1,247 @@
+# 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.
+#
+
+import argparse
+
+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 availability_zone
+from neutronclient.neutron.v2_0 import dns
+from neutronclient.neutron.v2_0.qos import policy as qos_policy
+
+
+def _format_subnets(network):
+ try:
+ return '\n'.join([' '.join([s['id'], s.get('cidr', '')])
+ for s in network['subnets']])
+ except (TypeError, KeyError):
+ return ''
+
+
+def args2body_common(body, parsed_args):
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'description'])
+
+
+class ListNetwork(neutronV20.ListCommand):
+ """List networks that belong to a given tenant."""
+
+ # Length of a query filter on subnet id
+ # id=& (with len(uuid)=36)
+ subnet_id_filter_len = 40
+ # Length of a marker in pagination
+ # &marker= (with len(uuid)=36)
+ marker_len = 44
+ resource = 'network'
+ _formatters = {'subnets': _format_subnets, }
+ list_columns = ['id', 'name', 'subnets']
+ 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 "),
+ '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 "),
+ '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 "),
+ '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 "
+ ""),
+ '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()
+ search_opts = {'fields': ['id', 'cidr']}
+ if self.pagination_support:
+ page_size = parsed_args.page_size
+ if page_size:
+ search_opts.update({'limit': page_size})
+ subnet_ids = []
+ for n in data:
+ if 'subnets' in n:
+ subnet_ids.extend(n['subnets'])
+
+ def _get_subnet_list(sub_ids):
+ search_opts['id'] = sub_ids
+ return neutron_client.list_subnets(
+ **search_opts).get('subnets', [])
+
+ try:
+ subnets = _get_subnet_list(subnet_ids)
+ except exceptions.RequestURITooLong as uri_len_exc:
+ # The URI is too long because of too many subnet_id filters
+ # Use the excess attribute of the exception to know how many
+ # subnet_id filters can be inserted into a single request
+ subnet_count = len(subnet_ids)
+ max_size = ((self.subnet_id_filter_len * subnet_count) -
+ uri_len_exc.excess)
+ if self.pagination_support:
+ max_size -= self.marker_len
+ chunk_size = max_size // self.subnet_id_filter_len
+ subnets = []
+ for i in range(0, subnet_count, chunk_size):
+ subnets.extend(
+ _get_subnet_list(subnet_ids[i: i + chunk_size]))
+
+ subnet_dict = dict([(s['id'], s) for s in subnets])
+ for n in data:
+ if 'subnets' in n:
+ n['subnets'] = [(subnet_dict.get(s) or {"id": s})
+ for s in n['subnets']]
+
+
+class ListExternalNetwork(ListNetwork):
+ """List external networks that belong to a given tenant."""
+
+ pagination_support = True
+ sorting_support = True
+
+ def retrieve_list(self, parsed_args):
+ external = '--router:external=True'
+ if external not in self.values_specs:
+ self.values_specs.append('--router:external=True')
+ return super(ListExternalNetwork, self).retrieve_list(parsed_args)
+
+
+class ShowNetwork(neutronV20.ShowCommand):
+ """Show information of a given network."""
+
+ resource = 'network'
+
+
+class CreateNetwork(neutronV20.CreateCommand, qos_policy.CreateQosPolicyMixin):
+ """Create a network for a given tenant."""
+
+ resource = 'network'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--admin_state_down',
+ dest='admin_state', action='store_false',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Set the network as shared.'),
+ default=argparse.SUPPRESS)
+ parser.add_argument(
+ '--provider:network_type',
+ metavar='',
+ help=_('The physical mechanism by which the virtual network'
+ ' is implemented.'))
+ parser.add_argument(
+ '--provider:physical_network',
+ metavar='',
+ help=_('Name of the physical network over which the virtual '
+ 'network is implemented.'))
+ parser.add_argument(
+ '--provider:segmentation_id',
+ metavar='',
+ help=_('VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN '
+ 'networks.'))
+ utils.add_boolean_argument(
+ parser,
+ '--vlan-transparent',
+ default=argparse.SUPPRESS,
+ help=_('Create a VLAN transparent network.'))
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the network to be created.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of network.'))
+
+ 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 = {'admin_state_up': parsed_args.admin_state}
+ args2body_common(body, parsed_args)
+ neutronV20.update_dict(parsed_args, body,
+ ['shared', 'tenant_id',
+ 'vlan_transparent',
+ 'provider:network_type',
+ 'provider:physical_network',
+ 'provider:segmentation_id',
+ 'description'])
+ 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}
+
+
+class DeleteNetwork(neutronV20.DeleteCommand):
+ """Delete a given network."""
+
+ resource = 'network'
+
+
+class UpdateNetwork(neutronV20.UpdateCommand, qos_policy.UpdateQosPolicyMixin):
+ """Update network's information."""
+
+ resource = 'network'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name of the network.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of this network.'))
+ self.add_arguments_qos_policy(parser)
+ dns.add_dns_argument_update(parser, self.resource, 'domain')
+
+ def args2body(self, parsed_args):
+ body = {}
+ args2body_common(body, parsed_args)
+ self.args2body_qos_policy(parsed_args, body)
+ dns.args2body_dns_update(parsed_args, body, 'domain')
+ return {'network': body}
diff --git a/neutronclient/neutron/v2_0/network_ip_availability.py b/neutronclient/neutron/v2_0/network_ip_availability.py
new file mode 100644
index 000000000..ba7af8f80
--- /dev/null
+++ b/neutronclient/neutron/v2_0/network_ip_availability.py
@@ -0,0 +1,72 @@
+# 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
+
+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(resource.items()))
+ else:
+ return None
diff --git a/neutronclient/neutron/v2_0/port.py b/neutronclient/neutron/v2_0/port.py
new file mode 100644
index 000000000..3fe73544b
--- /dev/null
+++ b/neutronclient/neutron/v2_0/port.py
@@ -0,0 +1,341 @@
+# 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.
+#
+
+import argparse
+
+from oslo_serialization import jsonutils
+
+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
+
+
+def _format_fixed_ips(port):
+ try:
+ return '\n'.join([jsonutils.dumps(ip) for ip in port['fixed_ips']])
+ except (TypeError, KeyError):
+ return ''
+
+
+def _add_updatable_args(parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name of this port.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of this port.'))
+ 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=,ip_address=. '
+ 'You can repeat this option.'))
+ parser.add_argument(
+ '--fixed_ip',
+ action='append',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--device-id',
+ help=_('Device ID of this port.'))
+ parser.add_argument(
+ '--device_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--device-owner',
+ help=_('Device owner of this port.'))
+ parser.add_argument(
+ '--device_owner',
+ help=argparse.SUPPRESS)
+
+
+def _updatable_args2body(parsed_args, body, client):
+ neutronV20.update_dict(parsed_args, body,
+ ['device_id', 'device_owner', 'name',
+ 'description'])
+ ips = []
+ if parsed_args.fixed_ip:
+ for ip_spec in parsed_args.fixed_ip:
+ 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_spec['subnet_id'] = _subnet_id
+ ips.append(ip_spec)
+ if ips:
+ body['fixed_ips'] = ips
+
+
+class ListPort(neutronV20.ListCommand):
+ """List ports that belong to a given tenant."""
+
+ resource = 'port'
+ _formatters = {'fixed_ips': _format_fixed_ips, }
+ list_columns = ['id', 'name', 'mac_address', 'fixed_ips']
+ pagination_support = True
+ sorting_support = True
+
+
+class ListRouterPort(neutronV20.ListCommand):
+ """List ports that belong to a given tenant, with specified router."""
+
+ resource = 'port'
+ _formatters = {'fixed_ips': _format_fixed_ips, }
+ list_columns = ['id', 'name', 'mac_address', 'fixed_ips']
+ pagination_support = True
+ sorting_support = True
+
+ def get_parser(self, prog_name):
+ parser = super(ListRouterPort, self).get_parser(prog_name)
+ parser.add_argument(
+ 'id', metavar='ROUTER',
+ help=_('ID or name of the router to look up.'))
+ return parser
+
+ 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).take_action(parsed_args)
+
+
+class ShowPort(neutronV20.ShowCommand):
+ """Show information of a given port."""
+
+ resource = 'port'
+
+
+class UpdatePortSecGroupMixin(object):
+ def add_arguments_secgroup(self, parser):
+ group_sg = parser.add_mutually_exclusive_group()
+ group_sg.add_argument(
+ '--security-group', metavar='SECURITY_GROUP',
+ default=[], action='append', dest='security_groups',
+ help=_('Security group associated with the port. You can '
+ 'repeat this option.'))
+ group_sg.add_argument(
+ '--no-security-groups',
+ action='store_true',
+ help=_('Associate no security groups with the port.'))
+
+ def _resolv_sgid(self, secgroup):
+ return neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'security_group', secgroup)
+
+ def args2body_secgroup(self, parsed_args, port):
+ if parsed_args.security_groups:
+ port['security_groups'] = [self._resolv_sgid(sg) for sg
+ in parsed_args.security_groups]
+ elif parsed_args.no_security_groups:
+ port['security_groups'] = []
+
+
+class UpdateExtraDhcpOptMixin(object):
+ def add_arguments_extradhcpopt(self, parser):
+ group_sg = parser.add_mutually_exclusive_group()
+ group_sg.add_argument(
+ '--extra-dhcp-opt',
+ 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=,opt_value=,'
+ 'ip_version={4,6}. You can repeat this option.'))
+
+ def args2body_extradhcpopt(self, parsed_args, port):
+ ops = []
+ if parsed_args.extra_dhcp_opts:
+ # the extra_dhcp_opt params (opt_name & opt_value)
+ # must come in pairs, if there is a parm error
+ # both must be thrown out.
+ opt_ele = {}
+ edo_err_msg = _("Invalid --extra-dhcp-opt option, can only be: "
+ "opt_name=,opt_value=,"
+ "ip_version={4,6}. "
+ "You can repeat this option.")
+ for opt in parsed_args.extra_dhcp_opts:
+ 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':
+ opt_ele['opt_value'] = None
+ ops.append(opt_ele)
+ opt_ele = {}
+ else:
+ raise exceptions.CommandError(edo_err_msg)
+
+ if ops:
+ port['extra_dhcp_opts'] = ops
+
+
+class UpdatePortAllowedAddressPair(object):
+ """Update Port for allowed_address_pairs"""
+
+ def add_arguments_allowedaddresspairs(self, parser):
+ group_aap = parser.add_mutually_exclusive_group()
+ group_aap.add_argument(
+ '--allowed-address-pair',
+ metavar='ip_address=IP_ADDR|CIDR[,mac_address=MAC_ADDR]',
+ default=[],
+ action='append',
+ dest='allowed_address_pairs',
+ type=utils.str2dict_type(
+ required_keys=['ip_address'],
+ optional_keys=['mac_address']),
+ help=_('Allowed address pair associated with the port. '
+ '"ip_address" parameter is required. IP address or '
+ 'CIDR can be specified for "ip_address". '
+ '"mac_address" parameter is optional. '
+ 'You can repeat this option.'))
+ group_aap.add_argument(
+ '--no-allowed-address-pairs',
+ action='store_true',
+ help=_('Associate no allowed address pairs with the port.'))
+
+ def args2body_allowedaddresspairs(self, parsed_args, port):
+ if parsed_args.allowed_address_pairs:
+ port['allowed_address_pairs'] = parsed_args.allowed_address_pairs
+ elif parsed_args.no_allowed_address_pairs:
+ port['allowed_address_pairs'] = []
+
+
+class CreatePort(neutronV20.CreateCommand, UpdatePortSecGroupMixin,
+ UpdateExtraDhcpOptMixin, qos_policy.CreateQosPolicyMixin,
+ UpdatePortAllowedAddressPair):
+ """Create a port for a given tenant."""
+
+ resource = 'port'
+
+ def add_known_arguments(self, parser):
+ _add_updatable_args(parser)
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--admin_state_down',
+ dest='admin_state', action='store_false',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--mac-address',
+ help=_('MAC address of this port.'))
+ parser.add_argument(
+ '--mac_address',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--vnic-type',
+ metavar='',
+ choices=['direct', 'direct-physical', 'macvtap',
+ 'normal', 'baremetal', 'smart-nic'],
+ type=utils.convert_to_lowercase,
+ help=_('VNIC type for this port.'))
+ parser.add_argument(
+ '--vnic_type',
+ choices=['direct', 'direct-physical', 'macvtap',
+ 'normal', 'baremetal', 'smart-nic'],
+ type=utils.convert_to_lowercase,
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--binding-profile',
+ help=_('Custom data to be passed as binding:profile.'))
+ parser.add_argument(
+ '--binding_profile',
+ help=argparse.SUPPRESS)
+ self.add_arguments_secgroup(parser)
+ self.add_arguments_extradhcpopt(parser)
+ self.add_arguments_qos_policy(parser)
+ self.add_arguments_allowedaddresspairs(parser)
+
+ parser.add_argument(
+ 'network_id', metavar='NETWORK',
+ help=_('ID or name of the network this port belongs to.'))
+ dns.add_dns_argument_create(parser, self.resource, 'name')
+
+ def args2body(self, parsed_args):
+ client = self.get_client()
+ _network_id = neutronV20.find_resourceid_by_name_or_id(
+ client, 'network', parsed_args.network_id)
+ body = {'admin_state_up': parsed_args.admin_state,
+ 'network_id': _network_id, }
+ _updatable_args2body(parsed_args, body, client)
+ neutronV20.update_dict(parsed_args, body,
+ ['mac_address', 'tenant_id'])
+ if parsed_args.vnic_type:
+ body['binding:vnic_type'] = parsed_args.vnic_type
+ if parsed_args.binding_profile:
+ body['binding:profile'] = jsonutils.loads(
+ parsed_args.binding_profile)
+
+ self.args2body_secgroup(parsed_args, body)
+ 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}
+
+
+class DeletePort(neutronV20.DeleteCommand):
+ """Delete a given port."""
+
+ resource = 'port'
+
+
+class UpdatePort(neutronV20.UpdateCommand, UpdatePortSecGroupMixin,
+ UpdateExtraDhcpOptMixin, qos_policy.UpdateQosPolicyMixin,
+ UpdatePortAllowedAddressPair):
+ """Update port's information."""
+
+ resource = 'port'
+
+ def add_known_arguments(self, parser):
+ _add_updatable_args(parser)
+ parser.add_argument(
+ '--admin-state-up',
+ choices=['True', 'False'],
+ help=_('Set admin state up for the port.'))
+ parser.add_argument(
+ '--admin_state_up',
+ choices=['True', 'False'],
+ help=argparse.SUPPRESS)
+ self.add_arguments_secgroup(parser)
+ 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 = {}
+ client = self.get_client()
+ _updatable_args2body(parsed_args, body, client)
+ if parsed_args.admin_state_up:
+ body['admin_state_up'] = parsed_args.admin_state_up
+
+ self.args2body_secgroup(parsed_args, body)
+ 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}
diff --git a/neutronclient/neutron/v2_0/purge.py b/neutronclient/neutron/v2_0/purge.py
new file mode 100644
index 000000000..332a20b32
--- /dev/null
+++ b/neutronclient/neutron/v2_0/purge.py
@@ -0,0 +1,150 @@
+# 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):
+ """Delete all resources that belong to a given tenant."""
+
+ 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':
+ router_interface_owners = ['network:router_interface',
+ 'network:router_interface_distributed']
+ if resource.get('device_owner', '') in router_interface_owners:
+ 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))
diff --git a/neutronclient/neutron/v2_0/qos/__init__.py b/neutronclient/neutron/v2_0/qos/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py b/neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py
new file mode 100644
index 000000000..27e7b626a
--- /dev/null
+++ b/neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py
@@ -0,0 +1,101 @@
+# Copyright 2015 Huawei Technologies India Pvt Ltd, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.neutron import v2_0 as neutronv20
+from neutronclient.neutron.v2_0.qos import rule as qos_rule
+
+
+BANDWIDTH_LIMIT_RULE_RESOURCE = 'bandwidth_limit_rule'
+
+
+def add_bandwidth_limit_arguments(parser):
+ parser.add_argument(
+ '--max-kbps',
+ help=_('Maximum bandwidth in kbps.'))
+ parser.add_argument(
+ '--max-burst-kbps',
+ help=_('Maximum burst bandwidth in kbps.'))
+
+
+def update_bandwidth_limit_args2body(parsed_args, body):
+ max_kbps = parsed_args.max_kbps
+ max_burst_kbps = parsed_args.max_burst_kbps
+ if not (max_kbps or max_burst_kbps):
+ raise exceptions.CommandError(_("Must provide max-kbps"
+ " or max-burst-kbps option."))
+ neutronv20.update_dict(parsed_args, body,
+ ['max_kbps', 'max_burst_kbps', 'tenant_id'])
+
+
+class CreateQoSBandwidthLimitRule(qos_rule.QosRuleMixin,
+ neutronv20.CreateCommand):
+ """Create a qos bandwidth limit rule."""
+
+ resource = BANDWIDTH_LIMIT_RULE_RESOURCE
+
+ def add_known_arguments(self, parser):
+ super(CreateQoSBandwidthLimitRule, self).add_known_arguments(parser)
+ add_bandwidth_limit_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_bandwidth_limit_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class ListQoSBandwidthLimitRules(qos_rule.QosRuleMixin,
+ neutronv20.ListCommand):
+ """List all qos bandwidth limit rules belonging to the specified policy."""
+
+ resource = BANDWIDTH_LIMIT_RULE_RESOURCE
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowQoSBandwidthLimitRule(qos_rule.QosRuleMixin, neutronv20.ShowCommand):
+ """Show information about the given qos bandwidth limit rule."""
+
+ resource = BANDWIDTH_LIMIT_RULE_RESOURCE
+ allow_names = False
+
+
+class UpdateQoSBandwidthLimitRule(qos_rule.QosRuleMixin,
+ neutronv20.UpdateCommand):
+ """Update the given qos bandwidth limit rule."""
+
+ resource = BANDWIDTH_LIMIT_RULE_RESOURCE
+ allow_names = False
+
+ def add_known_arguments(self, parser):
+ super(UpdateQoSBandwidthLimitRule, self).add_known_arguments(parser)
+ add_bandwidth_limit_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_bandwidth_limit_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class DeleteQoSBandwidthLimitRule(qos_rule.QosRuleMixin,
+ neutronv20.DeleteCommand):
+ """Delete a given qos bandwidth limit rule."""
+
+ resource = BANDWIDTH_LIMIT_RULE_RESOURCE
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/qos/dscp_marking_rule.py b/neutronclient/neutron/v2_0/qos/dscp_marking_rule.py
new file mode 100644
index 000000000..963c481a7
--- /dev/null
+++ b/neutronclient/neutron/v2_0/qos/dscp_marking_rule.py
@@ -0,0 +1,112 @@
+# Copyright 2016 Comcast, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.neutron import v2_0 as neutronv20
+from neutronclient.neutron.v2_0.qos import rule as qos_rule
+
+
+DSCP_MARKING_RESOURCE = 'dscp_marking_rule'
+# DSCP DETAILS
+# 0 - none | 8 - cs1 | 10 - af11 | 12 - af12 | 14 - af13 |
+# 16 - cs2 | 18 - af21 | 20 - af22 | 22 - af23 | 24 - cs3 |
+# 26 - af31 | 28 - af32 | 30 - af33 | 32 - cs4 | 34 - af41 |
+# 36 - af42 | 38 - af43 | 40 - cs5 | 46 - ef | 48 - cs6 |
+# 56 - cs7
+
+DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
+ 34, 36, 38, 40, 46, 48, 56]
+
+
+def add_dscp_marking_arguments(parser):
+ parser.add_argument(
+ '--dscp-mark',
+ required=True,
+ type=str,
+ help=_('DSCP mark: value can be 0, even numbers from 8-56, \
+ excluding 42, 44, 50, 52, and 54.'))
+
+
+def update_dscp_args2body(parsed_args, body):
+ dscp_mark = parsed_args.dscp_mark
+ if int(dscp_mark) not in DSCP_VALID_MARKS:
+ raise exceptions.CommandError(_("DSCP mark: %s not supported. "
+ "Please note value can either be 0 "
+ "or any even number from 8-56 "
+ "excluding 42, 44, 50, 52 and "
+ "54.") % dscp_mark)
+ neutronv20.update_dict(parsed_args, body,
+ ['dscp_mark'])
+
+
+class CreateQoSDscpMarkingRule(qos_rule.QosRuleMixin,
+ neutronv20.CreateCommand):
+ """Create a QoS DSCP marking rule."""
+
+ resource = DSCP_MARKING_RESOURCE
+
+ def add_known_arguments(self, parser):
+ super(CreateQoSDscpMarkingRule, self).add_known_arguments(parser)
+ add_dscp_marking_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_dscp_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class ListQoSDscpMarkingRules(qos_rule.QosRuleMixin,
+ neutronv20.ListCommand):
+ """List all QoS DSCP marking rules belonging to the specified policy."""
+
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+ resource = DSCP_MARKING_RESOURCE
+
+
+class ShowQoSDscpMarkingRule(qos_rule.QosRuleMixin,
+ neutronv20.ShowCommand):
+ """Show information about the given qos dscp marking rule."""
+
+ resource = DSCP_MARKING_RESOURCE
+ allow_names = False
+
+
+class UpdateQoSDscpMarkingRule(qos_rule.QosRuleMixin,
+ neutronv20.UpdateCommand):
+ """Update the given QoS DSCP marking rule."""
+
+ allow_names = False
+ resource = DSCP_MARKING_RESOURCE
+
+ def add_known_arguments(self, parser):
+ super(UpdateQoSDscpMarkingRule, self).add_known_arguments(parser)
+ add_dscp_marking_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_dscp_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class DeleteQoSDscpMarkingRule(qos_rule.QosRuleMixin,
+ neutronv20.DeleteCommand):
+ """Delete a given qos dscp marking rule."""
+
+ allow_names = False
+ resource = DSCP_MARKING_RESOURCE
diff --git a/neutronclient/neutron/v2_0/qos/minimum_bandwidth_rule.py b/neutronclient/neutron/v2_0/qos/minimum_bandwidth_rule.py
new file mode 100644
index 000000000..0dda0fb3d
--- /dev/null
+++ b/neutronclient/neutron/v2_0/qos/minimum_bandwidth_rule.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2016 Intel Corporation.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronv20
+from neutronclient.neutron.v2_0.qos import rule as qos_rule
+
+
+MINIMUM_BANDWIDTH_RULE_RESOURCE = 'minimum_bandwidth_rule'
+
+
+def add_minimum_bandwidth_arguments(parser):
+ parser.add_argument(
+ '--min-kbps',
+ required=True,
+ type=str,
+ help=_('QoS minimum bandwidth assurance, expressed in kilobits '
+ 'per second.'))
+ # NOTE(ralonsoh): the only direction implemented is "egress". Please,
+ # refer to the spec (https://review.opendev.org/#/c/316082/).
+ parser.add_argument(
+ '--direction',
+ # NOTE(ihrachys): though server picks the default for us (egress), it's
+ # better to require the argument to make the UI more explicit and the
+ # intentions more clear in the future when we add other values for the
+ # attribute on server side.
+ required=True,
+ type=utils.convert_to_lowercase,
+ choices=['egress'],
+ help=_('Traffic direction.'))
+
+
+def update_minimum_bandwidth_args2body(parsed_args, body):
+ neutronv20.update_dict(parsed_args, body, ['min_kbps', 'direction'])
+
+
+class CreateQoSMinimumBandwidthRule(qos_rule.QosRuleMixin,
+ neutronv20.CreateCommand):
+ """Create a qos minimum bandwidth rule."""
+
+ resource = MINIMUM_BANDWIDTH_RULE_RESOURCE
+
+ def add_known_arguments(self, parser):
+ super(CreateQoSMinimumBandwidthRule, self).add_known_arguments(
+ parser)
+ add_minimum_bandwidth_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_minimum_bandwidth_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class ListQoSMinimumBandwidthRules(qos_rule.QosRuleMixin,
+ neutronv20.ListCommand):
+ """List all qos minimum bandwidth rules belonging to the specified policy.
+
+ """
+
+ resource = MINIMUM_BANDWIDTH_RULE_RESOURCE
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowQoSMinimumBandwidthRule(qos_rule.QosRuleMixin,
+ neutronv20.ShowCommand):
+ """Show information about the given qos minimum bandwidth rule."""
+
+ resource = MINIMUM_BANDWIDTH_RULE_RESOURCE
+ allow_names = False
+
+
+class UpdateQoSMinimumBandwidthRule(qos_rule.QosRuleMixin,
+ neutronv20.UpdateCommand):
+ """Update the given qos minimum bandwidth rule."""
+
+ resource = MINIMUM_BANDWIDTH_RULE_RESOURCE
+ allow_names = False
+
+ def add_known_arguments(self, parser):
+ super(UpdateQoSMinimumBandwidthRule, self).add_known_arguments(
+ parser)
+ add_minimum_bandwidth_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_minimum_bandwidth_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class DeleteQoSMinimumBandwidthRule(qos_rule.QosRuleMixin,
+ neutronv20.DeleteCommand):
+ """Delete a given qos minimum bandwidth rule."""
+
+ resource = MINIMUM_BANDWIDTH_RULE_RESOURCE
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/qos/policy.py b/neutronclient/neutron/v2_0/qos/policy.py
new file mode 100644
index 000000000..3dc982f31
--- /dev/null
+++ b/neutronclient/neutron/v2_0/qos/policy.py
@@ -0,0 +1,160 @@
+# Copyright 2015 Huawei Technologies India Pvt Ltd, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import os
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronv20
+
+
+def get_qos_policy_id(client, policy_id_or_name):
+ _policy_id = neutronv20.find_resourceid_by_name_or_id(
+ client, 'policy', policy_id_or_name, cmd_resource='qos_policy')
+ return _policy_id
+
+
+class CreateQosPolicyMixin(object):
+ def add_arguments_qos_policy(self, parser):
+ qos_policy_args = parser.add_mutually_exclusive_group()
+ qos_policy_args.add_argument(
+ '--qos-policy',
+ help=_('ID or name of the QoS policy that should'
+ 'be attached to the resource.'))
+ return qos_policy_args
+
+ def args2body_qos_policy(self, parsed_args, resource):
+ if parsed_args.qos_policy:
+ _policy_id = get_qos_policy_id(self.get_client(),
+ parsed_args.qos_policy)
+ resource['qos_policy_id'] = _policy_id
+
+
+class UpdateQosPolicyMixin(CreateQosPolicyMixin):
+ def add_arguments_qos_policy(self, parser):
+ qos_policy_args = (super(UpdateQosPolicyMixin, self).
+ add_arguments_qos_policy(parser))
+ qos_policy_args.add_argument(
+ '--no-qos-policy',
+ action='store_true',
+ help=_('Detach QoS policy from the resource.'))
+ return qos_policy_args
+
+ def args2body_qos_policy(self, parsed_args, resource):
+ super(UpdateQosPolicyMixin, self).args2body_qos_policy(parsed_args,
+ resource)
+ if parsed_args.no_qos_policy:
+ resource['qos_policy_id'] = None
+
+
+class ListQoSPolicy(neutronv20.ListCommand):
+ """List QoS policies that belong to a given tenant connection."""
+
+ resource = 'policy'
+ shadow_resource = 'qos_policy'
+ list_columns = ['id', 'name']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowQoSPolicy(neutronv20.ShowCommand):
+ """Show information of a given qos policy."""
+
+ resource = 'policy'
+ shadow_resource = 'qos_policy'
+
+ def format_output_data(self, data):
+ rules = []
+ for rule in data['policy'].get('rules', []):
+ rules.append("%s (type: %s)" % (rule['id'], rule['type']))
+ data['policy']['rules'] = os.linesep.join(rules)
+
+ super(ShowQoSPolicy, self).format_output_data(data)
+
+
+class CreateQoSPolicy(neutronv20.CreateCommand):
+ """Create a qos policy."""
+
+ resource = 'policy'
+ shadow_resource = 'qos_policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the QoS policy to be created.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the QoS policy to be created.'))
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Accessible by other tenants. '
+ 'Set shared to True (default is False).'))
+
+ def args2body(self, parsed_args):
+ body = {'name': parsed_args.name}
+ if parsed_args.description:
+ body['description'] = parsed_args.description
+ if parsed_args.shared:
+ body['shared'] = parsed_args.shared
+ if parsed_args.tenant_id:
+ body['tenant_id'] = parsed_args.tenant_id
+ return {self.resource: body}
+
+
+class UpdateQoSPolicy(neutronv20.UpdateCommand):
+ """Update a given qos policy."""
+
+ resource = 'policy'
+ shadow_resource = 'qos_policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name of the QoS policy.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the QoS policy.'))
+ shared_group = parser.add_mutually_exclusive_group()
+ shared_group.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Accessible by other tenants. '
+ 'Set shared to True (default is False).'))
+ shared_group.add_argument(
+ '--no-shared',
+ action='store_true',
+ help=_('Not accessible by other tenants. '
+ 'Set shared to False.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ if parsed_args.name:
+ body['name'] = parsed_args.name
+ if parsed_args.description:
+ body['description'] = parsed_args.description
+ if parsed_args.shared:
+ body['shared'] = True
+ if parsed_args.no_shared:
+ body['shared'] = False
+
+ return {self.resource: body}
+
+
+class DeleteQoSPolicy(neutronv20.DeleteCommand):
+ """Delete a given qos policy."""
+
+ resource = 'policy'
+ shadow_resource = 'qos_policy'
diff --git a/neutronclient/neutron/v2_0/qos/rule.py b/neutronclient/neutron/v2_0/qos/rule.py
new file mode 100644
index 000000000..a2fc9a472
--- /dev/null
+++ b/neutronclient/neutron/v2_0/qos/rule.py
@@ -0,0 +1,63 @@
+# Copyright 2015 Huawei Technologies India Pvt Ltd, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronv20
+from neutronclient.neutron.v2_0.qos import policy as qos_policy
+
+
+def add_policy_argument(parser):
+ parser.add_argument(
+ 'policy', metavar='QOS_POLICY',
+ help=_('ID or name of the QoS policy.'))
+
+
+def add_rule_argument(parser):
+ parser.add_argument(
+ 'rule', metavar='QOS_RULE',
+ help=_('ID of the QoS rule.'))
+
+
+def update_policy_args2body(parsed_args, body):
+ neutronv20.update_dict(parsed_args, body, ['policy'])
+
+
+def update_rule_args2body(parsed_args, body):
+ neutronv20.update_dict(parsed_args, body, ['rule'])
+
+
+class QosRuleMixin(object):
+ def add_known_arguments(self, parser):
+ add_policy_argument(parser)
+
+ def set_extra_attrs(self, parsed_args):
+ self.parent_id = qos_policy.get_qos_policy_id(self.get_client(),
+ parsed_args.policy)
+
+ def args2body(self, parsed_args):
+ body = {}
+ update_policy_args2body(parsed_args, body)
+ return {'qos_rule': body}
+
+
+class ListQoSRuleTypes(neutronv20.ListCommand):
+ """List available qos rule types."""
+
+ resource = 'rule_type'
+ shadow_resource = 'qos_rule_type'
+ pagination_support = True
+ sorting_support = True
diff --git a/neutronclient/neutron/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py
new file mode 100644
index 000000000..a55e29f6d
--- /dev/null
+++ b/neutronclient/neutron/v2_0/quota.py
@@ -0,0 +1,256 @@
+# 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.
+#
+
+import abc
+import argparse
+
+from cliff import lister
+from cliff import show
+from oslo_serialization import jsonutils
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def get_tenant_id(args, client):
+ return (args.pos_tenant_id or args.tenant_id or
+ client.get_quotas_tenant()['tenant']['tenant_id'])
+
+
+class DeleteQuota(neutronV20.NeutronCommand):
+ """Delete defined quotas of a given tenant."""
+
+ resource = 'quota'
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteQuota, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help=_('The owner tenant ID.'))
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ 'pos_tenant_id',
+ help=argparse.SUPPRESS, nargs='?')
+ return parser
+
+ 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,
+ "delete_%s" % self.resource)
+ obj_deleter(tenant_id)
+ print((_('Deleted %(resource)s: %(tenant_id)s')
+ % {'tenant_id': tenant_id,
+ 'resource': self.resource}),
+ file=self.app.stdout)
+ return
+
+
+class ListQuota(neutronV20.NeutronCommand, lister.Lister):
+ """List quotas of all tenants who have non-default quota values."""
+
+ resource = 'quota'
+
+ def get_parser(self, prog_name):
+ parser = super(ListQuota, self).get_parser(prog_name)
+ return parser
+
+ def take_action(self, parsed_args):
+ neutron_client = self.get_client()
+ search_opts = {}
+ self.log.debug('search options: %s', search_opts)
+ obj_lister = getattr(neutron_client,
+ "list_%ss" % self.resource)
+ data = obj_lister(**search_opts)
+ info = []
+ collection = self.resource + "s"
+ if collection in data:
+ info = data[collection]
+ _columns = len(info) > 0 and sorted(info[0].keys()) or []
+ return (_columns, (utils.get_item_properties(s, _columns)
+ for s in info))
+
+
+class ShowQuotaBase(neutronV20.NeutronCommand, show.ShowOne):
+ """Base class to show quotas of a given tenant."""
+
+ resource = "quota"
+
+ @abc.abstractmethod
+ def retrieve_data(self, tenant_id, neutron_client):
+ """Retrieve data using neutron client for the given tenant."""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowQuotaBase, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help=_('The owner tenant ID.'))
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ # allow people to do neutron quota-show .
+ # we use a different name for this 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):
+ neutron_client = self.get_client()
+ tenant_id = get_tenant_id(parsed_args, neutron_client)
+ data = self.retrieve_data(tenant_id, neutron_client)
+ if self.resource in data:
+ return zip(*sorted(data[self.resource].items()))
+ return
+
+
+class ShowQuota(ShowQuotaBase):
+ """Show quotas for a given tenant."""
+
+ def retrieve_data(self, tenant_id, neutron_client):
+ return neutron_client.show_quota(tenant_id)
+
+
+class ShowQuotaDefault(ShowQuotaBase):
+ """Show default quotas for a given tenant."""
+
+ def retrieve_data(self, tenant_id, neutron_client):
+ return neutron_client.show_quota_default(tenant_id)
+
+
+class UpdateQuota(neutronV20.NeutronCommand, show.ShowOne):
+ """Update a given tenant's quotas."""
+
+ resource = 'quota'
+
+ def get_parser(self, prog_name):
+ parser = super(UpdateQuota, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help=_('The owner tenant ID.'))
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--network', metavar='networks',
+ help=_('The limit of networks.'))
+ parser.add_argument(
+ '--subnet', metavar='subnets',
+ help=_('The limit of subnets.'))
+ parser.add_argument(
+ '--port', metavar='ports',
+ help=_('The limit of ports.'))
+ parser.add_argument(
+ '--router', metavar='routers',
+ help=_('The limit of routers.'))
+ parser.add_argument(
+ '--floatingip', metavar='floatingips',
+ help=_('The limit of floating IPs.'))
+ parser.add_argument(
+ '--security-group', metavar='security_groups',
+ help=_('The limit of security groups.'))
+ parser.add_argument(
+ '--security-group-rule', metavar='security_group_rules',
+ help=_('The limit of security groups rules.'))
+ parser.add_argument(
+ '--vip', metavar='vips',
+ help=_('The limit of vips.'))
+ parser.add_argument(
+ '--pool', metavar='pools',
+ help=_('The limit of pools.'))
+ parser.add_argument(
+ '--member', metavar='members',
+ help=_('The limit of pool members.'))
+ parser.add_argument(
+ '--health-monitor', metavar='health_monitors',
+ dest='healthmonitor',
+ help=_('The limit of health monitors.'))
+ parser.add_argument(
+ '--loadbalancer', metavar='loadbalancers',
+ help=_('The limit of load balancers.'))
+ parser.add_argument(
+ '--listener', metavar='listeners',
+ help=_('The limit of listeners.'))
+ parser.add_argument(
+ '--rbac-policy', metavar='rbac_policies',
+ help=_('The limit of RBAC policies.'))
+ parser.add_argument(
+ 'pos_tenant_id',
+ help=argparse.SUPPRESS, nargs='?')
+
+ return parser
+
+ def _validate_int(self, name, value):
+ try:
+ return_value = int(value)
+ except Exception:
+ message = (_('Quota limit for %(name)s must be an integer') %
+ {'name': name})
+ raise exceptions.CommandError(message=message)
+ return return_value
+
+ def args2body(self, parsed_args):
+ quota = {}
+ for resource in ('network', 'subnet', 'port', 'router', 'floatingip',
+ 'security_group', 'security_group_rule',
+ 'vip', 'pool', 'member', 'healthmonitor',
+ 'loadbalancer', 'listener', 'rbac_policy'):
+ if getattr(parsed_args, resource):
+ quota[resource] = self._validate_int(
+ resource,
+ getattr(parsed_args, resource))
+ if not quota:
+ raise exceptions.CommandError(
+ message=_('Must specify a valid resource with new quota '
+ 'value'))
+ return {self.resource: quota}
+
+ 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,
+ self.values_specs)
+ body = self.args2body(parsed_args)
+ if self.resource in body:
+ body[self.resource].update(_extra_values)
+ else:
+ body[self.resource] = _extra_values
+ obj_updator = getattr(neutron_client,
+ "update_%s" % self.resource)
+ tenant_id = get_tenant_id(parsed_args, neutron_client)
+ data = obj_updator(tenant_id, body)
+ 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 is None:
+ data[self.resource][k] = ''
+ return zip(*sorted(data[self.resource].items()))
+ else:
+ return
diff --git a/neutronclient/neutron/v2_0/rbac.py b/neutronclient/neutron/v2_0/rbac.py
new file mode 100644
index 000000000..8b356d3ec
--- /dev/null
+++ b/neutronclient/neutron/v2_0/rbac.py
@@ -0,0 +1,118 @@
+# Copyright 2015 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 utils
+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_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_type', 'object_id']
+ pagination_support = True
+ sorting_support = True
+ allow_names = False
+
+
+class ShowRBACPolicy(neutronV20.ShowCommand):
+ """Show information of a given RBAC policy."""
+
+ resource = 'rbac_policy'
+ allow_names = False
+
+
+class CreateRBACPolicy(neutronV20.CreateCommand):
+ """Create a RBAC policy for a given tenant."""
+
+ resource = 'rbac_policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name',
+ metavar='RBAC_OBJECT',
+ help=_('ID or name of the RBAC object.'))
+ parser.add_argument(
+ '--type', choices=RBAC_OBJECTS.keys(),
+ required=True,
+ type=utils.convert_to_lowercase,
+ help=_('Type of the object that RBAC policy affects.'))
+ parser.add_argument(
+ '--target-tenant',
+ default='*',
+ help=_('ID of the tenant to which the RBAC '
+ 'policy will be enforced.'))
+ parser.add_argument(
+ '--action', choices=['access_as_external', 'access_as_shared'],
+ type=utils.convert_to_lowercase,
+ required=True,
+ help=_('Action for the RBAC policy.'))
+
+ def args2body(self, parsed_args):
+ neutron_client = self.get_client()
+ _object_id, _object_type = get_rbac_obj_params(neutron_client,
+ parsed_args.type,
+ parsed_args.name)
+ body = {
+ 'object_id': _object_id,
+ 'object_type': _object_type,
+ 'target_tenant': parsed_args.target_tenant,
+ 'action': parsed_args.action,
+ }
+ return {self.resource: body}
+
+
+class UpdateRBACPolicy(neutronV20.UpdateCommand):
+ """Update RBAC policy for given tenant."""
+
+ resource = 'rbac_policy'
+ allow_names = False
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--target-tenant',
+ help=_('ID of the tenant to which the RBAC '
+ 'policy will be enforced.'))
+
+ def args2body(self, parsed_args):
+ body = {'target_tenant': parsed_args.target_tenant}
+ return {self.resource: body}
+
+
+class DeleteRBACPolicy(neutronV20.DeleteCommand):
+ """Delete a RBAC policy."""
+
+ resource = 'rbac_policy'
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py
new file mode 100644
index 000000000..1f78cb85c
--- /dev/null
+++ b/neutronclient/neutron/v2_0/router.py
@@ -0,0 +1,297 @@
+# 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.
+#
+
+import argparse
+
+from oslo_serialization import jsonutils
+
+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 availability_zone
+
+
+def _format_external_gateway_info(router):
+ try:
+ return jsonutils.dumps(router['external_gateway_info'])
+ except (TypeError, KeyError):
+ return ''
+
+
+class ListRouter(neutronV20.ListCommand):
+ """List routers that belong to a given tenant."""
+
+ resource = 'router'
+ _formatters = {'external_gateway_info': _format_external_gateway_info, }
+ list_columns = ['id', 'name', 'external_gateway_info', 'distributed', 'ha']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowRouter(neutronV20.ShowCommand):
+ """Show information of a given router."""
+
+ resource = 'router'
+
+
+class CreateRouter(neutronV20.CreateCommand):
+ """Create a router for a given tenant."""
+
+ resource = 'router'
+ _formatters = {'external_gateway_info': _format_external_gateway_info, }
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--admin_state_down',
+ dest='admin_state', action='store_false',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the router to be created.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of router.'))
+ parser.add_argument(
+ '--flavor',
+ help=_('ID or name of flavor.'))
+ utils.add_boolean_argument(
+ parser, '--distributed', dest='distributed',
+ help=_('Create a distributed router.'))
+ utils.add_boolean_argument(
+ parser, '--ha', dest='ha',
+ help=_('Create a highly available router.'))
+
+ availability_zone.add_az_hint_argument(parser, self.resource)
+
+ def args2body(self, parsed_args):
+ body = {'admin_state_up': parsed_args.admin_state}
+ if parsed_args.flavor:
+ _flavor_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'flavor', parsed_args.flavor)
+ body['flavor_id'] = _flavor_id
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'tenant_id', 'distributed', 'ha',
+ 'description'])
+ availability_zone.args2body_az_hint(parsed_args, body)
+ return {self.resource: body}
+
+
+class DeleteRouter(neutronV20.DeleteCommand):
+ """Delete a given router."""
+
+ resource = 'router'
+
+
+class UpdateRouter(neutronV20.UpdateCommand):
+ """Update router's information."""
+
+ resource = 'router'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Updated name of the router.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of router.'))
+ utils.add_boolean_argument(
+ parser, '--admin-state-up', dest='admin_state',
+ help=_('Specify the administrative state of the router '
+ '(True means "Up").'))
+ utils.add_boolean_argument(
+ parser, '--admin_state_up', dest='admin_state',
+ help=argparse.SUPPRESS)
+ utils.add_boolean_argument(
+ parser, '--distributed', dest='distributed',
+ help=_('True means this router should operate in '
+ 'distributed mode.'))
+ 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_type(required_keys=['destination', 'nexthop']),
+ help=_('Route to associate with the router.'
+ ' You can repeat this option.'))
+ routes_group.add_argument(
+ '--no-routes',
+ action='store_true',
+ help=_('Remove routes associated with the router.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ if hasattr(parsed_args, 'admin_state'):
+ body['admin_state_up'] = parsed_args.admin_state
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'distributed', 'description'])
+ if parsed_args.no_routes:
+ body['routes'] = None
+ elif parsed_args.routes:
+ body['routes'] = parsed_args.routes
+ return {self.resource: body}
+
+
+class RouterInterfaceCommand(neutronV20.NeutronCommand):
+ """Based class to Add/Remove router interface."""
+
+ resource = 'router'
+
+ def call_api(self, neutron_client, router_id, body):
+ raise NotImplementedError()
+
+ def success_message(self, router_id, portinfo):
+ raise NotImplementedError()
+
+ def get_parser(self, prog_name):
+ parser = super(RouterInterfaceCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router', metavar='ROUTER',
+ help=_('ID or name of the router.'))
+ parser.add_argument(
+ 'interface', metavar='INTERFACE',
+ help=_('The format is "SUBNET|subnet=SUBNET|port=PORT". '
+ 'Either a subnet or port must be specified. '
+ 'Both ID and name are accepted as SUBNET or PORT. '
+ 'Note that "subnet=" can be omitted when specifying a '
+ 'subnet.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ neutron_client = self.get_client()
+
+ if '=' in parsed_args.interface:
+ resource, value = parsed_args.interface.split('=', 1)
+ if resource not in ['subnet', 'port']:
+ exceptions.CommandError(_('You must specify either subnet or '
+ 'port for INTERFACE parameter.'))
+ else:
+ resource = 'subnet'
+ value = parsed_args.interface
+
+ _router_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, self.resource, parsed_args.router)
+
+ _interface_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, resource, value)
+ body = {'%s_id' % resource: _interface_id}
+
+ portinfo = self.call_api(neutron_client, _router_id, body)
+ print(self.success_message(parsed_args.router, portinfo),
+ file=self.app.stdout)
+
+
+class AddInterfaceRouter(RouterInterfaceCommand):
+ """Add an internal network interface to a router."""
+
+ def call_api(self, neutron_client, router_id, body):
+ return neutron_client.add_interface_router(router_id, body)
+
+ def success_message(self, router_id, portinfo):
+ return (_('Added interface %(port)s to router %(router)s.') %
+ {'router': router_id, 'port': portinfo['port_id']})
+
+
+class RemoveInterfaceRouter(RouterInterfaceCommand):
+ """Remove an internal network interface from a router."""
+
+ def call_api(self, neutron_client, router_id, body):
+ return neutron_client.remove_interface_router(router_id, body)
+
+ def success_message(self, router_id, portinfo):
+ # portinfo is not used since it is None for router-interface-delete.
+ return _('Removed interface from router %s.') % router_id
+
+
+class SetGatewayRouter(neutronV20.NeutronCommand):
+ """Set the external network gateway for a router."""
+
+ resource = 'router'
+
+ def get_parser(self, prog_name):
+ parser = super(SetGatewayRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router', metavar='ROUTER',
+ help=_('ID or name of the router.'))
+ parser.add_argument(
+ 'external_network', metavar='EXTERNAL-NETWORK',
+ help=_('ID or name of the external network for the gateway.'))
+ parser.add_argument(
+ '--enable-snat', action='store_true',
+ help=_('Enable source NAT on the router gateway.'))
+ parser.add_argument(
+ '--disable-snat', action='store_true',
+ help=_('Disable source NAT on the router gateway.'))
+ 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 on external network: '
+ 'subnet_id=,ip_address=. '
+ '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 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)
+ _ext_net_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'network', parsed_args.external_network)
+ router_dict = {'network_id': _ext_net_id}
+ if parsed_args.enable_snat:
+ router_dict['enable_snat'] = True
+ if parsed_args.disable_snat:
+ router_dict['enable_snat'] = False
+ if parsed_args.fixed_ip:
+ ips = []
+ for ip_spec in parsed_args.fixed_ip:
+ 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_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,
+ file=self.app.stdout)
+
+
+class RemoveGatewayRouter(neutronV20.NeutronCommand):
+ """Remove an external network gateway from a router."""
+
+ resource = 'router'
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveGatewayRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router', metavar='ROUTER',
+ help=_('ID or name of the router.'))
+ return parser
+
+ 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)
+ neutron_client.remove_gateway_router(_router_id)
+ print(_('Removed gateway from router %s') % parsed_args.router,
+ file=self.app.stdout)
diff --git a/neutronclient/neutron/v2_0/securitygroup.py b/neutronclient/neutron/v2_0/securitygroup.py
new file mode 100644
index 000000000..3c0bafa0e
--- /dev/null
+++ b/neutronclient/neutron/v2_0/securitygroup.py
@@ -0,0 +1,379 @@
+# 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.
+#
+
+import argparse
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _get_remote(rule):
+ if rule['remote_ip_prefix']:
+ remote = '%s (CIDR)' % rule['remote_ip_prefix']
+ elif rule['remote_group_id']:
+ remote = '%s (group)' % rule['remote_group_id']
+ else:
+ remote = None
+ return remote
+
+
+def _get_protocol_port(rule):
+ proto = rule['protocol']
+ port_min = rule['port_range_min']
+ port_max = rule['port_range_max']
+ if proto in ('tcp', 'udp'):
+ if (port_min and port_min == port_max):
+ protocol_port = '%s/%s' % (port_min, proto)
+ elif port_min:
+ protocol_port = '%s-%s/%s' % (port_min, port_max, proto)
+ else:
+ protocol_port = proto
+ elif proto == 'icmp':
+ icmp_opts = []
+ if port_min is not None:
+ icmp_opts.append('type:%s' % port_min)
+ if port_max is not None:
+ icmp_opts.append('code:%s' % port_max)
+
+ if icmp_opts:
+ protocol_port = 'icmp (%s)' % ', '.join(icmp_opts)
+ else:
+ protocol_port = 'icmp'
+ elif proto is not None:
+ # port_range_min/max are not recognized for protocol
+ # other than TCP, UDP and ICMP.
+ protocol_port = proto
+ else:
+ protocol_port = None
+
+ return protocol_port
+
+
+def _format_sg_rule(rule):
+ formatted = []
+ for field in ['direction',
+ 'ethertype',
+ ('protocol_port', _get_protocol_port),
+ 'remote_ip_prefix',
+ 'remote_group_id']:
+ if isinstance(field, tuple):
+ field, get_method = field
+ data = get_method(rule)
+ else:
+ data = rule[field]
+ if not data:
+ continue
+ if field in ('remote_ip_prefix', 'remote_group_id'):
+ data = '%s: %s' % (field, data)
+ formatted.append(data)
+ return ', '.join(formatted)
+
+
+def _format_sg_rules(secgroup):
+ try:
+ return '\n'.join(sorted([_format_sg_rule(rule) for rule
+ in secgroup['security_group_rules']]))
+ except Exception:
+ return ''
+
+
+def generate_default_ethertype(protocol):
+ if protocol == 'icmpv6':
+ return 'IPv6'
+ return 'IPv4'
+
+
+class ListSecurityGroup(neutronV20.ListCommand):
+ """List security groups that belong to a given tenant."""
+
+ resource = 'security_group'
+ list_columns = ['id', 'name', 'security_group_rules']
+ _formatters = {'security_group_rules': _format_sg_rules}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowSecurityGroup(neutronV20.ShowCommand):
+ """Show information of a given security group."""
+
+ resource = 'security_group'
+ allow_names = True
+ json_indent = 5
+
+
+class CreateSecurityGroup(neutronV20.CreateCommand):
+ """Create a security group."""
+
+ resource = 'security_group'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the security group to be created.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the security group to be created.'))
+
+ def args2body(self, parsed_args):
+ body = {'name': parsed_args.name}
+ neutronV20.update_dict(parsed_args, body,
+ ['description', 'tenant_id'])
+ return {'security_group': body}
+
+
+class DeleteSecurityGroup(neutronV20.DeleteCommand):
+ """Delete a given security group."""
+
+ resource = 'security_group'
+ allow_names = True
+
+
+class UpdateSecurityGroup(neutronV20.UpdateCommand):
+ """Update a given security group."""
+
+ resource = 'security_group'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Updated name of the security group.'))
+ parser.add_argument(
+ '--description',
+ help=_('Updated description of the security group.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'description'])
+ return {'security_group': body}
+
+
+class ListSecurityGroupRule(neutronV20.ListCommand):
+ """List security group rules that belong to a given tenant."""
+
+ resource = 'security_group_rule'
+ list_columns = ['id', 'security_group_id', 'direction',
+ 'ethertype', 'port/protocol', 'remote']
+ # replace_rules: key is an attribute name in Neutron API and
+ # corresponding value is a display name shown by CLI.
+ replace_rules = {'security_group_id': 'security_group',
+ 'remote_group_id': 'remote_group'}
+ digest_fields = {
+ # The entry 'protocol/port' is left deliberately for backwards
+ # compatibility.
+ 'remote': {
+ 'method': _get_remote,
+ 'depends_on': ['remote_ip_prefix', 'remote_group_id']},
+ 'port/protocol': {
+ 'method': _get_protocol_port,
+ 'depends_on': ['protocol', 'port_range_min', 'port_range_max']},
+ 'protocol/port': {
+ 'method': _get_protocol_port,
+ 'depends_on': ['protocol', 'port_range_min', 'port_range_max']}}
+ pagination_support = True
+ sorting_support = True
+
+ def get_parser(self, prog_name):
+ parser = super(ListSecurityGroupRule, self).get_parser(prog_name)
+ parser.add_argument(
+ '--no-nameconv', action='store_true',
+ help=_('Do not convert security group ID to its name.'))
+ return parser
+
+ @staticmethod
+ def replace_columns(cols, rules, reverse=False):
+ if reverse:
+ rules = dict((rules[k], k) for k in rules.keys())
+ return [rules.get(col, col) for col in cols]
+
+ def get_required_fields(self, fields):
+ fields = self.replace_columns(fields, self.replace_rules, reverse=True)
+ for field, digest_fields in self.digest_fields.items():
+ if field in fields:
+ fields += digest_fields['depends_on']
+ fields.remove(field)
+ return fields
+
+ def retrieve_list(self, parsed_args):
+ parsed_args.fields = self.get_required_fields(parsed_args.fields)
+ return super(ListSecurityGroupRule, self).retrieve_list(parsed_args)
+
+ def _get_sg_name_dict(self, data, page_size, no_nameconv):
+ """Get names of security groups referred in the retrieved rules.
+
+ :return: a dict from secgroup ID to secgroup name
+ """
+ if no_nameconv:
+ return {}
+ neutron_client = self.get_client()
+ search_opts = {'fields': ['id', 'name']}
+ if self.pagination_support:
+ if page_size:
+ search_opts.update({'limit': page_size})
+ sec_group_ids = set()
+ for rule in data:
+ for key in self.replace_rules:
+ if rule.get(key):
+ sec_group_ids.add(rule[key])
+ sec_group_ids = list(sec_group_ids)
+
+ def _get_sec_group_list(sec_group_ids):
+ search_opts['id'] = sec_group_ids
+ return neutron_client.list_security_groups(
+ **search_opts).get('security_groups', [])
+
+ try:
+ secgroups = _get_sec_group_list(sec_group_ids)
+ except exceptions.RequestURITooLong as uri_len_exc:
+ # Length of a query filter on security group rule id
+ # id=& (with len(uuid)=36)
+ sec_group_id_filter_len = 40
+ # The URI is too long because of too many sec_group_id filters
+ # Use the excess attribute of the exception to know how many
+ # sec_group_id filters can be inserted into a single request
+ sec_group_count = len(sec_group_ids)
+ max_size = ((sec_group_id_filter_len * sec_group_count) -
+ uri_len_exc.excess)
+ chunk_size = max_size // sec_group_id_filter_len
+ secgroups = []
+ for i in range(0, sec_group_count, chunk_size):
+ secgroups.extend(
+ _get_sec_group_list(sec_group_ids[i: i + chunk_size]))
+
+ return dict([(sg['id'], sg['name'])
+ for sg in secgroups if sg['name']])
+
+ @staticmethod
+ def _has_fields(rule, required_fields):
+ return all([key in rule for key in required_fields])
+
+ def extend_list(self, data, parsed_args):
+ sg_dict = self._get_sg_name_dict(data, parsed_args.page_size,
+ parsed_args.no_nameconv)
+ for rule in data:
+ # Replace security group UUID with its name.
+ for key in self.replace_rules:
+ if key in rule:
+ rule[key] = sg_dict.get(rule[key], rule[key])
+ for field, digest_rule in self.digest_fields.items():
+ if self._has_fields(rule, digest_rule['depends_on']):
+ rule[field] = digest_rule['method'](rule) or 'any'
+
+ def setup_columns(self, info, parsed_args):
+ # Translate the specified columns from the command line
+ # into field names used in "info".
+ parsed_args.columns = self.replace_columns(parsed_args.columns,
+ self.replace_rules,
+ reverse=True)
+ # NOTE(amotoki): 2nd element of the tuple returned by setup_columns()
+ # is a generator, so if you need to create a look using the generator
+ # object, you need to recreate a generator to show a list expectedly.
+ info = super(ListSecurityGroupRule, self).setup_columns(info,
+ parsed_args)
+ cols = info[0]
+ if not parsed_args.no_nameconv:
+ # Replace column names in the header line (in info[0])
+ cols = self.replace_columns(info[0], self.replace_rules)
+ parsed_args.columns = cols
+ return (cols, info[1])
+
+
+class ShowSecurityGroupRule(neutronV20.ShowCommand):
+ """Show information of a given security group rule."""
+
+ resource = 'security_group_rule'
+ allow_names = False
+
+
+class CreateSecurityGroupRule(neutronV20.CreateCommand):
+ """Create a security group rule."""
+
+ resource = 'security_group_rule'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--description',
+ help=_('Description of security group rule.'))
+ parser.add_argument(
+ 'security_group_id', metavar='SECURITY_GROUP',
+ help=_('ID or name of the security group to '
+ 'which the rule is added.'))
+ parser.add_argument(
+ '--direction',
+ type=utils.convert_to_lowercase,
+ default='ingress', choices=['ingress', 'egress'],
+ help=_('Direction of traffic: ingress/egress.'))
+ parser.add_argument(
+ '--ethertype',
+ help=_('IPv4/IPv6'))
+ parser.add_argument(
+ '--protocol',
+ type=utils.convert_to_lowercase,
+ help=_('Protocol of packet. Allowed values are '
+ '[icmp, icmpv6, tcp, udp] and '
+ 'integer representations [0-255].'))
+ parser.add_argument(
+ '--port-range-min',
+ help=_('Starting port range. For ICMP it is type.'))
+ parser.add_argument(
+ '--port_range_min',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--port-range-max',
+ help=_('Ending port range. For ICMP it is code.'))
+ parser.add_argument(
+ '--port_range_max',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--remote-ip-prefix',
+ help=_('CIDR to match on.'))
+ parser.add_argument(
+ '--remote_ip_prefix',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--remote-group-id', metavar='REMOTE_GROUP',
+ help=_('ID or name of the remote security group '
+ 'to which the rule is applied.'))
+ parser.add_argument(
+ '--remote_group_id',
+ help=argparse.SUPPRESS)
+
+ def args2body(self, parsed_args):
+ _security_group_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'security_group', parsed_args.security_group_id)
+ body = {'security_group_id': _security_group_id,
+ 'direction': parsed_args.direction,
+ 'ethertype': parsed_args.ethertype or
+ generate_default_ethertype(parsed_args.protocol)}
+ neutronV20.update_dict(parsed_args, body,
+ ['protocol', 'port_range_min', 'port_range_max',
+ 'remote_ip_prefix', 'tenant_id',
+ 'description'])
+ if parsed_args.remote_group_id:
+ _remote_group_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'security_group',
+ parsed_args.remote_group_id)
+ body['remote_group_id'] = _remote_group_id
+ return {'security_group_rule': body}
+
+
+class DeleteSecurityGroupRule(neutronV20.DeleteCommand):
+ """Delete a given security group rule."""
+
+ resource = 'security_group_rule'
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/servicetype.py b/neutronclient/neutron/v2_0/servicetype.py
new file mode 100644
index 000000000..ec8454f94
--- /dev/null
+++ b/neutronclient/neutron/v2_0/servicetype.py
@@ -0,0 +1,27 @@
+# Copyright 2013 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 neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListServiceProvider(neutronV20.ListCommand):
+ """List service providers."""
+
+ resource = 'service_provider'
+ list_columns = ['service_type', 'name', 'default']
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
diff --git a/neutronclient/neutron/v2_0/subnet.py b/neutronclient/neutron/v2_0/subnet.py
new file mode 100644
index 000000000..633826422
--- /dev/null
+++ b/neutronclient/neutron/v2_0/subnet.py
@@ -0,0 +1,276 @@
+# 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.
+#
+
+import argparse
+
+from oslo_serialization import jsonutils
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_allocation_pools(subnet):
+ try:
+ return '\n'.join([jsonutils.dumps(pool) for pool in
+ subnet['allocation_pools']])
+ except (TypeError, KeyError):
+ return ''
+
+
+def _format_dns_nameservers(subnet):
+ try:
+ return '\n'.join([jsonutils.dumps(server) for server in
+ subnet['dns_nameservers']])
+ except (TypeError, KeyError):
+ return ''
+
+
+def _format_host_routes(subnet):
+ try:
+ return '\n'.join([jsonutils.dumps(route) for route in
+ subnet['host_routes']])
+ except (TypeError, KeyError):
+ return ''
+
+
+def add_updatable_arguments(parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name of this subnet.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of this subnet.'))
+ gateway_sg = parser.add_mutually_exclusive_group()
+ gateway_sg.add_argument(
+ '--gateway', metavar='GATEWAY_IP',
+ help=_('Gateway IP of this subnet.'))
+ gateway_sg.add_argument(
+ '--no-gateway',
+ action='store_true',
+ help=_('Do not configure a gateway for this subnet.'))
+ parser.add_argument(
+ '--allocation-pool', metavar='start=IP_ADDR,end=IP_ADDR',
+ 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_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_type(required_keys=['destination', 'nexthop']),
+ help=_('Additional route (This option can be repeated).'))
+ parser.add_argument(
+ '--dns-nameserver', metavar='DNS_NAMESERVER',
+ action='append', dest='dns_nameservers',
+ help=_('DNS name server for this subnet '
+ '(This option can be repeated).'))
+ parser.add_argument(
+ '--disable-dhcp',
+ action='store_true',
+ help=_('Disable DHCP for this subnet.'))
+ parser.add_argument(
+ '--enable-dhcp',
+ action='store_true',
+ help=_('Enable DHCP for this subnet.'))
+ # NOTE(ihrachys): yes, that's awful, but should be left as-is for
+ # backwards compatibility for versions <=2.3.4 that passed the
+ # boolean values through to the server without any argument
+ # validation.
+ parser.add_argument(
+ '--enable-dhcp=True',
+ action='store_true',
+ dest='enable_dhcp',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--enable-dhcp=False',
+ action='store_true',
+ dest='disable_dhcp',
+ help=argparse.SUPPRESS)
+
+
+def updatable_args2body(parsed_args, body, for_create=True, ip_version=None):
+ if parsed_args.disable_dhcp and parsed_args.enable_dhcp:
+ raise exceptions.CommandError(_(
+ "You cannot enable and disable DHCP at the same time."))
+
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'allocation_pools',
+ 'host_routes', 'dns_nameservers',
+ 'description'])
+ if parsed_args.no_gateway:
+ body['gateway_ip'] = None
+ elif parsed_args.gateway:
+ body['gateway_ip'] = parsed_args.gateway
+ if parsed_args.disable_dhcp:
+ body['enable_dhcp'] = False
+ if parsed_args.enable_dhcp:
+ body['enable_dhcp'] = True
+ if for_create and parsed_args.ipv6_ra_mode:
+ if ip_version == 4:
+ raise exceptions.CommandError(_("--ipv6-ra-mode is invalid "
+ "when --ip-version is 4"))
+ body['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode
+ if for_create and parsed_args.ipv6_address_mode:
+ if ip_version == 4:
+ raise exceptions.CommandError(_("--ipv6-address-mode is "
+ "invalid when --ip-version "
+ "is 4"))
+ body['ipv6_address_mode'] = parsed_args.ipv6_address_mode
+
+
+class ListSubnet(neutronV20.ListCommand):
+ """List subnets that belong to a given tenant."""
+
+ resource = 'subnet'
+ _formatters = {'allocation_pools': _format_allocation_pools,
+ 'dns_nameservers': _format_dns_nameservers,
+ 'host_routes': _format_host_routes, }
+ list_columns = ['id', 'name', 'cidr', 'allocation_pools']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowSubnet(neutronV20.ShowCommand):
+ """Show information of a given subnet."""
+
+ resource = 'subnet'
+
+
+class CreateSubnet(neutronV20.CreateCommand):
+ """Create a subnet for a given tenant."""
+
+ resource = 'subnet'
+
+ def add_known_arguments(self, parser):
+ add_updatable_arguments(parser)
+ parser.add_argument(
+ '--ip-version',
+ type=int,
+ default=4, choices=[4, 6],
+ help=_('IP version to use, default is 4. '
+ 'Note that when subnetpool is specified, '
+ 'IP version is determined from the subnetpool '
+ 'and this option is ignored.'))
+ parser.add_argument(
+ '--ip_version',
+ type=int,
+ choices=[4, 6],
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ 'network_id', metavar='NETWORK',
+ help=_('Network ID or name this subnet belongs to.'))
+ parser.add_argument(
+ 'cidr', nargs='?', metavar='CIDR',
+ help=_('CIDR of subnet to create.'))
+ parser.add_argument(
+ '--ipv6-ra-mode',
+ type=utils.convert_to_lowercase,
+ choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'],
+ help=_('IPv6 RA (Router Advertisement) mode.'))
+ parser.add_argument(
+ '--ipv6-address-mode',
+ type=utils.convert_to_lowercase,
+ choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'],
+ help=_('IPv6 address mode.'))
+ parser.add_argument(
+ '--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.'))
+ parser.add_argument(
+ '--segment', metavar='SEGMENT',
+ help=_('ID of segment with which this subnet will be associated.'))
+
+ def args2body(self, parsed_args):
+ _network_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'network', parsed_args.network_id)
+ body = {'network_id': _network_id}
+
+ 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.segment:
+ body['segment_id'] = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'segment', parsed_args.segment)
+ if parsed_args.subnetpool:
+ if parsed_args.subnetpool == 'None':
+ _subnetpool_id = None
+ else:
+ _subnetpool = neutronV20.find_resource_by_name_or_id(
+ self.get_client(), 'subnetpool', parsed_args.subnetpool)
+ _subnetpool_id = _subnetpool['id']
+ # Now that we have the pool_id - let's just have a check on the
+ # ip version used in the pool
+ ip_version = _subnetpool['ip_version']
+ body['subnetpool_id'] = _subnetpool_id
+
+ # IP version needs to be set as IP version can be
+ # determined by subnetpool.
+ body['ip_version'] = ip_version
+
+ if parsed_args.cidr:
+ # With subnetpool, cidr is now optional for creating subnet.
+ cidr = parsed_args.cidr
+ body['cidr'] = cidr
+ unusable_cidr = '/32' if ip_version == 4 else '/128'
+ if cidr.endswith(unusable_cidr):
+ self.log.warning(_("An IPv%(ip)d subnet with a %(cidr)s CIDR "
+ "will have only one usable IP address so "
+ "the device attached to it will not have "
+ "any IP connectivity."),
+ {"ip": ip_version,
+ "cidr": unusable_cidr})
+
+ updatable_args2body(parsed_args, body, ip_version=ip_version)
+ if parsed_args.tenant_id:
+ body['tenant_id'] = parsed_args.tenant_id
+
+ return {'subnet': body}
+
+
+class DeleteSubnet(neutronV20.DeleteCommand):
+ """Delete a given subnet."""
+
+ resource = 'subnet'
+
+
+class UpdateSubnet(neutronV20.UpdateCommand):
+ """Update subnet's information."""
+
+ resource = 'subnet'
+
+ def add_known_arguments(self, parser):
+ add_updatable_arguments(parser)
+
+ def args2body(self, parsed_args):
+ body = {}
+ updatable_args2body(parsed_args, body, for_create=False)
+ return {'subnet': body}
diff --git a/neutronclient/neutron/v2_0/subnetpool.py b/neutronclient/neutron/v2_0/subnetpool.py
new file mode 100644
index 000000000..fdefc8a47
--- /dev/null
+++ b/neutronclient/neutron/v2_0/subnetpool.py
@@ -0,0 +1,154 @@
+# Copyright 2015 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 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, for_create=False):
+ parser.add_argument(
+ '--description',
+ help=_('Description of subnetpool.'))
+ parser.add_argument(
+ '--min-prefixlen', type=int,
+ help=_('Subnetpool minimum prefix length.'))
+ parser.add_argument(
+ '--max-prefixlen', type=int,
+ help=_('Subnetpool maximum prefix length.'))
+ parser.add_argument(
+ '--default-prefixlen', type=int,
+ help=_('Subnetpool default prefix length.'))
+ parser.add_argument(
+ '--pool-prefix',
+ action='append', dest='prefixes',
+ required=for_create,
+ 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):
+ neutronV20.update_dict(parsed_args, body,
+ ['name', 'prefixes', 'default_prefixlen',
+ 'min_prefixlen', 'max_prefixlen', 'is_default',
+ 'description'])
+
+
+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', 'is_default']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowSubnetPool(neutronV20.ShowCommand):
+ """Show information of a given subnetpool."""
+
+ resource = 'subnetpool'
+
+
+class CreateSubnetPool(neutronV20.CreateCommand):
+ """Create a subnetpool for a given tenant."""
+
+ resource = 'subnetpool'
+
+ def add_known_arguments(self, parser):
+ add_updatable_arguments(parser, for_create=True)
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help=_('Set the subnetpool as shared.'))
+ parser.add_argument(
+ 'name',
+ metavar='NAME',
+ help=_('Name of the subnetpool to be created.'))
+ parser.add_argument(
+ '--address-scope',
+ metavar='ADDRSCOPE',
+ help=_('ID or name of the address scope with which the subnetpool '
+ 'is associated. Prefixes must be unique across address '
+ 'scopes.'))
+
+ def args2body(self, parsed_args):
+ body = {'prefixes': parsed_args.prefixes}
+ updatable_args2body(parsed_args, body)
+ neutronV20.update_dict(parsed_args, body, ['tenant_id'])
+ if parsed_args.shared:
+ body['shared'] = True
+
+ # Parse and update for "address-scope" option
+ if parsed_args.address_scope:
+ _addrscope_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'address_scope',
+ parsed_args.address_scope)
+ body['address_scope_id'] = _addrscope_id
+ return {'subnetpool': body}
+
+
+class DeleteSubnetPool(neutronV20.DeleteCommand):
+ """Delete a given subnetpool."""
+
+ resource = 'subnetpool'
+
+
+class UpdateSubnetPool(neutronV20.UpdateCommand):
+ """Update subnetpool's information."""
+
+ resource = 'subnetpool'
+
+ def add_known_arguments(self, parser):
+ add_updatable_arguments(parser)
+ parser.add_argument('--name',
+ help=_('Updated name of the subnetpool.'))
+ addrscope_args = parser.add_mutually_exclusive_group()
+ addrscope_args.add_argument('--address-scope',
+ metavar='ADDRSCOPE',
+ help=_('ID or name of the address scope '
+ 'with which the subnetpool is '
+ 'associated. Prefixes must be '
+ 'unique across address scopes.'))
+ addrscope_args.add_argument('--no-address-scope',
+ action='store_true',
+ help=_('Detach subnetpool from the '
+ 'address scope.'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ updatable_args2body(parsed_args, body)
+
+ # Parse and update for "address-scope" option/s
+ if parsed_args.no_address_scope:
+ body['address_scope_id'] = None
+ elif parsed_args.address_scope:
+ _addrscope_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'address_scope',
+ parsed_args.address_scope)
+ body['address_scope_id'] = _addrscope_id
+ return {'subnetpool': body}
diff --git a/neutronclient/neutron/v2_0/tag.py b/neutronclient/neutron/v2_0/tag.py
new file mode 100644
index 000000000..ff59b74ec
--- /dev/null
+++ b/neutronclient/neutron/v2_0/tag.py
@@ -0,0 +1,107 @@
+# 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', 'subnet', 'port', 'router', 'subnetpool']
+
+
+def _convert_resource_args(client, parsed_args):
+ resource_type = client.get_resource_plural(parsed_args.resource_type)
+ 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()
+ if not parsed_args.tag:
+ raise exceptions.CommandError(
+ _('Cannot add an empty value as tag'))
+ 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)
diff --git a/neutronclient/neutron/v2_0/vpn/__init__.py b/neutronclient/neutron/v2_0/vpn/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/neutron/v2_0/vpn/endpoint_group.py b/neutronclient/neutron/v2_0/vpn/endpoint_group.py
new file mode 100644
index 000000000..a112b315e
--- /dev/null
+++ b/neutronclient/neutron/v2_0/vpn/endpoint_group.py
@@ -0,0 +1,100 @@
+# (c) Copyright 2015 Cisco Systems, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from neutronclient._i18n import _
+from neutronclient.neutron import v2_0 as neutronv20
+
+
+def add_known_endpoint_group_arguments(parser, is_create=True):
+ parser.add_argument(
+ '--name',
+ help=_('Set a name for the endpoint group.'))
+ parser.add_argument(
+ '--description',
+ help=_('Set a description for the endpoint group.'))
+ if is_create:
+ parser.add_argument(
+ '--type',
+ required=is_create,
+ help=_('Type of endpoints in group (e.g. subnet, cidr, vlan).'))
+ parser.add_argument(
+ '--value',
+ action='append', dest='endpoints',
+ required=is_create,
+ help=_('Endpoint(s) for the group. Must all be of the same type.'))
+
+
+class ListEndpointGroup(neutronv20.ListCommand):
+ """List VPN endpoint groups that belong to a given tenant."""
+
+ resource = 'endpoint_group'
+ list_columns = ['id', 'name', 'type', 'endpoints']
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowEndpointGroup(neutronv20.ShowCommand):
+ """Show a specific VPN endpoint group."""
+
+ resource = 'endpoint_group'
+
+
+class CreateEndpointGroup(neutronv20.CreateCommand):
+ """Create a VPN endpoint group."""
+ resource = 'endpoint_group'
+
+ def add_known_arguments(self, parser):
+ add_known_endpoint_group_arguments(parser)
+
+ def subnet_name2id(self, endpoint):
+ return neutronv20.find_resourceid_by_name_or_id(self.get_client(),
+ 'subnet', endpoint)
+
+ def args2body(self, parsed_args):
+ if parsed_args.type == 'subnet':
+ endpoints = [self.subnet_name2id(ep)
+ for ep in parsed_args.endpoints]
+ else:
+ endpoints = parsed_args.endpoints
+
+ body = {'endpoints': endpoints}
+
+ neutronv20.update_dict(parsed_args, body,
+ ['name', 'description',
+ 'tenant_id', 'type'])
+ return {self.resource: body}
+
+
+class UpdateEndpointGroup(neutronv20.UpdateCommand):
+ """Update a given VPN endpoint group."""
+
+ resource = 'endpoint_group'
+
+ def add_known_arguments(self, parser):
+ add_known_endpoint_group_arguments(parser, is_create=False)
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronv20.update_dict(parsed_args, body,
+ ['name', 'description'])
+ return {self.resource: body}
+
+
+class DeleteEndpointGroup(neutronv20.DeleteCommand):
+ """Delete a given VPN endpoint group."""
+
+ resource = 'endpoint_group'
diff --git a/neutronclient/neutron/v2_0/vpn/ikepolicy.py b/neutronclient/neutron/v2_0/vpn/ikepolicy.py
new file mode 100644
index 000000000..20a96aaea
--- /dev/null
+++ b/neutronclient/neutron/v2_0/vpn/ikepolicy.py
@@ -0,0 +1,128 @@
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronv20
+from neutronclient.neutron.v2_0.vpn import utils as vpn_utils
+
+
+def add_common_args(parser, is_create=True):
+ parser.add_argument(
+ '--description',
+ help=_('Description of the IKE policy.'))
+ parser.add_argument(
+ '--auth-algorithm',
+ type=utils.convert_to_lowercase,
+ default='sha1' if is_create else argparse.SUPPRESS,
+ choices=['sha1', 'sha256', 'sha384', 'sha512'],
+ help=_('Authentication algorithm, default:sha1.'))
+ parser.add_argument(
+ '--encryption-algorithm',
+ default='aes-128' if is_create else argparse.SUPPRESS,
+ type=utils.convert_to_lowercase,
+ help=_('Encryption algorithm, default:aes-128.'))
+ parser.add_argument(
+ '--phase1-negotiation-mode',
+ default='main' if is_create else argparse.SUPPRESS,
+ choices=['main'],
+ type=utils.convert_to_lowercase,
+ help=_('IKE Phase1 negotiation mode, default:main.'))
+ parser.add_argument(
+ '--ike-version',
+ default='v1' if is_create else argparse.SUPPRESS,
+ choices=['v1', 'v2'],
+ type=utils.convert_to_lowercase,
+ help=_('IKE version for the policy, default:v1.'))
+ parser.add_argument(
+ '--pfs',
+ default='group5' if is_create else argparse.SUPPRESS,
+ type=utils.convert_to_lowercase,
+ help=_('Perfect Forward Secrecy, default:group5.'))
+ parser.add_argument(
+ '--lifetime',
+ metavar="units=UNITS,value=VALUE",
+ type=utils.str2dict_type(optional_keys=['units', 'value']),
+ help=vpn_utils.lifetime_help("IKE"))
+
+
+def parse_common_args2body(parsed_args, body):
+ neutronv20.update_dict(parsed_args, body,
+ ['auth_algorithm', 'encryption_algorithm',
+ 'phase1_negotiation_mode', 'ike_version',
+ 'pfs', 'name', 'description', 'tenant_id'])
+ if parsed_args.lifetime:
+ vpn_utils.validate_lifetime_dict(parsed_args.lifetime)
+ body['lifetime'] = parsed_args.lifetime
+ return body
+
+
+class ListIKEPolicy(neutronv20.ListCommand):
+ """List IKE policies that belong to a tenant."""
+
+ resource = 'ikepolicy'
+ list_columns = ['id', 'name', 'auth_algorithm',
+ 'encryption_algorithm', 'ike_version', 'pfs']
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowIKEPolicy(neutronv20.ShowCommand):
+ """Show information of a given IKE policy."""
+
+ resource = 'ikepolicy'
+ help_resource = 'IKE policy'
+
+
+class CreateIKEPolicy(neutronv20.CreateCommand):
+ """Create an IKE policy."""
+
+ resource = 'ikepolicy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the IKE policy.'))
+ add_common_args(parser)
+
+ def args2body(self, parsed_args):
+ return {'ikepolicy': parse_common_args2body(parsed_args, body={})}
+
+
+class UpdateIKEPolicy(neutronv20.UpdateCommand):
+ """Update a given IKE policy."""
+
+ resource = 'ikepolicy'
+ help_resource = 'IKE policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Updated name of the IKE policy.'))
+ add_common_args(parser, is_create=False)
+
+ def args2body(self, parsed_args):
+ return {'ikepolicy': parse_common_args2body(parsed_args, body={})}
+
+
+class DeleteIKEPolicy(neutronv20.DeleteCommand):
+ """Delete a given IKE policy."""
+
+ resource = 'ikepolicy'
+ help_resource = 'IKE policy'
diff --git a/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py b/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py
new file mode 100644
index 000000000..03a635f6e
--- /dev/null
+++ b/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py
@@ -0,0 +1,220 @@
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 oslo_serialization import jsonutils
+
+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.vpn import utils as vpn_utils
+
+
+def _format_peer_cidrs(ipsec_site_connection):
+ try:
+ return '\n'.join([jsonutils.dumps(cidrs) for cidrs in
+ ipsec_site_connection['peer_cidrs']])
+ except (TypeError, KeyError):
+ return ''
+
+
+class ListIPsecSiteConnection(neutronv20.ListCommand):
+ """List IPsec site connections that belong to a given tenant."""
+
+ resource = 'ipsec_site_connection'
+ _formatters = {'peer_cidrs': _format_peer_cidrs}
+ list_columns = [
+ 'id', 'name', 'peer_address', 'auth_mode', 'status']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowIPsecSiteConnection(neutronv20.ShowCommand):
+ """Show information of a given IPsec site connection."""
+
+ resource = 'ipsec_site_connection'
+ help_resource = 'IPsec site connection'
+
+
+class IPsecSiteConnectionMixin(object):
+
+ def add_known_arguments(self, parser, is_create=True):
+ parser.add_argument(
+ '--name',
+ help=_('Set friendly name for the connection.'))
+ parser.add_argument(
+ '--description',
+ help=_('Set a description for the connection.'))
+ parser.add_argument(
+ '--dpd',
+ metavar="action=ACTION,interval=INTERVAL,timeout=TIMEOUT",
+ type=utils.str2dict_type(
+ optional_keys=['action', 'interval', 'timeout']),
+ help=vpn_utils.dpd_help("IPsec connection."))
+ parser.add_argument(
+ '--local-ep-group',
+ help=_('Local endpoint group ID/name with subnet(s) for '
+ 'IPSec connection.'))
+ parser.add_argument(
+ '--peer-ep-group',
+ help=_('Peer endpoint group ID/name with CIDR(s) for '
+ 'IPSec connection.'))
+ parser.add_argument(
+ '--peer-cidr',
+ action='append', dest='peer_cidrs',
+ help=_('[DEPRECATED in Mitaka] Remote subnet(s) in CIDR format. '
+ 'Cannot be specified when using endpoint groups. Only '
+ 'applicable, if subnet provided for VPN service.'))
+ parser.add_argument(
+ '--peer-id',
+ required=is_create,
+ help=_('Peer router identity for authentication. Can be '
+ 'IPv4/IPv6 address, e-mail address, key id, or FQDN.'))
+ parser.add_argument(
+ '--peer-address',
+ required=is_create,
+ help=_('Peer gateway public IPv4/IPv6 address or FQDN.'))
+ parser.add_argument(
+ '--psk',
+ required=is_create,
+ help=_('Pre-shared key string.'))
+ parser.add_argument(
+ '--mtu',
+ default='1500' if is_create else argparse.SUPPRESS,
+ help=_('MTU size for the connection, default:1500.'))
+ parser.add_argument(
+ '--initiator',
+ default='bi-directional' if is_create else argparse.SUPPRESS,
+ choices=['bi-directional', 'response-only'],
+ help=_('Initiator state in lowercase, default:bi-directional'))
+
+ def args2body(self, parsed_args, body=None):
+ """Add in conditional args and then return all conn info."""
+
+ if body is None:
+ body = {}
+ if parsed_args.dpd:
+ vpn_utils.validate_dpd_dict(parsed_args.dpd)
+ body['dpd'] = parsed_args.dpd
+ if parsed_args.local_ep_group:
+ _local_epg = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'endpoint_group',
+ parsed_args.local_ep_group)
+ body['local_ep_group_id'] = _local_epg
+ if parsed_args.peer_ep_group:
+ _peer_epg = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'endpoint_group',
+ parsed_args.peer_ep_group)
+ body['peer_ep_group_id'] = _peer_epg
+ if hasattr(parsed_args, 'mtu') and int(parsed_args.mtu) < 68:
+ message = _("Invalid MTU value: MTU must be "
+ "greater than or equal to 68.")
+ raise exceptions.CommandError(message)
+ # ToDo (Reedip) : Remove below check when peer-cidr is removed
+ if parsed_args.peer_cidrs and parsed_args.local_ep_group:
+ message = _("You cannot specify both endpoint groups and peer "
+ "CIDR(s).")
+ raise exceptions.CommandError(message)
+ neutronv20.update_dict(parsed_args, body,
+ ['peer_id', 'mtu', 'initiator', 'psk',
+ 'peer_address', 'name', 'description',
+ 'peer_cidrs'])
+ return {self.resource: body}
+
+
+class CreateIPsecSiteConnection(IPsecSiteConnectionMixin,
+ neutronv20.CreateCommand):
+ """Create an IPsec site connection."""
+ resource = 'ipsec_site_connection'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ default=True, action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ '--vpnservice-id', metavar='VPNSERVICE',
+ required=True,
+ help=_('VPN service instance ID associated with this connection.'))
+ parser.add_argument(
+ '--ikepolicy-id', metavar='IKEPOLICY',
+ required=True,
+ help=_('IKE policy ID associated with this connection.'))
+ parser.add_argument(
+ '--ipsecpolicy-id', metavar='IPSECPOLICY',
+ required=True,
+ help=_('IPsec policy ID associated with this connection.'))
+ super(CreateIPsecSiteConnection, self).add_known_arguments(parser)
+
+ def args2body(self, parsed_args):
+ _vpnservice_id = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'vpnservice',
+ parsed_args.vpnservice_id)
+ _ikepolicy_id = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'ikepolicy',
+ parsed_args.ikepolicy_id)
+ _ipsecpolicy_id = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'ipsecpolicy',
+ parsed_args.ipsecpolicy_id)
+ body = {
+ 'vpnservice_id': _vpnservice_id,
+ 'ikepolicy_id': _ikepolicy_id,
+ 'ipsecpolicy_id': _ipsecpolicy_id,
+ 'admin_state_up': parsed_args.admin_state_down,
+ }
+ if parsed_args.tenant_id:
+ body['tenant_id'] = parsed_args.tenant_id
+
+ if (bool(parsed_args.local_ep_group) !=
+ bool(parsed_args.peer_ep_group)):
+ message = _("You must specify both local and peer endpoint "
+ "groups.")
+ raise exceptions.CommandError(message)
+ if not parsed_args.peer_cidrs and not parsed_args.local_ep_group:
+ message = _("You must specify endpoint groups or peer CIDR(s).")
+ raise exceptions.CommandError(message)
+ return super(CreateIPsecSiteConnection, self).args2body(parsed_args,
+ body)
+
+
+class UpdateIPsecSiteConnection(IPsecSiteConnectionMixin,
+ neutronv20.UpdateCommand):
+ """Update a given IPsec site connection."""
+
+ resource = 'ipsec_site_connection'
+ help_resource = 'IPsec site connection'
+
+ def add_known_arguments(self, parser):
+ utils.add_boolean_argument(
+ parser, '--admin-state-up',
+ help=_('Update the administrative state. (True meaning "Up")'))
+ super(UpdateIPsecSiteConnection, self).add_known_arguments(parser,
+ False)
+
+ def args2body(self, parsed_args):
+ body = {}
+ neutronv20.update_dict(parsed_args, body, ['admin_state_up'])
+ return super(UpdateIPsecSiteConnection, self).args2body(parsed_args,
+ body)
+
+
+class DeleteIPsecSiteConnection(neutronv20.DeleteCommand):
+ """Delete a given IPsec site connection."""
+
+ resource = 'ipsec_site_connection'
+ help_resource = 'IPsec site connection'
diff --git a/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py b/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py
new file mode 100644
index 000000000..ea70155b7
--- /dev/null
+++ b/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py
@@ -0,0 +1,129 @@
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 neutronclient._i18n import _
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronv20
+from neutronclient.neutron.v2_0.vpn import utils as vpn_utils
+
+
+def add_common_args(parser, is_create=True):
+ parser.add_argument(
+ '--auth-algorithm',
+ default='sha1' if is_create else argparse.SUPPRESS,
+ type=utils.convert_to_lowercase,
+ choices=['sha1', 'sha256', 'sha384', 'sha512'],
+ help=_('Authentication algorithm for IPsec policy, default:sha1.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description of the IPsec policy.'))
+ parser.add_argument(
+ '--encapsulation-mode',
+ default='tunnel' if is_create else argparse.SUPPRESS,
+ choices=['tunnel', 'transport'],
+ type=utils.convert_to_lowercase,
+ help=_('Encapsulation mode for IPsec policy, default:tunnel.'))
+ parser.add_argument(
+ '--encryption-algorithm',
+ default='aes-128' if is_create else argparse.SUPPRESS,
+ type=utils.convert_to_lowercase,
+ help=_('Encryption algorithm for IPsec policy, default:aes-128.'))
+ parser.add_argument(
+ '--lifetime',
+ metavar="units=UNITS,value=VALUE",
+ type=utils.str2dict_type(optional_keys=['units', 'value']),
+ help=vpn_utils.lifetime_help("IPsec"))
+ parser.add_argument(
+ '--pfs',
+ default='group5' if is_create else argparse.SUPPRESS,
+ type=utils.convert_to_lowercase,
+ help=_('Perfect Forward Secrecy for IPsec policy, default:group5.'))
+ parser.add_argument(
+ '--transform-protocol',
+ default='esp' if is_create else argparse.SUPPRESS,
+ type=utils.convert_to_lowercase,
+ choices=['esp', 'ah', 'ah-esp'],
+ help=_('Transform protocol for IPsec policy, default:esp.'))
+
+
+def parse_common_args2body(parsed_args, body):
+ neutronv20.update_dict(parsed_args, body,
+ ['auth_algorithm', 'encryption_algorithm',
+ 'encapsulation_mode', 'transform_protocol',
+ 'pfs', 'name', 'description', 'tenant_id'])
+ if parsed_args.lifetime:
+ vpn_utils.validate_lifetime_dict(parsed_args.lifetime)
+ body['lifetime'] = parsed_args.lifetime
+ return body
+
+
+class ListIPsecPolicy(neutronv20.ListCommand):
+ """List IPsec policies that belong to a given tenant connection."""
+
+ resource = 'ipsecpolicy'
+ list_columns = ['id', 'name', 'auth_algorithm',
+ 'encryption_algorithm', 'pfs']
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowIPsecPolicy(neutronv20.ShowCommand):
+ """Show information of a given IPsec policy."""
+
+ resource = 'ipsecpolicy'
+ help_resource = 'IPsec policy'
+
+
+class CreateIPsecPolicy(neutronv20.CreateCommand):
+ """Create an IPsec policy."""
+
+ resource = 'ipsecpolicy'
+ help_resource = 'IPsec policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help=_('Name of the IPsec policy.'))
+ add_common_args(parser)
+
+ def args2body(self, parsed_args):
+ return {'ipsecpolicy': parse_common_args2body(parsed_args, body={})}
+
+
+class UpdateIPsecPolicy(neutronv20.UpdateCommand):
+ """Update a given IPsec policy."""
+
+ resource = 'ipsecpolicy'
+ help_resource = 'IPsec policy'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help=_('Updated name of the IPsec policy.'))
+ add_common_args(parser, is_create=False)
+
+ def args2body(self, parsed_args):
+ return {'ipsecpolicy': parse_common_args2body(parsed_args, body={})}
+
+
+class DeleteIPsecPolicy(neutronv20.DeleteCommand):
+ """Delete a given IPsec policy."""
+
+ resource = 'ipsecpolicy'
+ help_resource = 'IPsec policy'
diff --git a/neutronclient/neutron/v2_0/vpn/utils.py b/neutronclient/neutron/v2_0/vpn/utils.py
new file mode 100644
index 000000000..65d9c7c4b
--- /dev/null
+++ b/neutronclient/neutron/v2_0/vpn/utils.py
@@ -0,0 +1,112 @@
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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.
+#
+
+
+"""VPN Utilities and helper functions."""
+
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+
+dpd_supported_actions = ['hold', 'clear', 'restart',
+ 'restart-by-peer', 'disabled']
+dpd_supported_keys = ['action', 'interval', 'timeout']
+
+lifetime_keys = ['units', 'value']
+lifetime_units = ['seconds']
+
+
+def validate_dpd_dict(dpd_dict):
+ for key, value in dpd_dict.items():
+ if key not in dpd_supported_keys:
+ message = _(
+ "DPD Dictionary KeyError: "
+ "Reason-Invalid DPD key : "
+ "'%(key)s' not in %(supported_key)s") % {
+ 'key': key, 'supported_key': dpd_supported_keys}
+ raise exceptions.CommandError(message)
+ if key == 'action' and value not in dpd_supported_actions:
+ message = _(
+ "DPD Dictionary ValueError: "
+ "Reason-Invalid DPD action : "
+ "'%(key_value)s' not in %(supported_action)s") % {
+ 'key_value': value,
+ 'supported_action': dpd_supported_actions}
+ raise exceptions.CommandError(message)
+ if key in ('interval', 'timeout'):
+ try:
+ if int(value) <= 0:
+ raise ValueError()
+ except ValueError:
+ message = _(
+ "DPD Dictionary ValueError: "
+ "Reason-Invalid positive integer value: "
+ "'%(key)s' = %(value)s") % {
+ 'key': key, 'value': value}
+ raise exceptions.CommandError(message)
+ else:
+ dpd_dict[key] = int(value)
+ return
+
+
+def validate_lifetime_dict(lifetime_dict):
+
+ for key, value in lifetime_dict.items():
+ if key not in lifetime_keys:
+ message = _(
+ "Lifetime Dictionary KeyError: "
+ "Reason-Invalid unit key : "
+ "'%(key)s' not in %(supported_key)s") % {
+ 'key': key, 'supported_key': lifetime_keys}
+ raise exceptions.CommandError(message)
+ if key == 'units' and value not in lifetime_units:
+ message = _(
+ "Lifetime Dictionary ValueError: "
+ "Reason-Invalid units : "
+ "'%(key_value)s' not in %(supported_units)s") % {
+ 'key_value': key, 'supported_units': lifetime_units}
+ raise exceptions.CommandError(message)
+ if key == 'value':
+ try:
+ if int(value) < 60:
+ raise ValueError()
+ except ValueError:
+ message = _(
+ "Lifetime Dictionary ValueError: "
+ "Reason-Invalid value should be at least 60:"
+ "'%(key_value)s' = %(value)s") % {
+ 'key_value': key, 'value': value}
+ raise exceptions.CommandError(message)
+ else:
+ lifetime_dict['value'] = int(value)
+ return
+
+
+def lifetime_help(policy):
+ lifetime = _("%s lifetime attributes. "
+ "'units'-seconds, default:seconds. "
+ "'value'-non negative integer, default:3600.") % policy
+ return lifetime
+
+
+def dpd_help(policy):
+ dpd = _(" %s Dead Peer Detection attributes."
+ " 'action'-hold,clear,disabled,restart,restart-by-peer."
+ " 'interval' and 'timeout' are non negative integers. "
+ " 'interval' should be less than 'timeout' value. "
+ " 'action', default:hold 'interval', default:30, "
+ " 'timeout', default:120.") % policy.capitalize()
+ return dpd
diff --git a/neutronclient/neutron/v2_0/vpn/vpnservice.py b/neutronclient/neutron/v2_0/vpn/vpnservice.py
new file mode 100644
index 000000000..dae531408
--- /dev/null
+++ b/neutronclient/neutron/v2_0/vpn/vpnservice.py
@@ -0,0 +1,117 @@
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 add_common_args(parser):
+ parser.add_argument(
+ '--name',
+ help=_('Name for the VPN service.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description for the VPN service.'))
+
+
+def common_args2body(parsed_args, body):
+ neutronv20.update_dict(parsed_args, body,
+ ['name', 'description'])
+
+
+class ListVPNService(neutronv20.ListCommand):
+ """List VPN service configurations that belong to a given tenant."""
+
+ resource = 'vpnservice'
+ list_columns = [
+ 'id', 'name', 'router_id', 'status'
+ ]
+ _formatters = {}
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowVPNService(neutronv20.ShowCommand):
+ """Show information of a given VPN service."""
+
+ resource = 'vpnservice'
+ help_resource = 'VPN service'
+
+
+class CreateVPNService(neutronv20.CreateCommand):
+ """Create a VPN service."""
+ resource = 'vpnservice'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help=_('Set admin state up to false.'))
+ parser.add_argument(
+ 'router', metavar='ROUTER',
+ help=_('Router unique identifier for the VPN service.'))
+ parser.add_argument(
+ 'subnet', nargs='?', metavar='SUBNET',
+ help=_('[DEPRECATED in Mitaka] Unique identifier for the local '
+ 'private subnet.'))
+ add_common_args(parser)
+
+ def args2body(self, parsed_args):
+ if parsed_args.subnet:
+ _subnet_id = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.subnet)
+ else:
+ _subnet_id = None
+ _router_id = neutronv20.find_resourceid_by_name_or_id(
+ self.get_client(), 'router',
+ parsed_args.router)
+
+ body = {'subnet_id': _subnet_id,
+ 'router_id': _router_id,
+ 'admin_state_up': parsed_args.admin_state}
+ neutronv20.update_dict(parsed_args, body,
+ ['tenant_id'])
+ common_args2body(parsed_args, body)
+ return {self.resource: body}
+
+
+class UpdateVPNService(neutronv20.UpdateCommand):
+ """Update a given VPN service."""
+
+ resource = 'vpnservice'
+ help_resource = 'VPN service'
+
+ def add_known_arguments(self, parser):
+ add_common_args(parser)
+ utils.add_boolean_argument(
+ parser, '--admin-state-up',
+ help=_('Update the admin state for the VPN Service.'
+ '(True means UP)'))
+
+ def args2body(self, parsed_args):
+ body = {}
+ common_args2body(parsed_args, body)
+ neutronv20.update_dict(parsed_args, body,
+ ['admin_state_up'])
+ return {self.resource: body}
+
+
+class DeleteVPNService(neutronv20.DeleteCommand):
+ """Delete a given VPN service."""
+
+ resource = 'vpnservice'
+ help_resource = 'VPN service'
diff --git a/neutronclient/osc/__init__.py b/neutronclient/osc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/plugin.py b/neutronclient/osc/plugin.py
new file mode 100644
index 000000000..c98099f2a
--- /dev/null
+++ b/neutronclient/osc/plugin.py
@@ -0,0 +1,63 @@
+# 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.
+#
+
+"""OpenStackClient plugin for advanced Networking service."""
+
+import logging
+
+# TODO(rtheis/amotoki): Add functional test infrastructure for OSC
+# plugin commands.
+# TODO(amotoki): Add and update document on OSC plugin.
+
+from osc_lib import utils
+
+LOG = logging.getLogger(__name__)
+
+DEFAULT_API_VERSION = '2.0'
+API_VERSION_OPTION = 'os_network_api_version'
+# NOTE(rtheis): API_NAME must NOT be set to 'network' since
+# 'network' is owned by OSC! The OSC 'network' client uses
+# the OpenStack SDK.
+API_NAME = 'neutronclient'
+API_VERSIONS = {
+ '2.0': 'neutronclient.v2_0.client.Client',
+ '2': 'neutronclient.v2_0.client.Client',
+}
+
+
+def make_client(instance):
+ """Returns an neutron client."""
+ neutron_client = utils.get_client_class(
+ API_NAME,
+ instance._api_version[API_NAME],
+ API_VERSIONS)
+ LOG.debug('Instantiating neutron client: %s', neutron_client)
+
+ # TODO(amotoki): Check the following arguments need to be passed
+ # to neutronclient class. Check keystoneauth code.
+ # - endpoint_type (do we need to specify it explicitly?)
+ # - auth (session object contains auth. Is it required?)
+ client = neutron_client(session=instance.session,
+ region_name=instance.region_name,
+ endpoint_type=instance.interface,
+ insecure=not instance.verify,
+ ca_cert=instance.cacert)
+ return client
+
+
+def build_option_parser(parser):
+ """Hook to add global options"""
+
+ # NOTE(amotoki): At now we register no option.
+ # OSC itself has an option for Network API version # and we refer to it.
+ return parser
diff --git a/neutronclient/osc/utils.py b/neutronclient/osc/utils.py
new file mode 100644
index 000000000..39b17b640
--- /dev/null
+++ b/neutronclient/osc/utils.py
@@ -0,0 +1,130 @@
+# Copyright 2016 NEC Corporation
+#
+# 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.
+
+"""This module should contain OSC plugin generic methods.
+
+Methods in this module are candidates adopted to osc-lib.
+
+Stuffs specific to neutronclient OSC plugin should not be added
+to this module. They should go to neutronclient.osc.v2.utils.
+"""
+
+from keystoneclient import exceptions as identity_exc
+from keystoneclient.v3 import domains
+from keystoneclient.v3 import projects
+from osc_lib import utils
+
+from neutronclient._i18n import _
+
+
+# TODO(amotoki): Use osc-lib version once osc-lib provides this.
+def add_project_owner_option_to_parser(parser):
+ """Register project and project domain options.
+
+ :param parser: argparse.Argument parser object.
+
+ """
+ parser.add_argument(
+ '--project',
+ metavar='',
+ help=_("Owner's project (name or ID)")
+ )
+ # Borrowed from openstackclient.identity.common
+ # as it is not exposed officially.
+ parser.add_argument(
+ '--project-domain',
+ metavar='',
+ help=_('Domain the project belongs to (name or ID). '
+ 'This can be used in case collisions between project names '
+ 'exist.'),
+ )
+
+
+# The following methods are borrowed from openstackclient.identity.common
+# as it is not exposed officially.
+# TODO(amotoki): Use osc-lib version once osc-lib provides this.
+
+
+def find_domain(identity_client, name_or_id):
+ return _find_identity_resource(identity_client.domains, name_or_id,
+ domains.Domain)
+
+
+def find_project(identity_client, name_or_id, domain_name_or_id=None):
+ domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id)
+ if not domain_id:
+ return _find_identity_resource(identity_client.projects, name_or_id,
+ projects.Project)
+ else:
+ return _find_identity_resource(identity_client.projects, name_or_id,
+ projects.Project, domain_id=domain_id)
+
+
+def _get_domain_id_if_requested(identity_client, domain_name_or_id):
+ if not domain_name_or_id:
+ return None
+ domain = find_domain(identity_client, domain_name_or_id)
+ return domain.id
+
+
+def _find_identity_resource(identity_client_manager, name_or_id,
+ resource_type, **kwargs):
+ """Find a specific identity resource.
+
+ Using keystoneclient's manager, attempt to find a specific resource by its
+ name or ID. If Forbidden to find the resource (a common case if the user
+ does not have permission), then return the resource by creating a local
+ instance of keystoneclient's Resource.
+
+ The parameter identity_client_manager is a keystoneclient manager,
+ for example: keystoneclient.v3.users or keystoneclient.v3.projects.
+
+ The parameter resource_type is a keystoneclient resource, for example:
+ keystoneclient.v3.users.User or keystoneclient.v3.projects.Project.
+
+ :param identity_client_manager: the manager that contains the resource
+ :type identity_client_manager: `keystoneclient.base.CrudManager`
+ :param name_or_id: the resources's name or ID
+ :type name_or_id: string
+ :param resource_type: class that represents the resource type
+ :type resource_type: `keystoneclient.base.Resource`
+
+ :returns: the resource in question
+ :rtype: `keystoneclient.base.Resource`
+
+ """
+
+ try:
+ identity_resource = utils.find_resource(identity_client_manager,
+ name_or_id, **kwargs)
+ if identity_resource is not None:
+ return identity_resource
+ except identity_exc.Forbidden:
+ pass
+
+ return resource_type(None, {'id': name_or_id, 'name': name_or_id})
+
+
+# The above are borrowed from openstackclient.identity.common.
+# DO NOT ADD original methods in neutronclient repo to the above area.
+
+
+def _get_columns(item):
+ column_map = {}
+ hidden_columns = ['location', 'tenant_id']
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item,
+ column_map,
+ hidden_columns
+ )
diff --git a/neutronclient/osc/v2/__init__.py b/neutronclient/osc/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/dynamic_routing/__init__.py b/neutronclient/osc/v2/dynamic_routing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/dynamic_routing/bgp_dragent.py b/neutronclient/osc/v2/dynamic_routing/bgp_dragent.py
new file mode 100644
index 000000000..91dc2a75b
--- /dev/null
+++ b/neutronclient/osc/v2/dynamic_routing/bgp_dragent.py
@@ -0,0 +1,112 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from neutronclient._i18n import _
+
+
+def _format_alive_state(item):
+ return ':-)' if item else 'XXX'
+
+
+_formatters = {
+ 'alive': _format_alive_state
+}
+
+
+def add_common_args(parser):
+ parser.add_argument('dragent_id',
+ metavar='',
+ help=_("ID of the dynamic routing agent"))
+ parser.add_argument('bgp_speaker',
+ metavar='',
+ help=_("ID or name of the BGP speaker"))
+
+
+class AddBgpSpeakerToDRAgent(command.Command):
+ """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):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker).id
+ client.add_bgp_speaker_to_dragent(parsed_args.dragent_id, speaker_id)
+
+
+class RemoveBgpSpeakerFromDRAgent(command.Command):
+ """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):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker).id
+ client.remove_bgp_speaker_from_dragent(parsed_args.dragent_id,
+ speaker_id)
+
+
+class ListDRAgent(command.Lister):
+ """List dynamic routing agents"""
+
+ resource = 'agent'
+ list_columns = ['id', 'host', 'admin_state_up', 'alive']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListDRAgent,
+ self).get_parser(prog_name)
+ parser.add_argument('--bgp-speaker',
+ metavar='',
+ help=_("List dynamic routing agents hosting a "
+ "BGP speaker (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ if parsed_args.bgp_speaker is not None:
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker).id
+ data = client.get_bgp_dragents_hosting_speaker(speaker_id)
+ else:
+ attrs = {'agent_type': 'BGP dynamic routing agent'}
+ data = client.agents(**attrs)
+ columns = (
+ 'id',
+ 'agent_type',
+ 'host',
+ 'availability_zone',
+ 'is_alive',
+ 'is_admin_state_up',
+ 'binary'
+ )
+ column_headers = (
+ 'ID',
+ 'Agent Type',
+ 'Host',
+ 'Availability Zone',
+ 'Alive',
+ 'State',
+ 'Binary'
+ )
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,) for s in data))
diff --git a/neutronclient/osc/v2/dynamic_routing/bgp_peer.py b/neutronclient/osc/v2/dynamic_routing/bgp_peer.py
new file mode 100644
index 000000000..0610a3ac0
--- /dev/null
+++ b/neutronclient/osc/v2/dynamic_routing/bgp_peer.py
@@ -0,0 +1,181 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+from neutronclient.common import utils as nc_utils
+from neutronclient.osc import utils as nc_osc_utils
+from neutronclient.osc.v2.dynamic_routing import constants
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+
+ # Validate password
+ if 'auth_type' in parsed_args:
+ if parsed_args.auth_type != 'none':
+ if 'password' not in parsed_args or 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 is not None
+ ):
+ raise exceptions.CommandError(_('Must provide auth-type if '
+ 'password is specified.'))
+ attrs['auth_type'] = parsed_args.auth_type
+
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if 'remote_as' in parsed_args:
+ attrs['remote_as'] = parsed_args.remote_as
+ if 'peer_ip' in parsed_args:
+ attrs['peer_ip'] = parsed_args.peer_ip
+ if 'password' in parsed_args:
+ attrs['password'] = parsed_args.password
+
+ if 'project' in parsed_args and parsed_args.project is not None:
+ identity_client = client_manager.identity
+ project_id = nc_osc_utils.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+ return attrs
+
+
+class CreateBgpPeer(command.ShowOne):
+ _description = _("Create a BGP peer")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateBgpPeer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_("Name of the BGP peer to create"))
+ parser.add_argument(
+ '--peer-ip',
+ metavar='',
+ required=True,
+ help=_("Peer IP address"))
+ parser.add_argument(
+ '--remote-as',
+ required=True,
+ metavar='',
+ help=_("Peer AS number. (Integer in [%(min_val)s, %(max_val)s] "
+ "is allowed)") % {'min_val': constants.MIN_AS_NUM,
+ 'max_val': constants.MAX_AS_NUM})
+ parser.add_argument(
+ '--auth-type',
+ metavar='',
+ choices=['none', 'md5'],
+ type=nc_utils.convert_to_lowercase,
+ default='none',
+ help=_("Authentication algorithm. Supported algorithms: "
+ "none (default), md5"))
+ parser.add_argument(
+ '--password',
+ metavar='',
+ help=_("Authentication password"))
+ nc_osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_bgp_peer(**attrs)
+ display_columns, columns = nc_osc_utils._get_columns(obj)
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteBgpPeer(command.Command):
+ _description = _("Delete a BGP peer")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteBgpPeer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_peer',
+ metavar="",
+ help=_("BGP peer to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ id = client.find_bgp_peer(parsed_args.bgp_peer)['id']
+ client.delete_bgp_peer(id)
+
+
+class ListBgpPeer(command.Lister):
+ _description = _("List BGP peers")
+
+ def take_action(self, parsed_args):
+ data = self.app.client_manager.network.bgp_peers(retrieve_all=True)
+ headers = ('ID', 'Name', 'Peer IP', 'Remote AS')
+ columns = ('id', 'name', 'peer_ip', 'remote_as')
+ return (headers,
+ (utils.get_dict_properties(s, columns,) for s in data))
+
+
+class SetBgpPeer(command.Command):
+ _description = _("Update a BGP peer")
+ resource = constants.BGP_PEER
+
+ def get_parser(self, prog_name):
+ parser = super(SetBgpPeer, self).get_parser(prog_name)
+ parser.add_argument(
+ '--name',
+ help=_("Updated name of the BGP peer"))
+ parser.add_argument(
+ '--password',
+ metavar='',
+ help=_("Updated authentication password"))
+ parser.add_argument(
+ 'bgp_peer',
+ metavar="",
+ help=_("BGP peer to update (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ id = client.find_bgp_peer(parsed_args.bgp_peer)['id']
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ client.update_bgp_peer(id, **attrs)
+
+
+class ShowBgpPeer(command.ShowOne):
+ _description = _("Show information for a BGP peer")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowBgpPeer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_peer',
+ metavar="",
+ help=_("BGP peer to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ id = client.find_bgp_peer(parsed_args.bgp_peer,
+ ignore_missing=False).id
+ obj = client.get_bgp_peer(id)
+ display_columns, columns = nc_osc_utils._get_columns(obj)
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
diff --git a/neutronclient/osc/v2/dynamic_routing/bgp_speaker.py b/neutronclient/osc/v2/dynamic_routing/bgp_speaker.py
new file mode 100644
index 000000000..76d8340b9
--- /dev/null
+++ b/neutronclient/osc/v2/dynamic_routing/bgp_speaker.py
@@ -0,0 +1,305 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from neutronclient._i18n import _
+from neutronclient.osc import utils as nc_osc_utils
+from neutronclient.osc.v2.dynamic_routing import constants
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
+ if 'local_as' in parsed_args:
+ attrs['local_as'] = parsed_args.local_as
+ if 'ip_version' in parsed_args:
+ attrs['ip_version'] = parsed_args.ip_version
+ if parsed_args.advertise_tenant_networks:
+ attrs['advertise_tenant_networks'] = True
+ if parsed_args.no_advertise_tenant_networks:
+ attrs['advertise_tenant_networks'] = False
+ if parsed_args.advertise_floating_ip_host_routes:
+ attrs['advertise_floating_ip_host_routes'] = True
+ if parsed_args.no_advertise_floating_ip_host_routes:
+ attrs['advertise_floating_ip_host_routes'] = False
+
+ if 'project' in parsed_args and parsed_args.project is not None:
+ identity_client = client_manager.identity
+ project_id = nc_osc_utils.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+ return attrs
+
+
+def add_common_arguments(parser):
+ parser.add_argument(
+ '--advertise-floating-ip-host-routes',
+ action='store_true',
+ help=_("Enable the advertisement of floating IP host routes "
+ "by the BGP speaker. (default)"))
+ parser.add_argument(
+ '--no-advertise-floating-ip-host-routes',
+ action='store_true',
+ help=_("Disable the advertisement of floating IP host routes "
+ "by the BGP speaker."))
+ parser.add_argument(
+ '--advertise-tenant-networks',
+ action='store_true',
+ help=_("Enable the advertisement of tenant network routes "
+ "by the BGP speaker. (default)"))
+ parser.add_argument(
+ '--no-advertise-tenant-networks',
+ action='store_true',
+ help=_("Disable the advertisement of tenant network routes "
+ "by the BGP speaker."))
+
+
+class AddNetworkToSpeaker(command.Command):
+ _description = _("Add a network to a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(AddNetworkToSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar='',
+ help=_("BGP speaker (name or ID)"))
+ parser.add_argument(
+ 'network',
+ metavar='',
+ help=_("Network to add (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker,
+ ignore_missing=False).id
+ net_id = client.find_network(parsed_args.network,
+ ignore_missing=False).id
+ client.add_gateway_network_to_speaker(speaker_id, net_id)
+
+
+class AddPeerToSpeaker(command.Command):
+ _description = _("Add a peer to a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(AddPeerToSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar='',
+ help=_("BGP speaker (name or ID)"))
+ parser.add_argument(
+ 'bgp_peer',
+ metavar='',
+ help=_("BGP Peer to add (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker)['id']
+ peer_id = client.find_bgp_peer(parsed_args.bgp_peer)['id']
+ client.add_bgp_peer_to_speaker(speaker_id, peer_id)
+
+
+class CreateBgpSpeaker(command.ShowOne):
+ _description = _("Create a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateBgpSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_("Name of the BGP speaker to create"))
+ parser.add_argument(
+ '--local-as',
+ metavar='',
+ required=True,
+ help=_("Local AS number. (Integer in [%(min_val)s, %(max_val)s] "
+ "is allowed.)") % {'min_val': constants.MIN_AS_NUM,
+ 'max_val': constants.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)
+ nc_osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_bgp_speaker(**attrs)
+ display_columns, columns = nc_osc_utils._get_columns(obj)
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteBgpSpeaker(command.Command):
+ _description = _("Delete a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteBgpSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar="",
+ help=_("BGP speaker to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ id = client.find_bgp_speaker(parsed_args.bgp_speaker)['id']
+ client.delete_bgp_speaker(id)
+
+
+class ListBgpSpeaker(command.Lister):
+ _description = _("List BGP speakers")
+
+ def get_parser(self, prog_name):
+ parser = super(ListBgpSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ '--agent',
+ metavar='',
+ help=_("List BGP speakers hosted by an agent (ID only)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ if parsed_args.agent is not None:
+ data = client.get_bgp_speakers_hosted_by_dragent(parsed_args.agent)
+ else:
+ data = client.bgp_speakers(retrieve_all=True)
+
+ headers = ('ID', 'Name', 'Local AS', 'IP Version')
+ columns = ('id', 'name', 'local_as', 'ip_version')
+ return (headers, (utils.get_dict_properties(s, columns)
+ for s in data))
+
+
+class ListRoutesAdvertisedBySpeaker(command.Lister):
+ _description = _("List routes advertised")
+
+ def get_parser(self, prog_name):
+ parser = super(ListRoutesAdvertisedBySpeaker,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar='',
+ help=_("BGP speaker (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker)['id']
+ data = client.get_advertised_routes_of_speaker(speaker_id)
+ headers = ('Destination', 'Nexthop')
+ columns = ('destination', 'next_hop')
+ return (headers, (utils.get_dict_properties(s, columns)
+ for s in data['advertised_routes']))
+
+
+class RemoveNetworkFromSpeaker(command.Command):
+ _description = _("Remove a network from a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveNetworkFromSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar='',
+ help=_("BGP speaker (name or ID)"))
+ parser.add_argument(
+ 'network',
+ metavar='',
+ help=_("Network to remove (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker)['id']
+ net_id = client.find_network(parsed_args.network)['id']
+ client.remove_gateway_network_from_speaker(speaker_id, net_id)
+
+
+class RemovePeerFromSpeaker(command.Command):
+ _description = _("Remove a peer from a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(RemovePeerFromSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar='',
+ help=_("BGP speaker (name or ID)"))
+ parser.add_argument(
+ 'bgp_peer',
+ metavar='',
+ help=_("BGP Peer to remove (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ speaker_id = client.find_bgp_speaker(parsed_args.bgp_speaker)['id']
+ peer_id = client.find_bgp_peer(parsed_args.bgp_peer)['id']
+ client.remove_bgp_peer_from_speaker(speaker_id, peer_id)
+
+
+class SetBgpSpeaker(command.Command):
+ _description = _("Set BGP speaker properties")
+
+ resource = constants.BGP_SPEAKER
+
+ def get_parser(self, prog_name):
+ parser = super(SetBgpSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar="",
+ help=_("BGP speaker to update (name or ID)")
+ )
+ parser.add_argument(
+ '--name',
+ help=_("New name for the BGP speaker"))
+ add_common_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ id = client.find_bgp_speaker(parsed_args.bgp_speaker)['id']
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ client.update_bgp_speaker(id, **attrs)
+
+
+class ShowBgpSpeaker(command.ShowOne):
+ _description = _("Show a BGP speaker")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowBgpSpeaker, self).get_parser(prog_name)
+ parser.add_argument(
+ 'bgp_speaker',
+ metavar="",
+ help=_("BGP speaker to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ id = client.find_bgp_speaker(parsed_args.bgp_speaker,
+ ignore_missing=False).id
+ obj = client.get_bgp_speaker(id)
+ display_columns, columns = nc_osc_utils._get_columns(obj)
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
diff --git a/quantumclient/common/__init__.py b/neutronclient/osc/v2/dynamic_routing/constants.py
similarity index 78%
rename from quantumclient/common/__init__.py
rename to neutronclient/osc/v2/dynamic_routing/constants.py
index 7e695ff08..8885b42ed 100644
--- a/quantumclient/common/__init__.py
+++ b/neutronclient/osc/v2/dynamic_routing/constants.py
@@ -1,7 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Copyright 2011 Nicira Networks, Inc.
-# All Rights Reserved.
-#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
@@ -13,4 +9,10 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-# @author: Somik Behera, Nicira Networks, Inc.
+
+BGP_SPEAKERS = 'bgp_speakers'
+BGP_SPEAKER = 'bgp_speaker'
+BGP_PEERS = 'bgp_peers'
+BGP_PEER = 'bgp_peer'
+MIN_AS_NUM = 1
+MAX_AS_NUM = 4294967295
diff --git a/neutronclient/osc/v2/fwaas/__init__.py b/neutronclient/osc/v2/fwaas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/fwaas/constants.py b/neutronclient/osc/v2/fwaas/constants.py
new file mode 100644
index 000000000..a5a74af73
--- /dev/null
+++ b/neutronclient/osc/v2/fwaas/constants.py
@@ -0,0 +1,28 @@
+# Copyright 2016-2017 FUJITSU LIMITED
+# 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.
+#
+
+# TODO(slaweq): remove it once "logging" OSC plugin will be moved to the
+# python-openstackclient repo
+
+FWG = 'firewall_group'
+FWGS = 'firewall_groups'
+FWP = 'firewall_policy'
+FWPS = 'firewall_policies'
+FWR = 'firewall_rule'
+FWRS = 'firewall_rules'
+CMD_FWG = 'fwaas_' + FWG
+CMD_FWP = 'fwaas_' + FWP
+CMD_FWR = 'fwaas_' + FWR
diff --git a/neutronclient/osc/v2/lbaas/__init__.py b/neutronclient/osc/v2/lbaas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/logging/__init__.py b/neutronclient/osc/v2/logging/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/logging/network_log.py b/neutronclient/osc/v2/logging/network_log.py
new file mode 100644
index 000000000..b3b8204c3
--- /dev/null
+++ b/neutronclient/osc/v2/logging/network_log.py
@@ -0,0 +1,304 @@
+# Copyright 2017-2018 FUJTISU LIMITED.
+# 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 copy
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+from oslo_log import log as logging
+
+from neutronclient._i18n import _
+from neutronclient.common import utils as nc_utils
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.fwaas import constants as fwaas_const
+
+LOG = logging.getLogger(__name__)
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('enabled', 'Enabled', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('target_id', 'Target', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+ ('resource_id', 'Resource', column_util.LIST_LONG_ONLY),
+ ('resource_type', 'Type', column_util.LIST_BOTH),
+ ('event', 'Event', column_util.LIST_LONG_ONLY),
+ ('summary', 'Summary', column_util.LIST_SHORT_ONLY),
+)
+
+_attr_map_for_loggable = (
+ ('type', 'Supported types', column_util.LIST_BOTH),
+)
+
+NET_LOG = 'network_log'
+
+
+def _get_common_parser(parser):
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description of the network log'))
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_('Enable this log'))
+ enable_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_('Disable this log (default is enabled)'))
+ return parser
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ client = client_manager.neutronclient
+
+ if is_create:
+ if 'project' in parsed_args and parsed_args.project is not None:
+ attrs['project_id'] = osc_utils.find_project(
+ client_manager.identity,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ resource_type = parsed_args.resource_type
+ attrs['resource_type'] = resource_type
+ if parsed_args.resource:
+ cmd_resource = None
+ if resource_type == fwaas_const.FWG:
+ cmd_resource = fwaas_const.CMD_FWG
+ attrs['resource_id'] = client.find_resource(
+ resource_type,
+ parsed_args.resource,
+ cmd_resource=cmd_resource)['id']
+
+ if parsed_args.target:
+ # NOTE(yushiro) Currently, we're supporting only port
+ attrs['target_id'] = client.find_resource(
+ 'port', parsed_args.target)['id']
+ if parsed_args.event:
+ attrs['event'] = parsed_args.event
+ if parsed_args.enable:
+ attrs['enabled'] = True
+ if parsed_args.disable:
+ attrs['enabled'] = False
+ if parsed_args.name:
+ attrs['name'] = parsed_args.name
+ if parsed_args.description:
+ attrs['description'] = parsed_args.description
+ return attrs
+
+
+class CreateNetworkLog(command.ShowOne):
+ _description = _("Create a new network log")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateNetworkLog, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ osc_utils.add_project_owner_option_to_parser(parser)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name for the network log'))
+ parser.add_argument(
+ '--event',
+ metavar='{ALL,ACCEPT,DROP}',
+ choices=['ALL', 'ACCEPT', 'DROP'],
+ type=nc_utils.convert_to_uppercase,
+ help=_('An event to store with log'))
+ # NOTE(yushiro) '--resource-type' is managed by following command:
+ # "openstack network loggable resources list". Therefore, this option
+ # shouldn't have "choices" like ['security_group', 'firewall_group']
+ parser.add_argument(
+ '--resource-type',
+ metavar='',
+ required=True,
+ type=nc_utils.convert_to_lowercase,
+ help=_('Network log type(s). '
+ 'You can see supported type(s) with following command:\n'
+ '$ openstack network loggable resources list'))
+ parser.add_argument(
+ '--resource',
+ metavar='',
+ help=_('Name or ID of resource (security group or firewall group) '
+ 'that used for logging. You can control for logging target '
+ 'combination with --target option.'))
+ parser.add_argument(
+ '--target',
+ metavar='',
+ help=_('Port (name or ID) for logging. You can control '
+ 'for logging target combination with --resource option.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_network_log({'log': attrs})['log']
+ columns, display_columns = column_util.get_columns(obj, _attr_map)
+ data = utils.get_dict_properties(obj, columns)
+ return (display_columns, data)
+
+
+class DeleteNetworkLog(command.Command):
+ _description = _("Delete network log(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkLog, self).get_parser(prog_name)
+ parser.add_argument(
+ 'network_log',
+ metavar='',
+ nargs='+',
+ help=_('Network log(s) to delete (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ result = 0
+ for log_res in parsed_args.network_log:
+ try:
+ log_id = client.find_resource(
+ 'log', log_res, cmd_resource=NET_LOG)['id']
+ client.delete_network_log(log_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete network log with "
+ "name or ID '%(network_log)s': %(e)s"),
+ {'network_log': log_res, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.network_log)
+ msg = (_("%(result)s of %(total)s network log(s) "
+ "failed to delete") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListLoggableResource(command.Lister):
+ _description = _("List supported loggable resources")
+
+ def get_parser(self, prog_name):
+ parser = super(ListLoggableResource, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ obj = client.list_network_loggable_resources()['loggable_resources']
+ headers, columns = column_util.get_column_definitions(
+ _attr_map_for_loggable, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(s, columns) for s in obj))
+
+
+class ListNetworkLog(command.Lister):
+ _description = _("List network logs")
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworkLog, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ help=_("List additional fields in output")
+ )
+ # TODO(yushiro): We'll support filtering in the future.
+ return parser
+
+ def _extend_list(self, data, parsed_args):
+ ext_data = copy.deepcopy(data)
+ for d in ext_data:
+ e_prefix = 'Event: '
+ if d['event']:
+ event = e_prefix + d['event'].upper()
+ port = '(port) ' + d['target_id'] if d['target_id'] else ''
+ resource_type = d['resource_type']
+ if d['resource_id']:
+ res = '(%s) %s' % (resource_type, d['resource_id'])
+ else:
+ res = ''
+ t_prefix = 'Logged: '
+ if port and res:
+ t = '%s on %s' % (res, port)
+ else:
+ # Either of res and port is empty, so concatenation works fine
+ t = res + port
+ target = t_prefix + t if t else t_prefix + '(None specified)'
+ d['summary'] = ',\n'.join([event, target])
+ return ext_data
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ obj = client.list_network_logs()['logs']
+ obj_extend = self._extend_list(obj, parsed_args)
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (
+ utils.get_dict_properties(s, columns) for s in obj_extend))
+
+
+class SetNetworkLog(command.Command):
+ _description = _("Set network log properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkLog, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ 'network_log',
+ metavar='',
+ help=_('Network log to set (name or ID)'))
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the network log'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ log_id = client.find_resource(
+ 'log', parsed_args.network_log, cmd_resource=NET_LOG)['id']
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ try:
+ client.update_network_log(log_id, {'log': attrs})
+ except Exception as e:
+ msg = (_("Failed to set network log '%(logging)s': %(e)s")
+ % {'logging': parsed_args.network_log, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowNetworkLog(command.ShowOne):
+ _description = _("Display network log details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkLog, self).get_parser(prog_name)
+ parser.add_argument(
+ 'network_log',
+ metavar='',
+ help=_('Network log to show (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ log_id = client.find_resource(
+ 'log', parsed_args.network_log, cmd_resource=NET_LOG)['id']
+ obj = client.show_network_log(log_id)['log']
+ columns, display_columns = column_util.get_columns(obj, _attr_map)
+ data = utils.get_dict_properties(obj, columns)
+ return (display_columns, data)
diff --git a/neutronclient/osc/v2/sfc/__init__.py b/neutronclient/osc/v2/sfc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/sfc/sfc_flow_classifier.py b/neutronclient/osc/v2/sfc/sfc_flow_classifier.py
new file mode 100755
index 000000000..775730fc2
--- /dev/null
+++ b/neutronclient/osc/v2/sfc/sfc_flow_classifier.py
@@ -0,0 +1,346 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# 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
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions as nc_exc
+
+LOG = logging.getLogger(__name__)
+
+resource = 'flow_classifier'
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('summary', 'Summary', column_util.LIST_SHORT_ONLY),
+ ('protocol', 'Protocol', column_util.LIST_LONG_ONLY),
+ ('ethertype', 'Ethertype', column_util.LIST_LONG_ONLY),
+ ('source_ip_prefix', 'Source IP',
+ column_util.LIST_LONG_ONLY),
+ ('destination_ip_prefix', 'Destination IP',
+ column_util.LIST_LONG_ONLY),
+ ('logical_source_port', 'Logical Source Port',
+ column_util.LIST_LONG_ONLY),
+ ('logical_destination_port', 'Logical Destination Port',
+ column_util.LIST_LONG_ONLY),
+ ('source_port_range_min', 'Source Port Range Min',
+ column_util.LIST_LONG_ONLY),
+ ('source_port_range_max', 'Source Port Range Max',
+ column_util.LIST_LONG_ONLY),
+ ('destination_port_range_min', 'Destination Port Range Min',
+ column_util.LIST_LONG_ONLY),
+ ('destination_port_range_max', 'Destination Port Range Max',
+ column_util.LIST_LONG_ONLY),
+ ('l7_parameters', 'L7 Parameters', column_util.LIST_LONG_ONLY),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'description': 'Description',
+ 'summary': 'Summary',
+ 'protocol': 'Protocol',
+ 'ethertype': 'Ethertype',
+ 'source_ip_prefix': 'Source IP',
+ 'destination_ip_prefix': 'Destination IP',
+ 'logical_source_port': 'Logical Source Port',
+ 'logical_destination_port': 'Logical Destination Port',
+ 'source_port_range_min': 'Source Port Range Min',
+ 'source_port_range_max': 'Source Port Range Max',
+ 'destination_port_range_min': 'Destination Port Range Min',
+ 'destination_port_range_max': 'Destination Port Range Max',
+ 'l7_parameters': 'L7 Parameters',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+
+class CreateSfcFlowClassifier(command.ShowOne):
+ _description = _("Create a flow classifier")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateSfcFlowClassifier, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the flow classifier'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the flow classifier'))
+ parser.add_argument(
+ '--protocol',
+ metavar='',
+ help=_('IP protocol name. Protocol name should be as per '
+ 'IANA standard.'))
+ parser.add_argument(
+ '--ethertype',
+ metavar='{IPv4,IPv6}',
+ default='IPv4', choices=['IPv4', 'IPv6'],
+ help=_('L2 ethertype, default is IPv4'))
+ parser.add_argument(
+ '--source-port',
+ metavar=':',
+ help=_('Source protocol port (allowed range [1,65535]. Must be '
+ 'specified as a:b, where a=min-port and b=max-port) '
+ 'in the allowed range.'))
+ parser.add_argument(
+ '--destination-port',
+ metavar=':',
+ help=_('Destination protocol port (allowed range [1,65535]. Must '
+ 'be specified as a:b, where a=min-port and b=max-port) '
+ 'in the allowed range.'))
+ parser.add_argument(
+ '--source-ip-prefix',
+ metavar='',
+ help=_('Source IP address in CIDR notation'))
+ parser.add_argument(
+ '--destination-ip-prefix',
+ metavar='',
+ help=_('Destination IP address in CIDR notation'))
+ parser.add_argument(
+ '--logical-source-port',
+ metavar='',
+ help=_('Neutron source port (name or ID)'))
+ parser.add_argument(
+ '--logical-destination-port',
+ metavar='',
+ help=_('Neutron destination port (name or ID)'))
+ parser.add_argument(
+ '--l7-parameters',
+ help=_('Dictionary of L7 parameters. Currently, no value is '
+ 'supported for this option.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_sfc_flow_classifier(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id', 'summary'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteSfcFlowClassifier(command.Command):
+ _description = _("Delete a given flow classifier")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSfcFlowClassifier, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flow_classifier',
+ metavar='',
+ nargs='+',
+ help=_("Flow classifier(s) to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for fcl in parsed_args.flow_classifier:
+ try:
+ fc_id = client.find_sfc_flow_classifier(
+ fcl, ignore_missing=False)['id']
+ client.delete_sfc_flow_classifier(fc_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete flow classifier with name "
+ "or ID '%(fc)s': %(e)s"), {'fc': fcl, 'e': e})
+ if result > 0:
+ total = len(parsed_args.flow_classifier)
+ msg = (_("%(result)s of %(total)s flow classifier(s) "
+ "failed to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListSfcFlowClassifier(command.Lister):
+ _description = _("List flow classifiers")
+
+ def get_parser(self, prog_name):
+ parser = super(ListSfcFlowClassifier, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def extend_list(self, data, parsed_args):
+ ext_data = []
+ for d in data:
+ val = []
+ protocol = d['protocol'].upper() if d['protocol'] else 'any'
+ val.append('protocol: ' + protocol)
+ val.append(self._get_protocol_port_details(d, 'source'))
+ val.append(self._get_protocol_port_details(d, 'destination'))
+ if 'logical_source_port' in d:
+ val.append('neutron_source_port: ' +
+ str(d['logical_source_port']))
+
+ if 'logical_destination_port' in d:
+ val.append('neutron_destination_port: ' +
+ str(d['logical_destination_port']))
+
+ if 'l7_parameters' in d:
+ l7_param = 'l7_parameters: {%s}' % ','.join(d['l7_parameters'])
+ val.append(l7_param)
+ d['summary'] = ',\n'.join(val)
+ ext_data.append(d)
+ return ext_data
+
+ def _get_protocol_port_details(self, data, val):
+ type_ip_prefix = val + '_ip_prefix'
+ ip_prefix = data.get(type_ip_prefix)
+ if not ip_prefix:
+ ip_prefix = 'any'
+ min_port = data.get(val + '_port_range_min')
+ if min_port is None:
+ min_port = 'any'
+ max_port = data.get(val + '_port_range_max')
+ if max_port is None:
+ max_port = 'any'
+ return '%s[port]: %s[%s:%s]' % (
+ val, ip_prefix, min_port, max_port)
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.sfc_flow_classifiers()
+ obj_extend = self.extend_list(obj, parsed_args)
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(
+ s, columns) for s in obj_extend))
+
+
+class SetSfcFlowClassifier(command.Command):
+ _description = _("Set flow classifier properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetSfcFlowClassifier, self).get_parser(prog_name)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the flow classifier'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the flow classifier'))
+ parser.add_argument(
+ 'flow_classifier',
+ metavar='',
+ help=_("Flow classifier to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ fc_id = client.find_sfc_flow_classifier(parsed_args.flow_classifier,
+ ignore_missing=False)['id']
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ try:
+ client.update_sfc_flow_classifier(fc_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to update flow classifier '%(fc)s': %(e)s")
+ % {'fc': parsed_args.flow_classifier, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowSfcFlowClassifier(command.ShowOne):
+ _description = _("Display flow classifier details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowSfcFlowClassifier, self).get_parser(prog_name)
+ parser.add_argument(
+ 'flow_classifier',
+ metavar='',
+ help=_("Flow classifier to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ fc_id = client.find_sfc_flow_classifier(parsed_args.flow_classifier,
+ ignore_missing=False)['id']
+ obj = client.get_sfc_flow_classifier(fc_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id', 'summary'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if is_create:
+ _get_attrs(client_manager, attrs, parsed_args)
+ return attrs
+
+
+def _get_attrs(client_manager, attrs, parsed_args):
+ if parsed_args.protocol is not None:
+ attrs['protocol'] = parsed_args.protocol
+ if parsed_args.ethertype:
+ attrs['ethertype'] = parsed_args.ethertype
+ if parsed_args.source_ip_prefix is not None:
+ attrs['source_ip_prefix'] = parsed_args.source_ip_prefix
+ if parsed_args.destination_ip_prefix is not None:
+ attrs['destination_ip_prefix'] = parsed_args.destination_ip_prefix
+ if parsed_args.logical_source_port is not None:
+ attrs['logical_source_port'] = client_manager.network.find_port(
+ parsed_args.logical_source_port, ignore_missing=False
+ )['id']
+ if parsed_args.logical_destination_port is not None:
+ attrs['logical_destination_port'] = client_manager.network.find_port(
+ parsed_args.logical_destination_port, ignore_missing=False
+ )['id']
+ if parsed_args.source_port is not None:
+ _fill_protocol_port_info(attrs, 'source',
+ parsed_args.source_port)
+ if parsed_args.destination_port is not None:
+ _fill_protocol_port_info(attrs, 'destination',
+ parsed_args.destination_port)
+ if parsed_args.l7_parameters is not None:
+ attrs['l7_parameters'] = parsed_args.l7_parameters
+
+
+def _fill_protocol_port_info(attrs, port_type, port_val):
+ min_port, sep, max_port = port_val.partition(":")
+ if not min_port:
+ msg = ("Invalid port value '%s', expected format is "
+ "min-port:max-port or min-port.")
+ raise argparse.ArgumentTypeError(msg % port_val)
+ if not max_port:
+ max_port = min_port
+ try:
+ attrs[port_type + '_port_range_min'] = int(min_port)
+ attrs[port_type + '_port_range_max'] = int(max_port)
+ except ValueError:
+ message = (_("Protocol port value %s must be an integer "
+ "or integer:integer.") % port_val)
+ raise nc_exc.CommandError(message=message)
diff --git a/neutronclient/osc/v2/sfc/sfc_port_chain.py b/neutronclient/osc/v2/sfc/sfc_port_chain.py
new file mode 100755
index 000000000..d96007b85
--- /dev/null
+++ b/neutronclient/osc/v2/sfc/sfc_port_chain.py
@@ -0,0 +1,381 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+
+from neutronclient._i18n import _
+
+LOG = logging.getLogger(__name__)
+
+resource = 'port_chain'
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('port_pair_groups', 'Port Pair Groups', column_util.LIST_BOTH),
+ ('flow_classifiers', 'Flow Classifiers',
+ column_util.LIST_BOTH),
+ ('chain_parameters', 'Chain Parameters',
+ column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'port_pair_groups': 'Port Pair Groups',
+ 'flow_classifiers': 'Flow Classifiers',
+ 'chain_parameters': 'Chain Parameters',
+ 'description': 'Description',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+
+class CreateSfcPortChain(command.ShowOne):
+ _description = _("Create a port chain")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateSfcPortChain, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the port chain'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the port chain'))
+ parser.add_argument(
+ '--flow-classifier',
+ default=[],
+ metavar='',
+ dest='flow_classifiers',
+ action='append',
+ help=_('Add flow classifier (name or ID). '
+ 'This option can be repeated.'))
+ parser.add_argument(
+ '--chain-parameters',
+ metavar='correlation=,symmetric=',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['correlation', 'symmetric'],
+ help=_('Dictionary of chain parameters. Supports '
+ 'correlation=(mpls|nsh) (default is mpls) '
+ 'and symmetric=(true|false).'))
+ parser.add_argument(
+ '--port-pair-group',
+ metavar='',
+ dest='port_pair_groups',
+ required=True,
+ action='append',
+ help=_('Add port pair group (name or ID). '
+ 'This option can be repeated.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_sfc_port_chain(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteSfcPortChain(command.Command):
+ _description = _("Delete a given port chain")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSfcPortChain, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_chain',
+ metavar="",
+ nargs='+',
+ help=_("Port chain(s) to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for pc in parsed_args.port_chain:
+ try:
+ pc_id = client.find_sfc_port_chain(
+ pc, ignore_missing=False)['id']
+ client.delete_sfc_port_chain(pc_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete port chain with name "
+ "or ID '%(pc)s': %(e)s"), {'pc': pc, 'e': e})
+ if result > 0:
+ total = len(parsed_args.port_chain)
+ msg = (_("%(result)s of %(total)s port chain(s) "
+ "failed to delete.") % {'result': result,
+ 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListSfcPortChain(command.Lister):
+ _description = _("List port chains")
+
+ def get_parser(self, prog_name):
+ parser = super(ListSfcPortChain, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ data = client.sfc_port_chains()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers,
+ (utils.get_dict_properties(s, columns)
+ for s in data))
+
+
+class SetSfcPortChain(command.Command):
+ _description = _("Set port chain properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetSfcPortChain, self).get_parser(prog_name)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the port chain'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the port chain'))
+ parser.add_argument(
+ '--flow-classifier',
+ metavar='',
+ dest='flow_classifiers',
+ action='append',
+ help=_('Add flow classifier (name or ID). '
+ 'This option can be repeated.'))
+ parser.add_argument(
+ '--no-flow-classifier',
+ action='store_true',
+ help=_('Remove associated flow classifiers from the port chain'))
+ parser.add_argument(
+ '--port-pair-group',
+ metavar='',
+ dest='port_pair_groups',
+ action='append',
+ help=_('Add port pair group (name or ID). '
+ 'Current port pair groups order is kept, the added port '
+ 'pair group will be placed at the end of the port chain. '
+ 'This option can be repeated.'))
+ parser.add_argument(
+ '--no-port-pair-group',
+ action='store_true',
+ help=_('Remove associated port pair groups from the port chain. '
+ 'At least one --port-pair-group must be specified '
+ 'together.'))
+ parser.add_argument(
+ 'port_chain',
+ metavar='',
+ help=_("Port chain to modify (name or ID)"))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ pc_id = client.find_sfc_port_chain(parsed_args.port_chain,
+ ignore_missing=False)['id']
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ if parsed_args.no_flow_classifier:
+ attrs['flow_classifiers'] = []
+ if parsed_args.flow_classifiers:
+ if parsed_args.no_flow_classifier:
+ fc_list = []
+ else:
+ fc_list = client.find_sfc_port_chain(
+ parsed_args.port_chain,
+ ignore_missing=False
+ )['flow_classifiers']
+ for fc in parsed_args.flow_classifiers:
+ fc_id = client.find_sfc_flow_classifier(
+ fc,
+ ignore_missing=False)['id']
+ if fc_id not in fc_list:
+ fc_list.append(fc_id)
+ attrs['flow_classifiers'] = fc_list
+ if (parsed_args.no_port_pair_group and not
+ parsed_args.port_pair_groups):
+ message = _('At least one --port-pair-group must be specified.')
+ raise exceptions.CommandError(message)
+ if parsed_args.no_port_pair_group and parsed_args.port_pair_groups:
+ ppg_list = []
+ for ppg in parsed_args.port_pair_groups:
+ ppg_id = client.find_sfc_port_pair_group(
+ ppg, ignore_missing=False)['id']
+ if ppg_id not in ppg_list:
+ ppg_list.append(ppg_id)
+ attrs['port_pair_groups'] = ppg_list
+ if (parsed_args.port_pair_groups and
+ not parsed_args.no_port_pair_group):
+ ppg_list = client.find_sfc_port_chain(
+ parsed_args.port_chain,
+ ignore_missing=False
+ )['port_pair_groups']
+ for ppg in parsed_args.port_pair_groups:
+ ppg_id = client.find_sfc_port_pair_group(
+ ppg, ignore_missing=False)['id']
+ if ppg_id not in ppg_list:
+ ppg_list.append(ppg_id)
+ attrs['port_pair_groups'] = ppg_list
+ try:
+ client.update_sfc_port_chain(pc_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to update port chain '%(pc)s': %(e)s")
+ % {'pc': parsed_args.port_chain, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowSfcPortChain(command.ShowOne):
+ _description = _("Display port chain details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowSfcPortChain, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_chain',
+ metavar="",
+ help=_("Port chain to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ pc_id = client.find_sfc_port_chain(parsed_args.port_chain,
+ ignore_missing=False)['id']
+ obj = client.get_sfc_port_chain(pc_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class UnsetSfcPortChain(command.Command):
+ _description = _("Unset port chain properties")
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetSfcPortChain, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_chain',
+ metavar='',
+ help=_("Port chain to unset (name or ID)"))
+ port_chain = parser.add_mutually_exclusive_group()
+ port_chain.add_argument(
+ '--flow-classifier',
+ action='append',
+ metavar='',
+ dest='flow_classifiers',
+ help=_('Remove flow classifier(s) from the port chain '
+ '(name or ID). This option can be repeated.'))
+ port_chain.add_argument(
+ '--all-flow-classifier',
+ action='store_true',
+ help=_('Remove all flow classifiers from the port chain'))
+ parser.add_argument(
+ '--port-pair-group',
+ metavar='',
+ dest='port_pair_groups',
+ action='append',
+ help=_('Remove port pair group(s) from the port chain '
+ '(name or ID). This option can be repeated.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ pc_id = client.find_sfc_port_chain(parsed_args.port_chain,
+ ignore_missing=False)['id']
+ attrs = {}
+ if parsed_args.flow_classifiers:
+ fc_list = client.find_sfc_port_chain(
+ parsed_args.port_chain, ignore_missing=False
+ )['flow_classifiers']
+ for fc in parsed_args.flow_classifiers:
+ fc_id = client.find_sfc_flow_classifier(
+ fc,
+ ignore_missing=False)['id']
+ if fc_id in fc_list:
+ fc_list.remove(fc_id)
+ attrs['flow_classifiers'] = fc_list
+ if parsed_args.all_flow_classifier:
+ attrs['flow_classifiers'] = []
+ if parsed_args.port_pair_groups:
+ ppg_list = client.find_sfc_port_chain(
+ parsed_args.port_chain,
+ ignore_missing=False)['port_pair_groups']
+ for ppg in parsed_args.port_pair_groups:
+ ppg_id = client.find_sfc_port_pair_group(
+ ppg,
+ ignore_missing=False)['id']
+ if ppg_id in ppg_list:
+ ppg_list.remove(ppg_id)
+ if ppg_list == []:
+ message = _('At least one port pair group must be'
+ ' specified.')
+ raise exceptions.CommandError(message)
+ attrs['port_pair_groups'] = ppg_list
+ try:
+ client.update_sfc_port_chain(pc_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to unset port chain '%(pc)s': %(e)s")
+ % {'pc': parsed_args.port_chain, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ client = client_manager.network
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.port_pair_groups:
+ attrs['port_pair_groups'] = [client.find_sfc_port_pair_group(
+ ppg, ignore_missing=False)['id']
+ for ppg in parsed_args.port_pair_groups]
+ if parsed_args.flow_classifiers:
+ attrs['flow_classifiers'] = [client.find_sfc_flow_classifier(
+ fc, ignore_missing=False)['id']
+ for fc in parsed_args.flow_classifiers]
+ if is_create is True:
+ _get_attrs(attrs, parsed_args)
+ return attrs
+
+
+def _get_attrs(attrs, parsed_args):
+ if parsed_args.chain_parameters is not None:
+ chain_params = {}
+ for chain_param in parsed_args.chain_parameters:
+ if 'correlation' in chain_param:
+ chain_params['correlation'] = chain_param['correlation']
+ if 'symmetric' in chain_param:
+ chain_params['symmetric'] = chain_param['symmetric']
+ attrs['chain_parameters'] = chain_params
diff --git a/neutronclient/osc/v2/sfc/sfc_port_pair.py b/neutronclient/osc/v2/sfc/sfc_port_pair.py
new file mode 100755
index 000000000..11e8e7631
--- /dev/null
+++ b/neutronclient/osc/v2/sfc/sfc_port_pair.py
@@ -0,0 +1,251 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+
+from neutronclient._i18n import _
+
+LOG = logging.getLogger(__name__)
+
+resource = 'port_pair'
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('ingress', 'Ingress Logical Port', column_util.LIST_BOTH),
+ ('egress', 'Egress Logical Port', column_util.LIST_BOTH),
+ ('service_function_parameters', 'Service Function Parameters',
+ column_util.LIST_LONG_ONLY),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'description': 'Description',
+ 'ingress': 'Ingress Logical Port',
+ 'egress': 'Egress Logical Port',
+ 'service_function_parameters': 'Service Function Parameters',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+
+class CreateSfcPortPair(command.ShowOne):
+ _description = _("Create a port pair")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateSfcPortPair, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the port pair'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the port pair'))
+ parser.add_argument(
+ '--service-function-parameters',
+ metavar='correlation=,weight=',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['correlation', 'weight'],
+ help=_('Dictionary of service function parameters. '
+ 'Currently, correlation=(None|mpls|nsh) and weight '
+ 'are supported. Weight is an integer that influences '
+ 'the selection of a port pair within a port pair group '
+ 'for a flow. The higher the weight, the more flows will '
+ 'hash to the port pair. The default weight is 1.'))
+ parser.add_argument(
+ '--ingress',
+ metavar='',
+ required=True,
+ help=_('Ingress neutron port (name or ID)'))
+ parser.add_argument(
+ '--egress',
+ metavar='',
+ required=True,
+ help=_('Egress neutron port (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_sfc_port_pair(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteSfcPortPair(command.Command):
+ _description = _("Delete a given port pair")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSfcPortPair, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_pair',
+ metavar="",
+ nargs='+',
+ help=_("Port pair(s) to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for pp in parsed_args.port_pair:
+ try:
+ port_pair_id = client.find_sfc_port_pair(
+ pp, ignore_missing=False)['id']
+ client.delete_sfc_port_pair(port_pair_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete port pair with name "
+ "or ID '%(port_pair)s': %(e)s"),
+ {'port_pair': pp, 'e': e})
+ if result > 0:
+ total = len(parsed_args.port_pair)
+ msg = (_("%(result)s of %(total)s port pair(s) "
+ "failed to delete.") % {'result': result,
+ 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListSfcPortPair(command.Lister):
+ _description = _("List port pairs")
+
+ def get_parser(self, prog_name):
+ parser = super(ListSfcPortPair, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ data = client.sfc_port_pairs()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in data))
+
+
+class SetSfcPortPair(command.Command):
+ _description = _("Set port pair properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetSfcPortPair, self).get_parser(prog_name)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the port pair'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the port pair'))
+ parser.add_argument(
+ 'port_pair',
+ metavar='',
+ help=_("Port pair to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ port_pair_id = client.find_sfc_port_pair(
+ parsed_args.port_pair, ignore_missing=False
+ )['id']
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ try:
+ client.update_sfc_port_pair(port_pair_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to update port pair '%(port_pair)s': %(e)s")
+ % {'port_pair': parsed_args.port_pair, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowSfcPortPair(command.ShowOne):
+ _description = _("Display port pair details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowSfcPortPair, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_pair',
+ metavar='',
+ help=_("Port pair to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ port_pair_id = client.find_sfc_port_pair(
+ parsed_args.port_pair, ignore_missing=False
+ )['id']
+ obj = client.get_sfc_port_pair(port_pair_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if is_create:
+ _get_attrs(client_manager, attrs, parsed_args)
+ return attrs
+
+
+def _get_attrs(client_manager, attrs, parsed_args):
+ client = client_manager.network
+ if parsed_args.ingress is not None:
+ attrs['ingress'] = client.find_port(
+ parsed_args.ingress, ignore_missing=False
+ )['id']
+ if parsed_args.egress is not None:
+ attrs['egress'] = client.find_port(
+ parsed_args.egress, ignore_missing=False
+ )['id']
+ if parsed_args.service_function_parameters is not None:
+ attrs['service_function_parameters'] = _get_service_function_params(
+ parsed_args.service_function_parameters)
+
+
+def _get_service_function_params(sf_params):
+ attrs = {}
+ for sf_param in sf_params:
+ if 'correlation' in sf_param:
+ if sf_param['correlation'] == 'None':
+ attrs['correlation'] = None
+ else:
+ attrs['correlation'] = sf_param['correlation']
+ if 'weight' in sf_param:
+ attrs['weight'] = sf_param['weight']
+ return attrs
diff --git a/neutronclient/osc/v2/sfc/sfc_port_pair_group.py b/neutronclient/osc/v2/sfc/sfc_port_pair_group.py
new file mode 100755
index 000000000..ec8859b42
--- /dev/null
+++ b/neutronclient/osc/v2/sfc/sfc_port_pair_group.py
@@ -0,0 +1,326 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+
+from neutronclient._i18n import _
+
+LOG = logging.getLogger(__name__)
+
+resource = 'port_pair_group'
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('port_pairs', 'Port Pair', column_util.LIST_BOTH),
+ ('port_pair_group_parameters', 'Port Pair Group Parameters',
+ column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+ ('is_tap_enabled', 'Tap Enabled', column_util.LIST_BOTH)
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'description': 'Description',
+ 'port_pairs': 'Port Pair',
+ 'port_pair_group_parameters': 'Port Pair Group Parameters',
+ 'is_tap_enabled': 'Tap Enabled',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+
+class CreateSfcPortPairGroup(command.ShowOne):
+ _description = _("Create a port pair group")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateSfcPortPairGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the port pair group'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the port pair group'))
+ parser.add_argument(
+ '--port-pair',
+ metavar='',
+ dest='port_pairs',
+ default=[],
+ action='append',
+ help=_('Port pair (name or ID). '
+ 'This option can be repeated.'))
+ tap_enable = parser.add_mutually_exclusive_group()
+ tap_enable.add_argument(
+ '--enable-tap',
+ action='store_true',
+ help=_('Port pairs of this port pair group are deployed as '
+ 'passive tap service function')
+ )
+ tap_enable.add_argument(
+ '--disable-tap',
+ action='store_true',
+ help=_('Port pairs of this port pair group are deployed as l3 '
+ 'service function (default)')
+ )
+ parser.add_argument(
+ '--port-pair-group-parameters',
+ metavar='lb-fields=',
+ action=parseractions.KeyValueAction,
+ help=_('Dictionary of port pair group parameters. '
+ 'Currently only one parameter lb-fields is supported. '
+ ' is a & separated list of load-balancing '
+ 'fields.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_sfc_port_pair_group(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteSfcPortPairGroup(command.Command):
+ _description = _("Delete a given port pair group")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSfcPortPairGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_pair_group',
+ metavar='',
+ nargs='+',
+ help=_("Port pair group(s) to delete (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for ppg in parsed_args.port_pair_group:
+ try:
+ ppg_id = client.find_sfc_port_pair_group(
+ ppg, ignore_missing=False)['id']
+ client.delete_sfc_port_pair_group(ppg_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete port pair group with name "
+ "or ID '%(ppg)s': %(e)s"), {'ppg': ppg, 'e': e})
+ if result > 0:
+ total = len(parsed_args.port_pair_group)
+ msg = (_("%(result)s of %(total)s port pair group(s) "
+ "failed to delete.") % {'result': result,
+ 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListSfcPortPairGroup(command.Lister):
+ _description = _("List port pair group")
+
+ def get_parser(self, prog_name):
+ parser = super(ListSfcPortPairGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ data = client.sfc_port_pair_groups()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in data))
+
+
+class SetSfcPortPairGroup(command.Command):
+ _description = _("Set port pair group properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetSfcPortPairGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_pair_group',
+ metavar='',
+ help=_("Port pair group to modify (name or ID)"))
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the port pair group'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the port pair group'))
+ parser.add_argument(
+ '--port-pair',
+ metavar='',
+ dest='port_pairs',
+ default=[],
+ action='append',
+ help=_('Port pair (name or ID). '
+ 'This option can be repeated.'))
+ parser.add_argument(
+ '--no-port-pair',
+ action='store_true',
+ help=_('Remove all port pair from port pair group'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ ppg_id = client.find_sfc_port_pair_group(
+ parsed_args.port_pair_group)['id']
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ if parsed_args.no_port_pair:
+ attrs['port_pairs'] = []
+ if parsed_args.port_pairs:
+ added = [client.find_sfc_port_pair(pp,
+ ignore_missing=False)['id']
+ for pp in parsed_args.port_pairs]
+ if parsed_args.no_port_pair:
+ existing = []
+ else:
+ existing = client.find_sfc_port_pair_group(
+ parsed_args.port_pair_group,
+ ignore_missing=False)['port_pairs']
+ attrs['port_pairs'] = sorted(list(set(existing) | set(added)))
+ try:
+ client.update_sfc_port_pair_group(ppg_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to update port pair group '%(ppg)s': %(e)s")
+ % {'ppg': parsed_args.port_pair_group, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowSfcPortPairGroup(command.ShowOne):
+ _description = _("Display port pair group details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowSfcPortPairGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_pair_group',
+ metavar='',
+ help=_("Port pair group to display (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ ppg_id = client.find_sfc_port_pair_group(
+ parsed_args.port_pair_group, ignore_missing=False)['id']
+ obj = client.get_sfc_port_pair_group(ppg_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class UnsetSfcPortPairGroup(command.Command):
+ _description = _("Unset port pairs from port pair group")
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetSfcPortPairGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'port_pair_group',
+ metavar='',
+ help=_("Port pair group to unset (name or ID)"))
+ port_pair_group = parser.add_mutually_exclusive_group()
+ port_pair_group.add_argument(
+ '--port-pair',
+ action='append',
+ metavar='',
+ dest='port_pairs',
+ help=_('Remove port pair(s) from the port pair group '
+ '(name or ID). This option can be repeated.'))
+ port_pair_group.add_argument(
+ '--all-port-pair',
+ action='store_true',
+ help=_('Remove all port pairs from the port pair group'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ ppg_id = client.find_sfc_port_pair_group(
+ parsed_args.port_pair_group, ignore_missing=False)['id']
+ attrs = {}
+ if parsed_args.port_pairs:
+ existing = client.find_sfc_port_pair_group(
+ parsed_args.port_pair_group,
+ ignore_missing=False)['port_pairs']
+ removed = [client.find_sfc_port_pair(pp,
+ ignore_missing=False)['id']
+ for pp in parsed_args.port_pairs]
+ attrs['port_pairs'] = list(set(existing) - set(removed))
+ if parsed_args.all_port_pair:
+ attrs['port_pairs'] = []
+ try:
+ client.update_sfc_port_pair_group(ppg_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to unset port pair group '%(ppg)s': %(e)s")
+ % {'ppg': parsed_args.port_pair_group, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+def _get_ppg_param(attrs, ppg):
+ attrs['port_pair_group_parameters'] = {}
+ for key, value in ppg.items():
+ if key == 'lb-fields':
+ attrs['port_pair_group_parameters']['lb_fields'] = ([
+ field for field in value.split('&') if field])
+ else:
+ attrs['port_pair_group_parameters'][key] = value
+ return attrs['port_pair_group_parameters']
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ client = client_manager.network
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = parsed_args.name
+ if parsed_args.description is not None:
+ attrs['description'] = parsed_args.description
+ if parsed_args.port_pairs:
+ attrs['port_pairs'] = [client.find_sfc_port_pair(
+ pp, ignore_missing=False)['id']
+ for pp in parsed_args.port_pairs]
+
+ if is_create:
+ _get_attrs(attrs, parsed_args)
+ return attrs
+
+
+def _get_attrs(attrs, parsed_args):
+ if parsed_args.port_pair_group_parameters is not None:
+ attrs['port_pair_group_parameters'] = (
+ _get_ppg_param(attrs, parsed_args.port_pair_group_parameters))
+ if parsed_args.enable_tap:
+ attrs['tap_enabled'] = True
+ if parsed_args.disable_tap:
+ attrs['tap_enabled'] = False
diff --git a/neutronclient/osc/v2/sfc/sfc_service_graph.py b/neutronclient/osc/v2/sfc/sfc_service_graph.py
new file mode 100644
index 000000000..2edf2ccd0
--- /dev/null
+++ b/neutronclient/osc/v2/sfc/sfc_service_graph.py
@@ -0,0 +1,272 @@
+# Copyright 2017 Intel Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+
+from neutronclient._i18n import _
+
+LOG = logging.getLogger(__name__)
+
+resource = 'service_graph'
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('port_chains', 'Branching Points', column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'description': 'Description',
+ 'port_chains': 'Branching Points',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+
+class CreateSfcServiceGraph(command.ShowOne):
+ """Create a service graph."""
+ def get_parser(self, prog_name):
+ parser = super(CreateSfcServiceGraph, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the service graph.'))
+ parser.add_argument(
+ '--description',
+ help=_('Description for the service graph.'))
+ parser.add_argument(
+ '--branching-point',
+ metavar='SRC_CHAIN:DST_CHAIN_1,DST_CHAIN_2,DST_CHAIN_N',
+ dest='branching_points',
+ action='append',
+ default=[], required=True,
+ help=_('Service graph branching point: the key is the source '
+ 'Port Chain while the value is a list of destination '
+ 'Port Chains. This option can be repeated.'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ try:
+ obj = client.create_sfc_service_graph(**attrs)
+ display_columns, columns = \
+ utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+ except Exception as e:
+ msg = (_("Failed to create service graph using '%(pcs)s': %(e)s")
+ % {'pcs': parsed_args.branching_points, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class SetSfcServiceGraph(command.Command):
+ _description = _("Set service graph properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetSfcServiceGraph, self).get_parser(prog_name)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the service graph'))
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the service graph'))
+ parser.add_argument(
+ 'service_graph',
+ metavar='',
+ help=_("Service graph to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ service_graph_id = client.find_sfc_service_graph(
+ parsed_args.service_graph, ignore_missing=False)['id']
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ try:
+ client.update_sfc_service_graph(service_graph_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to update service graph "
+ "'%(service_graph)s': %(e)s")
+ % {'service_graph': parsed_args.service_graph, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class DeleteSfcServiceGraph(command.Command):
+ """Delete a given service graph."""
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSfcServiceGraph, self).get_parser(prog_name)
+ parser.add_argument(
+ 'service_graph',
+ metavar="",
+ nargs='+',
+ help=_("ID or name of the service graph(s) to delete.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for sg in parsed_args.service_graph:
+ try:
+ sg_id = client.find_sfc_service_graph(
+ sg, ignore_missing=False)['id']
+ client.delete_sfc_service_graph(sg_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete service graph with name "
+ "or ID '%(sg)s': %(e)s"),
+ {'sg': sg, 'e': e})
+ if result > 0:
+ total = len(parsed_args.service_graph)
+ msg = (_("%(result)s of %(total)s service graph(s) "
+ "failed to delete.") % {'result': result,
+ 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListSfcServiceGraph(command.Lister):
+ _description = _("List service graphs")
+
+ def get_parser(self, prog_name):
+ parser = super(ListSfcServiceGraph, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ data = client.sfc_service_graphs()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers,
+ (utils.get_dict_properties(s, columns)
+ for s in data))
+
+
+class ShowSfcServiceGraph(command.ShowOne):
+ """Show information of a given service graph."""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowSfcServiceGraph, self).get_parser(prog_name)
+ parser.add_argument(
+ 'service_graph',
+ metavar="",
+ help=_("ID or name of the service graph to display.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ sg_id = client.find_sfc_service_graph(parsed_args.service_graph,
+ ignore_missing=False)['id']
+ obj = client.get_sfc_service_graph(sg_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
+ if parsed_args.description is not None:
+ attrs['description'] = str(parsed_args.description)
+ if is_create:
+ _get_attrs_for_create(client_manager, attrs, parsed_args)
+ return attrs
+
+
+def _validate_destination_chains(comma_split, attrs, client, sc_):
+ for e in comma_split:
+ if e != "":
+ dc_ = client.find_sfc_port_chain(e, ignore_missing=False)['id']
+ attrs['port_chains'][sc_].append(dc_)
+ if _check_cycle(attrs['port_chains'], sc_, dc_):
+ raise exceptions.CommandError(
+ "Error: Service graph contains a cycle")
+ else:
+ raise exceptions.CommandError(
+ "Error: you must specify at least one "
+ "destination chain for each source chain")
+ return attrs
+
+
+def _check_cycle(graph, new_src, new_dest):
+ for src in graph:
+ if src == new_dest:
+ if _visit(graph, src, new_dest, new_src):
+ return True
+ return False
+
+
+def _visit(graph, src, new_dest, new_src):
+ if src in graph:
+ found_cycle = False
+ for dest in graph[src]:
+ if new_src == dest or found_cycle:
+ return True
+ else:
+ found_cycle = _visit(graph, dest, new_dest, new_src)
+ return False
+
+
+def _get_attrs_for_create(client_manager, attrs, parsed_args):
+ client = client_manager.network
+ if parsed_args.branching_points:
+ attrs['port_chains'] = {}
+ src_chain = None
+ for c in parsed_args.branching_points:
+ if ':' not in c:
+ raise exceptions.CommandError(
+ "Error: You must specify at least one "
+ "destination chain for each source chain.")
+ colon_split = c.split(':')
+ src_chain = colon_split.pop(0)
+ sc_ = client.find_sfc_port_chain(src_chain,
+ ignore_missing=False)['id']
+ for i in colon_split:
+ comma_split = i.split(',')
+ unique = set(comma_split)
+ if len(unique) != len(comma_split):
+ raise exceptions.CommandError(
+ "Error: Duplicate "
+ "destination chains from "
+ "source chain {}".format(src_chain))
+ if sc_ in attrs['port_chains']:
+ raise exceptions.CommandError(
+ "Error: Source chain {} is in "
+ "use already ".format(src_chain))
+ attrs['port_chains'][sc_] = []
+ _validate_destination_chains(
+ comma_split, attrs, client, sc_)
diff --git a/neutronclient/osc/v2/subnet_onboard/__init__.py b/neutronclient/osc/v2/subnet_onboard/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/subnet_onboard/subnet_onboard.py b/neutronclient/osc/v2/subnet_onboard/subnet_onboard.py
new file mode 100644
index 000000000..10c6ab4c2
--- /dev/null
+++ b/neutronclient/osc/v2/subnet_onboard/subnet_onboard.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2019 SUSE Linux Products GmbH
+# 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.
+#
+
+"""Subnet onboard action implementation"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+
+from neutronclient._i18n import _
+
+LOG = logging.getLogger(__name__)
+
+
+class NetworkOnboardSubnets(command.Command):
+ """Onboard network subnets into a subnet pool"""
+
+ def get_parser(self, prog_name):
+ parser = super(NetworkOnboardSubnets, self).get_parser(prog_name)
+ parser.add_argument(
+ 'network',
+ metavar="",
+ help=_("Onboard all subnets associated with this network")
+ )
+ parser.add_argument(
+ 'subnetpool',
+ metavar="",
+ help=_("Target subnet pool for onboarding subnets")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ subnetpool_id = _get_id(client, parsed_args.subnetpool, 'subnetpool')
+ network_id = _get_id(client, parsed_args.network, 'network')
+ body = {'network_id': network_id}
+ try:
+ client.onboard_network_subnets(subnetpool_id, body)
+ except Exception as e:
+ msg = (_("Failed to onboard subnets for network '%(n)s': %(e)s")
+ % {'n': parsed_args.network, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+def _get_id(client, id_or_name, resource):
+ return client.find_resource(resource, str(id_or_name))['id']
diff --git a/neutronclient/osc/v2/utils.py b/neutronclient/osc/v2/utils.py
new file mode 100644
index 000000000..b03e29c3d
--- /dev/null
+++ b/neutronclient/osc/v2/utils.py
@@ -0,0 +1,24 @@
+# Copyright 2016 NEC Corporation
+#
+# 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.
+
+"""This module is intended to contain methods specific
+to Networking v2 API and its extensions.
+"""
+
+from cliff import columns as cliff_columns
+
+
+class AdminStateColumn(cliff_columns.FormattableColumn):
+ def human_readable(self):
+ return 'UP' if self._value else 'DOWN'
diff --git a/neutronclient/osc/v2/vpnaas/__init__.py b/neutronclient/osc/v2/vpnaas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/osc/v2/vpnaas/endpoint_group.py b/neutronclient/osc/v2/vpnaas/endpoint_group.py
new file mode 100644
index 000000000..e37c2cef4
--- /dev/null
+++ b/neutronclient/osc/v2/vpnaas/endpoint_group.py
@@ -0,0 +1,222 @@
+# Copyright 2017 FUJITSU LIMITED
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+from oslo_log import log as logging
+
+from neutronclient._i18n import _
+from neutronclient.osc import utils as osc_utils
+
+
+LOG = logging.getLogger(__name__)
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('type', 'Type', column_util.LIST_BOTH),
+ ('endpoints', 'Endpoints', column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'type': 'Type',
+ 'endpoints': 'Endpoints',
+ 'description': 'Description',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+
+def _get_common_parser(parser):
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the endpoint group'))
+ return parser
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if is_create:
+ if parsed_args.project is not None:
+ attrs['tenant_id'] = osc_utils.find_project(
+ client_manager.identity,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ if parsed_args.description:
+ attrs['description'] = parsed_args.description
+ return attrs
+
+
+class CreateEndpointGroup(command.ShowOne):
+ _description = _("Create an endpoint group")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateEndpointGroup, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name for the endpoint group'))
+ parser.add_argument(
+ '--type',
+ required=True,
+ help=_('Type of endpoints in group (e.g. subnet, cidr)'))
+ parser.add_argument(
+ '--value',
+ action='append',
+ dest='endpoints',
+ required=True,
+ help=_('Endpoint(s) for the group. Must all be of the same type. '
+ '(--value) option can be repeated'))
+ osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ attrs['type'] = parsed_args.type
+ if parsed_args.type == 'subnet':
+ _subnet_ids = [client.find_subnet(
+ endpoint,
+ ignore_missing=False)['id']
+ for endpoint in parsed_args.endpoints]
+ attrs['endpoints'] = _subnet_ids
+ else:
+ attrs['endpoints'] = parsed_args.endpoints
+ obj = client.create_vpn_endpoint_group(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteEndpointGroup(command.Command):
+ _description = _("Delete endpoint group(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteEndpointGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'endpoint_group',
+ metavar='',
+ nargs='+',
+ help=_('Endpoint group(s) to delete (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for endpoint in parsed_args.endpoint_group:
+ try:
+ endpoint_id = client.find_vpn_endpoint_group(
+ endpoint, ignore_missing=False)['id']
+ client.delete_vpn_endpoint_group(endpoint_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete endpoint group with "
+ "name or ID '%(endpoint_group)s': %(e)s"),
+ {'endpoint_group': endpoint, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.endpoint_group)
+ msg = (_("%(result)s of %(total)s endpoint group failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListEndpointGroup(command.Lister):
+ _description = _("List endpoint groups that belong to a given project")
+
+ def get_parser(self, prog_name):
+ parser = super(ListEndpointGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.vpn_endpoint_groups()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(s, columns) for s in obj))
+
+
+class SetEndpointGroup(command.Command):
+ _description = _("Set endpoint group properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetEndpointGroup, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Set a name for the endpoint group'))
+ parser.add_argument(
+ 'endpoint_group',
+ metavar='',
+ help=_('Endpoint group to set (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager,
+ parsed_args, is_create=False)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ endpoint_id = client.find_vpn_endpoint_group(
+ parsed_args.endpoint_group, ignore_missing=False)['id']
+ try:
+ client.update_vpn_endpoint_group(endpoint_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to set endpoint group "
+ "%(endpoint_group)s: %(e)s")
+ % {'endpoint_group': parsed_args.endpoint_group, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowEndpointGroup(command.ShowOne):
+ _description = _("Display endpoint group details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowEndpointGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'endpoint_group',
+ metavar='',
+ help=_('Endpoint group to display (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ endpoint_id = client.find_vpn_endpoint_group(
+ parsed_args.endpoint_group, ignore_missing=False)['id']
+ obj = client.get_vpn_endpoint_group(endpoint_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return (display_columns, data)
diff --git a/neutronclient/osc/v2/vpnaas/ikepolicy.py b/neutronclient/osc/v2/vpnaas/ikepolicy.py
new file mode 100644
index 000000000..0a8aecb08
--- /dev/null
+++ b/neutronclient/osc/v2/vpnaas/ikepolicy.py
@@ -0,0 +1,318 @@
+# Copyright 2017 FUJITSU LIMITED
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+from oslo_log import log as logging
+
+from neutronclient._i18n import _
+from neutronclient.common import utils as nc_utils
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import utils as vpn_utils
+
+
+LOG = logging.getLogger(__name__)
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('auth_algorithm', 'Authentication Algorithm', column_util.LIST_BOTH),
+ ('encryption_algorithm', 'Encryption Algorithm', column_util.LIST_BOTH),
+ ('ike_version', 'IKE Version', column_util.LIST_BOTH),
+ ('pfs', 'Perfect Forward Secrecy (PFS)', column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('phase1_negotiation_mode', 'Phase1 Negotiation Mode',
+ column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+ ('lifetime', 'Lifetime', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'auth_algorithm': 'Authentication Algorithm',
+ 'encryption_algorithm': 'Encryption Algorithm',
+ 'ike_version': 'IKE Version',
+ 'pfs': 'Perfect Forward Secrecy (PFS)',
+ 'phase1_negotiation_mode': 'Phase1 Negotiation Mode',
+ 'lifetime': 'Lifetime',
+ 'description': 'Description',
+ 'tenant_id': 'Project',
+ 'project_id': 'Project',
+}
+
+_auth_algorithms = [
+ 'sha1',
+ 'sha256',
+ 'sha384',
+ 'sha512',
+ 'aes-xcbc',
+ 'aes-cmac',
+]
+
+_encryption_algorithms = [
+ '3des',
+ 'aes-128',
+ 'aes-192',
+ 'aes-256',
+ 'aes-128-ccm-8',
+ 'aes-192-ccm-8',
+ 'aes-256-ccm-8',
+ 'aes-128-ccm-12',
+ 'aes-192-ccm-12',
+ 'aes-256-ccm-12',
+ 'aes-128-ccm-16',
+ 'aes-192-ccm-16',
+ 'aes-256-ccm-16',
+ 'aes-128-gcm-8',
+ 'aes-192-gcm-8',
+ 'aes-256-gcm-8',
+ 'aes-128-gcm-12',
+ 'aes-192-gcm-12',
+ 'aes-256-gcm-12',
+ 'aes-128-gcm-16',
+ 'aes-192-gcm-16',
+ 'aes-256-gcm-16',
+ 'aes-128-ctr',
+ 'aes-192-ctr',
+ 'aes-256-ctr',
+]
+
+_pfs_groups = [
+ 'group2',
+ 'group5',
+ 'group14',
+ 'group15',
+ 'group16',
+ 'group17',
+ 'group18',
+ 'group19',
+ 'group20',
+ 'group21',
+ 'group22',
+ 'group23',
+ 'group24',
+ 'group25',
+ 'group26',
+ 'group27',
+ 'group28',
+ 'group29',
+ 'group30',
+ 'group31',
+]
+
+
+def _convert_to_lowercase(string):
+ return string.lower()
+
+
+def _get_common_parser(parser):
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description of the IKE policy'))
+ parser.add_argument(
+ '--auth-algorithm',
+ choices=_auth_algorithms,
+ type=_convert_to_lowercase,
+ help=_('Authentication algorithm'))
+ parser.add_argument(
+ '--encryption-algorithm',
+ choices=_encryption_algorithms,
+ type=_convert_to_lowercase,
+ help=_('Encryption algorithm'))
+ parser.add_argument(
+ '--phase1-negotiation-mode',
+ choices=['main', 'aggressive'],
+ type=_convert_to_lowercase,
+ help=_('IKE Phase1 negotiation mode'))
+ parser.add_argument(
+ '--ike-version',
+ choices=['v1', 'v2'],
+ type=_convert_to_lowercase,
+ help=_('IKE version for the policy'))
+ parser.add_argument(
+ '--pfs',
+ choices=_pfs_groups,
+ type=_convert_to_lowercase,
+ help=_('Perfect Forward Secrecy'))
+ parser.add_argument(
+ '--lifetime',
+ metavar="units=UNITS,value=VALUE",
+ type=nc_utils.str2dict_type(optional_keys=['units', 'value']),
+ help=vpn_utils.lifetime_help("IKE"))
+ return parser
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if is_create:
+ if 'project' in parsed_args and parsed_args.project is not None:
+ attrs['project_id'] = osc_utils.find_project(
+ client_manager.identity,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ if parsed_args.description:
+ attrs['description'] = parsed_args.description
+ if parsed_args.auth_algorithm:
+ attrs['auth_algorithm'] = parsed_args.auth_algorithm
+ if parsed_args.encryption_algorithm:
+ attrs['encryption_algorithm'] = parsed_args.encryption_algorithm
+ if parsed_args.phase1_negotiation_mode:
+ attrs['phase1_negotiation_mode'] = parsed_args.phase1_negotiation_mode
+ if parsed_args.ike_version:
+ attrs['ike_version'] = parsed_args.ike_version
+ if parsed_args.pfs:
+ attrs['pfs'] = parsed_args.pfs
+ if parsed_args.lifetime:
+ vpn_utils.validate_lifetime_dict(parsed_args.lifetime)
+ attrs['lifetime'] = parsed_args.lifetime
+ return attrs
+
+
+class CreateIKEPolicy(command.ShowOne):
+ _description = _("Create an IKE policy")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateIKEPolicy, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the IKE policy'))
+ osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ obj = client.create_vpn_ike_policy(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id', 'units', 'value'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteIKEPolicy(command.Command):
+ _description = _("Delete IKE policy (policies)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteIKEPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'ikepolicy',
+ metavar='',
+ nargs='+',
+ help=_('IKE policy to delete (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for ike in parsed_args.ikepolicy:
+ try:
+ ike_id = client.find_vpn_ike_policy(ike,
+ ignore_missing=False)['id']
+ client.delete_vpn_ike_policy(ike_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete IKE policy with "
+ "name or ID '%(ikepolicy)s': %(e)s"),
+ {'ikepolicy': ike, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.ikepolicy)
+ msg = (_("%(result)s of %(total)s IKE policy failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListIKEPolicy(command.Lister):
+ _description = _("List IKE policies that belong to a given project")
+
+ def get_parser(self, prog_name):
+ parser = super(ListIKEPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.vpn_ike_policies()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(s, columns) for s in obj))
+
+
+class SetIKEPolicy(command.Command):
+ _description = _("Set IKE policy properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetIKEPolicy, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the IKE policy'))
+ parser.add_argument(
+ 'ikepolicy',
+ metavar='',
+ help=_('IKE policy to set (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager,
+ parsed_args, is_create=False)
+ if parsed_args.name:
+ attrs['name'] = parsed_args.name
+ ike_id = client.find_vpn_ike_policy(parsed_args.ikepolicy,
+ ignore_missing=False)['id']
+ try:
+ client.update_vpn_ike_policy(ike_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to set IKE policy '%(ike)s': %(e)s")
+ % {'ike': parsed_args.ikepolicy, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowIKEPolicy(command.ShowOne):
+ _description = _("Display IKE policy details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowIKEPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'ikepolicy',
+ metavar='',
+ help=_('IKE policy to display (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ ike_id = client.find_vpn_ike_policy(parsed_args.ikepolicy,
+ ignore_missing=False)['id']
+ obj = client.get_vpn_ike_policy(ike_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id', 'units', 'value'])
+ data = utils.get_dict_properties(obj, columns)
+ return (display_columns, data)
diff --git a/neutronclient/osc/v2/vpnaas/ipsec_site_connection.py b/neutronclient/osc/v2/vpnaas/ipsec_site_connection.py
new file mode 100644
index 000000000..1497793b0
--- /dev/null
+++ b/neutronclient/osc/v2/vpnaas/ipsec_site_connection.py
@@ -0,0 +1,385 @@
+# Copyright 2017 FUJITSU LIMITED
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.cli import format_columns
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+from oslo_log import log as logging
+
+from neutronclient._i18n import _
+from neutronclient.common import utils as nc_utils
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import utils as vpn_utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+_formatters = {
+ 'peer_cidrs': format_columns.ListColumn
+}
+
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('peer_address', 'Peer Address', column_util.LIST_BOTH),
+ ('auth_mode', 'Authentication Algorithm', column_util.LIST_BOTH),
+ ('status', 'Status', column_util.LIST_BOTH),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+ ('peer_cidrs', 'Peer CIDRs', column_util.LIST_LONG_ONLY),
+ ('vpnservice_id', 'VPN Service', column_util.LIST_LONG_ONLY),
+ ('ipsecpolicy_id', 'IPSec Policy', column_util.LIST_LONG_ONLY),
+ ('ikepolicy_id', 'IKE Policy', column_util.LIST_LONG_ONLY),
+ ('mtu', 'MTU', column_util.LIST_LONG_ONLY),
+ ('initiator', 'Initiator', column_util.LIST_LONG_ONLY),
+ ('is_admin_state_up', 'State', column_util.LIST_LONG_ONLY),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('psk', 'Pre-shared Key', column_util.LIST_LONG_ONLY),
+ ('route_mode', 'Route Mode', column_util.LIST_LONG_ONLY),
+ ('local_id', 'Local ID', column_util.LIST_LONG_ONLY),
+ ('peer_id', 'Peer ID', column_util.LIST_LONG_ONLY),
+ ('local_ep_group_id', 'Local Endpoint Group ID',
+ column_util.LIST_LONG_ONLY),
+ ('peer_ep_group_id', 'Peer Endpoint Group ID', column_util.LIST_LONG_ONLY),
+ ('dpd', 'DPD', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'peer_address': 'Peer Address',
+ 'auth_mode': 'Authentication Algorithm',
+ 'status': 'Status',
+ 'peer_cidrs': 'Peer CIDRs',
+ 'vpnservice_id': 'VPN Service',
+ 'ipsecpolicy_id': 'IPSec Policy',
+ 'ikepolicy_id': 'IKE Policy',
+ 'mtu': 'MTU',
+ 'initiator': 'Initiator',
+ 'is_admin_state_up': 'State',
+ 'psk': 'Pre-shared Key',
+ 'route_mode': 'Route Mode',
+ 'local_id': 'Local ID',
+ 'peer_id': 'Peer ID',
+ 'local_ep_group_id': 'Local Endpoint Group ID',
+ 'peer_ep_group_id': 'Peer Endpoint Group ID',
+ 'description': 'Description',
+ 'project_id': 'Project',
+ 'dpd': 'DPD',
+}
+
+
+def _convert_to_lowercase(string):
+ return string.lower()
+
+
+def _get_common_parser(parser, is_create=True):
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the connection'))
+ parser.add_argument(
+ '--dpd',
+ metavar="action=ACTION,interval=INTERVAL,timeout=TIMEOUT",
+ type=nc_utils.str2dict_type(
+ optional_keys=['action', 'interval', 'timeout']),
+ help=vpn_utils.dpd_help("IPsec connection"))
+ parser.add_argument(
+ '--mtu',
+ help=_('MTU size for the connection'))
+ parser.add_argument(
+ '--initiator',
+ choices=['bi-directional', 'response-only'],
+ type=_convert_to_lowercase,
+ help=_('Initiator state'))
+ peer_group = parser.add_mutually_exclusive_group()
+ peer_group.add_argument(
+ '--peer-cidr',
+ dest='peer_cidrs',
+ help=_('Remote subnet(s) in CIDR format. '
+ 'Cannot be specified when using endpoint groups. Only '
+ 'applicable, if subnet provided for VPN service.')
+ )
+ peer_group.add_argument(
+ '--local-endpoint-group',
+ help=_('Local endpoint group (name or ID) with subnet(s) '
+ 'for IPsec connection')
+ )
+ parser.add_argument(
+ '--peer-endpoint-group',
+ help=_('Peer endpoint group (name or ID) with CIDR(s) for '
+ 'IPSec connection'))
+ admin_group = parser.add_mutually_exclusive_group()
+ admin_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable IPSec site connection")
+ )
+ admin_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable IPSec site connection")
+ )
+ parser.add_argument(
+ '--local-id',
+ help=_('An ID to be used instead of the external IP '
+ 'address for a virtual router'))
+ return parser
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if is_create:
+ if 'project' in parsed_args and parsed_args.project is not None:
+ attrs['project_id'] = osc_utils.find_project(
+ client_manager.identity,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ if parsed_args.description:
+ attrs['description'] = str(parsed_args.description)
+ if parsed_args.mtu:
+ attrs['mtu'] = parsed_args.mtu
+ if parsed_args.enable:
+ attrs['admin_state_up'] = True
+ if parsed_args.disable:
+ attrs['admin_state_up'] = False
+ if parsed_args.initiator:
+ attrs['initiator'] = parsed_args.initiator
+ if parsed_args.dpd:
+ vpn_utils.validate_dpd_dict(parsed_args.dpd)
+ attrs['dpd'] = parsed_args.dpd
+ if parsed_args.local_endpoint_group:
+ _local_epg = client_manager.network.find_vpn_endpoint_group(
+ parsed_args.local_endpoint_group)['id']
+ attrs['local_ep_group_id'] = _local_epg
+ if parsed_args.peer_endpoint_group:
+ _peer_epg = client_manager.network.find_vpn_endpoint_group(
+ parsed_args.peer_endpoint_group)['id']
+ attrs['peer_ep_group_id'] = _peer_epg
+ if parsed_args.peer_cidrs:
+ attrs['peer_cidrs'] = parsed_args.peer_cidrs
+ if parsed_args.local_id:
+ attrs['local_id'] = parsed_args.local_id
+ return attrs
+
+
+class CreateIPsecSiteConnection(command.ShowOne):
+ _description = _("Create an IPsec site connection")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateIPsecSiteConnection, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ '--peer-id',
+ required=True,
+ help=_('Peer router identity for authentication. Can be '
+ 'IPv4/IPv6 address, e-mail address, key id, or FQDN'))
+ parser.add_argument(
+ '--peer-address',
+ required=True,
+ help=_('Peer gateway public IPv4/IPv6 address or FQDN'))
+ parser.add_argument(
+ '--psk',
+ required=True,
+ help=_('Pre-shared key string.'))
+ parser.add_argument(
+ '--vpnservice',
+ metavar='VPNSERVICE',
+ required=True,
+ help=_('VPN service instance associated with this '
+ 'connection (name or ID)'))
+ parser.add_argument(
+ '--ikepolicy',
+ metavar='IKEPOLICY',
+ required=True,
+ help=_('IKE policy associated with this connection (name or ID)'))
+ parser.add_argument(
+ '--ipsecpolicy',
+ metavar='IPSECPOLICY',
+ required=True,
+ help=_('IPsec policy associated with this connection '
+ '(name or ID)'))
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Set friendly name for the connection'))
+ osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ if parsed_args.vpnservice:
+ _vpnservice_id = client.find_vpn_service(
+ parsed_args.vpnservice, ignore_missing=False)['id']
+ attrs['vpnservice_id'] = _vpnservice_id
+ if parsed_args.ikepolicy:
+ _ikepolicy_id = client.find_vpn_ike_policy(
+ parsed_args.ikepolicy, ignore_missing=False)['id']
+ attrs['ikepolicy_id'] = _ikepolicy_id
+ if parsed_args.ipsecpolicy:
+ _ipsecpolicy_id = client.find_vpn_ipsec_policy(
+ parsed_args.ipsecpolicy, ignore_missing=False)['id']
+ attrs['ipsecpolicy_id'] = _ipsecpolicy_id
+ if parsed_args.peer_id:
+ attrs['peer_id'] = parsed_args.peer_id
+ if parsed_args.peer_address:
+ attrs['peer_address'] = parsed_args.peer_address
+ if parsed_args.psk:
+ attrs['psk'] = parsed_args.psk
+ if parsed_args.name:
+ attrs['name'] = parsed_args.name
+ if (bool(parsed_args.local_endpoint_group) !=
+ bool(parsed_args.peer_endpoint_group)):
+ message = _("You must specify both local and peer endpoint "
+ "groups")
+ raise exceptions.CommandError(message)
+ if not parsed_args.peer_cidrs and not parsed_args.local_endpoint_group:
+ message = _("You must specify endpoint groups or peer CIDR(s)")
+ raise exceptions.CommandError(message)
+ obj = client.create_vpn_ipsec_site_connection(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id', 'action',
+ 'timeout', 'interval'])
+ data = utils.get_dict_properties(obj, columns, formatters=_formatters)
+ return display_columns, data
+
+
+class DeleteIPsecSiteConnection(command.Command):
+ _description = _("Delete IPsec site connection(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteIPsecSiteConnection, self).get_parser(prog_name)
+ parser.add_argument(
+ 'ipsec_site_connection',
+ metavar='',
+ nargs='+',
+ help=_('IPsec site connection to delete (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for ipsec_conn in parsed_args.ipsec_site_connection:
+ try:
+ ipsec_con_id = client.find_vpn_ipsec_site_connection(
+ ipsec_conn, ignore_missing=False)['id']
+ client.delete_vpn_ipsec_site_connection(ipsec_con_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete IPsec site connection with "
+ "name or ID '%(ipsec_site_conn)s': %(e)s"),
+ {'ipsec_site_conn': ipsec_conn, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.ipsec_site_connection)
+ msg = (_("%(result)s of %(total)s IPsec site connection failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListIPsecSiteConnection(command.Lister):
+ _description = _("List IPsec site connections "
+ "that belong to a given project")
+
+ def get_parser(self, prog_name):
+ parser = super(ListIPsecSiteConnection, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.vpn_ipsec_site_connections()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(
+ s, columns, formatters=_formatters) for s in obj))
+
+
+class SetIPsecSiteConnection(command.Command):
+ _description = _("Set IPsec site connection properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetIPsecSiteConnection, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ '--peer-id',
+ help=_('Peer router identity for authentication. Can be '
+ 'IPv4/IPv6 address, e-mail address, key id, or FQDN'))
+ parser.add_argument(
+ '--peer-address',
+ help=_('Peer gateway public IPv4/IPv6 address or FQDN'))
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Set friendly name for the connection'))
+ parser.add_argument(
+ 'ipsec_site_connection',
+ metavar='',
+ help=_('IPsec site connection to set (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager,
+ parsed_args, is_create=False)
+ if parsed_args.peer_id:
+ attrs['peer_id'] = parsed_args.peer_id
+ if parsed_args.peer_address:
+ attrs['peer_address'] = parsed_args.peer_address
+ if parsed_args.name:
+ attrs['name'] = parsed_args.name
+ ipsec_conn_id = client.find_vpn_ipsec_site_connection(
+ parsed_args.ipsec_site_connection, ignore_missing=False)['id']
+ try:
+ client.update_vpn_ipsec_site_connection(ipsec_conn_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to set IPsec site "
+ "connection '%(ipsec_conn)s': %(e)s")
+ % {'ipsec_conn': parsed_args.ipsec_site_connection, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowIPsecSiteConnection(command.ShowOne):
+ _description = _("Show information of a given IPsec site connection")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowIPsecSiteConnection, self).get_parser(prog_name)
+ parser.add_argument(
+ 'ipsec_site_connection',
+ metavar='',
+ help=_('IPsec site connection to display (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ ipsec_site_id = client.find_vpn_ipsec_site_connection(
+ parsed_args.ipsec_site_connection, ignore_missing=False)['id']
+ obj = client.get_vpn_ipsec_site_connection(ipsec_site_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id', 'action',
+ 'timeout', 'interval'])
+ data = utils.get_dict_properties(obj, columns, formatters=_formatters)
+ return (display_columns, data)
diff --git a/neutronclient/osc/v2/vpnaas/ipsecpolicy.py b/neutronclient/osc/v2/vpnaas/ipsecpolicy.py
new file mode 100644
index 000000000..c6e42bd06
--- /dev/null
+++ b/neutronclient/osc/v2/vpnaas/ipsecpolicy.py
@@ -0,0 +1,318 @@
+# Copyright 2017 FUJITSU LIMITED
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+from oslo_log import log as logging
+
+from neutronclient._i18n import _
+from neutronclient.common import utils as nc_utils
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import utils as vpn_utils
+
+
+LOG = logging.getLogger(__name__)
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('auth_algorithm', 'Authentication Algorithm', column_util.LIST_BOTH),
+ ('encapsulation_mode', 'Encapsulation Mode', column_util.LIST_BOTH),
+ ('transform_protocol', 'Transform Protocol', column_util.LIST_BOTH),
+ ('encryption_algorithm', 'Encryption Algorithm', column_util.LIST_BOTH),
+ ('pfs', 'Perfect Forward Secrecy (PFS)', column_util.LIST_LONG_ONLY),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+ ('lifetime', 'Lifetime', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'auth_algorithm': 'Authentication Algorithm',
+ 'encapsulation_mode': 'Encapsulation Mode',
+ 'transform_protocol': 'Transform Protocol',
+ 'encryption_algorithm': 'Encryption Algorithm',
+ 'pfs': 'Perfect Forward Secrecy (PFS)',
+ 'lifetime': 'Lifetime',
+ 'description': 'Description',
+ 'project_id': 'Project',
+}
+
+_auth_algorithms = [
+ 'sha1',
+ 'sha256',
+ 'sha384',
+ 'sha512',
+ 'aes-xcbc',
+ 'aes-cmac',
+]
+
+_encryption_algorithms = [
+ '3des',
+ 'aes-128',
+ 'aes-192',
+ 'aes-256',
+ 'aes-128-ccm-8',
+ 'aes-192-ccm-8',
+ 'aes-256-ccm-8',
+ 'aes-128-ccm-12',
+ 'aes-192-ccm-12',
+ 'aes-256-ccm-12',
+ 'aes-128-ccm-16',
+ 'aes-192-ccm-16',
+ 'aes-256-ccm-16',
+ 'aes-128-gcm-8',
+ 'aes-192-gcm-8',
+ 'aes-256-gcm-8',
+ 'aes-128-gcm-12',
+ 'aes-192-gcm-12',
+ 'aes-256-gcm-12',
+ 'aes-128-gcm-16',
+ 'aes-192-gcm-16',
+ 'aes-256-gcm-16',
+ 'aes-128-ctr',
+ 'aes-192-ctr',
+ 'aes-256-ctr',
+]
+
+_pfs_groups = [
+ 'group2',
+ 'group5',
+ 'group14',
+ 'group15',
+ 'group16',
+ 'group17',
+ 'group18',
+ 'group19',
+ 'group20',
+ 'group21',
+ 'group22',
+ 'group23',
+ 'group24',
+ 'group25',
+ 'group26',
+ 'group27',
+ 'group28',
+ 'group29',
+ 'group30',
+ 'group31',
+]
+
+
+def _convert_to_lowercase(string):
+ return string.lower()
+
+
+def _get_common_parser(parser):
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description of the IPsec policy'))
+ parser.add_argument(
+ '--auth-algorithm',
+ choices=_auth_algorithms,
+ type=_convert_to_lowercase,
+ help=_('Authentication algorithm for IPsec policy'))
+ parser.add_argument(
+ '--encapsulation-mode',
+ choices=['tunnel', 'transport'],
+ type=_convert_to_lowercase,
+ help=_('Encapsulation mode for IPsec policy'))
+ parser.add_argument(
+ '--encryption-algorithm',
+ choices=_encryption_algorithms,
+ type=_convert_to_lowercase,
+ help=_('Encryption algorithm for IPsec policy'))
+ parser.add_argument(
+ '--lifetime',
+ metavar="units=UNITS,value=VALUE",
+ type=nc_utils.str2dict_type(optional_keys=['units', 'value']),
+ help=vpn_utils.lifetime_help("IPsec"))
+ parser.add_argument(
+ '--pfs',
+ choices=_pfs_groups,
+ type=_convert_to_lowercase,
+ help=_('Perfect Forward Secrecy for IPsec policy'))
+ parser.add_argument(
+ '--transform-protocol',
+ type=_convert_to_lowercase,
+ choices=['esp', 'ah', 'ah-esp'],
+ help=_('Transform protocol for IPsec policy'))
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if is_create:
+ if 'project' in parsed_args and parsed_args.project is not None:
+ attrs['project_id'] = osc_utils.find_project(
+ client_manager.identity,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ if parsed_args.description:
+ attrs['description'] = str(parsed_args.description)
+ if parsed_args.auth_algorithm:
+ attrs['auth_algorithm'] = parsed_args.auth_algorithm
+ if parsed_args.encapsulation_mode:
+ attrs['encapsulation_mode'] = parsed_args.encapsulation_mode
+ if parsed_args.transform_protocol:
+ attrs['transform_protocol'] = parsed_args.transform_protocol
+ if parsed_args.encryption_algorithm:
+ attrs['encryption_algorithm'] = parsed_args.encryption_algorithm
+ if parsed_args.pfs:
+ attrs['pfs'] = parsed_args.pfs
+ if parsed_args.lifetime:
+ vpn_utils.validate_lifetime_dict(parsed_args.lifetime)
+ attrs['lifetime'] = parsed_args.lifetime
+ return attrs
+
+
+class CreateIPsecPolicy(command.ShowOne):
+ _description = _("Create an IPsec policy")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateIPsecPolicy, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name of the IPsec policy'))
+ osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ obj = client.create_vpn_ipsec_policy(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id',
+ 'phase1_negotiation_mode', 'units', 'value'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteIPsecPolicy(command.Command):
+ _description = _("Delete IPsec policy(policies)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteIPsecPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'ipsecpolicy',
+ metavar='',
+ nargs='+',
+ help=_('ipsec policy to delete (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for ipsec in parsed_args.ipsecpolicy:
+ try:
+ ipsec_id = client.find_vpn_ipsec_policy(
+ ipsec, ignore_missing=False)['id']
+ client.delete_vpn_ipsec_policy(ipsec_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete IPsec policy with "
+ "name or ID '%(ipsecpolicy)s': %(e)s"),
+ {'ipsecpolicy': ipsec, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.ipsecpolicy)
+ msg = (_("%(result)s of %(total)s IPsec policy failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListIPsecPolicy(command.Lister):
+ _description = _("List IPsec policies that belong to a given project")
+
+ def get_parser(self, prog_name):
+ parser = super(ListIPsecPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.vpn_ipsec_policies()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(s, columns) for s in obj))
+
+
+class SetIPsecPolicy(command.Command):
+ _description = _("Set IPsec policy properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetIPsecPolicy, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name of the IPsec policy'))
+ parser.add_argument(
+ 'ipsecpolicy',
+ metavar='',
+ help=_('IPsec policy to set (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager,
+ parsed_args, is_create=False)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ ipsec_id = client.find_vpn_ipsec_policy(
+ parsed_args.ipsecpolicy, ignore_missing=False)['id']
+ try:
+ client.update_vpn_ipsec_policy(ipsec_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to set IPsec policy '%(ipsec)s': %(e)s")
+ % {'ipsec': parsed_args.ipsecpolicy, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowIPsecPolicy(command.ShowOne):
+ _description = _("Display IPsec policy details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowIPsecPolicy, self).get_parser(prog_name)
+ parser.add_argument(
+ 'ipsecpolicy',
+ metavar='',
+ help=_('IPsec policy to display (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ ipsec_id = client.find_vpn_ipsec_policy(
+ parsed_args.ipsecpolicy, ignore_missing=False)['id']
+ obj = client.get_vpn_ipsec_policy(ipsec_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id',
+ 'phase1_negotiation_mode', 'units', 'value'])
+ data = utils.get_dict_properties(obj, columns)
+ return (display_columns, data)
diff --git a/neutronclient/osc/v2/vpnaas/utils.py b/neutronclient/osc/v2/vpnaas/utils.py
new file mode 100644
index 000000000..2de5cc35e
--- /dev/null
+++ b/neutronclient/osc/v2/vpnaas/utils.py
@@ -0,0 +1,112 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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.
+#
+
+
+"""VPN Utilities and helper functions."""
+
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+
+DPD_SUPPORTED_ACTIONS = ['hold', 'clear', 'restart',
+ 'restart-by-peer', 'disabled']
+DPD_SUPPORTED_KEYS = ['action', 'interval', 'timeout']
+
+lifetime_keys = ['units', 'value']
+lifetime_units = ['seconds']
+
+
+def validate_dpd_dict(dpd_dict):
+ for key, value in dpd_dict.items():
+ if key not in DPD_SUPPORTED_KEYS:
+ message = _(
+ "DPD Dictionary KeyError: "
+ "Reason-Invalid DPD key : "
+ "'%(key)s' not in %(supported_key)s") % {
+ 'key': key, 'supported_key': DPD_SUPPORTED_KEYS}
+ raise exceptions.CommandError(message)
+ if key == 'action' and value not in DPD_SUPPORTED_ACTIONS:
+ message = _(
+ "DPD Dictionary ValueError: "
+ "Reason-Invalid DPD action : "
+ "'%(key_value)s' not in %(supported_action)s") % {
+ 'key_value': value,
+ 'supported_action': DPD_SUPPORTED_ACTIONS}
+ raise exceptions.CommandError(message)
+ if key in ('interval', 'timeout'):
+ try:
+ if int(value) <= 0:
+ raise ValueError()
+ except ValueError:
+ message = _(
+ "DPD Dictionary ValueError: "
+ "Reason-Invalid positive integer value: "
+ "'%(key)s' = %(value)s") % {
+ 'key': key, 'value': value}
+ raise exceptions.CommandError(message)
+ else:
+ dpd_dict[key] = int(value)
+ return
+
+
+def validate_lifetime_dict(lifetime_dict):
+
+ for key, value in lifetime_dict.items():
+ if key not in lifetime_keys:
+ message = _(
+ "Lifetime Dictionary KeyError: "
+ "Reason-Invalid unit key : "
+ "'%(key)s' not in %(supported_key)s") % {
+ 'key': key, 'supported_key': lifetime_keys}
+ raise exceptions.CommandError(message)
+ if key == 'units' and value not in lifetime_units:
+ message = _(
+ "Lifetime Dictionary ValueError: "
+ "Reason-Invalid units : "
+ "'%(key_value)s' not in %(supported_units)s") % {
+ 'key_value': key, 'supported_units': lifetime_units}
+ raise exceptions.CommandError(message)
+ if key == 'value':
+ try:
+ if int(value) < 60:
+ raise ValueError()
+ except ValueError:
+ message = _(
+ "Lifetime Dictionary ValueError: "
+ "Reason-Invalid value should be at least 60:"
+ "'%(key_value)s' = %(value)s") % {
+ 'key_value': key, 'value': value}
+ raise exceptions.CommandError(message)
+ else:
+ lifetime_dict['value'] = int(value)
+ return
+
+
+def lifetime_help(policy):
+ lifetime = _("%s lifetime attributes. "
+ "'units'-seconds, default:seconds. "
+ "'value'-non negative integer, default:3600.") % policy
+ return lifetime
+
+
+def dpd_help(policy):
+ dpd = _(" %s Dead Peer Detection attributes."
+ " 'action'-hold,clear,disabled,restart,restart-by-peer."
+ " 'interval' and 'timeout' are non negative integers. "
+ " 'interval' should be less than 'timeout' value. "
+ " 'action', default:hold 'interval', default:30, "
+ " 'timeout', default:120.") % policy.capitalize()
+ return dpd
diff --git a/neutronclient/osc/v2/vpnaas/vpnservice.py b/neutronclient/osc/v2/vpnaas/vpnservice.py
new file mode 100644
index 000000000..9e1e489ca
--- /dev/null
+++ b/neutronclient/osc/v2/vpnaas/vpnservice.py
@@ -0,0 +1,252 @@
+# Copyright 2017 FUJITSU LIMITED
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from osc_lib.utils import columns as column_util
+from oslo_log import log as logging
+
+from neutronclient._i18n import _
+from neutronclient.osc import utils as osc_utils
+
+
+LOG = logging.getLogger(__name__)
+
+_attr_map = (
+ ('id', 'ID', column_util.LIST_BOTH),
+ ('name', 'Name', column_util.LIST_BOTH),
+ ('router_id', 'Router', column_util.LIST_BOTH),
+ ('subnet_id', 'Subnet', column_util.LIST_BOTH),
+ ('flavor_id', 'Flavor', column_util.LIST_BOTH),
+ ('is_admin_state_up', 'State', column_util.LIST_BOTH),
+ ('status', 'Status', column_util.LIST_BOTH),
+ ('description', 'Description', column_util.LIST_LONG_ONLY),
+ ('project_id', 'Project', column_util.LIST_LONG_ONLY),
+ ('external_v4_ip', 'Ext v4 IP', column_util.LIST_LONG_ONLY),
+ ('external_v6_ip', 'Ext v6 IP', column_util.LIST_LONG_ONLY),
+)
+
+_attr_map_dict = {
+ 'id': 'ID',
+ 'name': 'Name',
+ 'router_id': 'Router',
+ 'subnet_id': 'Subnet',
+ 'flavor_id': 'Flavor',
+ 'is_admin_state_up': 'State',
+ 'status': 'Status',
+ 'description': 'Description',
+ 'project_id': 'Project',
+ 'external_v4_ip': 'Ext v4 IP',
+ 'external_v6_ip': 'Ext v6 IP',
+}
+
+
+def _get_common_parser(parser):
+ parser.add_argument(
+ '--description',
+ metavar='',
+ help=_('Description for the VPN service'))
+ parser.add_argument(
+ '--subnet',
+ metavar='',
+ help=_('Local private subnet (name or ID)'))
+ parser.add_argument(
+ '--flavor',
+ metavar='',
+ help=_('Flavor for the VPN service (name or ID)'))
+ admin_group = parser.add_mutually_exclusive_group()
+ admin_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable VPN service")
+ )
+ admin_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable VPN service")
+ )
+ return parser
+
+
+def _get_common_attrs(client_manager, parsed_args, is_create=True):
+ attrs = {}
+ if is_create:
+ if 'project' in parsed_args and parsed_args.project is not None:
+ attrs['project_id'] = osc_utils.find_project(
+ client_manager.identity,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ if parsed_args.description:
+ attrs['description'] = str(parsed_args.description)
+ if parsed_args.subnet:
+ _subnet_id = client_manager.network.find_subnet(
+ parsed_args.subnet).id
+ attrs['subnet_id'] = _subnet_id
+ if parsed_args.flavor:
+ _flavor_id = client_manager.network.find_flavor(
+ parsed_args.flavor,
+ ignore_missing=False
+ ).id
+ attrs['flavor_id'] = _flavor_id
+ if parsed_args.enable:
+ attrs['admin_state_up'] = True
+ if parsed_args.disable:
+ attrs['admin_state_up'] = False
+ return attrs
+
+
+class CreateVPNService(command.ShowOne):
+ _description = _("Create an VPN service")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateVPNService, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ 'name',
+ metavar='',
+ help=_('Name for the VPN service'))
+ parser.add_argument(
+ '--router',
+ metavar='ROUTER',
+ required=True,
+ help=_('Router for the VPN service (name or ID)'))
+ osc_utils.add_project_owner_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager, parsed_args)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ if parsed_args.router:
+ _router_id = client.find_router(parsed_args.router,
+ ignore_missing=False).id
+ attrs['router_id'] = _router_id
+ obj = client.create_vpn_service(**attrs)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return display_columns, data
+
+
+class DeleteVPNService(command.Command):
+ _description = _("Delete VPN service(s)")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteVPNService, self).get_parser(prog_name)
+ parser.add_argument(
+ 'vpnservice',
+ metavar='',
+ nargs='+',
+ help=_('VPN service to delete (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+ for vpn in parsed_args.vpnservice:
+ try:
+ vpn_id = client.find_vpn_service(vpn,
+ ignore_missing=False)['id']
+ client.delete_vpn_service(vpn_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete VPN service with "
+ "name or ID '%(vpnservice)s': %(e)s"),
+ {'vpnservice': vpn, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.vpnservice)
+ msg = (_("%(result)s of %(total)s vpn service failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListVPNService(command.Lister):
+ _description = _("List VPN services that belong to a given project")
+
+ def get_parser(self, prog_name):
+ parser = super(ListVPNService, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help=_("List additional fields in output")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.vpn_services()
+ headers, columns = column_util.get_column_definitions(
+ _attr_map, long_listing=parsed_args.long)
+ return (headers, (utils.get_dict_properties(s, columns) for s in obj))
+
+
+class SetVPNSercice(command.Command):
+ _description = _("Set VPN service properties")
+
+ def get_parser(self, prog_name):
+ parser = super(SetVPNSercice, self).get_parser(prog_name)
+ _get_common_parser(parser)
+ parser.add_argument(
+ '--name',
+ metavar='',
+ help=_('Name for the VPN service'))
+ parser.add_argument(
+ 'vpnservice',
+ metavar='',
+ help=_('VPN service to modify (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_common_attrs(self.app.client_manager,
+ parsed_args, is_create=False)
+ if parsed_args.name:
+ attrs['name'] = str(parsed_args.name)
+ vpn_id = client.find_vpn_service(parsed_args.vpnservice,
+ ignore_missing=False)['id']
+ try:
+ client.update_vpn_service(vpn_id, **attrs)
+ except Exception as e:
+ msg = (_("Failed to set vpn service '%(vpn)s': %(e)s")
+ % {'vpn': parsed_args.vpnservice, 'e': e})
+ raise exceptions.CommandError(msg)
+
+
+class ShowVPNService(command.ShowOne):
+ _description = _("Display VPN service details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowVPNService, self).get_parser(prog_name)
+ parser.add_argument(
+ 'vpnservice',
+ metavar='',
+ help=_('VPN service to display (name or ID)'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ vpn_id = client.find_vpn_service(parsed_args.vpnservice,
+ ignore_missing=False)['id']
+ obj = client.get_vpn_service(vpn_id)
+ display_columns, columns = utils.get_osc_show_columns_for_sdk_resource(
+ obj, _attr_map_dict, ['location', 'tenant_id'])
+ data = utils.get_dict_properties(obj, columns)
+ return (display_columns, data)
diff --git a/neutronclient/tests/__init__.py b/neutronclient/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/__init__.py b/neutronclient/tests/unit/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/__init__.py b/neutronclient/tests/unit/osc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/__init__.py b/neutronclient/tests/unit/osc/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/dynamic_routing/__init__.py b/neutronclient/tests/unit/osc/v2/dynamic_routing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/dynamic_routing/fakes.py b/neutronclient/tests/unit/osc/v2/dynamic_routing/fakes.py
new file mode 100644
index 000000000..188f06871
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/dynamic_routing/fakes.py
@@ -0,0 +1,145 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from unittest import mock
+import uuid
+
+from openstack.network.v2 import agent as _agent
+from openstack.network.v2 import bgp_peer as _bgp_peer
+from openstack.network.v2 import bgp_speaker as _bgp_speaker
+
+from neutronclient.tests.unit.osc.v2 import fakes
+
+
+class TestNeutronDynamicRoutingOSCV2(fakes.TestNeutronClientOSCV2):
+ def setUp(self):
+ super(TestNeutronDynamicRoutingOSCV2, self).setUp()
+ self.neutronclient.find_resource = mock.Mock(
+ side_effect=lambda resource, name_or_id, project_id=None,
+ cmd_resource=None, parent_id=None, fields=None:
+ {'id': name_or_id})
+
+ self.networkclient.find_bgp_speaker = mock.Mock(
+ side_effect=lambda name_or_id, project_id=None,
+ cmd_resource=None, parent_id=None, fields=None,
+ ignore_missing=False:
+ _bgp_speaker.BgpSpeaker(id=name_or_id))
+ self.networkclient.find_bgp_peer = mock.Mock(
+ side_effect=lambda name_or_id, project_id=None,
+ cmd_resource=None, parent_id=None, fields=None,
+ ignore_missing=False:
+ _bgp_peer.BgpPeer(id=name_or_id))
+
+
+class FakeBgpSpeaker(object):
+ """Fake one or more bgp speakers."""
+
+ @staticmethod
+ def create_one_bgp_speaker(attrs=None):
+ attrs = attrs or {}
+ # Set default attributes.
+ bgp_speaker_attrs = {
+ 'peers': [],
+ 'local_as': 200,
+ 'advertise_tenant_networks': True,
+ 'networks': [],
+ 'ip_version': 4,
+ 'advertise_floating_ip_host_routes': True,
+ 'id': uuid.uuid4().hex,
+ 'name': 'bgp-speaker-' + uuid.uuid4().hex,
+ 'tenant_id': uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ bgp_speaker_attrs.update(attrs)
+ ret_bgp_speaker = _bgp_speaker.BgpSpeaker(**bgp_speaker_attrs)
+
+ return ret_bgp_speaker
+
+ @staticmethod
+ def create_bgp_speakers(attrs=None, count=1):
+ """Create multiple fake bgp speakers.
+
+ """
+ bgp_speakers = []
+ for i in range(count):
+ bgp_speaker = FakeBgpSpeaker.create_one_bgp_speaker(attrs)
+ bgp_speakers.append(bgp_speaker)
+
+ return bgp_speakers
+
+
+class FakeBgpPeer(object):
+ """Fake one or more bgp peers."""
+
+ @staticmethod
+ def create_one_bgp_peer(attrs=None):
+ attrs = attrs or {}
+ # Set default attributes.
+ bgp_peer_attrs = {
+ 'auth_type': None,
+ 'peer_ip': '1.1.1.1',
+ 'remote_as': 100,
+ 'id': uuid.uuid4().hex,
+ 'name': 'bgp-peer-' + uuid.uuid4().hex,
+ 'tenant_id': uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ bgp_peer_attrs.update(attrs)
+ ret_bgp_peer = _bgp_peer.BgpPeer(**bgp_peer_attrs)
+
+ return ret_bgp_peer
+
+ @staticmethod
+ def create_bgp_peers(attrs=None, count=1):
+ """Create one or multiple fake bgp peers."""
+ bgp_peers = []
+ for i in range(count):
+ bgp_peer = FakeBgpPeer.create_one_bgp_peer(attrs)
+ bgp_peers.append(bgp_peer)
+
+ return bgp_peers
+
+
+class FakeDRAgent(object):
+ """Fake one or more dynamic routing agents."""
+
+ @staticmethod
+ def create_one_dragent(attrs=None):
+ attrs = attrs or {}
+ # Set default attributes.
+ dragent_attrs = {
+ 'binary': 'neutron-bgp-dragent',
+ 'admin_state_up': True,
+ 'availability_zone': None,
+ 'alive': True,
+ 'topic': 'bgp_dragent',
+ 'host': 'network-' + uuid.uuid4().hex,
+ 'name': 'bgp-dragent-' + uuid.uuid4().hex,
+ 'agent_type': 'BGP dynamic routing agent',
+ 'id': uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ dragent_attrs.update(attrs)
+ return _agent.Agent(**dragent_attrs)
+
+ @staticmethod
+ def create_dragents(attrs=None, count=1):
+ """Create one or multiple fake dynamic routing agents."""
+ agents = []
+ for i in range(count):
+ agent = FakeDRAgent.create_one_dragent(attrs)
+ agents.append(agent)
+
+ return agents
diff --git a/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_dragent.py b/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_dragent.py
new file mode 100644
index 000000000..6842cbbd1
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_dragent.py
@@ -0,0 +1,84 @@
+# 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 unittest import mock
+
+from neutronclient.osc.v2.dynamic_routing import bgp_dragent
+from neutronclient.tests.unit.osc.v2.dynamic_routing import fakes
+
+
+class TestAddBgpSpeakerToDRAgent(fakes.TestNeutronDynamicRoutingOSCV2):
+ _bgp_speaker = fakes.FakeBgpSpeaker.create_one_bgp_speaker()
+ _bgp_dragent = fakes.FakeDRAgent.create_one_dragent()
+ _bgp_speaker_id = _bgp_speaker['id']
+ _bgp_dragent_id = _bgp_dragent['id']
+
+ def setUp(self):
+ super(TestAddBgpSpeakerToDRAgent, self).setUp()
+
+ # Get the command object to test
+ self.cmd = bgp_dragent.AddBgpSpeakerToDRAgent(self.app, self.namespace)
+
+ def test_add_bgp_speaker_to_dragent(self):
+ arglist = [
+ self._bgp_dragent_id,
+ self._bgp_speaker_id,
+ ]
+ verifylist = [
+ ('dragent_id', self._bgp_dragent_id),
+ ('bgp_speaker', self._bgp_speaker_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch.object(self.networkclient,
+ "add_bgp_speaker_to_dragent",
+ return_value=None):
+
+ result = self.cmd.take_action(parsed_args)
+ self.networkclient.add_bgp_speaker_to_dragent.\
+ assert_called_once_with(
+ self._bgp_dragent_id, self._bgp_speaker_id)
+ self.assertIsNone(result)
+
+
+class TestRemoveBgpSpeakerFromDRAgent(fakes.TestNeutronDynamicRoutingOSCV2):
+ _bgp_speaker = fakes.FakeBgpSpeaker.create_one_bgp_speaker()
+ _bgp_dragent = fakes.FakeDRAgent.create_one_dragent()
+ _bgp_speaker_id = _bgp_speaker['id']
+ _bgp_dragent_id = _bgp_dragent['id']
+
+ def setUp(self):
+ super(TestRemoveBgpSpeakerFromDRAgent, self).setUp()
+
+ # Get the command object to test
+ self.cmd = bgp_dragent.RemoveBgpSpeakerFromDRAgent(
+ self.app, self.namespace)
+
+ def test_remove_bgp_speaker_from_dragent(self):
+ arglist = [
+ self._bgp_dragent_id,
+ self._bgp_speaker_id,
+ ]
+ verifylist = [
+ ('dragent_id', self._bgp_dragent_id),
+ ('bgp_speaker', self._bgp_speaker_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch.object(self.networkclient,
+ "remove_bgp_speaker_from_dragent",
+ return_value=None):
+ result = self.cmd.take_action(parsed_args)
+ self.networkclient.remove_bgp_speaker_from_dragent.\
+ assert_called_once_with(self._bgp_dragent_id,
+ self._bgp_speaker_id)
+ self.assertIsNone(result)
diff --git a/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_peer.py b/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_peer.py
new file mode 100644
index 000000000..73b6bf7ab
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_peer.py
@@ -0,0 +1,150 @@
+# 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 unittest import mock
+
+from neutronclient.osc.v2.dynamic_routing import bgp_peer
+from neutronclient.tests.unit.osc.v2.dynamic_routing import fakes
+
+
+class TestListBgpPeer(fakes.TestNeutronDynamicRoutingOSCV2):
+ _bgp_peers = fakes.FakeBgpPeer.create_bgp_peers(count=1)
+ columns = ('ID', 'Name', 'Peer IP', 'Remote AS')
+ data = []
+ for _bgp_peer in _bgp_peers:
+ data.append((
+ _bgp_peer['id'],
+ _bgp_peer['name'],
+ _bgp_peer['peer_ip'],
+ _bgp_peer['remote_as']))
+
+ def setUp(self):
+ super(TestListBgpPeer, self).setUp()
+
+ self.networkclient.bgp_peers = mock.Mock(
+ return_value=self._bgp_peers
+ )
+
+ # Get the command object to test
+ self.cmd = bgp_peer.ListBgpPeer(self.app, self.namespace)
+
+ def test_bgp_peer_list(self):
+ parsed_args = self.check_parser(self.cmd, [], [])
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.networkclient.bgp_peers.assert_called_once_with(
+ retrieve_all=True)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestDeleteBgpPeer(fakes.TestNeutronDynamicRoutingOSCV2):
+
+ _bgp_peer = fakes.FakeBgpPeer.create_one_bgp_peer()
+
+ def setUp(self):
+ super(TestDeleteBgpPeer, self).setUp()
+
+ self.networkclient.delete_bgp_peer = mock.Mock(return_value=None)
+
+ self.cmd = bgp_peer.DeleteBgpPeer(self.app, self.namespace)
+
+ def test_delete_bgp_peer(self):
+ arglist = [
+ self._bgp_peer['name'],
+ ]
+ verifylist = [
+ ('bgp_peer', self._bgp_peer['name']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.networkclient.delete_bgp_peer.assert_called_once_with(
+ self._bgp_peer['name'])
+ self.assertIsNone(result)
+
+
+class TestShowBgpPeer(fakes.TestNeutronDynamicRoutingOSCV2):
+ _one_bgp_peer = fakes.FakeBgpPeer.create_one_bgp_peer()
+ data = (
+ _one_bgp_peer['auth_type'],
+ _one_bgp_peer['id'],
+ _one_bgp_peer['name'],
+ _one_bgp_peer['peer_ip'],
+ _one_bgp_peer['tenant_id'],
+ _one_bgp_peer['remote_as'],
+ )
+ _bgp_peer = _one_bgp_peer
+ _bgp_peer_name = _one_bgp_peer['name']
+ columns = (
+ 'auth_type',
+ 'id',
+ 'name',
+ 'peer_ip',
+ 'project_id',
+ 'remote_as',
+ )
+
+ def setUp(self):
+ super(TestShowBgpPeer, self).setUp()
+
+ self.networkclient.get_bgp_peer = mock.Mock(
+ return_value=self._bgp_peer
+ )
+ # Get the command object to test
+ self.cmd = bgp_peer.ShowBgpPeer(self.app, self.namespace)
+
+ def test_bgp_peer_show(self):
+ arglist = [
+ self._bgp_peer_name,
+ ]
+ verifylist = [
+ ('bgp_peer', self._bgp_peer_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ data = self.cmd.take_action(parsed_args)
+ self.networkclient.get_bgp_peer.assert_called_once_with(
+ self._bgp_peer_name)
+ self.assertEqual(self.columns, data[0])
+ self.assertEqual(self.data, data[1])
+
+
+class TestSetBgpPeer(fakes.TestNeutronDynamicRoutingOSCV2):
+ _one_bgp_peer = fakes.FakeBgpPeer.create_one_bgp_peer()
+ _bgp_peer_name = _one_bgp_peer['name']
+
+ def setUp(self):
+ super(TestSetBgpPeer, self).setUp()
+ self.networkclient.update_bgp_peer = mock.Mock(return_value=None)
+ bgp_peer.get_bgp_peer_id = mock.Mock(return_value=self._bgp_peer_name)
+
+ self.cmd = bgp_peer.SetBgpPeer(self.app, self.namespace)
+
+ def test_set_bgp_peer(self):
+ arglist = [
+ self._bgp_peer_name,
+ '--name', 'noob',
+ ]
+ verifylist = [
+ ('bgp_peer', self._bgp_peer_name),
+ ('name', 'noob'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {'name': 'noob', 'password': None}
+ self.networkclient.update_bgp_peer.assert_called_once_with(
+ self._bgp_peer_name, **attrs)
+ self.assertIsNone(result)
diff --git a/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_speaker.py b/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_speaker.py
new file mode 100644
index 000000000..c5e42e588
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/dynamic_routing/test_bgp_speaker.py
@@ -0,0 +1,157 @@
+# 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 unittest import mock
+
+from neutronclient.osc.v2.dynamic_routing import bgp_speaker
+from neutronclient.tests.unit.osc.v2.dynamic_routing import fakes
+
+
+class TestListBgpSpeaker(fakes.TestNeutronDynamicRoutingOSCV2):
+ _bgp_speakers = fakes.FakeBgpSpeaker.create_bgp_speakers()
+ columns = ('ID', 'Name', 'Local AS', 'IP Version')
+ data = []
+ for _bgp_speaker in _bgp_speakers:
+ data.append((
+ _bgp_speaker['id'],
+ _bgp_speaker['name'],
+ _bgp_speaker['local_as'],
+ _bgp_speaker['ip_version']))
+
+ def setUp(self):
+ super(TestListBgpSpeaker, self).setUp()
+
+ self.networkclient.bgp_speakers = mock.Mock(
+ return_value=self._bgp_speakers
+ )
+
+ # Get the command object to test
+ self.cmd = bgp_speaker.ListBgpSpeaker(self.app, self.namespace)
+
+ def test_bgp_speaker_list(self):
+ parsed_args = self.check_parser(self.cmd, [], [])
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.networkclient.bgp_speakers.assert_called_once_with(
+ retrieve_all=True)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestDeleteBgpSpeaker(fakes.TestNeutronDynamicRoutingOSCV2):
+
+ _bgp_speaker = fakes.FakeBgpSpeaker.create_one_bgp_speaker()
+
+ def setUp(self):
+ super(TestDeleteBgpSpeaker, self).setUp()
+
+ self.networkclient.delete_bgp_speaker = mock.Mock(return_value=None)
+
+ self.cmd = bgp_speaker.DeleteBgpSpeaker(self.app, self.namespace)
+
+ def test_delete_bgp_speaker(self):
+ arglist = [
+ self._bgp_speaker['name'],
+ ]
+ verifylist = [
+ ('bgp_speaker', self._bgp_speaker['name']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.networkclient.delete_bgp_speaker.assert_called_once_with(
+ self._bgp_speaker['name'])
+ self.assertIsNone(result)
+
+
+class TestShowBgpSpeaker(fakes.TestNeutronDynamicRoutingOSCV2):
+ _one_bgp_speaker = fakes.FakeBgpSpeaker.create_one_bgp_speaker()
+ data = (
+ _one_bgp_speaker['advertise_floating_ip_host_routes'],
+ _one_bgp_speaker['advertise_tenant_networks'],
+ _one_bgp_speaker['id'],
+ _one_bgp_speaker['ip_version'],
+ _one_bgp_speaker['local_as'],
+ _one_bgp_speaker['name'],
+ _one_bgp_speaker['networks'],
+ _one_bgp_speaker['peers'],
+ _one_bgp_speaker['tenant_id']
+ )
+ _bgp_speaker = _one_bgp_speaker
+ _bgp_speaker_name = _one_bgp_speaker['name']
+ columns = (
+ 'advertise_floating_ip_host_routes',
+ 'advertise_tenant_networks',
+ 'id',
+ 'ip_version',
+ 'local_as',
+ 'name',
+ 'networks',
+ 'peers',
+ 'project_id'
+ )
+
+ def setUp(self):
+ super(TestShowBgpSpeaker, self).setUp()
+
+ self.networkclient.get_bgp_speaker = mock.Mock(
+ return_value=self._bgp_speaker
+ )
+ # Get the command object to test
+ self.cmd = bgp_speaker.ShowBgpSpeaker(self.app, self.namespace)
+
+ def test_bgp_speaker_show(self):
+ arglist = [
+ self._bgp_speaker_name,
+ ]
+ verifylist = [
+ ('bgp_speaker', self._bgp_speaker_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ data = self.cmd.take_action(parsed_args)
+ self.networkclient.get_bgp_speaker.assert_called_once_with(
+ self._bgp_speaker_name)
+ self.assertEqual(self.columns, data[0])
+ self.assertEqual(self.data, data[1])
+
+
+class TestSetBgpSpeaker(fakes.TestNeutronDynamicRoutingOSCV2):
+ _one_bgp_speaker = fakes.FakeBgpSpeaker.create_one_bgp_speaker()
+ _bgp_speaker_name = _one_bgp_speaker['name']
+
+ def setUp(self):
+ super(TestSetBgpSpeaker, self).setUp()
+ self.networkclient.update_bgp_speaker = mock.Mock(
+ return_value=None)
+
+ self.cmd = bgp_speaker.SetBgpSpeaker(self.app, self.namespace)
+
+ def test_set_bgp_speaker(self):
+ arglist = [
+ self._bgp_speaker_name,
+ '--name', 'noob',
+ ]
+ verifylist = [
+ ('bgp_speaker', self._bgp_speaker_name),
+ ('name', 'noob'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {'name': 'noob'}
+ self.networkclient.update_bgp_speaker.assert_called_once_with(
+ self._bgp_speaker_name, **attrs)
+ self.assertIsNone(result)
diff --git a/neutronclient/tests/unit/osc/v2/fakes.py b/neutronclient/tests/unit/osc/v2/fakes.py
new file mode 100644
index 000000000..7fec7347c
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/fakes.py
@@ -0,0 +1,50 @@
+# 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 unittest import mock
+
+from cliff import columns as cliff_columns
+from osc_lib.tests import utils
+
+
+class TestNeutronClientOSCV2(utils.TestCommand):
+
+ def setUp(self):
+ super(TestNeutronClientOSCV2, self).setUp()
+ self.namespace = argparse.Namespace()
+ self.app.client_manager.session = mock.Mock()
+ self.app.client_manager.neutronclient = mock.Mock()
+ self.neutronclient = self.app.client_manager.neutronclient
+
+ self.app.client_manager.network = mock.Mock()
+ self.networkclient = self.app.client_manager.network
+
+ self.addCleanup(mock.patch.stopall)
+
+ # TODO(amotoki): Move this to osc_lib
+ def assertListItemEqual(self, expected, actual):
+ self.assertEqual(len(expected), len(actual))
+ for item_expected, item_actual in zip(expected, actual):
+ self.assertItemEqual(item_expected, item_actual)
+
+ # TODO(amotoki): Move this to osc_lib
+ def assertItemEqual(self, expected, actual):
+ self.assertEqual(len(expected), len(actual))
+ for col_expected, col_actual in zip(expected, actual):
+ if isinstance(col_expected, cliff_columns.FormattableColumn):
+ self.assertIsInstance(col_actual, col_expected.__class__)
+ self.assertEqual(col_expected.human_readable(),
+ col_actual.human_readable())
+ else:
+ self.assertEqual(col_expected, col_actual)
diff --git a/neutronclient/tests/unit/osc/v2/lbaas/__init__.py b/neutronclient/tests/unit/osc/v2/lbaas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/logging/__init__.py b/neutronclient/tests/unit/osc/v2/logging/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/logging/fakes.py b/neutronclient/tests/unit/osc/v2/logging/fakes.py
new file mode 100644
index 000000000..a20e3712e
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/logging/fakes.py
@@ -0,0 +1,78 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 collections
+import copy
+from unittest import mock
+import uuid
+
+
+class FakeLogging(object):
+
+ def create(self, attrs={}):
+ """Create a fake network logs
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A OrderedDict faking the network log
+ """
+ self.ordered.update(attrs)
+ return copy.deepcopy(self.ordered)
+
+ def bulk_create(self, attrs=None, count=2):
+ """Create multiple fake network logs
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of network logs to fake
+ :return:
+ A list of dictionaries faking the network logs
+ """
+ return [self.create(attrs=attrs) for i in range(0, count)]
+
+ def get(self, attrs=None, count=2):
+ """Create multiple fake network logs
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of network logs to fake
+ :return:
+ A list of dictionaries faking the network log
+ """
+ if attrs is None:
+ self.attrs = self.bulk_create(count=count)
+ return mock.Mock(side_effect=attrs)
+
+
+class NetworkLog(FakeLogging):
+ """Fake one or more network log"""
+
+ def __init__(self):
+ super(NetworkLog, self).__init__()
+ self.ordered = collections.OrderedDict((
+ ('id', 'log-id-' + uuid.uuid4().hex),
+ ('description', 'my-desc-' + uuid.uuid4().hex),
+ ('enabled', False),
+ ('name', 'my-log-' + uuid.uuid4().hex),
+ ('target_id', None),
+ ('project_id', 'project-id-' + uuid.uuid4().hex),
+ ('resource_id', None),
+ ('resource_type', 'security_group'),
+ ('event', 'all'),
+ ))
diff --git a/neutronclient/tests/unit/osc/v2/logging/test_network_log.py b/neutronclient/tests/unit/osc/v2/logging/test_network_log.py
new file mode 100644
index 000000000..c2e0390ae
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/logging/test_network_log.py
@@ -0,0 +1,749 @@
+# Copyright 2017-2018 FUJITSU LIMITED
+# 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 copy
+from unittest import mock
+
+from osc_lib import exceptions
+from osc_lib.tests import utils
+import testtools
+
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.logging import network_log
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+from neutronclient.tests.unit.osc.v2.logging import fakes
+
+
+_log = fakes.NetworkLog().create()
+RES_TYPE_SG = 'security_group'
+RES_TYPE_FWG = 'firewall_group'
+CONVERT_MAP = {
+ 'project': 'project_id',
+ 'enable': 'enabled',
+ 'disable': 'enabled',
+ 'target': 'target_id',
+ 'resource': 'resource_id',
+ 'event': 'event',
+}
+
+
+def _generate_data(ordered_dict=None, data=None):
+ source = ordered_dict if ordered_dict else _log
+ if data:
+ source.update(data)
+ return tuple(source[key] for key in source)
+
+
+def _generate_req_and_res(verifylist):
+ request = dict(verifylist)
+ response = copy.deepcopy(_log)
+ for key, val in verifylist:
+ converted = CONVERT_MAP.get(key, key)
+ del request[key]
+ if key == 'enable' and val:
+ new_value = True
+ elif key == 'disable' and val:
+ new_value = False
+ else:
+ new_value = val
+ request[converted] = new_value
+ response[converted] = new_value
+ return request, response
+
+
+class TestNetworkLog(test_fakes.TestNeutronClientOSCV2):
+
+ def check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {'logs': [exp_req]}
+ else:
+ req_body = {'log': exp_req}
+ self.mocked.assert_called_once_with(req_body)
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestNetworkLog, self).setUp()
+ self.neutronclient.find_resource = mock.Mock()
+ self.neutronclient.find_resource.side_effect = \
+ lambda x, y, **k: {'id': y}
+ osc_utils.find_project = mock.Mock()
+ osc_utils.find_project.id = _log['project_id']
+ self.res = _log
+ self.headers = (
+ 'ID',
+ 'Description',
+ 'Enabled',
+ 'Name',
+ 'Target',
+ 'Project',
+ 'Resource',
+ 'Type',
+ 'Event',
+ )
+ self.data = _generate_data()
+ self.ordered_headers = (
+ 'Description',
+ 'Enabled',
+ 'Event',
+ 'ID',
+ 'Name',
+ 'Project',
+ 'Resource',
+ 'Target',
+ 'Type',
+ )
+ self.ordered_data = (
+ _log['description'],
+ _log['enabled'],
+ _log['event'],
+ _log['id'],
+ _log['name'],
+ _log['project_id'],
+ _log['resource_id'],
+ _log['target_id'],
+ _log['resource_type'],
+ )
+ self.ordered_columns = (
+ 'description',
+ 'enabled',
+ 'event',
+ 'id',
+ 'name',
+ 'project_id',
+ 'resource_id',
+ 'target_id',
+ 'resource_type',
+ )
+
+
+class TestCreateNetworkLog(TestNetworkLog):
+
+ def setUp(self):
+ super(TestCreateNetworkLog, self).setUp()
+ self.neutronclient.create_network_log = mock.Mock(
+ return_value={'log': _log})
+ self.mocked = self.neutronclient.create_network_log
+ self.cmd = network_log.CreateNetworkLog(self.app, self.namespace)
+ loggables = {
+ "loggable_resources": [{"type": RES_TYPE_SG},
+ {"type": RES_TYPE_FWG}]
+ }
+ self.neutronclient.list_network_loggable_resources = mock.Mock(
+ return_value=loggables)
+
+ def _update_expect_response(self, request, response):
+ """Set expected request and response
+
+ :param request
+ A dictionary of request body(dict of verifylist)
+ :param response
+ A OrderedDict of request body
+ """
+ # Update response body
+ self.neutronclient.create_network_log.return_value = \
+ {'log': dict(response)}
+ osc_utils.find_project.return_value.id = response['project_id']
+ # Update response(finally returns 'data')
+ self.data = _generate_data(ordered_dict=response)
+ self.ordered_data = tuple(
+ response[column] for column in self.ordered_columns
+ )
+
+ def _set_all_params(self, args={}):
+ name = args.get('name', 'my-log')
+ desc = args.get('description', 'my-description-for-log')
+ event = args.get('event', 'ACCEPT')
+ resource = args.get('resource', 'id-target-log')
+ target = args.get('target', 'id-target-log')
+ resource_type = args.get('resource_type', 'security_group')
+ project = args.get('project_id', 'id-my-project')
+
+ arglist = [
+ name,
+ '--description', desc,
+ '--enable',
+ '--target', target,
+ '--resource', resource,
+ '--event', event,
+ '--project', project,
+ '--resource-type', resource_type,
+ ]
+ verifylist = [
+ ('description', desc),
+ ('enable', True),
+ ('event', event),
+ ('name', name),
+ ('target', target),
+ ('project', project),
+ ('resource', target),
+ ('resource_type', resource_type),
+ ]
+ return arglist, verifylist
+
+ def _test_create_with_all_params(self, args={}):
+ arglist, verifylist = self._set_all_params(args)
+ request, response = _generate_req_and_res(verifylist)
+ self._update_expect_response(request, response)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.check_results(headers, data, request)
+
+ def test_create_with_no_options_and_raise(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_create_with_mandatory_params(self):
+ name = self.res['name']
+ arglist = [
+ name,
+ '--resource-type', RES_TYPE_SG,
+ ]
+ verifylist = [
+ ('name', name),
+ ('resource_type', RES_TYPE_SG),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+ expect = {
+ 'name': self.res['name'],
+ 'resource_type': self.res['resource_type'],
+ }
+ self.mocked.assert_called_once_with({'log': expect})
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def test_create_with_disable(self):
+ name = self.res['name']
+ arglist = [
+ name,
+ '--resource-type', RES_TYPE_SG,
+ '--disable',
+ ]
+ verifylist = [
+ ('name', name),
+ ('resource_type', RES_TYPE_SG),
+ ('disable', True),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+ expect = {
+ 'name': self.res['name'],
+ 'resource_type': self.res['resource_type'],
+ 'enabled': False,
+ }
+ self.mocked.assert_called_once_with({'log': expect})
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def test_create_with_all_params(self):
+ self._test_create_with_all_params()
+
+ def test_create_with_all_params_event_drop(self):
+ self._test_create_with_all_params({'event': 'DROP'})
+
+ def test_create_with_all_params_event_all(self):
+ self._test_create_with_all_params({'event': 'ALL'})
+
+ def test_create_with_all_params_except_event(self):
+ arglist, verifylist = self._set_all_params({'event': ''})
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_create_with_all_params_event_upper_capitalized(self):
+ for event in ('all', 'All', 'dROP', 'accePt', 'accept', 'drop'):
+ arglist, verifylist = self._set_all_params({'event': event})
+ self.assertRaises(
+ testtools.matchers._impl.MismatchError,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_create_with_all_params_resource_type_upper_capitalized(self):
+ for res_type in ('SECURITY_GROUP', 'Security_group', 'security_Group'):
+ arglist, verifylist = self._set_all_params(
+ {'resource_type': res_type})
+ self.assertRaises(
+ testtools.matchers._impl.MismatchError,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_create_with_valid_fwg_resource(self):
+ name = self.res['name']
+ resource_id = 'valid_fwg_id'
+ resource_type = RES_TYPE_FWG
+ # Test with valid FWG ID
+ with mock.patch.object(self.neutronclient, 'find_resource',
+ return_value={'id': resource_id}):
+ arglist = [name,
+ '--resource-type', resource_type,
+ '--resource', resource_id
+ ]
+ verifylist = [
+ ('name', name),
+ ('resource_type', resource_type),
+ ('resource', resource_id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+ expect = {
+ 'name': self.res['name'],
+ 'resource_type': RES_TYPE_FWG,
+ 'resource_id': 'valid_fwg_id',
+ }
+ self.neutronclient.find_resource.assert_called_with(
+ resource_type,
+ resource_id,
+ cmd_resource='fwaas_firewall_group')
+ self.mocked.assert_called_once_with({'log': expect})
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def test_create_with_invalid_fwg_resource(self):
+ name = self.res['name']
+ resource_id = 'invalid_fwg_id'
+ resource_type = RES_TYPE_FWG
+ # Test with invalid FWG ID
+ with mock.patch.object(self.neutronclient, 'find_resource',
+ side_effect=exceptions.NotFound(code=0)):
+ arglist = [name,
+ '--resource-type', resource_type,
+ '--resource', resource_id
+ ]
+ verifylist = [
+ ('name', name),
+ ('resource_type', resource_type),
+ ('resource', resource_id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.NotFound,
+ self.cmd.take_action,
+ parsed_args)
+ self.neutronclient.find_resource.assert_called_with(
+ resource_type,
+ resource_id,
+ cmd_resource='fwaas_firewall_group')
+ self.mocked.assert_not_called()
+
+ def test_create_with_invalid_resource_type(self):
+ name = self.res['name']
+ resource_type = 'invalid_resource_type'
+ resource_id = 'valid_fwg_id'
+ with mock.patch.object(self.neutronclient, 'find_resource',
+ side_effect=exceptions.NotFound(code=0)):
+ arglist = [name,
+ '--resource-type', resource_type,
+ '--resource', resource_id
+ ]
+ verifylist = [
+ ('name', name),
+ ('resource_type', resource_type),
+ ('resource', resource_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.NotFound,
+ self.cmd.take_action,
+ parsed_args)
+ self.neutronclient.find_resource.assert_called_with(
+ resource_type,
+ resource_id,
+ cmd_resource=None)
+ self.mocked.assert_not_called()
+
+
+class TestListNetworkLog(TestNetworkLog):
+
+ def _setup_summary(self, expect=None):
+ event = 'Event: ' + self.res['event'].upper()
+ target = 'Logged: (None specified)'
+ if expect:
+ if expect.get('event'):
+ event = expect['event']
+ if expect.get('resource'):
+ target = expect['resource']
+ summary = ',\n'.join([event, target])
+ self.short_data = (
+ expect['id'] if expect else self.res['id'],
+ expect['enabled'] if expect else self.res['enabled'],
+ expect['name'] if expect else self.res['name'],
+ expect['resource_type'] if expect else self.res['resource_type'],
+ summary
+ )
+
+ def setUp(self):
+ super(TestListNetworkLog, self).setUp()
+ self.cmd = network_log.ListNetworkLog(self.app, self.namespace)
+
+ self.short_header = (
+ 'ID',
+ 'Enabled',
+ 'Name',
+ 'Type',
+ 'Summary',
+ )
+ self._setup_summary()
+ self.neutronclient.list_network_logs = mock.Mock(
+ return_value={'logs': [self.res]})
+ self.mocked = self.neutronclient.list_network_logs
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+ self.assertEqual([self.data], list(data))
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+ def test_list_with_target_and_resource(self):
+ arglist = []
+ verifylist = []
+ target_id = 'aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaaaa'
+ resource_id = 'bbbbbbbb-bbbb-bbbb-bbbbbbbbbbbbbbbbb'
+ log = fakes.NetworkLog().create({
+ 'target_id': target_id,
+ 'resource_id': resource_id})
+ self.mocked.return_value = {'logs': [log]}
+ logged = 'Logged: (security_group) %(res_id)s on (port) %(t_id)s' % {
+ 'res_id': resource_id, 't_id': target_id}
+ expect_log = copy.deepcopy(log)
+ expect_log.update({
+ 'resource': logged,
+ 'event': 'Event: ALL'})
+ self._setup_summary(expect=expect_log)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+ def test_list_with_resource(self):
+ arglist = []
+ verifylist = []
+ resource_id = 'bbbbbbbb-bbbb-bbbb-bbbbbbbbbbbbbbbbb'
+ log = fakes.NetworkLog().create({'resource_id': resource_id})
+ self.mocked.return_value = {'logs': [log]}
+ logged = 'Logged: (security_group) %s' % resource_id
+ expect_log = copy.deepcopy(log)
+ expect_log.update({
+ 'resource': logged,
+ 'event': 'Event: ALL'})
+ self._setup_summary(expect=expect_log)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+ def test_list_with_target(self):
+ arglist = []
+ verifylist = []
+ target_id = 'aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaaaa'
+ log = fakes.NetworkLog().create({'target_id': target_id})
+ self.mocked.return_value = {'logs': [log]}
+ logged = 'Logged: (port) %s' % target_id
+ expect_log = copy.deepcopy(log)
+ expect_log.update({
+ 'resource': logged,
+ 'event': 'Event: ALL'})
+ self._setup_summary(expect=expect_log)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+
+class TestShowNetworkLog(TestNetworkLog):
+
+ def setUp(self):
+ super(TestShowNetworkLog, self).setUp()
+ self.neutronclient.show_network_log = mock.Mock(
+ return_value={'log': self.res})
+ self.mocked = self.neutronclient.show_network_log
+ self.cmd = network_log.ShowNetworkLog(self.app, self.namespace)
+
+ def test_show_filtered_by_id_or_name(self):
+ target = self.res['id']
+ arglist = [target]
+ verifylist = [('network_log', target)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(target)
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+
+class TestSetNetworkLog(TestNetworkLog):
+
+ def setUp(self):
+ super(TestSetNetworkLog, self).setUp()
+ self.neutronclient.update_network_log = mock.Mock(
+ return_value={'log': self.res})
+ self.mocked = self.neutronclient.update_network_log
+ self.cmd = network_log.SetNetworkLog(self.app, self.namespace)
+
+ def test_set_name(self):
+ target = self.res['id']
+ update = 'change'
+ arglist = [target, '--name', update]
+ verifylist = [
+ ('network_log', target),
+ ('name', update),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, {'log': {'name': update}})
+ self.assertIsNone(result)
+
+ def test_set_description(self):
+ target = self.res['id']
+ update = 'change-desc'
+ arglist = [target, '--description', update]
+ verifylist = [
+ ('network_log', target),
+ ('description', update),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, {'log': {'description': update}})
+ self.assertIsNone(result)
+
+ def test_set_enable(self):
+ target = self.res['id']
+ arglist = [target, '--enable']
+ verifylist = [
+ ('network_log', target),
+ ('enable', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, {'log': {'enabled': True}})
+ self.assertIsNone(result)
+
+ def test_set_disable(self):
+ target = self.res['id']
+ arglist = [target, '--disable']
+ verifylist = [
+ ('network_log', target),
+ ('disable', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, {'log': {'enabled': False}})
+ self.assertIsNone(result)
+
+ # Illegal tests
+ def test_illegal_set_resource_type(self):
+ target = self.res['id']
+ resource_type = 'security_group'
+ arglist = [target, '--resource-type', resource_type]
+ verifylist = [
+ ('network_log', target),
+ ('resource_type', resource_type),
+ ]
+
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_illegal_set_event(self):
+ target = self.res['id']
+ for event in ['all', 'accept', 'drop']:
+ arglist = [target, '--event', event]
+ verifylist = [
+ ('network_log', target),
+ ('event', event),
+ ]
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_illegal_set_resource_id(self):
+ target = self.res['id']
+ resource_id = 'resource-id-for-logged-target'
+ arglist = [target, '--resource', resource_id]
+ verifylist = [
+ ('network_log', target),
+ ('resource', resource_id),
+ ]
+
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_illegal_set_project(self):
+ target = self.res['id']
+ arglist = [
+ target,
+ '--project',
+ ]
+ verifylist = [
+ ('network_log', target),
+ ('project', 'other-project'),
+ ]
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_illegal_set_project_domain(self):
+ target = self.res['id']
+ arglist = [
+ target,
+ '--project-domain',
+ ]
+ verifylist = [
+ ('network_log', target),
+ ('project_domain', 'other-project-domain'),
+ ]
+ self.assertRaises(
+ utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_illegal_set_and_raises(self):
+ self.neutronclient.update_network_log = mock.Mock(
+ side_effect=Exception)
+ target = self.res['id']
+ arglist = [target, '--name', 'my-name']
+ verifylist = [('network_log', target), ('name', 'my-name')]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
+
+class TestDeleteNetworkLog(TestNetworkLog):
+
+ def setUp(self):
+ super(TestDeleteNetworkLog, self).setUp()
+ self.neutronclient.delete_network_log = mock.Mock(
+ return_value={'log': self.res})
+ self.mocked = self.neutronclient.delete_network_log
+ self.cmd = network_log.DeleteNetworkLog(self.app, self.namespace)
+
+ def test_delete_with_one_resource(self):
+ target = self.res['id']
+ arglist = [target]
+ verifylist = [('network_log', [target])]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(target)
+ self.assertIsNone(result)
+
+ def test_delete_with_multiple_resources(self):
+ target1 = 'target1'
+ target2 = 'target2'
+ arglist = [target1, target2]
+ verifylist = [('network_log', [target1, target2])]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ self.assertEqual(2, self.mocked.call_count)
+ for idx, reference in enumerate([target1, target2]):
+ actual = ''.join(self.mocked.call_args_list[idx][0])
+ self.assertEqual(reference, actual)
+
+ def test_delete_with_no_exist_id(self):
+ self.neutronclient.find_resource.side_effect = Exception
+ target = 'not_exist'
+ arglist = [target]
+ verifylist = [('network_log', [target])]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
+
+class TestLoggableResource(test_fakes.TestNeutronClientOSCV2):
+
+ def check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {'logs': [exp_req]}
+ else:
+ req_body = {'log': exp_req}
+ self.mocked.assert_called_once_with(req_body)
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestLoggableResource, self).setUp()
+ self.headers = ('Supported types',)
+ self.data = ('security_group', )
+
+
+class TestListLoggableResource(TestLoggableResource):
+
+ def setUp(self):
+ super(TestListLoggableResource, self).setUp()
+ self.cmd = network_log.ListLoggableResource(self.app, self.namespace)
+
+ loggables = {
+ "loggable_resources": [{"type": "security_group"}]
+ }
+ self.neutronclient.list_network_loggable_resources = mock.Mock(
+ return_value=loggables)
+ self.mocked = self.neutronclient.list_network_loggable_resources
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+ self.assertEqual([self.data], list(data))
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+ self.assertEqual([self.data], list(data))
diff --git a/neutronclient/tests/unit/osc/v2/sfc/__init__.py b/neutronclient/tests/unit/osc/v2/sfc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/sfc/fakes.py b/neutronclient/tests/unit/osc/v2/sfc/fakes.py
new file mode 100755
index 000000000..390c67e4e
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/sfc/fakes.py
@@ -0,0 +1,302 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# 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 unittest import mock
+
+
+from osc_lib.tests import utils
+from oslo_utils import uuidutils
+
+from openstack.network.v2 import sfc_flow_classifier as flow_classifier
+from openstack.network.v2 import sfc_port_chain as port_chain
+from openstack.network.v2 import sfc_port_pair as port_pair
+from openstack.network.v2 import sfc_port_pair_group as port_pair_group
+from openstack.network.v2 import sfc_service_graph as service_graph
+
+
+class TestNeutronClientOSCV2(utils.TestCommand):
+
+ def setUp(self):
+ super(TestNeutronClientOSCV2, self).setUp()
+ self.namespace = argparse.Namespace()
+ self.app.client_manager.session = mock.Mock()
+ self.app.client_manager.network = mock.Mock()
+ self.network = self.app.client_manager.network
+ self.network.find_sfc_flow_classifier = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_sfc_port_chain = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_sfc_port_pair = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_sfc_port_pair_group = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_sfc_service_graph = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_port = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+
+
+class FakeSfcPortPair(object):
+ """Fake port pair attributes."""
+
+ @staticmethod
+ def create_port_pair(attrs=None):
+ """Create a fake port pair.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A Dictionary with id, name, description, ingress, egress,
+ service-function-parameter, project_id
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ port_pair_attrs = {
+ 'description': 'description',
+ 'egress': uuidutils.generate_uuid(),
+ 'id': uuidutils.generate_uuid(),
+ 'ingress': uuidutils.generate_uuid(),
+ 'name': 'port-pair-name',
+ 'service_function_parameters': [('correlation', None),
+ ('weight', 1)],
+ 'project_id': uuidutils.generate_uuid(),
+ }
+
+ # Overwrite default attributes.
+ port_pair_attrs.update(attrs)
+ return port_pair.SfcPortPair(**port_pair_attrs)
+
+ @staticmethod
+ def create_port_pairs(attrs=None, count=1):
+ """Create multiple port_pairs.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of port_pairs to fake
+ :return:
+ A list of dictionaries faking the port_pairs
+ """
+ port_pairs = []
+ for _ in range(count):
+ port_pairs.append(FakeSfcPortPair.create_port_pair(attrs))
+
+ return port_pairs
+
+
+class FakeSfcPortPairGroup(object):
+ """Fake port pair group attributes."""
+
+ @staticmethod
+ def create_port_pair_group(attrs=None):
+ """Create a fake port pair group.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A Dictionary with id, name, description, port_pairs, group_id
+ port_pair_group_parameters, project_id
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ port_pair_group_attrs = {
+ 'id': uuidutils.generate_uuid(),
+ 'name': 'port-pair-group-name',
+ 'description': 'description',
+ 'port_pairs': uuidutils.generate_uuid(),
+ 'port_pair_group_parameters': {"lb_fields": []},
+ 'project_id': uuidutils.generate_uuid(),
+ 'tap_enabled': False
+ }
+
+ port_pair_group_attrs.update(attrs)
+ return port_pair_group.SfcPortPairGroup(**port_pair_group_attrs)
+
+ @staticmethod
+ def create_port_pair_groups(attrs=None, count=1):
+ """Create multiple port pair groups.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of port_pair_groups to fake
+ :return:
+ A list of dictionaries faking the port pair groups
+ """
+ port_pair_groups = []
+ for _ in range(count):
+ port_pair_groups.append(
+ FakeSfcPortPairGroup.create_port_pair_group(attrs))
+
+ return port_pair_groups
+
+
+class FakeSfcFlowClassifier(object):
+ """Fake flow classifier attributes."""
+
+ @staticmethod
+ def create_flow_classifier(attrs=None):
+ """Create a fake flow classifier.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A Dictionary with faking port chain attributes
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ flow_classifier_attrs = {
+ 'id': uuidutils.generate_uuid(),
+ 'destination_ip_prefix': '2.2.2.2/32',
+ 'destination_port_range_max': '90',
+ 'destination_port_range_min': '80',
+ 'ethertype': 'IPv4',
+ 'logical_destination_port': uuidutils.generate_uuid(),
+ 'logical_source_port': uuidutils.generate_uuid(),
+ 'name': 'flow-classifier-name',
+ 'description': 'fc_description',
+ 'protocol': 'tcp',
+ 'source_ip_prefix': '1.1.1.1/32',
+ 'source_port_range_max': '20',
+ 'source_port_range_min': '10',
+ 'project_id': uuidutils.generate_uuid(),
+ 'l7_parameters': {}
+ }
+ flow_classifier_attrs.update(attrs)
+ return flow_classifier.SfcFlowClassifier(**flow_classifier_attrs)
+
+ @staticmethod
+ def create_flow_classifiers(attrs=None, count=1):
+ """Create multiple flow classifiers.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of flow classifiers to fake
+ :return:
+ A list of dictionaries faking the flow classifiers
+ """
+ flow_classifiers = []
+ for _ in range(count):
+ flow_classifiers.append(
+ FakeSfcFlowClassifier.create_flow_classifier(attrs))
+
+ return flow_classifiers
+
+
+class FakeSfcPortChain(object):
+ """Fake port chain attributes."""
+
+ @staticmethod
+ def create_port_chain(attrs=None):
+ """Create a fake port chain.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A Dictionary with faking port chain attributes
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ port_chain_attrs = {
+ 'id': uuidutils.generate_uuid(),
+ 'name': 'port-chain-name',
+ 'description': 'description',
+ 'port_pair_groups': uuidutils.generate_uuid(),
+ 'flow_classifiers': uuidutils.generate_uuid(),
+ 'chain_parameters': {"correlation": "mpls", "symmetric": False},
+ 'project_id': uuidutils.generate_uuid(),
+ }
+
+ port_chain_attrs.update(attrs)
+ return port_chain.SfcPortChain(**port_chain_attrs)
+
+ @staticmethod
+ def create_port_chains(attrs=None, count=1):
+ """Create multiple port chains.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of port chains to fake
+ :return:
+ A list of dictionaries faking the port chains.
+ """
+ port_chains = []
+ for _ in range(count):
+ port_chains.append(FakeSfcPortChain.create_port_chain(attrs))
+ return port_chains
+
+
+class FakeSfcServiceGraph(object):
+ """Fake service graph attributes."""
+
+ @staticmethod
+ def create_sfc_service_graph(attrs=None):
+ """Create a fake service graph.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A Dictionary with faking service graph attributes
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ service_graph_attrs = {
+ 'id': uuidutils.generate_uuid(),
+ 'name': 'port-pair-group-name',
+ 'description': 'description',
+ 'port_chains': {uuidutils.generate_uuid(): [
+ uuidutils.generate_uuid()]},
+ 'project_id': uuidutils.generate_uuid(),
+ }
+
+ service_graph_attrs.update(attrs)
+ return service_graph.SfcServiceGraph(**service_graph_attrs)
+
+ @staticmethod
+ def create_sfc_service_graphs(attrs=None, count=1):
+ """Create multiple service graphs.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of service graphs to fake
+ :return:
+ A list of dictionaries faking the service graphs.
+ """
+ service_graphs = []
+ for _ in range(count):
+ service_graphs.append(
+ FakeSfcServiceGraph.create_sfc_service_graph(attrs))
+ return service_graphs
diff --git a/neutronclient/tests/unit/osc/v2/sfc/test_flow_classifier.py b/neutronclient/tests/unit/osc/v2/sfc/test_flow_classifier.py
new file mode 100755
index 000000000..ea1c4f3ab
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/sfc/test_flow_classifier.py
@@ -0,0 +1,385 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# 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 unittest import mock
+
+from osc_lib import exceptions
+import testtools
+
+from neutronclient.osc.v2.sfc import sfc_flow_classifier
+from neutronclient.tests.unit.osc.v2.sfc import fakes
+
+
+class TestCreateSfcFlowClassifier(fakes.TestNeutronClientOSCV2):
+
+ _fc = fakes.FakeSfcFlowClassifier.create_flow_classifier()
+
+ columns = ('Description',
+ 'Destination IP',
+ 'Destination Port Range Max',
+ 'Destination Port Range Min',
+ 'Ethertype',
+ 'ID',
+ 'L7 Parameters',
+ 'Logical Destination Port',
+ 'Logical Source Port',
+ 'Name',
+ 'Project',
+ 'Protocol',
+ 'Source IP',
+ 'Source Port Range Max',
+ 'Source Port Range Min',
+ 'Summary',)
+
+ def get_data(self):
+ return (
+ self._fc['description'],
+ self._fc['destination_ip_prefix'],
+ self._fc['destination_port_range_max'],
+ self._fc['destination_port_range_min'],
+ self._fc['ethertype'],
+ self._fc['id'],
+ self._fc['l7_parameters'],
+ self._fc['logical_destination_port'],
+ self._fc['logical_source_port'],
+ self._fc['name'],
+ self._fc['project_id'],
+ self._fc['protocol'],
+ self._fc['source_ip_prefix'],
+ self._fc['source_port_range_max'],
+ self._fc['source_port_range_min']
+ )
+
+ def setUp(self):
+ super(TestCreateSfcFlowClassifier, self).setUp()
+ self.network.create_sfc_flow_classifier = mock.Mock(
+ return_value=self._fc)
+ self.data = self.get_data()
+
+ # Get the command object to test
+ self.cmd = sfc_flow_classifier.CreateSfcFlowClassifier(self.app,
+ self.namespace)
+
+ def test_create_flow_classifier_default_options(self):
+ arglist = [
+ "--logical-source-port", self._fc['logical_source_port'],
+ "--ethertype", self._fc['ethertype'],
+ self._fc['name'],
+ ]
+ verifylist = [
+ ('logical_source_port', self._fc['logical_source_port']),
+ ('ethertype', self._fc['ethertype']),
+ ('name', self._fc['name']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_flow_classifier.assert_called_once_with(
+ **{'name': self._fc['name'],
+ 'logical_source_port': self._fc['logical_source_port'],
+ 'ethertype': self._fc['ethertype']
+ }
+ )
+ self.assertEqual(self.columns, columns)
+
+ def test_create_flow_classifier(self):
+ arglist = [
+ "--description", self._fc['description'],
+ "--ethertype", self._fc['ethertype'],
+ "--protocol", self._fc['protocol'],
+ "--source-ip-prefix", self._fc['source_ip_prefix'],
+ "--destination-ip-prefix", self._fc['destination_ip_prefix'],
+ "--logical-source-port", self._fc['logical_source_port'],
+ "--logical-destination-port", self._fc['logical_destination_port'],
+ self._fc['name'],
+ "--l7-parameters", 'url=path',
+ ]
+
+ param = 'url=path'
+
+ verifylist = [
+ ('description', self._fc['description']),
+ ('name', self._fc['name']),
+ ('ethertype', self._fc['ethertype']),
+ ('protocol', self._fc['protocol']),
+ ('source_ip_prefix', self._fc['source_ip_prefix']),
+ ('destination_ip_prefix', self._fc['destination_ip_prefix']),
+ ('logical_source_port', self._fc['logical_source_port']),
+ ('logical_destination_port',
+ self._fc['logical_destination_port']),
+ ('l7_parameters', param)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+ self.network.create_sfc_flow_classifier.assert_called_once_with(
+ **{
+ 'name': self._fc['name'],
+ 'description': self._fc['description'],
+ 'ethertype': self._fc['ethertype'],
+ 'protocol': self._fc['protocol'],
+ 'source_ip_prefix': self._fc['source_ip_prefix'],
+ 'destination_ip_prefix': self._fc['destination_ip_prefix'],
+ 'logical_source_port': self._fc['logical_source_port'],
+ 'logical_destination_port':
+ self._fc['logical_destination_port'],
+ 'l7_parameters': param
+ }
+ )
+ self.assertEqual(self.columns, columns)
+
+
+class TestDeleteSfcFlowClassifier(fakes.TestNeutronClientOSCV2):
+
+ _flow_classifier = \
+ fakes.FakeSfcFlowClassifier.create_flow_classifiers(count=1)
+
+ def setUp(self):
+ super(TestDeleteSfcFlowClassifier, self).setUp()
+ self.network.delete_sfc_flow_classifier = mock.Mock(
+ return_value=None)
+ self.cmd = sfc_flow_classifier.DeleteSfcFlowClassifier(self.app,
+ self.namespace)
+
+ def test_delete_flow_classifier(self):
+ client = self.app.client_manager.network
+ mock_flow_classifier_delete = client.delete_sfc_flow_classifier
+ arglist = [
+ self._flow_classifier[0]['id'],
+ ]
+ verifylist = [
+ ('flow_classifier', [self._flow_classifier[0]['id']]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ mock_flow_classifier_delete.assert_called_once_with(
+ self._flow_classifier[0]['id'])
+ self.assertIsNone(result)
+
+ def test_delete_multiple_flow_classifiers_with_exception(self):
+ client = self.app.client_manager.network
+ target1 = self._flow_classifier[0]['id']
+ arglist = [target1]
+ verifylist = [('flow_classifier', [target1])]
+
+ client.find_sfc_flow_classifier.side_effect = [
+ target1, exceptions.CommandError
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = "1 of 2 flow classifier(s) failed to delete."
+ with testtools.ExpectedException(exceptions.CommandError) as e:
+ self.cmd.take_action(parsed_args)
+ self.assertEqual(msg, str(e))
+
+
+class TestSetSfcFlowClassifier(fakes.TestNeutronClientOSCV2):
+ _flow_classifier = fakes.FakeSfcFlowClassifier.create_flow_classifier()
+ _flow_classifier_name = _flow_classifier['name']
+ _flow_classifier_id = _flow_classifier['id']
+
+ def setUp(self):
+ super(TestSetSfcFlowClassifier, self).setUp()
+ self.network.update_sfc_flow_classifier = mock.Mock(
+ return_value=None)
+ self.cmd = sfc_flow_classifier.SetSfcFlowClassifier(self.app,
+ self.namespace)
+
+ def test_set_flow_classifier(self):
+ client = self.app.client_manager.network
+ mock_flow_classifier_update = client.update_sfc_flow_classifier
+ arglist = [
+ self._flow_classifier_name,
+ '--name', 'name_updated',
+ '--description', 'desc_updated'
+ ]
+ verifylist = [
+ ('flow_classifier', self._flow_classifier_name),
+ ('name', 'name_updated'),
+ ('description', 'desc_updated'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'name': 'name_updated',
+ 'description': 'desc_updated'}
+ mock_flow_classifier_update.assert_called_once_with(
+ self._flow_classifier_name, **attrs)
+ self.assertIsNone(result)
+
+
+class TestShowSfcFlowClassifier(fakes.TestNeutronClientOSCV2):
+
+ _fc = fakes.FakeSfcFlowClassifier.create_flow_classifier()
+ data = (
+ _fc['description'],
+ _fc['destination_ip_prefix'],
+ _fc['destination_port_range_max'],
+ _fc['destination_port_range_min'],
+ _fc['ethertype'],
+ _fc['id'],
+ _fc['l7_parameters'],
+ _fc['logical_destination_port'],
+ _fc['logical_source_port'],
+ _fc['name'],
+ _fc['project_id'],
+ _fc['protocol'],
+ _fc['source_ip_prefix'],
+ _fc['source_port_range_max'],
+ _fc['source_port_range_min']
+ )
+ _flow_classifier = _fc
+ _flow_classifier_id = _fc['id']
+ columns = ('Description',
+ 'Destination IP',
+ 'Destination Port Range Max',
+ 'Destination Port Range Min',
+ 'Ethertype',
+ 'ID',
+ 'L7 Parameters',
+ 'Logical Destination Port',
+ 'Logical Source Port',
+ 'Name',
+ 'Project',
+ 'Protocol',
+ 'Source IP',
+ 'Source Port Range Max',
+ 'Source Port Range Min',
+ 'Summary',)
+
+ def setUp(self):
+ super(TestShowSfcFlowClassifier, self).setUp()
+ self.network.get_sfc_flow_classifier = mock.Mock(
+ return_value=self._flow_classifier
+ )
+ # Get the command object to test
+ self.cmd = sfc_flow_classifier.ShowSfcFlowClassifier(self.app,
+ self.namespace)
+
+ def test_show_flow_classifier(self):
+ client = self.app.client_manager.network
+ mock_flow_classifier_show = client.get_sfc_flow_classifier
+ arglist = [
+ self._flow_classifier_id,
+ ]
+ verifylist = [
+ ('flow_classifier', self._flow_classifier_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ mock_flow_classifier_show.assert_called_once_with(
+ self._flow_classifier_id)
+ self.assertEqual(self.columns, columns)
+
+
+class TestListSfcFlowClassifier(fakes.TestNeutronClientOSCV2):
+
+ _fc = fakes.FakeSfcFlowClassifier.create_flow_classifiers(count=1)
+
+ columns = ('ID', 'Name', 'Summary')
+
+ columns_long = ('ID', 'Name', 'Protocol', 'Ethertype', 'Source IP',
+ 'Destination IP', 'Logical Source Port',
+ 'Logical Destination Port', 'Source Port Range Min',
+ 'Source Port Range Max', 'Destination Port Range Min',
+ 'Destination Port Range Max', 'L7 Parameters',
+ 'Description', 'Project')
+ _flow_classifier = _fc[0]
+ data = [
+ _flow_classifier['id'],
+ _flow_classifier['name'],
+ _flow_classifier['protocol'],
+ _flow_classifier['source_ip_prefix'],
+ _flow_classifier['destination_ip_prefix'],
+ _flow_classifier['logical_source_port'],
+ _flow_classifier['logical_destination_port']
+ ]
+ data_long = [
+ _flow_classifier['id'],
+ _flow_classifier['name'],
+ _flow_classifier['protocol'],
+ _flow_classifier['ethertype'],
+ _flow_classifier['source_ip_prefix'],
+ _flow_classifier['destination_ip_prefix'],
+ _flow_classifier['logical_source_port'],
+ _flow_classifier['logical_destination_port'],
+ _flow_classifier['source_port_range_min'],
+ _flow_classifier['source_port_range_max'],
+ _flow_classifier['destination_port_range_min'],
+ _flow_classifier['destination_port_range_max'],
+ _flow_classifier['l7_parameters'],
+ _flow_classifier['description']
+ ]
+
+ _flow_classifier1 = {'flow_classifiers': _flow_classifier}
+ _flow_classifier_id = _flow_classifier['id']
+
+ def setUp(self):
+ super(TestListSfcFlowClassifier, self).setUp()
+ self.network.sfc_flow_classifiers = mock.Mock(
+ return_value=self._fc
+ )
+ # Get the command object to test
+ self.cmd = sfc_flow_classifier.ListSfcFlowClassifier(self.app,
+ self.namespace)
+
+ def test_list_flow_classifiers(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)
+ fcs = self.network.sfc_flow_classifiers()
+ fc = fcs[0]
+ data = [
+ fc['id'],
+ fc['name'],
+ fc['protocol'],
+ fc['source_ip_prefix'],
+ fc['destination_ip_prefix'],
+ fc['logical_source_port'],
+ fc['logical_destination_port']
+ ]
+ self.assertEqual(list(self.columns), columns[0])
+ self.assertEqual(self.data, data)
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ fcs = self.network.sfc_flow_classifiers()
+ fc = fcs[0]
+ data = [
+ fc['id'],
+ fc['name'],
+ fc['protocol'],
+ fc['ethertype'],
+ fc['source_ip_prefix'],
+ fc['destination_ip_prefix'],
+ fc['logical_source_port'],
+ fc['logical_destination_port'],
+ fc['source_port_range_min'],
+ fc['source_port_range_max'],
+ fc['destination_port_range_min'],
+ fc['destination_port_range_max'],
+ fc['l7_parameters'],
+ fc['description']
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns_long = self.cmd.take_action(parsed_args)[0]
+ self.assertEqual(list(self.columns_long), columns_long)
+ self.assertEqual(self.data_long, data)
diff --git a/neutronclient/tests/unit/osc/v2/sfc/test_port_chain.py b/neutronclient/tests/unit/osc/v2/sfc/test_port_chain.py
new file mode 100755
index 000000000..15f7a431e
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/sfc/test_port_chain.py
@@ -0,0 +1,499 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# 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 unittest import mock
+
+from osc_lib import exceptions
+import testtools
+
+from neutronclient.osc.v2.sfc import sfc_port_chain
+from neutronclient.tests.unit.osc.v2.sfc import fakes
+
+
+def _get_id(client, id_or_name, resource):
+ return id_or_name
+
+
+class TestCreateSfcPortChain(fakes.TestNeutronClientOSCV2):
+ # The new port_chain created
+ _port_chain = fakes.FakeSfcPortChain.create_port_chain()
+
+ columns = ('Chain Parameters',
+ 'Description',
+ 'Flow Classifiers',
+ 'ID',
+ 'Name',
+ 'Port Pair Groups',
+ 'Project')
+
+ def get_data(self):
+ return (
+ self._port_chain['chain_parameters'],
+ self._port_chain['description'],
+ self._port_chain['flow_classifiers'],
+ self._port_chain['id'],
+ self._port_chain['name'],
+ self._port_chain['port_pair_groups'],
+ self._port_chain['project_id'],
+ )
+
+ def setUp(self):
+ super(TestCreateSfcPortChain, self).setUp()
+ self.network.create_sfc_port_chain = mock.Mock(
+ return_value=self._port_chain)
+ self.data = self.get_data()
+
+ # Get the command object to test
+ self.cmd = sfc_port_chain.CreateSfcPortChain(self.app, self.namespace)
+
+ def test_create_port_chain_default_options(self):
+ arglist = [
+ self._port_chain['name'],
+ "--port-pair-group", self._port_chain['port_pair_groups']
+ ]
+ verifylist = [
+ ('name', self._port_chain['name']),
+ ('port_pair_groups', [self._port_chain['port_pair_groups']]),
+ ('flow_classifiers', []),
+ ('chain_parameters', None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_port_chain.assert_called_once_with(
+ **{
+ 'name': self._port_chain['name'],
+ 'port_pair_groups': [self._port_chain['port_pair_groups']]
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_port_chain_all_options(self):
+ arglist = [
+ "--description", self._port_chain['description'],
+ "--port-pair-group", self._port_chain['port_pair_groups'],
+ self._port_chain['name'],
+ "--flow-classifier", self._port_chain['flow_classifiers'],
+ "--chain-parameters", 'correlation=mpls,symmetric=true',
+ ]
+
+ cp = {'correlation': 'mpls', 'symmetric': 'true'}
+
+ verifylist = [
+ ('port_pair_groups', [self._port_chain['port_pair_groups']]),
+ ('name', self._port_chain['name']),
+ ('description', self._port_chain['description']),
+ ('flow_classifiers', [self._port_chain['flow_classifiers']]),
+ ('chain_parameters', [cp])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_port_chain.assert_called_once_with(
+ **{
+ 'name': self._port_chain['name'],
+ 'port_pair_groups': [self._port_chain['port_pair_groups']],
+ 'description': self._port_chain['description'],
+ 'flow_classifiers': [self._port_chain['flow_classifiers']],
+ 'chain_parameters': cp
+ }
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestDeleteSfcPortChain(fakes.TestNeutronClientOSCV2):
+
+ _port_chain = fakes.FakeSfcPortChain.create_port_chains(count=1)
+
+ def setUp(self):
+ super(TestDeleteSfcPortChain, self).setUp()
+ self.network.delete_sfc_port_chain = mock.Mock(return_value=None)
+ self.cmd = sfc_port_chain.DeleteSfcPortChain(self.app, self.namespace)
+
+ def test_delete_port_chain(self):
+ client = self.app.client_manager.network
+ mock_port_chain_delete = client.delete_sfc_port_chain
+ arglist = [
+ self._port_chain[0]['id'],
+ ]
+ verifylist = [
+ ('port_chain', [self._port_chain[0]['id']]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ mock_port_chain_delete.assert_called_once_with(
+ self._port_chain[0]['id'])
+ self.assertIsNone(result)
+
+ def test_delete_multiple_port_chains_with_exception(self):
+ client = self.app.client_manager.network
+ target1 = self._port_chain[0]['id']
+ arglist = [target1]
+ verifylist = [('port_chain', [target1])]
+
+ client.find_sfc_port_chain.side_effect = [
+ target1, exceptions.CommandError
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = "1 of 2 port chain(s) failed to delete."
+ with testtools.ExpectedException(exceptions.CommandError) as e:
+ self.cmd.take_action(parsed_args)
+ self.assertEqual(msg, str(e))
+
+
+class TestListSfcPortChain(fakes.TestNeutronClientOSCV2):
+ _port_chains = fakes.FakeSfcPortChain.create_port_chains(count=1)
+ columns = ('ID', 'Name', 'Port Pair Groups', 'Flow Classifiers',
+ 'Chain Parameters')
+ columns_long = ('ID', 'Name', 'Port Pair Groups', 'Flow Classifiers',
+ 'Chain Parameters', 'Description', 'Project')
+ _port_chain = _port_chains[0]
+ data = [
+ _port_chain['id'],
+ _port_chain['name'],
+ _port_chain['port_pair_groups'],
+ _port_chain['flow_classifiers'],
+ _port_chain['chain_parameters'],
+ ]
+ data_long = [
+ _port_chain['id'],
+ _port_chain['name'],
+ _port_chain['project_id'],
+ _port_chain['port_pair_groups'],
+ _port_chain['flow_classifiers'],
+ _port_chain['chain_parameters'],
+ _port_chain['description']
+ ]
+ _port_chain1 = {'port_chains': _port_chain}
+ _port_chain_id = _port_chain['id']
+
+ def setUp(self):
+ super(TestListSfcPortChain, self).setUp()
+ self.network.sfc_port_chains = mock.Mock(
+ return_value=self._port_chains
+ )
+ # Get the command object to test
+ self.cmd = sfc_port_chain.ListSfcPortChain(self.app, self.namespace)
+
+ def test_list_port_chains(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)[0]
+ pcs = self.network.sfc_port_chains()
+ pc = pcs[0]
+ data = [
+ pc['id'],
+ pc['name'],
+ pc['port_pair_groups'],
+ pc['flow_classifiers'],
+ pc['chain_parameters'],
+ ]
+ self.assertEqual(list(self.columns), columns)
+ self.assertEqual(self.data, data)
+
+ def test_list_port_chain_with_long_opion(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)[0]
+ pcs = self.network.sfc_port_chains()
+ pc = pcs[0]
+ data = [
+ pc['id'],
+ pc['name'],
+ pc['project_id'],
+ pc['port_pair_groups'],
+ pc['flow_classifiers'],
+ pc['chain_parameters'],
+ pc['description']
+ ]
+ self.assertEqual(list(self.columns_long), columns)
+ self.assertEqual(self.data_long, data)
+
+
+class TestSetSfcPortChain(fakes.TestNeutronClientOSCV2):
+ _port_chain = fakes.FakeSfcPortChain.create_port_chain()
+ resource = _port_chain
+ res = 'port_chain'
+ _port_chain_name = _port_chain['name']
+ _port_chain_id = _port_chain['id']
+ pc_ppg = _port_chain['port_pair_groups']
+ pc_fc = _port_chain['flow_classifiers']
+
+ def setUp(self):
+ super(TestSetSfcPortChain, self).setUp()
+ self.mocked = self.network.update_sfc_port_chain
+ self.cmd = sfc_port_chain.SetSfcPortChain(self.app, self.namespace)
+
+ def test_set_port_chain(self):
+ client = self.app.client_manager.network
+ mock_port_chain_update = client.update_sfc_port_chain
+ arglist = [
+ self._port_chain_name,
+ '--name', 'name_updated',
+ '--description', 'desc_updated',
+ ]
+ verifylist = [
+ ('port_chain', self._port_chain_name),
+ ('name', 'name_updated'),
+ ('description', 'desc_updated'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {'name': 'name_updated', 'description': 'desc_updated'}
+ mock_port_chain_update.assert_called_once_with(self._port_chain_name,
+ **attrs)
+ self.assertIsNone(result)
+
+ def test_set_flow_classifiers(self):
+ target = self.resource['id']
+ fc1 = 'flow_classifier1'
+ fc2 = 'flow_classifier2'
+
+ self.network.find_sfc_port_chain = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id, 'flow_classifiers': [self.pc_fc]}
+ )
+ self.network.find_sfc_flow_classifier.side_effect = \
+ lambda name_or_id, ignore_missing=False: {'id': name_or_id}
+ arglist = [
+ target,
+ '--flow-classifier', fc1,
+ '--flow-classifier', fc2,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('flow_classifiers', [fc1, fc2])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'flow_classifiers': [self.pc_fc, fc1, fc2]}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_set_no_flow_classifier(self):
+ client = self.app.client_manager.network
+ mock_port_chain_update = client.update_sfc_port_chain
+ arglist = [
+ self._port_chain_name,
+ '--no-flow-classifier',
+ ]
+ verifylist = [
+ ('port_chain', self._port_chain_name),
+ ('no_flow_classifier', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {'flow_classifiers': []}
+ mock_port_chain_update.assert_called_once_with(self._port_chain_name,
+ **attrs)
+ self.assertIsNone(result)
+
+ def test_set_port_pair_groups(self):
+ target = self.resource['id']
+ existing_ppg = self.pc_ppg
+ ppg1 = 'port_pair_group1'
+ ppg2 = 'port_pair_group2'
+
+ self.network.find_sfc_port_chain = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id, 'port_pair_groups': [self.pc_ppg]}
+ )
+ arglist = [
+ target,
+ '--port-pair-group', ppg1,
+ '--port-pair-group', ppg2,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('port_pair_groups', [ppg1, ppg2])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'port_pair_groups': [existing_ppg, ppg1, ppg2]}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_set_no_port_pair_group(self):
+ target = self.resource['id']
+ ppg1 = 'port_pair_group1'
+
+ arglist = [
+ target,
+ '--no-port-pair-group',
+ '--port-pair-group', ppg1,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('no_port_pair_group', True),
+ ('port_pair_groups', [ppg1])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'port_pair_groups': [ppg1]}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_set_only_no_port_pair_group(self):
+ target = self.resource['id']
+ arglist = [
+ target,
+ '--no-port-pair-group',
+ ]
+ verifylist = [
+ (self.res, target),
+ ('no_port_pair_group', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
+
+class TestShowSfcPortChain(fakes.TestNeutronClientOSCV2):
+
+ _pc = fakes.FakeSfcPortChain.create_port_chain()
+ data = (
+ _pc['chain_parameters'],
+ _pc['description'],
+ _pc['flow_classifiers'],
+ _pc['id'],
+ _pc['name'],
+ _pc['port_pair_groups'],
+ _pc['project_id']
+ )
+ _port_chain = _pc
+ _port_chain_id = _pc['id']
+ columns = ('Chain Parameters',
+ 'Description',
+ 'Flow Classifiers',
+ 'ID',
+ 'Name',
+ 'Port Pair Groups',
+ 'Project')
+
+ def setUp(self):
+ super(TestShowSfcPortChain, self).setUp()
+ self.network.get_sfc_port_chain = mock.Mock(
+ return_value=self._port_chain
+ )
+ # Get the command object to test
+ self.cmd = sfc_port_chain.ShowSfcPortChain(self.app, self.namespace)
+
+ def test_show_port_chain(self):
+ client = self.app.client_manager.network
+ mock_port_chain_show = client.get_sfc_port_chain
+ arglist = [
+ self._port_chain_id,
+ ]
+ verifylist = [
+ ('port_chain', self._port_chain_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ mock_port_chain_show.assert_called_once_with(self._port_chain_id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestUnsetSfcPortChain(fakes.TestNeutronClientOSCV2):
+ _port_chain = fakes.FakeSfcPortChain.create_port_chain()
+ resource = _port_chain
+ res = 'port_chain'
+ _port_chain_name = _port_chain['name']
+ _port_chain_id = _port_chain['id']
+ pc_ppg = _port_chain['port_pair_groups']
+ pc_fc = _port_chain['flow_classifiers']
+
+ def setUp(self):
+ super(TestUnsetSfcPortChain, self).setUp()
+ self.network.update_sfc_port_chain = mock.Mock(
+ return_value=None)
+ self.mocked = self.network.update_sfc_port_chain
+ self.cmd = sfc_port_chain.UnsetSfcPortChain(self.app, self.namespace)
+
+ def test_unset_port_pair_group(self):
+ target = self.resource['id']
+ ppg1 = 'port_pair_group1'
+
+ self.network.find_sfc_port_chain = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id, 'port_pair_groups': [self.pc_ppg]}
+ )
+ self.network.find_sfc_port_pair_group.side_effect = \
+ lambda name_or_id, ignore_missing=False: {'id': name_or_id}
+
+ arglist = [
+ target,
+ '--port-pair-group', ppg1,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('port_pair_groups', [ppg1])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'port_pair_groups': [self.pc_ppg]}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_unset_flow_classifier(self):
+ target = self.resource['id']
+ fc1 = 'flow_classifier1'
+ self.network.find_sfc_port_chain = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id, 'flow_classifiers': [self.pc_fc]}
+ )
+ self.network.find_sfc_flow_classifier.side_effect = \
+ lambda name_or_id, ignore_missing=False: {'id': name_or_id}
+
+ arglist = [
+ target,
+ '--flow-classifier', fc1,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('flow_classifiers', [fc1])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'flow_classifiers': [self.pc_fc]}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_unset_all_flow_classifier(self):
+ client = self.app.client_manager.network
+ target = self.resource['id']
+ mock_port_chain_update = client.update_sfc_port_chain
+ arglist = [
+ target,
+ '--all-flow-classifier',
+ ]
+ verifylist = [
+ (self.res, target),
+ ('all_flow_classifier', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'flow_classifiers': []}
+ mock_port_chain_update.assert_called_once_with(target,
+ **expect)
+ self.assertIsNone(result)
diff --git a/neutronclient/tests/unit/osc/v2/sfc/test_port_pair.py b/neutronclient/tests/unit/osc/v2/sfc/test_port_pair.py
new file mode 100755
index 000000000..e0610a360
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/sfc/test_port_pair.py
@@ -0,0 +1,313 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# 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 unittest import mock
+
+from osc_lib import exceptions
+import testtools
+
+from neutronclient.osc.v2.sfc import sfc_port_pair
+from neutronclient.tests.unit.osc.v2.sfc import fakes
+
+
+class TestCreateSfcPortPair(fakes.TestNeutronClientOSCV2):
+ # The new port_pair created
+ _port_pair = fakes.FakeSfcPortPair.create_port_pair()
+
+ columns = ('Description',
+ 'Egress Logical Port',
+ 'ID',
+ 'Ingress Logical Port',
+ 'Name',
+ 'Project',
+ 'Service Function Parameters')
+
+ def get_data(self):
+ return (
+ self._port_pair['description'],
+ self._port_pair['egress'],
+ self._port_pair['id'],
+ self._port_pair['ingress'],
+ self._port_pair['name'],
+ self._port_pair['project_id'],
+ self._port_pair['service_function_parameters']
+ )
+
+ def setUp(self):
+ super(TestCreateSfcPortPair, self).setUp()
+ self.network.create_sfc_port_pair = mock.Mock(
+ return_value=self._port_pair)
+ self.data = self.get_data()
+
+ # Get the command object to test
+ self.cmd = sfc_port_pair.CreateSfcPortPair(self.app, self.namespace)
+
+ def test_create_port_pair_default_options(self):
+ arglist = [
+ "--ingress", self._port_pair['ingress'],
+ "--egress", self._port_pair['egress'],
+ self._port_pair['name'],
+ ]
+ verifylist = [
+ ('ingress', self._port_pair['ingress']),
+ ('egress', self._port_pair['egress']),
+ ('name', self._port_pair['name'])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_port_pair.assert_called_once_with(
+ **{'name': self._port_pair['name'],
+ 'ingress': self._port_pair['ingress'],
+ 'egress': self._port_pair['egress']}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def _test_create_port_pair_all_options(self, correlation):
+ arglist = [
+ "--description", self._port_pair['description'],
+ "--egress", self._port_pair['egress'],
+ "--ingress", self._port_pair['ingress'],
+ self._port_pair['name'],
+ "--service-function-parameters",
+ 'correlation=%s,weight=1' % correlation,
+ ]
+
+ verifylist = [
+ ('ingress', self._port_pair['ingress']),
+ ('egress', self._port_pair['egress']),
+ ('name', self._port_pair['name']),
+ ('description', self._port_pair['description']),
+ ('service_function_parameters',
+ [{'correlation': correlation, 'weight': '1'}])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ if correlation == "None":
+ correlation_param = None
+ else:
+ correlation_param = correlation
+ self.network.create_sfc_port_pair.assert_called_once_with(
+ **{'name': self._port_pair['name'],
+ 'ingress': self._port_pair['ingress'],
+ 'egress': self._port_pair['egress'],
+ 'description': self._port_pair['description'],
+ 'service_function_parameters':
+ {'correlation': correlation_param, 'weight': '1'}}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_port_pair_all_options(self):
+ self._test_create_port_pair_all_options('None')
+
+ def test_create_port_pair_all_options_mpls(self):
+ self._test_create_port_pair_all_options('mpls')
+
+
+class TestDeleteSfcPortPair(fakes.TestNeutronClientOSCV2):
+
+ _port_pair = fakes.FakeSfcPortPair.create_port_pairs(count=1)
+
+ def setUp(self):
+ super(TestDeleteSfcPortPair, self).setUp()
+ self.network.delete_sfc_port_pair = mock.Mock(return_value=None)
+ self.cmd = sfc_port_pair.DeleteSfcPortPair(self.app, self.namespace)
+
+ def test_delete_port_pair(self):
+ client = self.app.client_manager.network
+ mock_port_pair_delete = client.delete_sfc_port_pair
+ arglist = [
+ self._port_pair[0]['id'],
+ ]
+ verifylist = [
+ ('port_pair', [self._port_pair[0]['id']]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ mock_port_pair_delete.assert_called_once_with(
+ self._port_pair[0]['id'])
+ self.assertIsNone(result)
+
+ def test_delete_multiple_port_pairs_with_exception(self):
+ client = self.app.client_manager.network
+ target1 = self._port_pair[0]['id']
+ arglist = [target1]
+ verifylist = [('port_pair', [target1])]
+
+ client.find_sfc_port_pair.side_effect = [
+ target1, exceptions.CommandError
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = "1 of 2 port pair(s) failed to delete."
+ with testtools.ExpectedException(exceptions.CommandError) as e:
+ self.cmd.take_action(parsed_args)
+ self.assertEqual(msg, str(e))
+
+
+class TestListSfcPortPair(fakes.TestNeutronClientOSCV2):
+ _port_pairs = fakes.FakeSfcPortPair.create_port_pairs()
+ columns = ('ID', 'Name', 'Ingress Logical Port', 'Egress Logical Port')
+ columns_long = ('ID', 'Name', 'Ingress Logical Port',
+ 'Egress Logical Port', 'Service Function Parameters',
+ 'Description', 'Project')
+ _port_pair = _port_pairs[0]
+ data = [
+ _port_pair['id'],
+ _port_pair['name'],
+ _port_pair['ingress'],
+ _port_pair['egress']
+ ]
+ data_long = [
+ _port_pair['id'],
+ _port_pair['name'],
+ _port_pair['ingress'],
+ _port_pair['egress'],
+ _port_pair['service_function_parameters'],
+ _port_pair['description']
+ ]
+ _port_pair1 = {'port_pairs': _port_pair}
+ _port_pair_id = _port_pair['id'],
+
+ def setUp(self):
+ super(TestListSfcPortPair, self).setUp()
+ self.network.sfc_port_pairs = mock.Mock(
+ return_value=self._port_pairs)
+ # Get the command object to test
+ self.cmd = sfc_port_pair.ListSfcPortPair(self.app, self.namespace)
+
+ def test_list_port_pairs(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)[0]
+ port_pairs = self.network.sfc_port_pairs()
+ port_pair = port_pairs[0]
+ data = [
+ port_pair['id'],
+ port_pair['name'],
+ port_pair['ingress'],
+ port_pair['egress']
+ ]
+ self.assertEqual(list(self.columns), columns)
+ self.assertEqual(self.data, data)
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ port_pairs = self.network.sfc_port_pairs()
+ port_pair = port_pairs[0]
+ data = [
+ port_pair['id'],
+ port_pair['name'],
+ port_pair['ingress'],
+ port_pair['egress'],
+ port_pair['service_function_parameters'],
+ port_pair['description']
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns_long = self.cmd.take_action(parsed_args)[0]
+ self.assertEqual(list(self.columns_long), columns_long)
+ self.assertEqual(self.data_long, data)
+
+
+class TestSetSfcPortPair(fakes.TestNeutronClientOSCV2):
+ _port_pair = fakes.FakeSfcPortPair.create_port_pair()
+ _port_pair_name = _port_pair['name']
+ _port_pair_id = _port_pair['id']
+
+ def setUp(self):
+ super(TestSetSfcPortPair, self).setUp()
+ self.network.update_sfc_port_pair = mock.Mock(return_value=None)
+ self.cmd = sfc_port_pair.SetSfcPortPair(self.app, self.namespace)
+
+ def test_set_port_pair(self):
+ client = self.app.client_manager.network
+ mock_port_pair_update = client.update_sfc_port_pair
+ arglist = [
+ self._port_pair_name,
+ '--name', 'name_updated',
+ '--description', 'desc_updated'
+ ]
+ verifylist = [
+ ('port_pair', self._port_pair_name),
+ ('name', 'name_updated'),
+ ('description', 'desc_updated'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'name': 'name_updated',
+ 'description': 'desc_updated'
+ }
+ mock_port_pair_update.assert_called_once_with(self._port_pair_name,
+ **attrs)
+ self.assertIsNone(result)
+
+
+class TestShowSfcPortPair(fakes.TestNeutronClientOSCV2):
+
+ _pp = fakes.FakeSfcPortPair.create_port_pair()
+
+ data = (
+ _pp['description'],
+ _pp['egress'],
+ _pp['id'],
+ _pp['ingress'],
+ _pp['name'],
+ _pp['project_id'],
+ _pp['service_function_parameters'],
+ )
+ _port_pair = _pp
+ _port_pair_id = _pp['id']
+ columns = (
+ 'Description',
+ 'Egress Logical Port',
+ 'ID',
+ 'Ingress Logical Port',
+ 'Name',
+ 'Project',
+ 'Service Function Parameters'
+ )
+
+ def setUp(self):
+ super(TestShowSfcPortPair, self).setUp()
+
+ self.network.get_sfc_port_pair = mock.Mock(
+ return_value=self._port_pair
+ )
+
+ # Get the command object to test
+ self.cmd = sfc_port_pair.ShowSfcPortPair(self.app, self.namespace)
+
+ def test_show_port_pair(self):
+ client = self.app.client_manager.network
+ mock_port_pair_show = client.get_sfc_port_pair
+ arglist = [
+ self._port_pair_id,
+ ]
+ verifylist = [
+ ('port_pair', self._port_pair_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ mock_port_pair_show.assert_called_once_with(self._port_pair_id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/neutronclient/tests/unit/osc/v2/sfc/test_port_pair_group.py b/neutronclient/tests/unit/osc/v2/sfc/test_port_pair_group.py
new file mode 100755
index 000000000..49f3fcf06
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/sfc/test_port_pair_group.py
@@ -0,0 +1,433 @@
+# Copyright (c) 2017 Huawei Technologies India Pvt.Limited.
+# 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 unittest import mock
+
+from osc_lib import exceptions
+import testtools
+
+from neutronclient.osc.v2.sfc import sfc_port_pair_group
+from neutronclient.tests.unit.osc.v2.sfc import fakes
+
+
+class TestCreateSfcPortPairGroup(fakes.TestNeutronClientOSCV2):
+
+ _port_pair_group = fakes.FakeSfcPortPairGroup.create_port_pair_group()
+
+ columns = ('Description',
+ 'ID',
+ 'Name',
+ 'Port Pair',
+ 'Port Pair Group Parameters',
+ 'Project',
+ 'Tap Enabled')
+
+ def get_data(self, ppg):
+ return (
+ ppg['description'],
+ ppg['id'],
+ ppg['name'],
+ ppg['port_pairs'],
+ ppg['port_pair_group_parameters'],
+ ppg['project_id'],
+ ppg['tap_enabled']
+ )
+
+ def setUp(self):
+ super(TestCreateSfcPortPairGroup, self).setUp()
+ self.network.create_sfc_port_pair_group = mock.Mock(
+ return_value=self._port_pair_group)
+ self.data = self.get_data(self._port_pair_group)
+
+ # Get the command object to test
+ self.cmd = sfc_port_pair_group.CreateSfcPortPairGroup(self.app,
+ self.namespace)
+
+ def test_create_port_pair_group_default_options(self):
+ arglist = [
+ "--port-pair", self._port_pair_group['port_pairs'],
+ self._port_pair_group['name'],
+ ]
+ verifylist = [
+ ('port_pairs', [self._port_pair_group['port_pairs']]),
+ ('name', self._port_pair_group['name']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+ self.network.create_sfc_port_pair_group.assert_called_once_with(
+ **{'name': self._port_pair_group['name'],
+ 'port_pairs': [self._port_pair_group['port_pairs']]})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_port_pair_group(self):
+ arglist = [
+ "--description", self._port_pair_group['description'],
+ "--port-pair", self._port_pair_group['port_pairs'],
+ self._port_pair_group['name'],
+ ]
+ verifylist = [
+ ('port_pairs', [self._port_pair_group['port_pairs']]),
+ ('name', self._port_pair_group['name']),
+ ('description', self._port_pair_group['description']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_port_pair_group.assert_called_once_with(
+ **{
+ 'name': self._port_pair_group['name'],
+ 'port_pairs': [self._port_pair_group['port_pairs']],
+ 'description': self._port_pair_group['description']})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_tap_enabled_port_pair_group(self):
+ arglist = [
+ "--description", self._port_pair_group['description'],
+ "--port-pair", self._port_pair_group['port_pairs'],
+ self._port_pair_group['name'],
+ "--enable-tap"
+ ]
+ verifylist = [
+ ('port_pairs', [self._port_pair_group['port_pairs']]),
+ ('name', self._port_pair_group['name']),
+ ('description', self._port_pair_group['description']),
+ ('enable_tap', True)
+ ]
+
+ expected_data = self._update_expected_response_data(
+ data={
+ 'tap_enabled': True
+ }
+ )
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_port_pair_group.assert_called_once_with(
+ **{
+ 'name': self._port_pair_group['name'],
+ 'port_pairs': [self._port_pair_group['port_pairs']],
+ 'description': self._port_pair_group['description'],
+ 'tap_enabled': True})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(expected_data, data)
+
+ def _update_expected_response_data(self, data):
+ # REVISIT(vks1) - This method can be common for other test functions.
+ ppg = fakes.FakeSfcPortPairGroup.create_port_pair_group(data)
+ self.network.create_sfc_port_pair_group.return_value = ppg
+ return self.get_data(ppg)
+
+
+class TestDeleteSfcPortPairGroup(fakes.TestNeutronClientOSCV2):
+
+ _port_pair_group = (fakes.FakeSfcPortPairGroup.create_port_pair_groups
+ (count=1))
+
+ def setUp(self):
+ super(TestDeleteSfcPortPairGroup, self).setUp()
+ self.network.delete_sfc_port_pair_group = mock.Mock(
+ return_value=None)
+ self.cmd = sfc_port_pair_group.DeleteSfcPortPairGroup(self.app,
+ self.namespace)
+
+ def test_delete_port_pair_group(self):
+ client = self.app.client_manager.network
+ mock_port_pair_group_delete = client.delete_sfc_port_pair_group
+ arglist = [
+ self._port_pair_group[0]['id'],
+ ]
+ verifylist = [
+ ('port_pair_group', [self._port_pair_group[0]['id']]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ mock_port_pair_group_delete.assert_called_once_with(
+ self._port_pair_group[0]['id'])
+ self.assertIsNone(result)
+
+ def test_delete_multiple_port_pair_groups_with_exception(self):
+ client = self.app.client_manager.network
+ target1 = 'target'
+ arglist = [target1]
+ verifylist = [('port_pair_group', [target1])]
+
+ client.find_sfc_port_pair_group.side_effect = [
+ target1, exceptions.CommandError
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = "1 of 2 port pair group(s) failed to delete."
+ with testtools.ExpectedException(exceptions.CommandError) as e:
+ self.cmd.take_action(parsed_args)
+ self.assertEqual(msg, str(e))
+
+
+class TestListSfcPortPairGroup(fakes.TestNeutronClientOSCV2):
+ _ppgs = fakes.FakeSfcPortPairGroup.create_port_pair_groups(count=1)
+ columns = ('ID', 'Name', 'Port Pair', 'Port Pair Group Parameters',
+ 'Tap Enabled')
+ columns_long = ('ID', 'Name', 'Port Pair', 'Port Pair Group Parameters',
+ 'Description', 'Project', 'Tap Enabled')
+ _port_pair_group = _ppgs[0]
+ data = [
+ _port_pair_group['id'],
+ _port_pair_group['name'],
+ _port_pair_group['port_pairs'],
+ _port_pair_group['port_pair_group_parameters'],
+ _port_pair_group['tap_enabled']
+ ]
+ data_long = [
+ _port_pair_group['id'],
+ _port_pair_group['name'],
+ _port_pair_group['port_pairs'],
+ _port_pair_group['port_pair_group_parameters'],
+ _port_pair_group['description'],
+ _port_pair_group['tap_enabled']
+ ]
+ _port_pair_group1 = {'port_pair_groups': _port_pair_group}
+ _port_pair_id = _port_pair_group['id']
+
+ def setUp(self):
+ super(TestListSfcPortPairGroup, self).setUp()
+
+ self.network.sfc_port_pair_groups = mock.Mock(
+ return_value=self._ppgs
+ )
+ # Get the command object to test
+ self.cmd = sfc_port_pair_group.ListSfcPortPairGroup(self.app,
+ self.namespace)
+
+ def test_list_port_pair_groups(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)[0]
+ ppgs = self.network.sfc_port_pair_groups()
+ ppg = ppgs[0]
+ data = [
+ ppg['id'],
+ ppg['name'],
+ ppg['port_pairs'],
+ ppg['port_pair_group_parameters'],
+ ppg['tap_enabled']
+ ]
+ self.assertEqual(list(self.columns), columns)
+ self.assertEqual(self.data, data)
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ ppgs = self.network.sfc_port_pair_groups()
+ ppg = ppgs[0]
+ data = [
+ ppg['id'],
+ ppg['name'],
+ ppg['port_pairs'],
+ ppg['port_pair_group_parameters'],
+ ppg['description'],
+ ppg['tap_enabled']
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns_long = self.cmd.take_action(parsed_args)[0]
+ self.assertEqual(list(self.columns_long), columns_long)
+ self.assertEqual(self.data_long, data)
+
+
+class TestSetSfcPortPairGroup(fakes.TestNeutronClientOSCV2):
+ _port_pair_group = fakes.FakeSfcPortPairGroup.create_port_pair_group()
+ resource = _port_pair_group
+ res = 'port_pair_group'
+ _port_pair_group_name = _port_pair_group['name']
+ ppg_pp = _port_pair_group['port_pairs']
+ _port_pair_group_id = _port_pair_group['id']
+
+ def setUp(self):
+ super(TestSetSfcPortPairGroup, self).setUp()
+
+ self.network.update_sfc_port_pair_group = mock.Mock(
+ return_value=None)
+ self.mocked = self.network.update_sfc_port_pair_group
+ self.cmd = sfc_port_pair_group.SetSfcPortPairGroup(self.app,
+ self.namespace)
+
+ def test_set_port_pair_group(self):
+ target = self.resource['id']
+ port_pair1 = 'additional_port1'
+ port_pair2 = 'additional_port2'
+
+ self.network.find_sfc_port_pair = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_sfc_port_pair_group = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id, 'port_pairs': self.ppg_pp}
+ )
+
+ arglist = [
+ target,
+ '--port-pair', port_pair1,
+ '--port-pair', port_pair2,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('port_pairs', [port_pair1, port_pair2])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'port_pairs': sorted([*self.ppg_pp, port_pair1, port_pair2])}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_set_no_port_pair(self):
+ client = self.app.client_manager.network
+ mock_port_pair_group_update = client.update_sfc_port_pair_group
+ arglist = [
+ self._port_pair_group_name,
+ '--name', 'name_updated',
+ '--description', 'desc_updated',
+ '--no-port-pair',
+ ]
+ verifylist = [
+ ('port_pair_group', self._port_pair_group_name),
+ ('name', 'name_updated'),
+ ('description', 'desc_updated'),
+ ('no_port_pair', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {'name': 'name_updated', 'description': 'desc_updated',
+ 'port_pairs': []}
+ mock_port_pair_group_update.assert_called_once_with(
+ self._port_pair_group_name, **attrs)
+ self.assertIsNone(result)
+
+
+class TestShowSfcPortPairGroup(fakes.TestNeutronClientOSCV2):
+
+ _ppg = fakes.FakeSfcPortPairGroup.create_port_pair_group()
+ data = (
+ _ppg['description'],
+ _ppg['id'],
+ _ppg['name'],
+ _ppg['port_pairs'],
+ _ppg['port_pair_group_parameters'],
+ _ppg['project_id'],
+ _ppg['tap_enabled'])
+ _port_pair_group = _ppg
+ _port_pair_group_id = _ppg['id']
+ columns = (
+ 'Description',
+ 'ID',
+ 'Name',
+ 'Port Pair',
+ 'Port Pair Group Parameters',
+ 'Project',
+ 'Tap Enabled'
+ )
+
+ def setUp(self):
+ super(TestShowSfcPortPairGroup, self).setUp()
+
+ self.network.get_sfc_port_pair_group = mock.Mock(
+ return_value=self._port_pair_group
+ )
+ self.cmd = sfc_port_pair_group.ShowSfcPortPairGroup(self.app,
+ self.namespace)
+
+ def test_show_port_pair_group(self):
+ client = self.app.client_manager.network
+ mock_port_pair_group_show = client.get_sfc_port_pair_group
+ arglist = [
+ self._port_pair_group_id,
+ ]
+ verifylist = [
+ ('port_pair_group', self._port_pair_group_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ mock_port_pair_group_show.assert_called_once_with(
+ self._port_pair_group_id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestUnsetSfcPortPairGroup(fakes.TestNeutronClientOSCV2):
+ _port_pair_group = fakes.FakeSfcPortPairGroup.create_port_pair_group()
+ resource = _port_pair_group
+ res = 'port_pair_group'
+ _port_pair_group_name = _port_pair_group['name']
+ _port_pair_group_id = _port_pair_group['id']
+ ppg_pp = _port_pair_group['port_pairs']
+
+ def setUp(self):
+ super(TestUnsetSfcPortPairGroup, self).setUp()
+ self.network.update_sfc_port_pair_group = mock.Mock(
+ return_value=None)
+ self.mocked = self.network.update_sfc_port_pair_group
+ self.cmd = sfc_port_pair_group.UnsetSfcPortPairGroup(
+ self.app, self.namespace)
+
+ def test_unset_port_pair(self):
+ target = self.resource['id']
+ port_pair1 = 'additional_port1'
+ port_pair2 = 'additional_port2'
+
+ self.network.find_sfc_port_pair = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id}
+ )
+ self.network.find_sfc_port_pair_group = mock.Mock(
+ side_effect=lambda name_or_id, ignore_missing=False:
+ {'id': name_or_id, 'port_pairs': self.ppg_pp}
+ )
+ arglist = [
+ target,
+ '--port-pair', port_pair1,
+ '--port-pair', port_pair2,
+ ]
+ verifylist = [
+ (self.res, target),
+ ('port_pairs', [port_pair1, port_pair2])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ expect = {'port_pairs': sorted([*self.ppg_pp])}
+ self.mocked.assert_called_once_with(target, **expect)
+ self.assertIsNone(result)
+
+ def test_unset_all_port_pair(self):
+ client = self.app.client_manager.network
+ mock_port_pair_group_update = client.update_sfc_port_pair_group
+ arglist = [
+ self._port_pair_group_name,
+ '--all-port-pair',
+ ]
+ verifylist = [
+ ('port_pair_group', self._port_pair_group_name),
+ ('all_port_pair', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {'port_pairs': []}
+ mock_port_pair_group_update.assert_called_once_with(
+ self._port_pair_group_name, **attrs)
+ self.assertIsNone(result)
diff --git a/neutronclient/tests/unit/osc/v2/sfc/test_service_graph.py b/neutronclient/tests/unit/osc/v2/sfc/test_service_graph.py
new file mode 100644
index 000000000..167f1fcf8
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/sfc/test_service_graph.py
@@ -0,0 +1,338 @@
+# Copyright 2017 Intel Corporation.
+#
+# 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 unittest import mock
+
+from osc_lib import exceptions
+from osc_lib.tests import utils as tests_utils
+import testtools
+
+from neutronclient.osc.v2.sfc import sfc_service_graph
+from neutronclient.tests.unit.osc.v2.sfc import fakes
+
+
+class TestListSfcServiceGraph(fakes.TestNeutronClientOSCV2):
+ _service_graphs = fakes.FakeSfcServiceGraph.create_sfc_service_graphs(
+ count=1)
+ columns = ('ID', 'Name', 'Branching Points')
+ columns_long = ('ID', 'Name', 'Branching Points', 'Description', 'Project')
+ _service_graph = _service_graphs[0]
+ data = [
+ _service_graph['id'],
+ _service_graph['name'],
+ _service_graph['port_chains']
+ ]
+ data_long = [
+ _service_graph['id'],
+ _service_graph['name'],
+ _service_graph['port_chains'],
+ _service_graph['description'],
+ _service_graph['project_id']
+ ]
+ _service_graph1 = {'service_graphs': _service_graph}
+ _service_graph_id = _service_graph['id']
+
+ def setUp(self):
+ super(TestListSfcServiceGraph, self).setUp()
+ self.network.sfc_service_graphs = mock.Mock(
+ return_value=self._service_graphs
+ )
+ # Get the command object to test
+ self.cmd = sfc_service_graph.ListSfcServiceGraph(
+ self.app, self.namespace)
+
+ def test_list_sfc_service_graphs(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)[0]
+ sgs = self.network.sfc_service_graphs()
+ sg = sgs[0]
+ data = [
+ sg['id'],
+ sg['name'],
+ sg['port_chains']
+ ]
+ self.assertEqual(list(self.columns), columns)
+ self.assertEqual(self.data, data)
+
+ def test_list_sfc_service_graphs_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns = self.cmd.take_action(parsed_args)[0]
+ sgs = self.network.sfc_service_graphs()
+ sg = sgs[0]
+ data = [
+ sg['id'],
+ sg['name'],
+ sg['port_chains'],
+ sg['description'],
+ sg['project_id']
+ ]
+ self.assertEqual(list(self.columns_long), columns)
+ self.assertEqual(self.data_long, data)
+
+
+class TestCreateSfcServiceGraph(fakes.TestNeutronClientOSCV2):
+ _service_graph = fakes.FakeSfcServiceGraph.create_sfc_service_graph()
+
+ columns = ('ID', 'Name', 'Branching Points')
+ columns_long = ('Branching Points', 'Description', 'ID', 'Name', 'Project')
+
+ def get_data(self):
+ return (
+ self._service_graph['port_chains'],
+ self._service_graph['description'],
+ self._service_graph['id'],
+ self._service_graph['name'],
+ self._service_graph['project_id'],
+ )
+
+ def setUp(self):
+ super(TestCreateSfcServiceGraph, self).setUp()
+ self.network.create_sfc_service_graph = mock.Mock(
+ return_value=self._service_graph)
+ self.data = self.get_data()
+ self.cmd = sfc_service_graph.CreateSfcServiceGraph(
+ self.app, self.namespace)
+
+ def test_create_sfc_service_graph(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_sfc_service_graph_without_loop(self):
+ bp1_str = 'pc1:pc2,pc3'
+ bp2_str = 'pc2:pc4'
+ self.cmd = sfc_service_graph.CreateSfcServiceGraph(
+ self.app, self.namespace)
+
+ arglist = [
+ "--description", self._service_graph['description'],
+ "--branching-point", bp1_str,
+ "--branching-point", bp2_str,
+ self._service_graph['name']]
+
+ pcs = {'pc1': ['pc2', 'pc3'], 'pc2': ['pc4']}
+
+ verifylist = [
+ ("description", self._service_graph['description']),
+ ("branching_points", [bp1_str, bp2_str]),
+ ("name", self._service_graph['name'])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_sfc_service_graph.assert_called_once_with(**{
+ 'description': self._service_graph['description'],
+ 'name': self._service_graph['name'],
+ 'port_chains': pcs
+ })
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_sfc_service_graph_with_loop(self):
+ bp1_str = 'pc1:pc2,pc3;'
+ bp2_str = 'pc2:pc1'
+ self.cmd = sfc_service_graph.CreateSfcServiceGraph(
+ self.app, self.namespace)
+
+ arglist = [
+ "--description", self._service_graph['description'],
+ "--branching-point", bp1_str,
+ "--branching-point", bp2_str,
+ self._service_graph['name']]
+
+ verifylist = [
+ ("description", self._service_graph['description']),
+ ("branching_points", [bp1_str, bp2_str]),
+ ("name", self._service_graph['name'])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
+ def test_create_sfc_service_graph_invalid_port_chains(self):
+ bp1_str = 'pc1:pc2,pc3:'
+ self.cmd = sfc_service_graph.CreateSfcServiceGraph(
+ self.app, self.namespace)
+
+ arglist = [
+ "--description", self._service_graph['description'],
+ "--branching-point", bp1_str,
+ self._service_graph['name']]
+
+ verifylist = [
+ ("description", self._service_graph['description']),
+ ("branching_points", [bp1_str]),
+ ("name", self._service_graph['name'])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
+ def test_create_sfc_service_graph_duplicate_src_chains(self):
+ bp1_str = 'pc1:pc2,pc3;'
+ bp2_str = 'pc1:pc4'
+ self.cmd = sfc_service_graph.CreateSfcServiceGraph(
+ self.app, self.namespace)
+
+ arglist = [
+ "--description", self._service_graph['description'],
+ "--branching-point", bp1_str,
+ "--branching-point", bp2_str,
+ self._service_graph['name']]
+
+ verifylist = [
+ ("description", self._service_graph['description']),
+ ("branching_points", [bp1_str, bp2_str]),
+ ("name", self._service_graph['name'])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
+
+class TestDeleteSfcServiceGraph(fakes.TestNeutronClientOSCV2):
+
+ _service_graph = fakes.FakeSfcServiceGraph.create_sfc_service_graphs(
+ count=1)
+
+ def setUp(self):
+ super(TestDeleteSfcServiceGraph, self).setUp()
+ self.network.delete_sfc_service_graph = mock.Mock(
+ return_value=None)
+ self.cmd = sfc_service_graph.DeleteSfcServiceGraph(
+ self.app, self.namespace)
+
+ def test_delete_sfc_service_graph(self):
+ client = self.app.client_manager.network
+ mock_service_graph_delete = client.delete_sfc_service_graph
+ arglist = [
+ self._service_graph[0]['id'],
+ ]
+ verifylist = [
+ ('service_graph', [self._service_graph[0]['id']]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ mock_service_graph_delete.assert_called_once_with(
+ self._service_graph[0]['id'])
+ self.assertIsNone(result)
+
+ def test_delete_multiple_service_graphs_with_exception(self):
+ client = self.app.client_manager.network
+ target = self._service_graph[0]['id']
+ arglist = [target]
+ verifylist = [('service_graph', [target])]
+
+ client.find_sfc_service_graph.side_effect = [
+ target, exceptions.CommandError
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = "1 of 2 service graph(s) failed to delete."
+ with testtools.ExpectedException(exceptions.CommandError) as e:
+ self.cmd.take_action(parsed_args)
+ self.assertEqual(msg, str(e))
+
+
+class TestShowSfcServiceGraph(fakes.TestNeutronClientOSCV2):
+
+ _sg = fakes.FakeSfcServiceGraph.create_sfc_service_graph()
+ columns = ('ID', 'Name', 'Branching Points')
+ columns_long = ('Branching Points', 'Description', 'ID', 'Name', 'Project')
+ data = (
+ _sg['id'],
+ _sg['name'],
+ _sg['port_chains']
+ )
+ data_long = (
+ _sg['port_chains'],
+ _sg['description'],
+ _sg['id'],
+ _sg['name'],
+ _sg['project_id']
+ )
+
+ _service_graph = _sg
+ _service_graph_id = _sg['id']
+
+ def setUp(self):
+ super(TestShowSfcServiceGraph, self).setUp()
+ self.network.get_sfc_service_graph = mock.Mock(
+ return_value=self._service_graph
+ )
+ # Get the command object to test
+ self.cmd = sfc_service_graph.ShowSfcServiceGraph(
+ self.app, self.namespace)
+
+ def test_service_graph_show(self):
+ client = self.app.client_manager.network
+ mock_service_graph_show = client.get_sfc_service_graph
+ arglist = [
+ self._service_graph_id,
+ ]
+ verifylist = [
+ ('service_graph', self._service_graph_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ mock_service_graph_show.assert_called_once_with(self._service_graph_id)
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, data)
+
+
+class TestSetSfcServiceGraph(fakes.TestNeutronClientOSCV2):
+ _service_graph = fakes.FakeSfcServiceGraph.create_sfc_service_graph()
+ _service_graph_name = _service_graph['name']
+ _service_graph_id = _service_graph['id']
+
+ def setUp(self):
+ super(TestSetSfcServiceGraph, self).setUp()
+ self.network.update_sfc_service_graph = mock.Mock(
+ return_value=None)
+ self.cmd = sfc_service_graph.SetSfcServiceGraph(
+ self.app, self.namespace)
+
+ def test_set_service_graph(self):
+ client = self.app.client_manager.network
+ mock_service_graph_update = client.update_sfc_service_graph
+ arglist = [
+ self._service_graph_name,
+ '--name', 'name_updated',
+ '--description', 'desc_updated'
+ ]
+ verifylist = [
+ ('service_graph', self._service_graph_name),
+ ('name', 'name_updated'),
+ ('description', 'desc_updated'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'name': 'name_updated',
+ 'description': 'desc_updated'
+ }
+ mock_service_graph_update.assert_called_once_with(
+ self._service_graph_name, **attrs)
+ self.assertIsNone(result)
diff --git a/neutronclient/tests/unit/osc/v2/subnet_onboard/__init__.py b/neutronclient/tests/unit/osc/v2/subnet_onboard/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/subnet_onboard/test_network_onboard_subnets.py b/neutronclient/tests/unit/osc/v2/subnet_onboard/test_network_onboard_subnets.py
new file mode 100644
index 000000000..c2ca14044
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/subnet_onboard/test_network_onboard_subnets.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2019 SUSE Linux Products GmbH
+# 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 unittest import mock
+
+from neutronclient.osc.v2.subnet_onboard import subnet_onboard
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+
+
+def _get_id(client, id_or_name, resource):
+ return id_or_name
+
+
+class TestNetworkOnboardSubnets(test_fakes.TestNeutronClientOSCV2):
+
+ def setUp(self):
+ super(TestNetworkOnboardSubnets, self).setUp()
+ mock.patch(
+ 'neutronclient.osc.v2.subnet_onboard.subnet_onboard._get_id',
+ new=_get_id).start()
+
+ self.network_id = 'my_network_id'
+ self.subnetpool_id = 'my_subnetpool_id'
+
+ # Get the command object to test
+ self.cmd = subnet_onboard.NetworkOnboardSubnets(self.app,
+ self.namespace)
+
+ def test_options(self):
+ arglist = [
+ self.network_id,
+ self.subnetpool_id
+ ]
+ verifylist = [
+ ('network', self.network_id),
+ ('subnetpool', self.subnetpool_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ self.neutronclient.onboard_network_subnets.assert_called_once_with(
+ self.subnetpool_id, {'network_id': self.network_id})
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/__init__.py b/neutronclient/tests/unit/osc/v2/vpnaas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/common.py b/neutronclient/tests/unit/osc/v2/vpnaas/common.py
new file mode 100644
index 000000000..7aaebdbe3
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/common.py
@@ -0,0 +1,183 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 testtools
+
+from osc_lib import exceptions
+
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+
+
+class TestCreateVPNaaS(test_fakes.TestNeutronClientOSCV2):
+ pass
+
+
+class TestDeleteVPNaaS(test_fakes.TestNeutronClientOSCV2):
+
+ def test_delete_with_one_resource(self):
+ target = self.resource['id']
+
+ def _mock_vpnaas(*args, **kwargs):
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_endpoint_group.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_ipsec_site_connection.side_effect = \
+ _mock_vpnaas
+ self.networkclient.find_vpn_ike_policy.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_ipsec_policy.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_service.side_effect = _mock_vpnaas
+
+ arglist = [target]
+ verifylist = [(self.res, [target])]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(target)
+ self.assertIsNone(result)
+
+ def test_delete_with_multiple_resources(self):
+
+ def _mock_vpnaas(*args, **kwargs):
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_endpoint_group.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_ipsec_site_connection.side_effect = \
+ _mock_vpnaas
+ self.networkclient.find_vpn_ike_policy.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_ipsec_policy.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_service.side_effect = _mock_vpnaas
+
+ target1 = 'target1'
+ target2 = 'target2'
+ arglist = [target1, target2]
+ verifylist = [(self.res, [target1, target2])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ self.assertEqual(2, self.mocked.call_count)
+ for idx, reference in enumerate([target1, target2]):
+ actual = ''.join(self.mocked.call_args_list[idx][0])
+ self.assertEqual(reference, actual)
+
+ def test_delete_multiple_with_exception(self):
+ target1 = 'target'
+ arglist = [target1]
+ verifylist = [(self.res, [target1])]
+
+ self.networkclient.find_vpn_ipsec_site_connection.side_effect = [
+ target1, exceptions.CommandError
+ ]
+ self.networkclient.find_vpn_endpoint_group.side_effect = [
+ target1, exceptions.CommandError
+ ]
+ self.networkclient.find_vpn_ike_policy.side_effect = [
+ target1, exceptions.CommandError
+ ]
+ self.networkclient.find_vpn_service.side_effect = [
+ target1, exceptions.CommandError
+ ]
+ self.networkclient.find_vpn_ipsec_policy.side_effect = [
+ target1, exceptions.CommandError
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ resource_name = self.res.replace('_', ' ')
+ msg = "1 of 2 %s(s) failed to delete." % resource_name
+ with testtools.ExpectedException(exceptions.CommandError) as e:
+ self.cmd.take_action(parsed_args)
+ self.assertEqual(msg, str(e))
+
+
+class TestListVPNaaS(test_fakes.TestNeutronClientOSCV2):
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.list_headers), headers)
+ self.assertEqual([self.list_data], list(data))
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+ self.assertEqual([self.data], list(data))
+
+
+class TestSetVPNaaS(test_fakes.TestNeutronClientOSCV2):
+
+ def test_set_name(self):
+ target = self.resource['id']
+ update = 'change'
+ arglist = [target, '--name', update]
+ verifylist = [
+ (self.res, target),
+ ('name', update),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, **{'name': update})
+ self.assertIsNone(result)
+
+ def test_set_description(self):
+ target = self.resource['id']
+ update = 'change-desc'
+ arglist = [target, '--description', update]
+ verifylist = [
+ (self.res, target),
+ ('description', update),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, **{'description': update})
+ self.assertIsNone(result)
+
+
+class TestShowVPNaaS(test_fakes.TestNeutronClientOSCV2):
+
+ def test_show_filtered_by_id_or_name(self):
+ target = self.resource['id']
+
+ def _mock_vpnaas(*args, **kwargs):
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_endpoint_group.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_ipsec_site_connection.side_effect = \
+ _mock_vpnaas
+ self.networkclient.find_vpn_ike_policy.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_ipsec_policy.side_effect = _mock_vpnaas
+ self.networkclient.find_vpn_service.side_effect = _mock_vpnaas
+
+ arglist = [target]
+ verifylist = [(self.res, target)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(target)
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertItemEqual(self.ordered_data, data)
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/fakes.py b/neutronclient/tests/unit/osc/v2/vpnaas/fakes.py
new file mode 100644
index 000000000..24e6497fc
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/fakes.py
@@ -0,0 +1,191 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 collections
+from unittest import mock
+import uuid
+
+from openstack.network.v2 import vpn_endpoint_group as vpn_epg
+from openstack.network.v2 import vpn_ike_policy as vpn_ikep
+from openstack.network.v2 import vpn_ipsec_policy as vpn_ipsecp
+from openstack.network.v2 import vpn_ipsec_site_connection as vpn_sitec
+from openstack.network.v2 import vpn_service
+
+
+class FakeVPNaaS(object):
+
+ def create(self, attrs={}):
+ """Create a fake vpnaas resources
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A OrderedDict faking the vpnaas resource
+ """
+ self.ordered.update(attrs)
+ if 'IKEPolicy' == self.__class__.__name__:
+ return vpn_ikep.VpnIkePolicy(**self.ordered)
+ if 'IPSecPolicy' == self.__class__.__name__:
+ return vpn_ipsecp.VpnIpsecPolicy(**self.ordered)
+ if 'VPNService' == self.__class__.__name__:
+ return vpn_service.VpnService(**self.ordered)
+ if 'EndpointGroup' == self.__class__.__name__:
+ return vpn_epg.VpnEndpointGroup(**self.ordered)
+
+ def bulk_create(self, attrs=None, count=2):
+ """Create multiple fake vpnaas resources
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of vpnaas resources to fake
+ :return:
+ A list of dictionaries faking the vpnaas resources
+ """
+ return [self.create(attrs=attrs) for i in range(0, count)]
+
+ def get(self, attrs=None, count=2):
+ """Get multiple fake vpnaas resources
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of vpnaas resources to fake
+ :return:
+ A list of dictionaries faking the vpnaas resource
+ """
+ if attrs is None:
+ self.attrs = self.bulk_create(count=count)
+ return mock.Mock(side_effect=attrs)
+
+
+class IKEPolicy(FakeVPNaaS):
+ """Fake one or more IKE policies"""
+
+ def __init__(self):
+ super(IKEPolicy, self).__init__()
+ self.ordered = collections.OrderedDict((
+ ('id', 'ikepolicy-id-' + uuid.uuid4().hex),
+ ('name', 'my-ikepolicy-' + uuid.uuid4().hex),
+ ('auth_algorithm', 'sha1'),
+ ('encryption_algorithm', 'aes-128'),
+ ('ike_version', 'v1'),
+ ('pfs', 'group5'),
+ ('description', 'my-desc-' + uuid.uuid4().hex),
+ ('phase1_negotiation_mode', 'main'),
+ ('project_id', 'project-id-' + uuid.uuid4().hex),
+ ('lifetime', {'units': 'seconds', 'value': 3600}),
+ ))
+
+
+class IPSecPolicy(FakeVPNaaS):
+ """Fake one or more IPsec policies"""
+
+ def __init__(self):
+ super(IPSecPolicy, self).__init__()
+ self.ordered = collections.OrderedDict((
+ ('id', 'ikepolicy-id-' + uuid.uuid4().hex),
+ ('name', 'my-ikepolicy-' + uuid.uuid4().hex),
+ ('auth_algorithm', 'sha1'),
+ ('encapsulation_mode', 'tunnel'),
+ ('transform_protocol', 'esp'),
+ ('encryption_algorithm', 'aes-128'),
+ ('pfs', 'group5'),
+ ('description', 'my-desc-' + uuid.uuid4().hex),
+ ('project_id', 'project-id-' + uuid.uuid4().hex),
+ ('lifetime', {'units': 'seconds', 'value': 3600}),
+ ))
+
+
+class VPNService(FakeVPNaaS):
+ """Fake one or more VPN services"""
+
+ def __init__(self):
+ super(VPNService, self).__init__()
+ self.ordered = collections.OrderedDict((
+ ('id', 'vpnservice-id-' + uuid.uuid4().hex),
+ ('name', 'my-vpnservice-' + uuid.uuid4().hex),
+ ('router_id', 'router-id-' + uuid.uuid4().hex),
+ ('subnet_id', 'subnet-id-' + uuid.uuid4().hex),
+ ('flavor_id', 'flavor-id-' + uuid.uuid4().hex),
+ ('admin_state_up', True),
+ ('status', 'ACTIVE'),
+ ('description', 'my-desc-' + uuid.uuid4().hex),
+ ('project_id', 'project-id-' + uuid.uuid4().hex),
+ ('external_v4_ip', '192.0.2.42'),
+ ('external_v6_ip', '2001:0db8:207a:4a3a:053b:6fab:7df9:1afd'),
+ ))
+
+
+class EndpointGroup(FakeVPNaaS):
+ """Fake one or more Endpoint Groups"""
+
+ def __init__(self):
+ super(EndpointGroup, self).__init__()
+ self.ordered = collections.OrderedDict((
+ ('id', 'ep-group-id-' + uuid.uuid4().hex),
+ ('name', 'my-ep-group-' + uuid.uuid4().hex),
+ ('type', 'cidr'),
+ ('endpoints', ['10.0.0.0/24', '20.0.0.0/24']),
+ ('description', 'my-desc-' + uuid.uuid4().hex),
+ ('project_id', 'project-id-' + uuid.uuid4().hex),
+ ))
+
+
+class IPsecSiteConnection(object):
+ """Fake one or more IPsec site connections"""
+ @staticmethod
+ def create_conn(attrs=None):
+ """Create a fake IPsec conn.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A Dictionary with id, name, peer_address, auth_mode, status,
+ project_id, peer_cidrs, vpnservice_id, ipsecpolicy_id,
+ ikepolicy_id, mtu, initiator, admin_state_up, description,
+ psk, route_mode, local_id, peer_id, local_ep_group_id,
+ peer_ep_group_id
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ conn_attrs = {
+ 'id': 'ipsec-site-conn-id-' + uuid.uuid4().hex,
+ 'name': 'my-ipsec-site-conn-' + uuid.uuid4().hex,
+ 'peer_address': '192.168.2.10',
+ 'auth_mode': '',
+ 'status': '',
+ 'project_id': 'project-id-' + uuid.uuid4().hex,
+ 'peer_cidrs': [],
+ 'vpnservice_id': 'vpnservice-id-' + uuid.uuid4().hex,
+ 'ipsecpolicy_id': 'ipsecpolicy-id-' + uuid.uuid4().hex,
+ 'ikepolicy_id': 'ikepolicy-id-' + uuid.uuid4().hex,
+ 'mtu': 1500,
+ 'initiator': 'bi-directional',
+ 'admin_state_up': True,
+ 'description': 'my-vpn-connection',
+ 'psk': 'abcd',
+ 'route_mode': '',
+ 'local_id': '',
+ 'peer_id': '192.168.2.10',
+ 'local_ep_group_id': 'local-ep-group-id-' + uuid.uuid4().hex,
+ 'peer_ep_group_id': 'peer-ep-group-id-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ conn_attrs.update(attrs)
+ return vpn_sitec.VpnIPSecSiteConnection(**conn_attrs)
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/test_endpoint_group.py b/neutronclient/tests/unit/osc/v2/vpnaas/test_endpoint_group.py
new file mode 100644
index 000000000..e9da939b1
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/test_endpoint_group.py
@@ -0,0 +1,254 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 unittest import mock
+
+from osc_lib.tests import utils as tests_utils
+
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import endpoint_group
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+from neutronclient.tests.unit.osc.v2.vpnaas import common
+from neutronclient.tests.unit.osc.v2.vpnaas import fakes
+
+
+_endpoint_group = fakes.EndpointGroup().create()
+CONVERT_MAP = {
+ 'project': 'tenant_id',
+}
+
+
+def _generate_data(ordered_dict=None, data=None):
+ source = ordered_dict if ordered_dict else _endpoint_group
+ if data:
+ source.update(data)
+ return source
+
+
+def _generate_req_and_res(verifylist):
+ request = dict(verifylist)
+ response = _endpoint_group
+ for key, val in verifylist:
+ converted = CONVERT_MAP.get(key, key)
+ del request[key]
+ new_value = val
+ request[converted] = new_value
+ response[converted] = new_value
+ return request, response
+
+
+class TestEndpointGroup(test_fakes.TestNeutronClientOSCV2):
+
+ def check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {self.res_plural: list(exp_req)}
+ else:
+ req_body = exp_req
+ self.mocked.assert_called_once_with(**req_body)
+ self.assertEqual(self.ordered_headers, tuple(sorted(headers)))
+ self.assertEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestEndpointGroup, self).setUp()
+
+ def _mock_endpoint_group(*args, **kwargs):
+ self.networkclient.find_vpn_endpoint_group.assert_called_once_with(
+ self.resource['id'], ignore_missing=False)
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_endpoint_group.side_effect = mock.Mock(
+ side_effect=_mock_endpoint_group)
+ osc_utils.find_project = mock.Mock()
+ osc_utils.find_project.id = _endpoint_group['project_id']
+ self.res = 'endpoint_group'
+ self.res_plural = 'endpoint_groups'
+ self.resource = _endpoint_group
+ self.headers = (
+ 'ID',
+ 'Name',
+ 'Type',
+ 'Endpoints',
+ 'Description',
+ 'Project',
+ )
+ self.data = _generate_data()
+ self.ordered_headers = (
+ 'Description',
+ 'Endpoints',
+ 'ID',
+ 'Name',
+ 'Project',
+ 'Type',
+ )
+ self.ordered_data = (
+ _endpoint_group['description'],
+ _endpoint_group['endpoints'],
+ _endpoint_group['id'],
+ _endpoint_group['name'],
+ _endpoint_group['project_id'],
+ _endpoint_group['type'],
+ )
+ self.ordered_columns = (
+ 'description',
+ 'endpoints',
+ 'id',
+ 'name',
+ 'project_id',
+ 'type',
+ )
+
+
+class TestCreateEndpointGroup(TestEndpointGroup, common.TestCreateVPNaaS):
+
+ def setUp(self):
+ super(TestCreateEndpointGroup, self).setUp()
+ self.networkclient.create_vpn_endpoint_group = mock.Mock(
+ return_value=_endpoint_group)
+ self.mocked = self.networkclient.create_vpn_endpoint_group
+ self.cmd = endpoint_group.CreateEndpointGroup(self.app, self.namespace)
+
+ def _update_expect_response(self, request, response):
+ """Set expected request and response
+
+ :param request
+ A dictionary of request body(dict of verifylist)
+ :param response
+ A OrderedDict of request body
+ """
+ # Update response body
+ self.neutronclient.create_endpoint_group.return_value = \
+ {self.res: dict(response)}
+ osc_utils.find_project.return_value.id = response['tenant_id']
+ # Update response(finally returns 'data')
+ self.data = _generate_data(ordered_dict=response)
+ self.ordered_data = tuple(
+ response[column] for column in self.ordered_columns
+ )
+
+ def _set_all_params_cidr(self, args={}):
+ name = args.get('name') or 'my-name'
+ description = args.get('description') or 'my-desc'
+ endpoint_type = args.get('type') or 'cidr'
+ endpoints = args.get('endpoints') or ['10.0.0.0/24', '20.0.0.0/24']
+ tenant_id = args.get('project_id') or 'my-tenant'
+ arglist = [
+ '--description', description,
+ '--type', endpoint_type,
+ '--value', '10.0.0.0/24',
+ '--value', '20.0.0.0/24',
+ '--project', tenant_id,
+ name,
+ ]
+ verifylist = [
+ ('description', description),
+ ('type', endpoint_type),
+ ('endpoints', endpoints),
+ ('project', tenant_id),
+ ('name', name),
+ ]
+ return arglist, verifylist
+
+ def _test_create_with_all_params_cidr(self, args={}):
+ arglist, verifylist = self._set_all_params_cidr(args)
+ request, response = _generate_req_and_res(verifylist)
+ self._update_expect_response(request, response)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.check_results(headers, data, request)
+
+ def test_create_with_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_with_all_params_cidr(self):
+ self._test_create_with_all_params_cidr()
+
+
+class TestDeleteEndpointGroup(TestEndpointGroup, common.TestDeleteVPNaaS):
+
+ def setUp(self):
+ super(TestDeleteEndpointGroup, self).setUp()
+ self.networkclient.delete_vpn_endpoint_group = mock.Mock()
+ self.mocked = self.networkclient.delete_vpn_endpoint_group
+ self.cmd = endpoint_group.DeleteEndpointGroup(self.app, self.namespace)
+
+
+class TestListEndpointGroup(TestEndpointGroup):
+
+ def setUp(self):
+ super(TestListEndpointGroup, self).setUp()
+ self.cmd = endpoint_group.ListEndpointGroup(self.app, self.namespace)
+
+ self.short_header = (
+ 'ID',
+ 'Name',
+ 'Type',
+ 'Endpoints',
+ )
+
+ self.short_data = (
+ _endpoint_group['id'],
+ _endpoint_group['name'],
+ _endpoint_group['type'],
+ _endpoint_group['endpoints'],
+ )
+
+ self.networkclient.vpn_endpoint_groups = mock.Mock(
+ return_value=[_endpoint_group])
+ self.mocked = self.networkclient.vpn_endpoint_groups
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+
+class TestSetEndpointGroup(TestEndpointGroup, common.TestSetVPNaaS):
+
+ def setUp(self):
+ super(TestSetEndpointGroup, self).setUp()
+ self.networkclient.update_vpn_endpoint_group = mock.Mock(
+ return_value=_endpoint_group)
+ self.mocked = self.networkclient.update_vpn_endpoint_group
+ self.cmd = endpoint_group.SetEndpointGroup(self.app, self.namespace)
+
+
+class TestShowEndpointGroup(TestEndpointGroup, common.TestShowVPNaaS):
+
+ def setUp(self):
+ super(TestShowEndpointGroup, self).setUp()
+ self.networkclient.get_vpn_endpoint_group = mock.Mock(
+ return_value=_endpoint_group)
+ self.mocked = self.networkclient.get_vpn_endpoint_group
+ self.cmd = endpoint_group.ShowEndpointGroup(self.app, self.namespace)
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/test_ikepolicy.py b/neutronclient/tests/unit/osc/v2/vpnaas/test_ikepolicy.py
new file mode 100644
index 000000000..bf68611f8
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/test_ikepolicy.py
@@ -0,0 +1,319 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 unittest import mock
+
+from osc_lib.tests import utils as tests_utils
+
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import ikepolicy
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+from neutronclient.tests.unit.osc.v2.vpnaas import common
+from neutronclient.tests.unit.osc.v2.vpnaas import fakes
+
+
+_ikepolicy = fakes.IKEPolicy().create()
+CONVERT_MAP = {
+ 'project': 'project_id',
+}
+
+
+def _generate_data(ordered_dict=None, data=None):
+ source = ordered_dict if ordered_dict else _ikepolicy
+ if data:
+ source.update(data)
+ return source
+
+
+def _generate_req_and_res(verifylist):
+ request = dict(verifylist)
+ response = _ikepolicy
+ for key, val in verifylist:
+ converted = CONVERT_MAP.get(key, key)
+ del request[key]
+ new_value = val
+ request[converted] = new_value
+ response[converted] = new_value
+ return request, response
+
+
+class TestIKEPolicy(test_fakes.TestNeutronClientOSCV2):
+
+ def check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {self.res_plural: list(exp_req)}
+ else:
+ req_body = exp_req
+ self.mocked.assert_called_once_with(**req_body)
+ self.assertEqual(self.ordered_headers, tuple(sorted(headers)))
+ self.assertEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestIKEPolicy, self).setUp()
+
+ def _mock_ikepolicy(*args, **kwargs):
+ self.networkclient.find_vpn_ike_policy.assert_called_once_with(
+ self.resource['id'], ignore_missing=False)
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_ike_policy.side_effect = mock.Mock(
+ side_effect=_mock_ikepolicy)
+ osc_utils.find_project = mock.Mock()
+ osc_utils.find_project.id = _ikepolicy['project_id']
+ self.res = 'ikepolicy'
+ self.res_plural = 'ikepolicies'
+ self.resource = _ikepolicy
+ self.headers = (
+ 'ID',
+ 'Name',
+ 'Authentication Algorithm',
+ 'Encryption Algorithm',
+ 'IKE Version',
+ 'Perfect Forward Secrecy (PFS)',
+ 'Description',
+ 'Phase1 Negotiation Mode',
+ 'Project',
+ 'Lifetime',
+ )
+ self.data = _generate_data()
+ self.ordered_headers = (
+ 'Authentication Algorithm',
+ 'Description',
+ 'Encryption Algorithm',
+ 'ID',
+ 'IKE Version',
+ 'Lifetime',
+ 'Name',
+ 'Perfect Forward Secrecy (PFS)',
+ 'Phase1 Negotiation Mode',
+ 'Project',
+ )
+ self.ordered_data = (
+ _ikepolicy['auth_algorithm'],
+ _ikepolicy['description'],
+ _ikepolicy['encryption_algorithm'],
+ _ikepolicy['id'],
+ _ikepolicy['ike_version'],
+ _ikepolicy['lifetime'],
+ _ikepolicy['name'],
+ _ikepolicy['pfs'],
+ _ikepolicy['phase1_negotiation_mode'],
+ _ikepolicy['project_id'],
+ )
+ self.ordered_columns = (
+ 'auth_algorithm',
+ 'description',
+ 'encryption_algorithm',
+ 'id',
+ 'ike_version',
+ 'lifetime',
+ 'name',
+ 'pfs',
+ 'phase1_negotiation_mode',
+ 'project_id',
+ )
+
+
+class TestCreateIKEPolicy(TestIKEPolicy, common.TestCreateVPNaaS):
+
+ def setUp(self):
+ super(TestCreateIKEPolicy, self).setUp()
+ self.networkclient.create_vpn_ike_policy = mock.Mock(
+ return_value=_ikepolicy)
+ self.mocked = self.networkclient.create_vpn_ike_policy
+ self.cmd = ikepolicy.CreateIKEPolicy(self.app, self.namespace)
+
+ def _update_expect_response(self, request, response):
+ """Set expected request and response
+
+ :param request
+ A dictionary of request body(dict of verifylist)
+ :param response
+ A OrderedDict of request body
+ """
+ # Update response body
+ self.networkclient.create_vpn_ikepolicy.return_value = response
+ osc_utils.find_project.return_value.id = response['project_id']
+ # Update response(finally returns 'data')
+ self.data = _generate_data(ordered_dict=response)
+ self.ordered_data = tuple(
+ response[column] for column in self.ordered_columns
+ )
+
+ def _set_all_params(self, args={}):
+ name = args.get('name') or 'my-name'
+ description = args.get('description') or 'my-desc'
+ auth_algorithm = args.get('auth_algorithm') or 'sha1'
+ encryption_algorithm = args.get('encryption_algorithm') or 'aes-128'
+ phase1_negotiation_mode = args.get('phase1_negotiation_mode') or 'main'
+ ike_version = args.get('ike_version') or 'v1'
+ pfs = args.get('pfs') or 'group5'
+ tenant_id = args.get('tenant_id') or 'my-tenant'
+ arglist = [
+ '--description', description,
+ '--auth-algorithm', auth_algorithm,
+ '--encryption-algorithm', encryption_algorithm,
+ '--phase1-negotiation-mode', phase1_negotiation_mode,
+ '--ike-version', ike_version,
+ '--pfs', pfs,
+ '--project', tenant_id,
+ name,
+ ]
+ verifylist = [
+ ('description', description),
+ ('auth_algorithm', auth_algorithm),
+ ('encryption_algorithm', encryption_algorithm),
+ ('phase1_negotiation_mode', phase1_negotiation_mode),
+ ('ike_version', ike_version),
+ ('pfs', pfs),
+ ('project', tenant_id),
+ ('name', name),
+ ]
+ return arglist, verifylist
+
+ def _test_create_with_all_params(self, args={}):
+ arglist, verifylist = self._set_all_params(args)
+ request, response = _generate_req_and_res(verifylist)
+ self._update_expect_response(request, response)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.check_results(headers, data, request)
+
+ def test_create_with_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_with_all_params(self):
+ self._test_create_with_all_params()
+
+ def test_create_with_all_params_name(self):
+ self._test_create_with_all_params({'name': 'new_ikepolicy'})
+
+ def test_create_with_all_params_aggressive_mode(self):
+ self._test_create_with_all_params(
+ {'phase1_negotiation_mode': 'aggressive'})
+
+
+class TestDeleteIKEPolicy(TestIKEPolicy, common.TestDeleteVPNaaS):
+
+ def setUp(self):
+ super(TestDeleteIKEPolicy, self).setUp()
+ self.networkclient.delete_vpn_ike_policy = mock.Mock()
+ self.mocked = self.networkclient.delete_vpn_ike_policy
+ self.cmd = ikepolicy.DeleteIKEPolicy(self.app, self.namespace)
+
+
+class TestListIKEPolicy(TestIKEPolicy):
+
+ def setUp(self):
+ super(TestListIKEPolicy, self).setUp()
+ self.cmd = ikepolicy.ListIKEPolicy(self.app, self.namespace)
+
+ self.short_header = (
+ 'ID',
+ 'Name',
+ 'Authentication Algorithm',
+ 'Encryption Algorithm',
+ 'IKE Version',
+ 'Perfect Forward Secrecy (PFS)',
+ )
+
+ self.short_data = (
+ _ikepolicy['id'],
+ _ikepolicy['name'],
+ _ikepolicy['auth_algorithm'],
+ _ikepolicy['encryption_algorithm'],
+ _ikepolicy['ike_version'],
+ _ikepolicy['pfs'],
+ )
+
+ self.networkclient.vpn_ike_policies = mock.Mock(
+ return_value=[_ikepolicy])
+ self.mocked = self.networkclient.vpn_ike_policies
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+
+class TestSetIKEPolicy(TestIKEPolicy, common.TestSetVPNaaS):
+
+ def setUp(self):
+ super(TestSetIKEPolicy, self).setUp()
+ self.networkclient.update_vpn_ike_policy = mock.Mock(
+ return_value=_ikepolicy)
+ self.mocked = self.networkclient.update_vpn_ike_policy
+ self.cmd = ikepolicy.SetIKEPolicy(self.app, self.namespace)
+
+ def test_set_auth_algorithm_with_sha256(self):
+ target = self.resource['id']
+ auth_algorithm = 'sha256'
+ arglist = [target, '--auth-algorithm', auth_algorithm]
+ verifylist = [
+ (self.res, target),
+ ('auth_algorithm', auth_algorithm),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, **{'auth_algorithm': 'sha256'})
+ self.assertIsNone(result)
+
+ def test_set_phase1_negotiation_mode_with_aggressive(self):
+ target = self.resource['id']
+ phase1_negotiation_mode = 'aggressive'
+ arglist = [target,
+ '--phase1-negotiation-mode', phase1_negotiation_mode]
+ verifylist = [
+ (self.res, target),
+ ('phase1_negotiation_mode', phase1_negotiation_mode),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, **{'phase1_negotiation_mode': 'aggressive'})
+ self.assertIsNone(result)
+
+
+class TestShowIKEPolicy(TestIKEPolicy, common.TestShowVPNaaS):
+
+ def setUp(self):
+ super(TestShowIKEPolicy, self).setUp()
+ self.networkclient.get_vpn_ike_policy = mock.Mock(
+ return_value=_ikepolicy)
+ self.mocked = self.networkclient.get_vpn_ike_policy
+ self.cmd = ikepolicy.ShowIKEPolicy(self.app, self.namespace)
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/test_ipsec_site_connection.py b/neutronclient/tests/unit/osc/v2/vpnaas/test_ipsec_site_connection.py
new file mode 100644
index 000000000..4929ea843
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/test_ipsec_site_connection.py
@@ -0,0 +1,372 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 unittest import mock
+
+from osc_lib.cli import format_columns
+from osc_lib.tests import utils as tests_utils
+
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import ipsec_site_connection
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+from neutronclient.tests.unit.osc.v2.vpnaas import common
+from neutronclient.tests.unit.osc.v2.vpnaas import fakes
+
+
+_ipsec_site_conn = fakes.IPsecSiteConnection().create_conn()
+CONVERT_MAP = {
+ 'project': 'project_id',
+ 'ikepolicy': 'ikepolicy_id',
+ 'ipsecpolicy': 'ipsecpolicy_id',
+ 'vpnservice': 'vpnservice_id',
+ 'peer_endpoint_group': 'peer_ep_group_id',
+ 'local_endpoint_group': 'local_ep_group_id',
+}
+
+
+def _generate_data(ordered_dict=None, data=None):
+ source = ordered_dict if ordered_dict else _ipsec_site_conn
+ if data:
+ source.update(data)
+ return source
+
+
+def _generate_req_and_res(verifylist):
+ request = dict(verifylist)
+ response = _ipsec_site_conn
+ for key, val in verifylist:
+ converted = CONVERT_MAP.get(key, key)
+ del request[key]
+ new_value = val
+ request[converted] = new_value
+ response[converted] = new_value
+ return request, response
+
+
+class TestIPsecSiteConn(test_fakes.TestNeutronClientOSCV2):
+
+ def check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {self.res_plural: list(exp_req)}
+ else:
+ req_body = exp_req
+ self.mocked.assert_called_once_with(**req_body)
+ self.assertEqual(self.ordered_headers, tuple(sorted(headers)))
+ self.assertItemEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestIPsecSiteConn, self).setUp()
+
+ def _mock_ipsec_site_conn(*args, **kwargs):
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_ipsec_site_connection.side_effect = \
+ mock.Mock(side_effect=_mock_ipsec_site_conn)
+ osc_utils.find_project = mock.Mock()
+ osc_utils.find_project.id = _ipsec_site_conn['project_id']
+ self.res = 'ipsec_site_connection'
+ self.res_plural = 'ipsec_site_connections'
+ self.resource = _ipsec_site_conn
+ self.headers = (
+ 'ID',
+ 'Name',
+ 'Peer Address',
+ 'Authentication Algorithm',
+ 'Status',
+ 'Project',
+ 'Peer CIDRs',
+ 'VPN Service',
+ 'IPSec Policy',
+ 'IKE Policy',
+ 'MTU',
+ 'Initiator',
+ 'State',
+ 'Description',
+ 'Pre-shared Key',
+ 'Route Mode',
+ 'Local ID',
+ 'Peer ID',
+ 'Local Endpoint Group ID',
+ 'Peer Endpoint Group ID',
+ 'DPD',
+ )
+ self.data = _generate_data()
+ self.ordered_headers = (
+ 'Authentication Algorithm',
+ 'DPD',
+ 'Description',
+ 'ID',
+ 'IKE Policy',
+ 'IPSec Policy',
+ 'Initiator',
+ 'Local Endpoint Group ID',
+ 'Local ID',
+ 'MTU',
+ 'Name',
+ 'Peer Address',
+ 'Peer CIDRs',
+ 'Peer Endpoint Group ID',
+ 'Peer ID',
+ 'Pre-shared Key',
+ 'Project',
+ 'Route Mode',
+ 'State',
+ 'Status',
+ 'VPN Service',
+ )
+ self.ordered_data = (
+ _ipsec_site_conn['auth_mode'],
+ _ipsec_site_conn['dpd'],
+ _ipsec_site_conn['description'],
+ _ipsec_site_conn['id'],
+ _ipsec_site_conn['ikepolicy_id'],
+ _ipsec_site_conn['ipsecpolicy_id'],
+ _ipsec_site_conn['initiator'],
+ _ipsec_site_conn['local_ep_group_id'],
+ _ipsec_site_conn['local_id'],
+ _ipsec_site_conn['mtu'],
+ _ipsec_site_conn['name'],
+ _ipsec_site_conn['peer_address'],
+ format_columns.ListColumn(_ipsec_site_conn['peer_cidrs']),
+ _ipsec_site_conn['peer_ep_group_id'],
+ _ipsec_site_conn['peer_id'],
+ _ipsec_site_conn['psk'],
+ _ipsec_site_conn['project_id'],
+ _ipsec_site_conn['route_mode'],
+ _ipsec_site_conn['admin_state_up'],
+ _ipsec_site_conn['status'],
+ _ipsec_site_conn['vpnservice_id'],
+ )
+
+
+class TestCreateIPsecSiteConn(TestIPsecSiteConn, common.TestCreateVPNaaS):
+
+ def setUp(self):
+ super(TestCreateIPsecSiteConn, self).setUp()
+ self.networkclient.create_vpn_ipsec_site_connection = mock.Mock(
+ return_value=_ipsec_site_conn)
+ self.mocked = self.networkclient.create_vpn_ipsec_site_connection
+ self.cmd = ipsec_site_connection.CreateIPsecSiteConnection(
+ self.app, self.namespace)
+
+ def _update_expect_response(self, request, response):
+ """Set expected request and response
+
+ :param request
+ A dictionary of request body(dict of verifylist)
+ :param response
+ A OrderedDict of request body
+ """
+ # Update response body
+ self.networkclient.create_vpn_ipsec_site_connection.return_value = \
+ response
+ osc_utils.find_project.return_value.id = response['project_id']
+ # Update response(finally returns 'data')
+ self.data = _generate_data(ordered_dict=response)
+ self.ordered_data = (
+ response['auth_mode'],
+ response['dpd'],
+ response['description'],
+ response['id'],
+ response['ikepolicy_id'],
+ response['ipsecpolicy_id'],
+ response['initiator'],
+ response['local_ep_group_id'],
+ response['local_id'],
+ response['mtu'],
+ response['name'],
+ response['peer_address'],
+ format_columns.ListColumn(response['peer_cidrs']),
+ response['peer_ep_group_id'],
+ response['peer_id'],
+ response['psk'],
+ response['project_id'],
+ response['route_mode'],
+ response['admin_state_up'],
+ response['status'],
+ response['vpnservice_id'],
+ )
+
+ def _set_all_params(self, args={}):
+ tenant_id = args.get('tenant_id') or 'my-tenant'
+ name = args.get('name') or 'connection1'
+ peer_address = args.get('peer_address') or '192.168.2.10'
+ peer_id = args.get('peer_id') or '192.168.2.10'
+ psk = args.get('psk') or 'abcd'
+ mtu = args.get('mtu') or '1500'
+ initiator = args.get('initiator') or 'bi-directional'
+ vpnservice_id = args.get('vpnservice') or 'vpnservice_id'
+ ikepolicy_id = args.get('ikepolicy') or 'ikepolicy_id'
+ ipsecpolicy_id = args.get('ipsecpolicy') or 'ipsecpolicy_id'
+ local_ep_group = args.get('local_ep_group_id') or 'local-epg'
+ peer_ep_group = args.get('peer_ep_group_id') or 'peer-epg'
+ description = args.get('description') or 'my-vpn-connection'
+
+ arglist = [
+ '--project', tenant_id,
+ '--peer-address', peer_address,
+ '--peer-id', peer_id,
+ '--psk', psk,
+ '--initiator', initiator,
+ '--vpnservice', vpnservice_id,
+ '--ikepolicy', ikepolicy_id,
+ '--ipsecpolicy', ipsecpolicy_id,
+ '--mtu', mtu,
+ '--description', description,
+ '--local-endpoint-group', local_ep_group,
+ '--peer-endpoint-group', peer_ep_group,
+ name,
+ ]
+ verifylist = [
+ ('project', tenant_id),
+ ('peer_address', peer_address),
+ ('peer_id', peer_id),
+ ('psk', psk),
+ ('initiator', initiator),
+ ('vpnservice', vpnservice_id),
+ ('ikepolicy', ikepolicy_id),
+ ('ipsecpolicy', ipsecpolicy_id),
+ ('mtu', mtu),
+ ('description', description),
+ ('local_endpoint_group', local_ep_group),
+ ('peer_endpoint_group', peer_ep_group),
+ ('name', name),
+ ]
+ return arglist, verifylist
+
+ def _test_create_with_all_params(self, args={}):
+ arglist, verifylist = self._set_all_params(args)
+ request, response = _generate_req_and_res(verifylist)
+
+ def _mock_endpoint_group(*args, **kwargs):
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_endpoint_group.side_effect = mock.Mock(
+ side_effect=_mock_endpoint_group)
+ self.networkclient.find_vpn_service.side_effect = mock.Mock(
+ side_effect=_mock_endpoint_group)
+ self.networkclient.find_vpn_ike_policy.side_effect = mock.Mock(
+ side_effect=_mock_endpoint_group)
+ self.networkclient.find_vpn_ipsec_policy.side_effect = mock.Mock(
+ side_effect=_mock_endpoint_group)
+ self._update_expect_response(request, response)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.check_results(headers, data, request)
+
+ def test_create_with_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_with_all_params(self):
+ self._test_create_with_all_params()
+
+
+class TestDeleteIPsecSiteConn(TestIPsecSiteConn, common.TestDeleteVPNaaS):
+
+ def setUp(self):
+ super(TestDeleteIPsecSiteConn, self).setUp()
+ self.networkclient.delete_vpn_ipsec_site_connection = mock.Mock()
+ self.mocked = self.networkclient.delete_vpn_ipsec_site_connection
+ self.cmd = ipsec_site_connection.DeleteIPsecSiteConnection(
+ self.app, self.namespace)
+
+
+class TestListIPsecSiteConn(TestIPsecSiteConn):
+
+ def setUp(self):
+ super(TestListIPsecSiteConn, self).setUp()
+ self.cmd = ipsec_site_connection.ListIPsecSiteConnection(
+ self.app, self.namespace)
+
+ self.short_header = (
+ 'ID',
+ 'Name',
+ 'Peer Address',
+ 'Authentication Algorithm',
+ 'Status',
+ )
+
+ self.short_data = (
+ _ipsec_site_conn['id'],
+ _ipsec_site_conn['name'],
+ _ipsec_site_conn['peer_address'],
+ _ipsec_site_conn['auth_mode'],
+ _ipsec_site_conn['status'],
+ )
+
+ self.networkclient.vpn_ipsec_site_connections = mock.Mock(
+ return_value=[_ipsec_site_conn])
+ self.mocked = self.networkclient.vpn_ipsec_site_connections
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+
+class TestSetIPsecSiteConn(TestIPsecSiteConn, common.TestSetVPNaaS):
+
+ def setUp(self):
+ super(TestSetIPsecSiteConn, self).setUp()
+ self.networkclient.update_vpn_ipsec_site_connection = mock.Mock(
+ return_value=_ipsec_site_conn)
+ self.mocked = self.networkclient.update_vpn_ipsec_site_connection
+ self.cmd = ipsec_site_connection.SetIPsecSiteConnection(
+ self.app, self.namespace)
+
+ def test_set_ipsec_site_conn_with_peer_id(self):
+ target = self.resource['id']
+ peer_id = '192.168.3.10'
+ arglist = [target, '--peer-id', peer_id]
+ verifylist = [
+ (self.res, target),
+ ('peer_id', peer_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(target, **{'peer_id': peer_id})
+ self.assertIsNone(result)
+
+
+class TestShowIPsecSiteConn(TestIPsecSiteConn, common.TestShowVPNaaS):
+
+ def setUp(self):
+ super(TestShowIPsecSiteConn, self).setUp()
+ self.networkclient.get_vpn_ipsec_site_connection = mock.Mock(
+ return_value=_ipsec_site_conn)
+ self.mocked = self.networkclient.get_vpn_ipsec_site_connection
+ self.cmd = ipsec_site_connection.ShowIPsecSiteConnection(
+ self.app, self.namespace)
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/test_ipsecpolicy.py b/neutronclient/tests/unit/osc/v2/vpnaas/test_ipsecpolicy.py
new file mode 100644
index 000000000..3729a11fe
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/test_ipsecpolicy.py
@@ -0,0 +1,299 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 unittest import mock
+
+from osc_lib.tests import utils as tests_utils
+
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import ipsecpolicy
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+from neutronclient.tests.unit.osc.v2.vpnaas import common
+from neutronclient.tests.unit.osc.v2.vpnaas import fakes
+
+
+_ipsecpolicy = fakes.IPSecPolicy().create()
+CONVERT_MAP = {
+ 'project': 'project_id',
+}
+
+
+def _generate_data(ordered_dict=None, data=None):
+ source = ordered_dict if ordered_dict else _ipsecpolicy
+ if data:
+ source.update(data)
+ return source
+
+
+def _generate_req_and_res(verifylist):
+ request = dict(verifylist)
+ response = _ipsecpolicy
+ for key, val in verifylist:
+ converted = CONVERT_MAP.get(key, key)
+ del request[key]
+ new_value = val
+ request[converted] = new_value
+ response[converted] = new_value
+ return request, response
+
+
+class TestIPSecPolicy(test_fakes.TestNeutronClientOSCV2):
+
+ def check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {self.res_plural: list(exp_req)}
+ else:
+ req_body = exp_req
+ self.mocked.assert_called_once_with(**req_body)
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestIPSecPolicy, self).setUp()
+
+ def _mock_ipsecpolicy(*args, **kwargs):
+ self.networkclient.find_vpn_ipsec_policy.assert_called_once_with(
+ self.resource['id'], ignore_missing=False)
+ return {'id': args[0]}
+
+ self.networkclient.find_vpn_ipsec_policy.side_effect = mock.Mock(
+ side_effect=_mock_ipsecpolicy)
+ osc_utils.find_project = mock.Mock()
+ osc_utils.find_project.id = _ipsecpolicy['project_id']
+ self.res = 'ipsecpolicy'
+ self.res_plural = 'ipsecpolicies'
+ self.resource = _ipsecpolicy
+ self.headers = (
+ 'ID',
+ 'Name',
+ 'Authentication Algorithm',
+ 'Encapsulation Mode',
+ 'Transform Protocol',
+ 'Encryption Algorithm',
+ 'Perfect Forward Secrecy (PFS)',
+ 'Description',
+ 'Project',
+ 'Lifetime',
+ )
+ self.data = _generate_data()
+ self.ordered_headers = (
+ 'Authentication Algorithm',
+ 'Description',
+ 'Encapsulation Mode',
+ 'Encryption Algorithm',
+ 'ID',
+ 'Lifetime',
+ 'Name',
+ 'Perfect Forward Secrecy (PFS)',
+ 'Project',
+ 'Transform Protocol',
+ )
+ self.ordered_data = (
+ _ipsecpolicy['auth_algorithm'],
+ _ipsecpolicy['description'],
+ _ipsecpolicy['encapsulation_mode'],
+ _ipsecpolicy['encryption_algorithm'],
+ _ipsecpolicy['id'],
+ _ipsecpolicy['lifetime'],
+ _ipsecpolicy['name'],
+ _ipsecpolicy['pfs'],
+ _ipsecpolicy['project_id'],
+ _ipsecpolicy['transform_protocol'],
+ )
+ self.ordered_columns = (
+ 'auth_algorithm',
+ 'description',
+ 'encapsulation_mode',
+ 'encryption_algorithm',
+ 'id',
+ 'lifetime',
+ 'name',
+ 'pfs',
+ 'project_id',
+ 'transform_protocol',
+ )
+
+
+class TestCreateIPSecPolicy(TestIPSecPolicy, common.TestCreateVPNaaS):
+
+ def setUp(self):
+ super(TestCreateIPSecPolicy, self).setUp()
+ self.networkclient.create_vpn_ipsec_policy = mock.Mock(
+ return_value=_ipsecpolicy)
+ self.mocked = self.networkclient.create_vpn_ipsec_policy
+ self.cmd = ipsecpolicy.CreateIPsecPolicy(self.app, self.namespace)
+
+ def _update_expect_response(self, request, response):
+ """Set expected request and response
+
+ :param request
+ A dictionary of request body(dict of verifylist)
+ :param response
+ A OrderedDict of request body
+ """
+ # Update response body
+ self.networkclient.create_vpn_ipsec_policy.return_value = \
+ response
+ osc_utils.find_project.return_value.id = response['project_id']
+ # Update response(finally returns 'data')
+ self.data = _generate_data(ordered_dict=response)
+ self.ordered_data = tuple(
+ response[column] for column in self.ordered_columns
+ )
+
+ def _set_all_params(self, args={}):
+ name = args.get('name') or 'my-name'
+ auth_algorithm = args.get('auth_algorithm') or 'sha1'
+ encapsulation_mode = args.get('encapsulation_mode') or 'tunnel'
+ transform_protocol = args.get('transform_protocol') or 'esp'
+ encryption_algorithm = args.get('encryption_algorithm') or 'aes-128'
+ pfs = args.get('pfs') or 'group5'
+ description = args.get('description') or 'my-desc'
+ project_id = args.get('project_id') or 'my-project'
+ arglist = [
+ name,
+ '--auth-algorithm', auth_algorithm,
+ '--encapsulation-mode', encapsulation_mode,
+ '--transform-protocol', transform_protocol,
+ '--encryption-algorithm', encryption_algorithm,
+ '--pfs', pfs,
+ '--description', description,
+ '--project', project_id,
+ ]
+ verifylist = [
+ ('name', name),
+ ('auth_algorithm', auth_algorithm),
+ ('encapsulation_mode', encapsulation_mode),
+ ('transform_protocol', transform_protocol),
+ ('encryption_algorithm', encryption_algorithm),
+ ('pfs', pfs),
+ ('description', description),
+ ('project', project_id),
+ ]
+ return arglist, verifylist
+
+ def _test_create_with_all_params(self, args={}):
+ arglist, verifylist = self._set_all_params(args)
+ request, response = _generate_req_and_res(verifylist)
+ self._update_expect_response(request, response)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+ self.check_results(headers, data, request)
+
+ def test_create_with_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_create_with_all_params(self):
+ self._test_create_with_all_params()
+
+ def test_create_with_all_params_name(self):
+ self._test_create_with_all_params({'name': 'new_ipsecpolicy'})
+
+
+class TestDeleteIPSecPolicy(TestIPSecPolicy, common.TestDeleteVPNaaS):
+
+ def setUp(self):
+ super(TestDeleteIPSecPolicy, self).setUp()
+ self.networkclient.delete_vpn_ipsec_policy = mock.Mock()
+ self.mocked = self.networkclient.delete_vpn_ipsec_policy
+ self.cmd = ipsecpolicy.DeleteIPsecPolicy(self.app, self.namespace)
+
+
+class TestListIPSecPolicy(TestIPSecPolicy):
+
+ def setUp(self):
+ super(TestListIPSecPolicy, self).setUp()
+ self.cmd = ipsecpolicy.ListIPsecPolicy(self.app, self.namespace)
+
+ self.short_header = (
+ 'ID',
+ 'Name',
+ 'Authentication Algorithm',
+ 'Encapsulation Mode',
+ 'Transform Protocol',
+ 'Encryption Algorithm',
+ )
+
+ self.short_data = (
+ _ipsecpolicy['id'],
+ _ipsecpolicy['name'],
+ _ipsecpolicy['auth_algorithm'],
+ _ipsecpolicy['encapsulation_mode'],
+ _ipsecpolicy['transform_protocol'],
+ _ipsecpolicy['encryption_algorithm'],
+ )
+
+ self.networkclient.vpn_ipsec_policies = mock.Mock(
+ return_value=[_ipsecpolicy])
+ self.mocked = self.networkclient.vpn_ipsec_policies
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+
+class TestSetIPSecPolicy(TestIPSecPolicy, common.TestSetVPNaaS):
+
+ def setUp(self):
+ super(TestSetIPSecPolicy, self).setUp()
+ self.networkclient.update_vpn_ipsec_policy = mock.Mock(
+ return_value=_ipsecpolicy)
+ self.mocked = self.networkclient.update_vpn_ipsec_policy
+ self.cmd = ipsecpolicy.SetIPsecPolicy(self.app, self.namespace)
+
+ def test_set_auth_algorithm_with_sha256(self):
+ target = self.resource['id']
+ auth_algorithm = 'sha256'
+ arglist = [target, '--auth-algorithm', auth_algorithm]
+ verifylist = [
+ (self.res, target),
+ ('auth_algorithm', auth_algorithm),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, **{'auth_algorithm': 'sha256'})
+ self.assertIsNone(result)
+
+
+class TestShowIPSecPolicy(TestIPSecPolicy, common.TestShowVPNaaS):
+
+ def setUp(self):
+ super(TestShowIPSecPolicy, self).setUp()
+ self.networkclient.get_vpn_ipsec_policy = mock.Mock(
+ return_value=_ipsecpolicy)
+ self.mocked = self.networkclient.get_vpn_ipsec_policy
+ self.cmd = ipsecpolicy.ShowIPsecPolicy(self.app, self.namespace)
diff --git a/neutronclient/tests/unit/osc/v2/vpnaas/test_vpnservice.py b/neutronclient/tests/unit/osc/v2/vpnaas/test_vpnservice.py
new file mode 100644
index 000000000..521fe741c
--- /dev/null
+++ b/neutronclient/tests/unit/osc/v2/vpnaas/test_vpnservice.py
@@ -0,0 +1,305 @@
+# Copyright 2017 FUJITSU LIMITED
+# 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 unittest import mock
+import uuid
+
+
+from neutronclient.osc import utils as osc_utils
+from neutronclient.osc.v2.vpnaas import vpnservice
+from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+from neutronclient.tests.unit.osc.v2.vpnaas import common
+from neutronclient.tests.unit.osc.v2.vpnaas import fakes
+
+
+_vpnservice = fakes.VPNService().create()
+CONVERT_MAP = {
+ 'project': 'project_id',
+ 'router': 'router_id',
+ 'subnet': 'subnet_id'
+}
+
+
+def _generate_data(ordered_dict=None, data=None):
+ source = ordered_dict if ordered_dict else _vpnservice
+ if data:
+ source.update(data)
+ return source
+
+
+def _generate_req_and_res(verifylist):
+ request = dict(verifylist)
+ response = _vpnservice
+ for key, val in verifylist:
+ converted = CONVERT_MAP.get(key, key)
+ del request[key]
+ new_value = val
+ request[converted] = new_value
+ response[converted] = new_value
+ return request, response
+
+
+class TestVPNService(test_fakes.TestNeutronClientOSCV2):
+
+ def _check_results(self, headers, data, exp_req, is_list=False):
+ if is_list:
+ req_body = {self.res_plural: list(exp_req)}
+ else:
+ req_body = exp_req
+ self.mocked.assert_called_once_with(**req_body)
+ self.assertEqual(self.ordered_headers, headers)
+ self.assertEqual(self.ordered_data, data)
+
+ def setUp(self):
+ super(TestVPNService, self).setUp()
+
+ def _mock_vpnservice(*args, **kwargs):
+ self.networkclient.find_vpn_service.assert_called_once_with(
+ self.resource['id'], ignore_missing=False)
+ return {'id': args[0]}
+
+ self.networkclient.find_router = mock.Mock()
+ self.networkclient.find_subnet = mock.Mock()
+ self.fake_router = mock.Mock()
+ self.fake_subnet = mock.Mock()
+ self.networkclient.find_router.return_value = self.fake_router
+ self.networkclient.find_subnet.return_value = self.fake_subnet
+ self.args = {
+ 'name': 'my-name',
+ 'description': 'my-desc',
+ 'project_id': 'project-id-' + uuid.uuid4().hex,
+ 'router_id': 'router-id-' + uuid.uuid4().hex,
+ 'subnet_id': 'subnet-id-' + uuid.uuid4().hex,
+
+ }
+ self.fake_subnet.id = self.args['subnet_id']
+ self.fake_router.id = self.args['router_id']
+
+ self.networkclient.find_vpn_service.side_effect = mock.Mock(
+ side_effect=_mock_vpnservice)
+ osc_utils.find_project = mock.Mock()
+ osc_utils.find_project.id = _vpnservice['project_id']
+
+ self.res = 'vpnservice'
+ self.res_plural = 'vpnservices'
+ self.resource = _vpnservice
+ self.headers = (
+ 'ID',
+ 'Name',
+ 'Router',
+ 'Subnet',
+ 'Flavor',
+ 'State',
+ 'Status',
+ 'Description',
+ 'Project',
+ 'Ext v4 IP',
+ 'Ext v6 IP',
+ )
+ self.data = _generate_data()
+ self.ordered_headers = (
+ 'Description',
+ 'Ext v4 IP',
+ 'Ext v6 IP',
+ 'Flavor',
+ 'ID',
+ 'Name',
+ 'Project',
+ 'Router',
+ 'State',
+ 'Status',
+ 'Subnet',
+ )
+ self.ordered_data = (
+ _vpnservice['description'],
+ _vpnservice['external_v4_ip'],
+ _vpnservice['external_v6_ip'],
+ _vpnservice['flavor_id'],
+ _vpnservice['id'],
+ _vpnservice['name'],
+ _vpnservice['project_id'],
+ _vpnservice['router_id'],
+ _vpnservice['admin_state_up'],
+ _vpnservice['status'],
+ _vpnservice['subnet_id'],
+ )
+ self.ordered_columns = (
+ 'description',
+ 'external_v4_ip',
+ 'external_v6_ip',
+ 'flavor_id',
+ 'id',
+ 'name',
+ 'project_id',
+ 'router_id',
+ 'admin_state_up',
+ 'status',
+ 'subnet_id',
+ )
+
+
+class TestCreateVPNService(TestVPNService, common.TestCreateVPNaaS):
+
+ def setUp(self):
+ super(TestCreateVPNService, self).setUp()
+ self.networkclient.create_vpn_service = mock.Mock(
+ return_value=_vpnservice)
+ self.mocked = self.networkclient.create_vpn_service
+ self.cmd = vpnservice.CreateVPNService(self.app, self.namespace)
+
+ def _update_expect_response(self, request, response):
+ """Set expected request and response
+
+ :param request
+ A dictionary of request body(dict of verifylist)
+ :param response
+ A OrderedDict of request body
+ """
+ # Update response body
+ self.networkclient.create_vpn_service.return_value = response
+ osc_utils.find_project.return_value.id = response['project_id']
+ # Update response(finally returns 'data')
+ self.data = _generate_data(ordered_dict=response)
+ self.ordered_data = tuple(
+ response[column] for column in self.ordered_columns
+ )
+
+ def _set_all_params(self):
+ name = self.args.get('name')
+ description = self.args.get('description')
+ router_id = self.args.get('router_id')
+ subnet_id = self.args.get('subnet_id')
+ project_id = self.args.get('project_id')
+ arglist = [
+ '--description', description,
+ '--project', project_id,
+ '--subnet', subnet_id,
+ '--router', router_id,
+ name,
+ ]
+ verifylist = [
+ ('description', description),
+ ('project', project_id),
+ ('subnet', subnet_id),
+ ('router', router_id),
+ ('name', name),
+ ]
+ return arglist, verifylist
+
+ def _test_create_with_all_params(self):
+ arglist, verifylist = self._set_all_params()
+ request, response = _generate_req_and_res(verifylist)
+ self._update_expect_response(request, response)
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self._check_results(headers, data, request)
+
+ def test_create_with_all_params(self):
+ self._test_create_with_all_params()
+
+
+class TestDeleteVPNService(TestVPNService, common.TestDeleteVPNaaS):
+
+ def setUp(self):
+ super(TestDeleteVPNService, self).setUp()
+ self.networkclient.delete_vpn_service = mock.Mock()
+ self.mocked = self.networkclient.delete_vpn_service
+ self.cmd = vpnservice.DeleteVPNService(self.app, self.namespace)
+
+
+class TestListVPNService(TestVPNService):
+
+ def setUp(self):
+ super(TestListVPNService, self).setUp()
+ self.cmd = vpnservice.ListVPNService(self.app, self.namespace)
+
+ self.short_header = (
+ 'ID',
+ 'Name',
+ 'Router',
+ 'Subnet',
+ 'Flavor',
+ 'State',
+ 'Status',
+ )
+
+ self.short_data = (
+ _vpnservice['id'],
+ _vpnservice['name'],
+ _vpnservice['router_id'],
+ _vpnservice['subnet_id'],
+ _vpnservice['flavor_id'],
+ _vpnservice['admin_state_up'],
+ _vpnservice['status'],
+ )
+
+ self.networkclient.vpn_services = mock.Mock(return_value=[_vpnservice])
+ self.mocked = self.networkclient.vpn_services
+
+ def test_list_with_long_option(self):
+ arglist = ['--long']
+ verifylist = [('long', True)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.headers), headers)
+
+ def test_list_with_no_option(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ headers, data = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with()
+ self.assertEqual(list(self.short_header), headers)
+ self.assertEqual([self.short_data], list(data))
+
+
+class TestSetVPNService(TestVPNService, common.TestSetVPNaaS):
+
+ def setUp(self):
+ super(TestSetVPNService, self).setUp()
+ self.networkclient.update_vpn_service = mock.Mock(
+ return_value=_vpnservice)
+ self.mocked = self.networkclient.update_vpn_service
+ self.cmd = vpnservice.SetVPNSercice(self.app, self.namespace)
+
+ def test_set_name(self):
+ target = self.resource['id']
+ update = 'change'
+ arglist = [target, '--name', update]
+ verifylist = [
+ (self.res, target),
+ ('name', update),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.mocked.assert_called_once_with(
+ target, **{'name': update})
+ self.assertIsNone(result)
+
+
+class TestShowVPNService(TestVPNService, common.TestShowVPNaaS):
+
+ def setUp(self):
+ super(TestShowVPNService, self).setUp()
+ self.networkclient.get_vpn_service = mock.Mock(
+ return_value=_vpnservice)
+ self.mocked = self.networkclient.get_vpn_service
+ self.cmd = vpnservice.ShowVPNService(self.app, self.namespace)
diff --git a/neutronclient/tests/unit/test_casual_args.py b/neutronclient/tests/unit/test_casual_args.py
new file mode 100644
index 000000000..cbf061617
--- /dev/null
+++ b/neutronclient/tests/unit/test_casual_args.py
@@ -0,0 +1,141 @@
+# 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.
+#
+
+import testtools
+
+from neutronclient.common import exceptions
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class CLITestArgs(testtools.TestCase):
+
+ def test_empty(self):
+ _mydict = neutronV20.parse_args_to_dict([])
+ self.assertEqual({}, _mydict)
+
+ def test_default_bool(self):
+ _specs = ['--my_bool', '--arg1', 'value1']
+ _mydict = neutronV20.parse_args_to_dict(_specs)
+ self.assertTrue(_mydict['my_bool'])
+
+ def test_bool_true(self):
+ _specs = ['--my-bool', 'type=bool', 'true', '--arg1', 'value1']
+ _mydict = neutronV20.parse_args_to_dict(_specs)
+ self.assertTrue(_mydict['my_bool'])
+
+ def test_bool_false(self):
+ _specs = ['--my_bool', 'type=bool', 'false', '--arg1', 'value1']
+ _mydict = neutronV20.parse_args_to_dict(_specs)
+ self.assertFalse(_mydict['my_bool'])
+
+ def test_int_and_str(self):
+ _specs = ['--my-int', 'type=int', '10',
+ '--my-str', 'type=str', 'value1']
+ _mydict = neutronV20.parse_args_to_dict(_specs)
+ self.assertEqual(10, _mydict['my_int'])
+ self.assertEqual('value1', _mydict['my_str'])
+
+ def test_nargs(self):
+ _specs = ['--tag', 'x', 'y', '--arg1', 'value1']
+ _mydict = neutronV20.parse_args_to_dict(_specs)
+ self.assertIn('x', _mydict['tag'])
+ self.assertIn('y', _mydict['tag'])
+
+ def test_badarg(self):
+ _specs = ['--tag=t', 'x', 'y', '--arg1', 'value1']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+
+ def test_badarg_with_minus(self):
+ _specs = ['--arg1', 'value1', '-D']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+
+ def test_goodarg_with_minus_number(self):
+ _specs = ['--arg1', 'value1', '-1', '-1.0']
+ _mydict = neutronV20.parse_args_to_dict(_specs)
+ self.assertEqual(['value1', '-1', '-1.0'],
+ _mydict['arg1'])
+
+ def test_badarg_duplicate(self):
+ _specs = ['--tag=t', '--arg1', 'value1', '--arg1', 'value1']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+
+ def test_badarg_early_type_specification(self):
+ _specs = ['type=dict', 'key=value']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+
+ def test_arg(self):
+ _specs = ['--tag=t', '--arg1', 'value1']
+ self.assertEqual('value1',
+ neutronV20.parse_args_to_dict(_specs)['arg1'])
+
+ def test_arg_invalid_syntax(self):
+ _specs = ['--tag=t', '---arg1', 'value1']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+
+ def test_dict_arg(self):
+ _specs = ['--tag=t', '--arg1', 'type=dict', 'key1=value1,key2=value2']
+ arg1 = neutronV20.parse_args_to_dict(_specs)['arg1']
+ self.assertEqual('value1', arg1['key1'])
+ self.assertEqual('value2', arg1['key2'])
+
+ def test_dict_arg_with_attribute_named_type(self):
+ _specs = ['--tag=t', '--arg1', 'type=dict', 'type=value1,key2=value2']
+ arg1 = neutronV20.parse_args_to_dict(_specs)['arg1']
+ self.assertEqual('value1', arg1['type'])
+ self.assertEqual('value2', arg1['key2'])
+
+ def test_list_of_dict_arg(self):
+ _specs = ['--tag=t', '--arg1', 'type=dict',
+ 'list=true', 'key1=value1,key2=value2']
+ arg1 = neutronV20.parse_args_to_dict(_specs)['arg1']
+ self.assertEqual('value1', arg1[0]['key1'])
+ self.assertEqual('value2', arg1[0]['key2'])
+
+ def test_parse_args_to_dict_bad_type(self):
+ _specs = ['--badtypearg', 'type=badtype', 'val1']
+ ex = self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+ self.assertEqual('Invalid value_specs --badtypearg type=badtype val1: '
+ 'type badtype is not supported',
+ str(ex))
+
+ def test_clear_action(self):
+ _specs = ['--anyarg', 'action=clear']
+ args = neutronV20.parse_args_to_dict(_specs)
+ self.assertIsNone(args['anyarg'])
+
+ def test_bad_values_str_without_value(self):
+ _specs = ['--strarg', 'type=str']
+ ex = self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+ self.assertEqual('Invalid values_specs --strarg type=str',
+ str(ex))
+
+ def test_bad_values_list(self):
+ _specs = ['--listarg', 'list=true', 'type=str']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+ _specs = ['--listarg', 'type=list']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
+ _specs = ['--listarg', 'type=list', 'action=clear']
+ self.assertRaises(exceptions.CommandError,
+ neutronV20.parse_args_to_dict, _specs)
diff --git a/neutronclient/tests/unit/test_command_meta.py b/neutronclient/tests/unit/test_command_meta.py
new file mode 100644
index 000000000..dedc3dd1c
--- /dev/null
+++ b/neutronclient/tests/unit/test_command_meta.py
@@ -0,0 +1,41 @@
+# Copyright 2013 Intel
+# Copyright 2013 Isaku Yamahata
+#
+# All Rights Reserved.
+#
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import logging
+
+import testtools
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class TestCommandMeta(testtools.TestCase):
+ def test_neutron_command_meta_defines_log(self):
+ class FakeCommand(neutronV20.NeutronCommand):
+ pass
+
+ self.assertTrue(hasattr(FakeCommand, 'log'))
+ self.assertIsInstance(FakeCommand.log, logging.getLoggerClass())
+ self.assertEqual(__name__ + ".FakeCommand", FakeCommand.log.name)
+
+ def test_neutron_command_log_defined_explicitly(self):
+ class FakeCommand(neutronV20.NeutronCommand):
+ log = None
+
+ self.assertTrue(hasattr(FakeCommand, 'log'))
+ self.assertIsNone(FakeCommand.log)
diff --git a/neutronclient/tests/unit/test_exceptions.py b/neutronclient/tests/unit/test_exceptions.py
new file mode 100644
index 000000000..6e063244c
--- /dev/null
+++ b/neutronclient/tests/unit/test_exceptions.py
@@ -0,0 +1,48 @@
+# 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 unittest import mock
+
+from oslo_utils import encodeutils
+import testtools
+
+from neutronclient._i18n import _
+from neutronclient.common import exceptions
+
+
+class TestExceptions(testtools.TestCase):
+
+ def test_exception_print_with_unicode(self):
+ class TestException(exceptions.NeutronException):
+ message = _('Exception with %(reason)s')
+
+ multibyte_unicode_string = u'\uff21\uff22\uff23'
+ e = TestException(reason=multibyte_unicode_string)
+
+ with mock.patch.object(sys, 'stdout') as mock_stdout:
+ print(e)
+
+ exc_str = 'Exception with %s' % multibyte_unicode_string
+ mock_stdout.assert_has_calls([mock.call.write(exc_str)])
+
+ def test_exception_message_with_encoded_unicode(self):
+ class TestException(exceptions.NeutronException):
+ message = _('Exception with %(reason)s')
+
+ multibyte_string = u'\uff21\uff22\uff23'
+ multibyte_binary = encodeutils.safe_encode(multibyte_string)
+ e = TestException(reason=multibyte_binary)
+ self.assertEqual('Exception with %s' % multibyte_string,
+ str(e))
diff --git a/neutronclient/tests/unit/test_http.py b/neutronclient/tests/unit/test_http.py
new file mode 100644
index 000000000..5680d4375
--- /dev/null
+++ b/neutronclient/tests/unit/test_http.py
@@ -0,0 +1,157 @@
+# Copyright (C) 2013 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.
+
+import abc
+
+from oslo_utils import uuidutils
+import osprofiler.profiler
+import osprofiler.web
+from requests_mock.contrib import fixture as mock_fixture
+import testtools
+
+from neutronclient import client
+from neutronclient.common import exceptions
+
+
+AUTH_TOKEN = 'test_token'
+END_URL = 'test_url'
+METHOD = 'GET'
+URL = 'http://test.test:1234/v2.0/test'
+BODY = 'IAMFAKE'
+
+
+class TestHTTPClientMixin(object, metaclass=abc.ABCMeta):
+
+ def setUp(self):
+ super(TestHTTPClientMixin, self).setUp()
+
+ self.requests = self.useFixture(mock_fixture.Fixture())
+ self.http = self.initialize()
+
+ @abc.abstractmethod
+ def initialize(self):
+ """Return client class, instance."""
+
+ def _test_headers(self, expected_headers, **kwargs):
+ # Test headers.
+ self.requests.register_uri(METHOD, URL,
+ request_headers=expected_headers)
+ self.http.request(URL, METHOD, **kwargs)
+ self.assertEqual(kwargs.get('body'), self.requests.last_request.body)
+
+ def test_headers_without_body(self):
+ self._test_headers({'Accept': 'application/json'})
+
+ def test_headers_with_body(self):
+ headers = {'Accept': 'application/json',
+ 'Content-Type': 'application/json'}
+ self._test_headers(headers, body=BODY)
+
+ def test_headers_without_body_with_content_type(self):
+ headers = {'Accept': 'application/json'}
+ self._test_headers(headers, content_type='application/json')
+
+ def test_headers_with_body_with_content_type(self):
+ headers = {'Accept': 'application/json',
+ 'Content-Type': 'application/json'}
+ self._test_headers(headers, body=BODY, content_type='application/json')
+
+ def test_headers_defined_in_headers(self):
+ headers = {'Accept': 'application/json',
+ 'Content-Type': 'application/json'}
+ self._test_headers(headers, body=BODY, headers=headers)
+
+ def test_osprofiler_headers_are_injected(self):
+ osprofiler.profiler.init('SWORDFISH')
+ self.addCleanup(osprofiler.profiler.clean)
+
+ headers = {'Accept': 'application/json'}
+ headers.update(osprofiler.web.get_trace_id_headers())
+ self._test_headers(headers)
+
+
+class TestHTTPClient(TestHTTPClientMixin, testtools.TestCase):
+
+ def initialize(self):
+ return client.HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL)
+
+ def test_request_error(self):
+ def cb(*args, **kwargs):
+ raise Exception('error msg')
+
+ self.requests.get(URL, body=cb)
+ self.assertRaises(
+ exceptions.ConnectionFailed,
+ self.http._cs_request,
+ URL, METHOD
+ )
+
+ def test_request_success(self):
+ text = 'test content'
+ self.requests.register_uri(METHOD, URL, text=text)
+
+ resp, resp_text = self.http._cs_request(URL, METHOD)
+ self.assertEqual(200, resp.status_code)
+ self.assertEqual(text, resp_text)
+
+ def test_request_unauthorized(self):
+ text = 'unauthorized message'
+ self.requests.register_uri(METHOD, URL, status_code=401, text=text)
+ e = self.assertRaises(exceptions.Unauthorized,
+ self.http._cs_request, URL, METHOD)
+ self.assertEqual(text, e.message)
+
+ def test_request_forbidden_is_returned_to_caller(self):
+ text = 'forbidden message'
+ self.requests.register_uri(METHOD, URL, status_code=403, text=text)
+
+ resp, resp_text = self.http._cs_request(URL, METHOD)
+ self.assertEqual(403, resp.status_code)
+ self.assertEqual(text, resp_text)
+
+ def test_do_request_success(self):
+ text = 'test content'
+ self.requests.register_uri(METHOD, END_URL + URL, text=text)
+
+ resp, resp_text = self.http.do_request(URL, METHOD)
+ self.assertEqual(200, resp.status_code)
+ self.assertEqual(text, resp_text)
+
+ def test_do_request_with_headers_success(self):
+ text = 'test content'
+ self.requests.register_uri(METHOD, END_URL + URL, text=text,
+ request_headers={'key': 'value'})
+
+ resp, resp_text = self.http.do_request(URL, METHOD,
+ headers={'key': 'value'})
+ self.assertEqual(200, resp.status_code)
+ self.assertEqual(text, resp_text)
+
+
+class TestHTTPClientWithReqId(TestHTTPClientMixin, testtools.TestCase):
+ """Tests for when global_request_id is set."""
+
+ def initialize(self):
+ self.req_id = "req-%s" % uuidutils.generate_uuid()
+ return client.HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL,
+ global_request_id=self.req_id)
+
+ def test_request_success(self):
+ headers = {
+ 'Accept': 'application/json',
+ 'X-OpenStack-Request-ID': self.req_id
+ }
+ self.requests.register_uri(METHOD, URL, request_headers=headers)
+ self.http.request(URL, METHOD)
diff --git a/neutronclient/tests/unit/test_utils.py b/neutronclient/tests/unit/test_utils.py
new file mode 100644
index 000000000..4806682b6
--- /dev/null
+++ b/neutronclient/tests/unit/test_utils.py
@@ -0,0 +1,161 @@
+# Copyright (C) 2013 Yahoo! Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import argparse
+
+from oslo_utils import netutils
+
+import testtools
+
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+
+
+class TestUtils(testtools.TestCase):
+ def test_string_to_bool_true(self):
+ self.assertTrue(utils.str2bool('true'))
+
+ def test_string_to_bool_false(self):
+ self.assertFalse(utils.str2bool('false'))
+
+ def test_string_to_bool_None(self):
+ self.assertIsNone(utils.str2bool(None))
+
+ def test_string_to_dictionary(self):
+ input_str = 'key1=value1,key2=value2'
+ expected = {'key1': 'value1', 'key2': 'value2'}
+ self.assertEqual(expected, utils.str2dict(input_str))
+
+ def test_none_string_to_dictionary(self):
+ input_str = ''
+ expected = {}
+ self.assertEqual(expected, utils.str2dict(input_str))
+ input_str = None
+ expected = {}
+ self.assertEqual(expected, utils.str2dict(input_str))
+
+ def test_invalid_string_to_dictionary(self):
+ input_str = 'invalid'
+ self.assertRaises(argparse.ArgumentTypeError,
+ utils.str2dict, input_str)
+
+ def test_string_with_comma_value_to_dictionary(self):
+ input_str = ('opt_name=classless-static-route,'
+ 'opt_value=169.254.169.254/32,10.0.0.1')
+ expected = {'opt_name': 'classless-static-route',
+ 'opt_value': '169.254.169.254/32,10.0.0.1'}
+ self.assertEqual(expected, 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')
+ actual = utils.get_item_properties(item=item, fields=fields)
+ self.assertEqual(('test_name', 'test_id'), actual)
+
+ def test_get_object_item_properties_mixed_case_fields(self):
+ class Fake(object):
+ def __init__(self):
+ self.id = 'test_id'
+ self.name = 'test_name'
+ self.test_user = 'test'
+
+ fields = ('name', 'id', 'test user')
+ mixed_fields = ('test user', 'ID')
+ item = Fake()
+ actual = utils.get_item_properties(item, fields, mixed_fields)
+ self.assertEqual(('test_name', 'test_id', 'test'), actual)
+
+ def test_get_object_item_desired_fields_differ_from_item(self):
+ class Fake(object):
+ def __init__(self):
+ self.id = 'test_id_1'
+ self.name = 'test_name'
+ self.test_user = 'test'
+
+ fields = ('name', 'id', 'test user')
+ item = Fake()
+ actual = utils.get_item_properties(item, fields)
+ self.assertNotEqual(('test_name', 'test_id', 'test'), actual)
+
+ def test_get_object_item_desired_fields_is_empty(self):
+ class Fake(object):
+ def __init__(self):
+ self.id = 'test_id_1'
+ self.name = 'test_name'
+ self.test_user = 'test'
+
+ fields = []
+ item = Fake()
+ actual = utils.get_item_properties(item, fields)
+ self.assertEqual((), actual)
+
+ def test_get_object_item_with_formatters(self):
+ class Fake(object):
+ def __init__(self):
+ self.id = 'test_id'
+ self.name = 'test_name'
+ self.test_user = 'test'
+
+ class FakeCallable(object):
+ def __call__(self, *args, **kwargs):
+ return 'pass'
+
+ fields = ('name', 'id', 'test user', 'is_public')
+ formatters = {'is_public': FakeCallable()}
+ item = Fake()
+ act = utils.get_item_properties(item, fields, formatters=formatters)
+ self.assertEqual(('test_name', 'test_id', 'test', 'pass'), act)
+
+ def test_is_cidr(self):
+ self.assertTrue(netutils.is_valid_cidr('10.10.10.0/24'))
+ self.assertFalse(netutils.is_valid_cidr('10.10.10..0/24'))
+ self.assertFalse(netutils.is_valid_cidr('wrong_cidr_format'))
+
+
+class ImportClassTestCase(testtools.TestCase):
+ def test_get_client_class_invalid_version(self):
+ self.assertRaises(
+ exceptions.UnsupportedVersion,
+ utils.get_client_class, 'image', '2', {'image': '2'})
diff --git a/neutronclient/tests/unit/test_validators.py b/neutronclient/tests/unit/test_validators.py
new file mode 100644
index 000000000..a92496baa
--- /dev/null
+++ b/neutronclient/tests/unit/test_validators.py
@@ -0,0 +1,101 @@
+# Copyright 2014 NEC Corporation
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import testtools
+
+from neutronclient.common import exceptions
+from neutronclient.common import validators
+
+
+class FakeParsedArgs(object):
+ pass
+
+
+class ValidatorTest(testtools.TestCase):
+
+ def _test_validate_int(self, attr_val, attr_name='attr1',
+ min_value=1, max_value=10):
+ obj = FakeParsedArgs()
+ setattr(obj, attr_name, attr_val)
+ ret = validators.validate_int_range(obj, attr_name,
+ min_value, max_value)
+ # Come here only if there is no exception.
+ self.assertIsNone(ret)
+
+ def _test_validate_int_error(self, attr_val, expected_msg,
+ attr_name='attr1', expected_exc=None,
+ min_value=1, max_value=10):
+ if expected_exc is None:
+ expected_exc = exceptions.CommandError
+ e = self.assertRaises(expected_exc,
+ self._test_validate_int,
+ attr_val, attr_name, min_value, max_value)
+ self.assertEqual(expected_msg, str(e))
+
+ def test_validate_int_min_max(self):
+ self._test_validate_int(1)
+ self._test_validate_int(10)
+ self._test_validate_int('1')
+ self._test_validate_int('10')
+ self._test_validate_int('0x0a')
+
+ self._test_validate_int_error(
+ 0, 'attr1 "0" should be an integer [1:10].')
+ self._test_validate_int_error(
+ 11, 'attr1 "11" should be an integer [1:10].')
+ self._test_validate_int_error(
+ '0x10', 'attr1 "0x10" should be an integer [1:10].')
+
+ def test_validate_int_min_only(self):
+ self._test_validate_int(1, max_value=None)
+ self._test_validate_int(10, max_value=None)
+ self._test_validate_int(11, max_value=None)
+ self._test_validate_int_error(
+ 0, 'attr1 "0" should be an integer greater than or equal to 1.',
+ max_value=None)
+
+ def test_validate_int_max_only(self):
+ self._test_validate_int(0, min_value=None)
+ self._test_validate_int(1, min_value=None)
+ self._test_validate_int(10, min_value=None)
+ self._test_validate_int_error(
+ 11, 'attr1 "11" should be an integer smaller than or equal to 10.',
+ min_value=None)
+
+ def test_validate_int_no_limit(self):
+ self._test_validate_int(0, min_value=None, max_value=None)
+ self._test_validate_int(1, min_value=None, max_value=None)
+ self._test_validate_int(10, min_value=None, max_value=None)
+ self._test_validate_int(11, min_value=None, max_value=None)
+ self._test_validate_int_error(
+ 'abc', 'attr1 "abc" should be an integer.',
+ min_value=None, max_value=None)
+
+ def _test_validate_subnet(self, attr_val, attr_name='attr1'):
+ obj = FakeParsedArgs()
+ setattr(obj, attr_name, attr_val)
+ ret = validators.validate_ip_subnet(obj, attr_name)
+ # Come here only if there is no exception.
+ self.assertIsNone(ret)
+
+ def test_validate_ip_subnet(self):
+ self._test_validate_subnet('192.168.2.0/24')
+ self._test_validate_subnet('192.168.2.3/20')
+ self._test_validate_subnet('192.168.2.1')
+
+ e = self.assertRaises(exceptions.CommandError,
+ self._test_validate_subnet,
+ '192.168.2.256')
+ self.assertEqual('attr1 "192.168.2.256" is not a valid CIDR.', str(e))
diff --git a/neutronclient/v2_0/__init__.py b/neutronclient/v2_0/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
new file mode 100644
index 000000000..82676b1c1
--- /dev/null
+++ b/neutronclient/v2_0/client.py
@@ -0,0 +1,2631 @@
+# Copyright 2012 OpenStack Foundation.
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# Copyright 2017 FUJITSU LIMITED
+# 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 inspect
+import itertools
+import logging
+import re
+import time
+import urllib.parse as urlparse
+
+import debtcollector.renames
+from keystoneauth1 import exceptions as ksa_exc
+import requests
+
+from neutronclient._i18n import _
+from neutronclient import client
+from neutronclient.common import exceptions
+from neutronclient.common import extension as client_extension
+from neutronclient.common import serializer
+from neutronclient.common import utils
+
+
+_logger = logging.getLogger(__name__)
+
+HEX_ELEM = '[0-9A-Fa-f]'
+UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
+ HEX_ELEM + '{4}', HEX_ELEM + '{4}',
+ HEX_ELEM + '{12}'])
+
+
+def exception_handler_v20(status_code, error_content):
+ """Exception handler for API v2.0 client.
+
+ This routine generates the appropriate Neutron exception according to
+ the contents of the response body.
+
+ :param status_code: HTTP error status code
+ :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
+ client_exc = None
+ if error_dict:
+ # If Neutron key is found, it will definitely contain
+ # a 'message' and 'type' keys?
+ try:
+ error_type = error_dict['type']
+ error_message = error_dict['message']
+ if error_dict['detail']:
+ error_message += "\n" + error_dict['detail']
+ # If corresponding exception is defined, use it.
+ client_exc = getattr(exceptions, '%sClient' % error_type, None)
+ except Exception:
+ error_message = "%s" % error_dict
+ else:
+ error_message = None
+ if isinstance(error_content, dict):
+ 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 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 _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.
+
+ :param string username: Username for authentication. (optional)
+ :param string user_id: User ID for authentication. (optional)
+ :param string password: Password for authentication. (optional)
+ :param string token: Token for authentication. (optional)
+ :param string tenant_name: DEPRECATED! Use project_name instead.
+ :param string project_name: Project name. (optional)
+ :param string tenant_id: DEPRECATED! Use project_id instead.
+ :param string project_id: Project id. (optional)
+ :param string auth_strategy: 'keystone' by default, 'noauth' for no
+ authentication against keystone. (optional)
+ :param string auth_url: Keystone service endpoint for authorization.
+ :param string service_type: Network service type to pull from the
+ keystone catalog (e.g. 'network') (optional)
+ :param string endpoint_type: Network service endpoint type to pull from the
+ keystone catalog (e.g. 'publicURL',
+ 'internalURL', or 'adminURL') (optional)
+ :param string region_name: Name of a region to select when choosing an
+ endpoint from the service catalog.
+ :param string endpoint_url: A user-supplied endpoint URL for the neutron
+ service. Lazy-authentication is possible for API
+ service calls if endpoint is set at
+ instantiation.(optional)
+ :param integer timeout: Allows customization of the timeout for client
+ http requests. (optional)
+ :param bool insecure: SSL certificate validation. (optional)
+ :param bool log_credentials: Allow for logging of passwords or not.
+ Defaults to False. (optional)
+ :param string ca_cert: SSL CA bundle file to use. (optional)
+ :param cert: A client certificate to pass to requests. These are of the
+ same form as requests expects. Either a single filename
+ containing both the certificate and key or a tuple containing
+ the path to the certificate then a path to the key. (optional)
+ :param integer retries: How many times idempotent (GET, PUT, DELETE)
+ requests to Neutron server should be retried if
+ they fail (default: 0).
+ :param bool raise_errors: If True then exceptions caused by connection
+ failure are propagated to the caller.
+ (default: True)
+ :param session: Keystone client auth session to use. (optional)
+ :param auth: Keystone auth plugin to use. (optional)
+
+ Example::
+
+ from neutronclient.v2_0 import client
+ neutron = client.Client(username=USER,
+ password=PASS,
+ project_name=PROJECT_NAME,
+ auth_url=KEYSTONE_URL)
+
+ nets = neutron.list_networks()
+ ...
+ """
+
+ # API has no way to report plurals, so we have to hard code them
+ # This variable should be overridden by a child class.
+ EXTED_PLURALS = {}
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def __init__(self, **kwargs):
+ """Initialize a new client for the Neutron v2.0 API."""
+ super(ClientBase, self).__init__()
+ _logger.warning("The python binding code in neutronclient is "
+ "deprecated in favor of OpenstackSDK, please use "
+ "that as this will be removed in a future release.")
+ self.retries = kwargs.pop('retries', 0)
+ self.raise_errors = kwargs.pop('raise_errors', True)
+ self.httpclient = client.construct_http_client(**kwargs)
+ self.version = '2.0'
+ self.action_prefix = "/v%s" % (self.version)
+ self.retry_interval = 1
+
+ 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
+ try:
+ des_error_body = self.deserialize(response_body, status_code)
+ except Exception:
+ # 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, error_body)
+
+ def do_request(self, method, action, body=None, headers=None, params=None):
+ # Add format and project_id
+ action = self.action_prefix + action
+ if isinstance(params, dict) and params:
+ params = utils.safe_encode_dict(params)
+ action += '?' + urlparse.urlencode(params, doseq=1)
+
+ if body:
+ body = self.serialize(body)
+
+ resp, replybody = self.httpclient.do_request(action, method, body=body,
+ headers=headers)
+
+ status_code = resp.status_code
+ if status_code in (requests.codes.ok,
+ requests.codes.created,
+ requests.codes.accepted,
+ requests.codes.no_content):
+ 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, resp)
+
+ def get_auth_info(self):
+ return self.httpclient.get_auth_info()
+
+ def serialize(self, data):
+ """Serializes a dictionary into JSON.
+
+ A dictionary with a single key can be passed and it can contain any
+ structure.
+ """
+ if data is None:
+ return None
+ elif isinstance(data, dict):
+ return serializer.Serializer().serialize(data)
+ else:
+ raise Exception(_("Unable to serialize object of type = '%s'") %
+ type(data))
+
+ def deserialize(self, data, status_code):
+ """Deserializes a JSON string into a dictionary."""
+ if not data:
+ return data
+ return serializer.Serializer().deserialize(
+ data)['body']
+
+ def retry_request(self, method, action, body=None,
+ headers=None, params=None):
+ """Call do_request with the default retry configuration.
+
+ Only idempotent requests should retry failed connection attempts.
+ :raises: ConnectionFailed if the maximum # of retries is exceeded
+ """
+ max_attempts = self.retries + 1
+ for i in range(max_attempts):
+ try:
+ return self.do_request(method, action, body=body,
+ headers=headers, params=params)
+ except (exceptions.ConnectionFailed, ksa_exc.ConnectionError):
+ # Exception has already been logged by do_request()
+ if i < self.retries:
+ _logger.debug('Retrying connection to Neutron service')
+ time.sleep(self.retry_interval)
+ elif self.raise_errors:
+ raise
+
+ if self.retries:
+ msg = (_("Failed to connect to Neutron server after %d attempts")
+ % max_attempts)
+ else:
+ msg = _("Failed to connect Neutron server")
+
+ raise exceptions.ConnectionFailed(reason=msg)
+
+ def delete(self, action, body=None, headers=None, params=None):
+ return self.retry_request("DELETE", action, body=body,
+ headers=headers, params=params)
+
+ def get(self, action, body=None, headers=None, params=None):
+ return self.retry_request("GET", action, body=body,
+ headers=headers, params=params)
+
+ def post(self, action, body=None, headers=None, params=None):
+ # Do not retry POST requests to avoid the orphan objects problem.
+ return self.do_request("POST", action, body=body,
+ headers=headers, params=params)
+
+ def put(self, action, body=None, headers=None, params=None):
+ return self.retry_request("PUT", action, body=body,
+ headers=headers, params=params)
+
+ 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])
+ request_ids.extend(r.request_ids)
+ return _DictWithMeta({collection: res}, request_ids)
+ else:
+ return _GeneratorWithMeta(self._pagination, collection,
+ path, **params)
+
+ def _pagination(self, collection, path, **params):
+ if params.get('page_reverse', False):
+ linkrel = 'previous'
+ else:
+ linkrel = 'next'
+ next = True
+ while next:
+ res = self.get(path, params=params)
+ yield res
+ next = False
+ try:
+ for link in res['%s_links' % collection]:
+ if link['rel'] == linkrel:
+ query_str = urlparse.urlparse(link['href']).query
+ params = urlparse.parse_qs(query_str)
+ next = True
+ break
+ except KeyError:
+ break
+
+ def _convert_into_with_meta(self, item, resp):
+ if item:
+ if isinstance(item, dict):
+ return _DictWithMeta(item, resp)
+ elif isinstance(item, str):
+ return _StrWithMeta(item, resp)
+ else:
+ return _TupleWithMeta((), resp)
+
+ def get_resource_plural(self, resource):
+ for k in self.EXTED_PLURALS:
+ if self.EXTED_PLURALS[k] == resource:
+ return k
+ return resource + 's'
+
+ def find_resource_by_id(self, resource, resource_id, cmd_resource=None,
+ parent_id=None, fields=None):
+ if not cmd_resource:
+ cmd_resource = resource
+ cmd_resource_plural = self.get_resource_plural(cmd_resource)
+ resource_plural = self.get_resource_plural(resource)
+ # TODO(amotoki): Use show_%s instead of list_%s
+ obj_lister = getattr(self, "list_%s" % cmd_resource_plural)
+ # perform search by id only if we are passing a valid UUID
+ match = re.match(UUID_PATTERN, resource_id)
+ collection = resource_plural
+ if match:
+ params = {'id': resource_id}
+ if fields:
+ params['fields'] = fields
+ if parent_id:
+ data = obj_lister(parent_id, **params)
+ else:
+ data = obj_lister(**params)
+ if data and data[collection]:
+ return data[collection][0]
+ not_found_message = (_("Unable to find %(resource)s with id "
+ "'%(id)s'") %
+ {'resource': resource, 'id': resource_id})
+ # 404 is raised by exceptions.NotFound to simulate serverside behavior
+ raise exceptions.NotFound(message=not_found_message)
+
+ def _find_resource_by_name(self, resource, name, project_id=None,
+ cmd_resource=None, parent_id=None, fields=None):
+ if not cmd_resource:
+ cmd_resource = resource
+ cmd_resource_plural = self.get_resource_plural(cmd_resource)
+ resource_plural = self.get_resource_plural(resource)
+ obj_lister = getattr(self, "list_%s" % cmd_resource_plural)
+ params = {'name': name}
+ if fields:
+ params['fields'] = fields
+ if project_id:
+ params['tenant_id'] = project_id
+ if parent_id:
+ data = obj_lister(parent_id, **params)
+ else:
+ data = obj_lister(**params)
+ collection = resource_plural
+ info = data[collection]
+ if len(info) > 1:
+ raise exceptions.NeutronClientNoUniqueMatch(resource=resource,
+ name=name)
+ elif len(info) == 0:
+ not_found_message = (_("Unable to find %(resource)s with name "
+ "'%(name)s'") %
+ {'resource': resource, 'name': name})
+ # 404 is raised by exceptions.NotFound
+ # to simulate serverside behavior
+ raise exceptions.NotFound(message=not_found_message)
+ else:
+ return info[0]
+
+ def find_resource(self, resource, name_or_id, project_id=None,
+ cmd_resource=None, parent_id=None, fields=None):
+ try:
+ return self.find_resource_by_id(resource, name_or_id,
+ cmd_resource, parent_id, fields)
+ except exceptions.NotFound:
+ try:
+ return self._find_resource_by_name(
+ 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)
+
+
+class Client(ClientBase):
+
+ networks_path = "/networks"
+ network_path = "/networks/%s"
+ ports_path = "/ports"
+ port_path = "/ports/%s"
+ port_bindings_path = "/ports/%s/bindings"
+ port_binding_path = "/ports/%s/bindings/%s"
+ port_binding_path_activate = "/ports/%s/bindings/%s/activate"
+ subnets_path = "/subnets"
+ subnet_path = "/subnets/%s"
+ onboard_network_subnets_path = "/subnetpools/%s/onboard_network_subnets"
+ subnetpools_path = "/subnetpools"
+ subnetpool_path = "/subnetpools/%s"
+ address_scopes_path = "/address-scopes"
+ address_scope_path = "/address-scopes/%s"
+ quotas_path = "/quotas"
+ quota_path = "/quotas/%s"
+ quota_default_path = "/quotas/%s/default"
+ quota_details_path = "/quotas/%s/details.json"
+ extensions_path = "/extensions"
+ extension_path = "/extensions/%s"
+ routers_path = "/routers"
+ router_path = "/routers/%s"
+ floatingips_path = "/floatingips"
+ floatingip_path = "/floatingips/%s"
+ port_forwardings_path = "/floatingips/%s/port_forwardings"
+ port_forwarding_path = "/floatingips/%s/port_forwardings/%s"
+ security_groups_path = "/security-groups"
+ security_group_path = "/security-groups/%s"
+ security_group_rules_path = "/security-group-rules"
+ security_group_rule_path = "/security-group-rules/%s"
+ segments_path = "/segments"
+ segment_path = "/segments/%s"
+
+ sfc_flow_classifiers_path = "/sfc/flow_classifiers"
+ sfc_flow_classifier_path = "/sfc/flow_classifiers/%s"
+ sfc_port_pairs_path = "/sfc/port_pairs"
+ sfc_port_pair_path = "/sfc/port_pairs/%s"
+ sfc_port_pair_groups_path = "/sfc/port_pair_groups"
+ sfc_port_pair_group_path = "/sfc/port_pair_groups/%s"
+ sfc_port_chains_path = "/sfc/port_chains"
+ sfc_port_chain_path = "/sfc/port_chains/%s"
+ sfc_service_graphs_path = "/sfc/service_graphs"
+ sfc_service_graph_path = "/sfc/service_graphs/%s"
+
+ endpoint_groups_path = "/vpn/endpoint-groups"
+ endpoint_group_path = "/vpn/endpoint-groups/%s"
+ vpnservices_path = "/vpn/vpnservices"
+ vpnservice_path = "/vpn/vpnservices/%s"
+ ipsecpolicies_path = "/vpn/ipsecpolicies"
+ ipsecpolicy_path = "/vpn/ipsecpolicies/%s"
+ ikepolicies_path = "/vpn/ikepolicies"
+ ikepolicy_path = "/vpn/ikepolicies/%s"
+ ipsec_site_connections_path = "/vpn/ipsec-site-connections"
+ ipsec_site_connection_path = "/vpn/ipsec-site-connections/%s"
+
+ 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"
+ lbaas_healthmonitor_path = "/lbaas/healthmonitors/%s"
+ lbaas_members_path = lbaas_pool_path + "/members"
+ lbaas_member_path = lbaas_pool_path + "/members/%s"
+
+ vips_path = "/lb/vips"
+ vip_path = "/lb/vips/%s"
+ pools_path = "/lb/pools"
+ pool_path = "/lb/pools/%s"
+ pool_path_stats = "/lb/pools/%s/stats"
+ members_path = "/lb/members"
+ member_path = "/lb/members/%s"
+ health_monitors_path = "/lb/health_monitors"
+ health_monitor_path = "/lb/health_monitors/%s"
+ associate_pool_health_monitors_path = "/lb/pools/%s/health_monitors"
+ disassociate_pool_health_monitors_path = (
+ "/lb/pools/%(pool)s/health_monitors/%(health_monitor)s")
+ qos_queues_path = "/qos-queues"
+ qos_queue_path = "/qos-queues/%s"
+ agents_path = "/agents"
+ agent_path = "/agents/%s"
+ network_gateways_path = "/network-gateways"
+ network_gateway_path = "/network-gateways/%s"
+ gateway_devices_path = "/gateway-devices"
+ gateway_device_path = "/gateway-devices/%s"
+ service_providers_path = "/service-providers"
+ metering_labels_path = "/metering/metering-labels"
+ metering_label_path = "/metering/metering-labels/%s"
+ metering_label_rules_path = "/metering/metering-label-rules"
+ metering_label_rule_path = "/metering/metering-label-rules/%s"
+
+ DHCP_NETS = '/dhcp-networks'
+ DHCP_AGENTS = '/dhcp-agents'
+ L3_ROUTERS = '/l3-routers'
+ L3_AGENTS = '/l3-agents'
+ LOADBALANCER_POOLS = '/loadbalancer-pools'
+ LOADBALANCER_AGENT = '/loadbalancer-agent'
+ AGENT_LOADBALANCERS = '/agent-loadbalancers'
+ LOADBALANCER_HOSTING_AGENT = '/loadbalancer-hosting-agent'
+ firewall_rules_path = "/fw/firewall_rules"
+ firewall_rule_path = "/fw/firewall_rules/%s"
+ firewall_policies_path = "/fw/firewall_policies"
+ firewall_policy_path = "/fw/firewall_policies/%s"
+ firewall_policy_insert_path = "/fw/firewall_policies/%s/insert_rule"
+ firewall_policy_remove_path = "/fw/firewall_policies/%s/remove_rule"
+ firewalls_path = "/fw/firewalls"
+ firewall_path = "/fw/firewalls/%s"
+ fwaas_firewall_groups_path = "/fwaas/firewall_groups"
+ fwaas_firewall_group_path = "/fwaas/firewall_groups/%s"
+ fwaas_firewall_rules_path = "/fwaas/firewall_rules"
+ fwaas_firewall_rule_path = "/fwaas/firewall_rules/%s"
+ fwaas_firewall_policies_path = "/fwaas/firewall_policies"
+ fwaas_firewall_policy_path = "/fwaas/firewall_policies/%s"
+ fwaas_firewall_policy_insert_path = \
+ "/fwaas/firewall_policies/%s/insert_rule"
+ fwaas_firewall_policy_remove_path = \
+ "/fwaas/firewall_policies/%s/remove_rule"
+ rbac_policies_path = "/rbac-policies"
+ rbac_policy_path = "/rbac-policies/%s"
+ qos_policies_path = "/qos/policies"
+ qos_policy_path = "/qos/policies/%s"
+ qos_bandwidth_limit_rules_path = "/qos/policies/%s/bandwidth_limit_rules"
+ qos_bandwidth_limit_rule_path = "/qos/policies/%s/bandwidth_limit_rules/%s"
+ qos_packet_rate_limit_rules_path = \
+ "/qos/policies/%s/packet_rate_limit_rules"
+ qos_packet_rate_limit_rule_path = \
+ "/qos/policies/%s/packet_rate_limit_rules/%s"
+ qos_dscp_marking_rules_path = "/qos/policies/%s/dscp_marking_rules"
+ qos_dscp_marking_rule_path = "/qos/policies/%s/dscp_marking_rules/%s"
+ qos_minimum_bandwidth_rules_path = \
+ "/qos/policies/%s/minimum_bandwidth_rules"
+ qos_minimum_bandwidth_rule_path = \
+ "/qos/policies/%s/minimum_bandwidth_rules/%s"
+ qos_minimum_packet_rate_rules_path = \
+ "/qos/policies/%s/minimum_packet_rate_rules"
+ qos_minimum_packet_rate_rule_path = \
+ "/qos/policies/%s/minimum_packet_rate_rules/%s"
+ qos_rule_types_path = "/qos/rule-types"
+ qos_rule_type_path = "/qos/rule-types/%s"
+ flavors_path = "/flavors"
+ flavor_path = "/flavors/%s"
+ service_profiles_path = "/service_profiles"
+ service_profile_path = "/service_profiles/%s"
+ 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"
+ trunks_path = "/trunks"
+ trunk_path = "/trunks/%s"
+ subports_path = "/trunks/%s/get_subports"
+ subports_add_path = "/trunks/%s/add_subports"
+ subports_remove_path = "/trunks/%s/remove_subports"
+ bgpvpns_path = "/bgpvpn/bgpvpns"
+ bgpvpn_path = "/bgpvpn/bgpvpns/%s"
+ bgpvpn_network_associations_path =\
+ "/bgpvpn/bgpvpns/%s/network_associations"
+ bgpvpn_network_association_path =\
+ "/bgpvpn/bgpvpns/%s/network_associations/%s"
+ bgpvpn_router_associations_path = "/bgpvpn/bgpvpns/%s/router_associations"
+ bgpvpn_router_association_path =\
+ "/bgpvpn/bgpvpns/%s/router_associations/%s"
+ bgpvpn_port_associations_path = "/bgpvpn/bgpvpns/%s/port_associations"
+ bgpvpn_port_association_path = "/bgpvpn/bgpvpns/%s/port_associations/%s"
+ network_logs_path = "/log/logs"
+ network_log_path = "/log/logs/%s"
+ network_loggables_path = "/log/loggable-resources"
+
+ # API has no way to report plurals, so we have to hard code them
+ EXTED_PLURALS = {'routers': 'router',
+ 'floatingips': 'floatingip',
+ 'service_types': 'service_type',
+ 'service_definitions': 'service_definition',
+ 'security_groups': 'security_group',
+ 'security_group_rules': 'security_group_rule',
+ 'segments': 'segment',
+ 'ipsecpolicies': 'ipsecpolicy',
+ 'ikepolicies': 'ikepolicy',
+ 'ipsec_site_connections': 'ipsec_site_connection',
+ 'vpnservices': 'vpnservice',
+ 'endpoint_groups': 'endpoint_group',
+ 'vips': 'vip',
+ 'pools': 'pool',
+ 'members': 'member',
+ 'health_monitors': 'health_monitor',
+ 'quotas': 'quota',
+ 'service_providers': 'service_provider',
+ 'firewall_rules': 'firewall_rule',
+ 'firewall_policies': 'firewall_policy',
+ 'firewalls': 'firewall',
+ 'fwaas_firewall_rules': 'fwaas_firewall_rule',
+ 'fwaas_firewall_policies': 'fwaas_firewall_policy',
+ 'fwaas_firewall_groups': 'fwaas_firewall_group',
+ 'metering_labels': 'metering_label',
+ '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',
+ 'healthmonitors': 'healthmonitor',
+ 'rbac_policies': 'rbac_policy',
+ 'address_scopes': 'address_scope',
+ 'qos_policies': 'qos_policy',
+ 'policies': 'policy',
+ 'bandwidth_limit_rules': 'bandwidth_limit_rule',
+ 'packet_rate_limit_rules': 'packet_rate_limit_rule',
+ 'minimum_bandwidth_rules': 'minimum_bandwidth_rule',
+ 'minimum_packet_rate_rules': 'minimum_packet_rate_rule',
+ 'rules': 'rule',
+ 'dscp_marking_rules': 'dscp_marking_rule',
+ 'rule_types': 'rule_type',
+ 'flavors': 'flavor',
+ 'bgp_speakers': 'bgp_speaker',
+ 'bgp_peers': 'bgp_peer',
+ 'network_ip_availabilities': 'network_ip_availability',
+ 'trunks': 'trunk',
+ 'bgpvpns': 'bgpvpn',
+ 'network_associations': 'network_association',
+ 'router_associations': 'router_association',
+ 'port_associations': 'port_association',
+ 'flow_classifiers': 'flow_classifier',
+ 'port_pairs': 'port_pair',
+ 'port_pair_groups': 'port_pair_group',
+ 'port_chains': 'port_chain',
+ 'service_graphs': 'service_graph',
+ 'logs': 'log',
+ 'loggable_resources': 'loggable_resource',
+ }
+
+ def list_ext(self, collection, path, retrieve_all, **_params):
+ """Client extension hook for list."""
+ return self.list(collection, path, retrieve_all, **_params)
+
+ def show_ext(self, path, id, **_params):
+ """Client extension hook for show."""
+ return self.get(path % id, params=_params)
+
+ def create_ext(self, path, body=None):
+ """Client extension hook for create."""
+ return self.post(path, body=body)
+
+ def update_ext(self, path, id, body=None):
+ """Client extension hook for update."""
+ return self.put(path % id, body=body)
+
+ def delete_ext(self, path, id):
+ """Client extension hook for delete."""
+ return self.delete(path % id)
+
+ def get_quotas_tenant(self, **_params):
+ """Fetch project info for following quota operation."""
+ return self.get(self.quota_path % 'tenant', params=_params)
+
+ def list_quotas(self, **_params):
+ """Fetch all projects' quotas."""
+ return self.get(self.quotas_path, params=_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def show_quota(self, project_id, **_params):
+ """Fetch information of a certain project's quotas."""
+ return self.get(self.quota_path % (project_id), params=_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def show_quota_details(self, project_id, **_params):
+ """Fetch information of a certain project's quota details."""
+ return self.get(self.quota_details_path % (project_id),
+ params=_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def show_quota_default(self, project_id, **_params):
+ """Fetch information of a certain project's default quotas."""
+ return self.get(self.quota_default_path % (project_id), params=_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def update_quota(self, project_id, body=None):
+ """Update a project's quotas."""
+ return self.put(self.quota_path % (project_id), body=body)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def delete_quota(self, project_id):
+ """Delete the specified project's quota values."""
+ return self.delete(self.quota_path % (project_id))
+
+ def list_extensions(self, **_params):
+ """Fetch a list of all extensions on server side."""
+ return self.get(self.extensions_path, params=_params)
+
+ def show_extension(self, ext_alias, **_params):
+ """Fetches information of a certain extension."""
+ return self.get(self.extension_path % ext_alias, params=_params)
+
+ def list_ports(self, retrieve_all=True, **_params):
+ """Fetches a list of all ports for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('ports', self.ports_path, retrieve_all,
+ **_params)
+
+ def show_port(self, port, **_params):
+ """Fetches information of a certain port."""
+ return self.get(self.port_path % (port), params=_params)
+
+ def create_port(self, body=None):
+ """Creates a new port."""
+ return self.post(self.ports_path, body=body)
+
+ def update_port(self, port, body=None, revision_number=None):
+ """Updates a port."""
+ return self._update_resource(self.port_path % (port), body=body,
+ revision_number=revision_number)
+
+ def delete_port(self, port):
+ """Deletes the specified port."""
+ return self.delete(self.port_path % (port))
+
+ def create_port_binding(self, port_id, body=None):
+ """Creates a new port binding."""
+ return self.post(self.port_bindings_path % port_id, body=body)
+
+ def delete_port_binding(self, port_id, host_id):
+ """Deletes the specified port binding."""
+ return self.delete(self.port_binding_path % (port_id, host_id))
+
+ def show_port_binding(self, port_id, host_id, **_params):
+ """Fetches information for a certain port binding."""
+ return self.get(self.port_binding_path % (port_id, host_id),
+ params=_params)
+
+ def list_port_bindings(self, port_id, retrieve_all=True, **_params):
+ """Fetches a list of all bindings for a certain port."""
+ return self.list('bindings', self.port_bindings_path % port_id,
+ retrieve_all, **_params)
+
+ def activate_port_binding(self, port_id, host_id):
+ """Activates a port binding."""
+ return self.put(self.port_binding_path_activate % (port_id, host_id))
+
+ def list_networks(self, retrieve_all=True, **_params):
+ """Fetches a list of all networks for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('networks', self.networks_path, retrieve_all,
+ **_params)
+
+ def show_network(self, network, **_params):
+ """Fetches information of a certain network."""
+ return self.get(self.network_path % (network), params=_params)
+
+ def create_network(self, body=None):
+ """Creates a new network."""
+ return self.post(self.networks_path, body=body)
+
+ def update_network(self, network, body=None, revision_number=None):
+ """Updates a network."""
+ return self._update_resource(self.network_path % (network), body=body,
+ revision_number=revision_number)
+
+ def delete_network(self, network):
+ """Deletes the specified network."""
+ return self.delete(self.network_path % (network))
+
+ def list_subnets(self, retrieve_all=True, **_params):
+ """Fetches a list of all subnets for a project."""
+ return self.list('subnets', self.subnets_path, retrieve_all,
+ **_params)
+
+ def show_subnet(self, subnet, **_params):
+ """Fetches information of a certain subnet."""
+ return self.get(self.subnet_path % (subnet), params=_params)
+
+ def create_subnet(self, body=None):
+ """Creates a new subnet."""
+ return self.post(self.subnets_path, body=body)
+
+ def update_subnet(self, subnet, body=None, revision_number=None):
+ """Updates a subnet."""
+ return self._update_resource(self.subnet_path % (subnet), body=body,
+ revision_number=revision_number)
+
+ def delete_subnet(self, subnet):
+ """Deletes the specified subnet."""
+ return self.delete(self.subnet_path % (subnet))
+
+ def list_subnetpools(self, retrieve_all=True, **_params):
+ """Fetches a list of all subnetpools for a project."""
+ return self.list('subnetpools', self.subnetpools_path, retrieve_all,
+ **_params)
+
+ def show_subnetpool(self, subnetpool, **_params):
+ """Fetches information of a certain subnetpool."""
+ return self.get(self.subnetpool_path % (subnetpool), params=_params)
+
+ def create_subnetpool(self, body=None):
+ """Creates a new subnetpool."""
+ return self.post(self.subnetpools_path, body=body)
+
+ def update_subnetpool(self, subnetpool, body=None, revision_number=None):
+ """Updates a subnetpool."""
+ return self._update_resource(self.subnetpool_path % (subnetpool),
+ body=body,
+ revision_number=revision_number)
+
+ def delete_subnetpool(self, subnetpool):
+ """Deletes the specified subnetpool."""
+ return self.delete(self.subnetpool_path % (subnetpool))
+
+ def list_routers(self, retrieve_all=True, **_params):
+ """Fetches a list of all routers for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('routers', self.routers_path, retrieve_all,
+ **_params)
+
+ def show_router(self, router, **_params):
+ """Fetches information of a certain router."""
+ return self.get(self.router_path % (router), params=_params)
+
+ def create_router(self, body=None):
+ """Creates a new router."""
+ return self.post(self.routers_path, body=body)
+
+ def update_router(self, router, body=None, revision_number=None):
+ """Updates a router."""
+ return self._update_resource(self.router_path % (router), body=body,
+ revision_number=revision_number)
+
+ def delete_router(self, router):
+ """Deletes the specified router."""
+ return self.delete(self.router_path % (router))
+
+ def list_address_scopes(self, retrieve_all=True, **_params):
+ """Fetches a list of all address scopes for a project."""
+ return self.list('address_scopes', self.address_scopes_path,
+ retrieve_all, **_params)
+
+ def show_address_scope(self, address_scope, **_params):
+ """Fetches information of a certain address scope."""
+ return self.get(self.address_scope_path % (address_scope),
+ params=_params)
+
+ def create_address_scope(self, body=None):
+ """Creates a new address scope."""
+ return self.post(self.address_scopes_path, body=body)
+
+ def update_address_scope(self, address_scope, body=None):
+ """Updates a address scope."""
+ return self.put(self.address_scope_path % (address_scope), body=body)
+
+ def delete_address_scope(self, address_scope):
+ """Deletes the specified address scope."""
+ return self.delete(self.address_scope_path % (address_scope))
+
+ def add_interface_router(self, router, body=None):
+ """Adds an internal network interface to the specified router."""
+ return self.put((self.router_path % router) + "/add_router_interface",
+ body=body)
+
+ def remove_interface_router(self, router, body=None):
+ """Removes an internal network interface from the specified router."""
+ return self.put((self.router_path % router) +
+ "/remove_router_interface", body=body)
+
+ def add_extra_routes_to_router(self, router, body=None):
+ """Adds extra routes to the specified router."""
+ return self.put((self.router_path % router) + "/add_extraroutes",
+ body=body)
+
+ def remove_extra_routes_from_router(self, router, body=None):
+ """Removes extra routes from the specified router."""
+ return self.put((self.router_path % router) + "/remove_extraroutes",
+ body=body)
+
+ def add_gateway_router(self, router, body=None):
+ """Adds an external network gateway to the specified router."""
+ return self.put((self.router_path % router),
+ body={'router': {'external_gateway_info': body}})
+
+ def remove_gateway_router(self, router):
+ """Removes an external network gateway from the specified router."""
+ return self.put((self.router_path % router),
+ body={'router': {'external_gateway_info': {}}})
+
+ def list_floatingips(self, retrieve_all=True, **_params):
+ """Fetches a list of all floatingips for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('floatingips', self.floatingips_path, retrieve_all,
+ **_params)
+
+ def show_floatingip(self, floatingip, **_params):
+ """Fetches information of a certain floatingip."""
+ return self.get(self.floatingip_path % (floatingip), params=_params)
+
+ def create_floatingip(self, body=None):
+ """Creates a new floatingip."""
+ return self.post(self.floatingips_path, body=body)
+
+ def update_floatingip(self, floatingip, body=None, revision_number=None):
+ """Updates a floatingip."""
+ return self._update_resource(self.floatingip_path % (floatingip),
+ body=body,
+ revision_number=revision_number)
+
+ def delete_floatingip(self, floatingip):
+ """Deletes the specified floatingip."""
+ return self.delete(self.floatingip_path % (floatingip))
+
+ def show_port_forwarding(self, floatingip, portforwarding):
+ """Fetches information of a certain portforwarding"""
+ return self.get(self.port_forwarding_path % (floatingip,
+ portforwarding))
+
+ def list_port_forwardings(self, floatingip, retrieve_all=True, **_params):
+ """Fetches a list of all portforwardings for a floatingip."""
+ return self.list('port_forwardings',
+ self.port_forwardings_path % floatingip, retrieve_all,
+ **_params)
+
+ def create_port_forwarding(self, floatingip, body=None):
+ """Creates a new portforwarding."""
+ return self.post(self.port_forwardings_path % floatingip, body=body)
+
+ def delete_port_forwarding(self, floatingip, portforwarding):
+ """Deletes the specified portforwarding."""
+ return self.delete(self.port_forwarding_path % (floatingip,
+ portforwarding))
+
+ def update_port_forwarding(self, floatingip, portforwarding, body=None):
+ """Updates a portforwarding."""
+ return self.put(self.port_forwarding_path % (floatingip,
+ portforwarding),
+ body=body)
+
+ def create_security_group(self, body=None):
+ """Creates a new security group."""
+ return self.post(self.security_groups_path, body=body)
+
+ def update_security_group(self, security_group, body=None,
+ revision_number=None):
+ """Updates a security group."""
+ return self._update_resource(self.security_group_path %
+ security_group, body=body,
+ revision_number=revision_number)
+
+ def list_security_groups(self, retrieve_all=True, **_params):
+ """Fetches a list of all security groups for a project."""
+ return self.list('security_groups', self.security_groups_path,
+ retrieve_all, **_params)
+
+ def show_security_group(self, security_group, **_params):
+ """Fetches information of a certain security group."""
+ return self.get(self.security_group_path % (security_group),
+ params=_params)
+
+ def delete_security_group(self, security_group):
+ """Deletes the specified security group."""
+ return self.delete(self.security_group_path % (security_group))
+
+ def create_security_group_rule(self, body=None):
+ """Creates a new security group rule."""
+ return self.post(self.security_group_rules_path, body=body)
+
+ def delete_security_group_rule(self, security_group_rule):
+ """Deletes the specified security group rule."""
+ return self.delete(self.security_group_rule_path %
+ (security_group_rule))
+
+ def list_security_group_rules(self, retrieve_all=True, **_params):
+ """Fetches a list of all security group rules for a project."""
+ return self.list('security_group_rules',
+ self.security_group_rules_path,
+ retrieve_all, **_params)
+
+ def show_security_group_rule(self, security_group_rule, **_params):
+ """Fetches information of a certain security group rule."""
+ return self.get(self.security_group_rule_path % (security_group_rule),
+ params=_params)
+
+ def create_segment(self, body=None):
+ """Creates a new segment."""
+ return self.post(self.segments_path, body=body)
+
+ def update_segment(self, segment, body=None, revision_number=None):
+ """Updates a segment."""
+ return self._update_resource(self.segment_path % segment, body=body,
+ revision_number=revision_number)
+
+ def list_segments(self, retrieve_all=True, **_params):
+ """Fetches a list of all segments for a project."""
+ return self.list('segments', self.segments_path, retrieve_all,
+ **_params)
+
+ def show_segment(self, segment, **_params):
+ """Fetches information of a certain segment."""
+ return self.get(self.segment_path % segment, params=_params)
+
+ def delete_segment(self, segment):
+ """Deletes the specified segment."""
+ return self.delete(self.segment_path % segment)
+
+ def list_endpoint_groups(self, retrieve_all=True, **_params):
+ """Fetches a list of all VPN endpoint groups for a project."""
+ return self.list('endpoint_groups', self.endpoint_groups_path,
+ retrieve_all, **_params)
+
+ def show_endpoint_group(self, endpointgroup, **_params):
+ """Fetches information for a specific VPN endpoint group."""
+ return self.get(self.endpoint_group_path % endpointgroup,
+ params=_params)
+
+ def create_endpoint_group(self, body=None):
+ """Creates a new VPN endpoint group."""
+ return self.post(self.endpoint_groups_path, body=body)
+
+ def update_endpoint_group(self, endpoint_group, body=None):
+ """Updates a VPN endpoint group."""
+ return self.put(self.endpoint_group_path % endpoint_group, body=body)
+
+ def delete_endpoint_group(self, endpoint_group):
+ """Deletes the specified VPN endpoint group."""
+ return self.delete(self.endpoint_group_path % endpoint_group)
+
+ def list_vpnservices(self, retrieve_all=True, **_params):
+ """Fetches a list of all configured VPN services for a project."""
+ return self.list('vpnservices', self.vpnservices_path, retrieve_all,
+ **_params)
+
+ def show_vpnservice(self, vpnservice, **_params):
+ """Fetches information of a specific VPN service."""
+ return self.get(self.vpnservice_path % (vpnservice), params=_params)
+
+ def create_vpnservice(self, body=None):
+ """Creates a new VPN service."""
+ return self.post(self.vpnservices_path, body=body)
+
+ def update_vpnservice(self, vpnservice, body=None):
+ """Updates a VPN service."""
+ return self.put(self.vpnservice_path % (vpnservice), body=body)
+
+ def delete_vpnservice(self, vpnservice):
+ """Deletes the specified VPN service."""
+ return self.delete(self.vpnservice_path % (vpnservice))
+
+ def list_ipsec_site_connections(self, retrieve_all=True, **_params):
+ """Fetches all configured IPsecSiteConnections for a project."""
+ return self.list('ipsec_site_connections',
+ self.ipsec_site_connections_path,
+ retrieve_all,
+ **_params)
+
+ def show_ipsec_site_connection(self, ipsecsite_conn, **_params):
+ """Fetches information of a specific IPsecSiteConnection."""
+ return self.get(
+ self.ipsec_site_connection_path % (ipsecsite_conn), params=_params
+ )
+
+ def create_ipsec_site_connection(self, body=None):
+ """Creates a new IPsecSiteConnection."""
+ return self.post(self.ipsec_site_connections_path, body=body)
+
+ def update_ipsec_site_connection(self, ipsecsite_conn, body=None):
+ """Updates an IPsecSiteConnection."""
+ return self.put(
+ self.ipsec_site_connection_path % (ipsecsite_conn), body=body
+ )
+
+ def delete_ipsec_site_connection(self, ipsecsite_conn):
+ """Deletes the specified IPsecSiteConnection."""
+ return self.delete(self.ipsec_site_connection_path % (ipsecsite_conn))
+
+ def list_ikepolicies(self, retrieve_all=True, **_params):
+ """Fetches a list of all configured IKEPolicies for a project."""
+ return self.list('ikepolicies', self.ikepolicies_path, retrieve_all,
+ **_params)
+
+ def show_ikepolicy(self, ikepolicy, **_params):
+ """Fetches information of a specific IKEPolicy."""
+ return self.get(self.ikepolicy_path % (ikepolicy), params=_params)
+
+ def create_ikepolicy(self, body=None):
+ """Creates a new IKEPolicy."""
+ return self.post(self.ikepolicies_path, body=body)
+
+ def update_ikepolicy(self, ikepolicy, body=None):
+ """Updates an IKEPolicy."""
+ return self.put(self.ikepolicy_path % (ikepolicy), body=body)
+
+ def delete_ikepolicy(self, ikepolicy):
+ """Deletes the specified IKEPolicy."""
+ return self.delete(self.ikepolicy_path % (ikepolicy))
+
+ def list_ipsecpolicies(self, retrieve_all=True, **_params):
+ """Fetches a list of all configured IPsecPolicies for a project."""
+ return self.list('ipsecpolicies',
+ self.ipsecpolicies_path,
+ retrieve_all,
+ **_params)
+
+ def show_ipsecpolicy(self, ipsecpolicy, **_params):
+ """Fetches information of a specific IPsecPolicy."""
+ return self.get(self.ipsecpolicy_path % (ipsecpolicy), params=_params)
+
+ def create_ipsecpolicy(self, body=None):
+ """Creates a new IPsecPolicy."""
+ return self.post(self.ipsecpolicies_path, body=body)
+
+ def update_ipsecpolicy(self, ipsecpolicy, body=None):
+ """Updates an IPsecPolicy."""
+ return self.put(self.ipsecpolicy_path % (ipsecpolicy), body=body)
+
+ def delete_ipsecpolicy(self, ipsecpolicy):
+ """Deletes the specified IPsecPolicy."""
+ return self.delete(self.ipsecpolicy_path % (ipsecpolicy))
+
+ def list_loadbalancers(self, retrieve_all=True, **_params):
+ """Fetches a list of all loadbalancers for a project."""
+ return self.list('loadbalancers', self.lbaas_loadbalancers_path,
+ retrieve_all, **_params)
+
+ def show_loadbalancer(self, lbaas_loadbalancer, **_params):
+ """Fetches information for a load balancer."""
+ return self.get(self.lbaas_loadbalancer_path % (lbaas_loadbalancer),
+ params=_params)
+
+ def create_loadbalancer(self, body=None):
+ """Creates a new load balancer."""
+ return self.post(self.lbaas_loadbalancers_path, body=body)
+
+ def update_loadbalancer(self, lbaas_loadbalancer, body=None):
+ """Updates a load balancer."""
+ return self.put(self.lbaas_loadbalancer_path % (lbaas_loadbalancer),
+ body=body)
+
+ def delete_loadbalancer(self, lbaas_loadbalancer):
+ """Deletes the specified load balancer."""
+ return self.delete(self.lbaas_loadbalancer_path %
+ (lbaas_loadbalancer))
+
+ def retrieve_loadbalancer_stats(self, loadbalancer, **_params):
+ """Retrieves stats for a certain load balancer."""
+ return self.get(self.lbaas_loadbalancer_path_stats % (loadbalancer),
+ params=_params)
+
+ 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)
+
+ def list_listeners(self, retrieve_all=True, **_params):
+ """Fetches a list of all lbaas_listeners for a project."""
+ return self.list('listeners', self.lbaas_listeners_path,
+ retrieve_all, **_params)
+
+ def show_listener(self, lbaas_listener, **_params):
+ """Fetches information for a lbaas_listener."""
+ return self.get(self.lbaas_listener_path % (lbaas_listener),
+ params=_params)
+
+ def create_listener(self, body=None):
+ """Creates a new lbaas_listener."""
+ return self.post(self.lbaas_listeners_path, body=body)
+
+ def update_listener(self, lbaas_listener, body=None):
+ """Updates a lbaas_listener."""
+ return self.put(self.lbaas_listener_path % (lbaas_listener),
+ body=body)
+
+ def delete_listener(self, lbaas_listener):
+ """Deletes the specified lbaas_listener."""
+ return self.delete(self.lbaas_listener_path % (lbaas_listener))
+
+ 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)
+
+ 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)
+
+ def create_lbaas_l7policy(self, body=None):
+ """Creates L7 policy for a certain listener."""
+ return self.post(self.lbaas_l7policies_path, body=body)
+
+ def update_lbaas_l7policy(self, l7policy, body=None):
+ """Updates L7 policy."""
+ return self.put(self.lbaas_l7policy_path % l7policy,
+ body=body)
+
+ def delete_lbaas_l7policy(self, l7policy):
+ """Deletes the specified L7 policy."""
+ return self.delete(self.lbaas_l7policy_path % l7policy)
+
+ 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)
+
+ 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)
+
+ 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)
+
+ def update_lbaas_l7rule(self, l7rule, l7policy, body=None):
+ """Updates L7 rule."""
+ return self.put(self.lbaas_l7rule_path % (l7policy, l7rule),
+ body=body)
+
+ def delete_lbaas_l7rule(self, l7rule, l7policy):
+ """Deletes the specified L7 rule."""
+ return self.delete(self.lbaas_l7rule_path % (l7policy, l7rule))
+
+ def list_lbaas_pools(self, retrieve_all=True, **_params):
+ """Fetches a list of all lbaas_pools for a project."""
+ return self.list('pools', self.lbaas_pools_path,
+ retrieve_all, **_params)
+
+ def show_lbaas_pool(self, lbaas_pool, **_params):
+ """Fetches information for a lbaas_pool."""
+ return self.get(self.lbaas_pool_path % (lbaas_pool),
+ params=_params)
+
+ def create_lbaas_pool(self, body=None):
+ """Creates a new lbaas_pool."""
+ return self.post(self.lbaas_pools_path, body=body)
+
+ def update_lbaas_pool(self, lbaas_pool, body=None):
+ """Updates a lbaas_pool."""
+ return self.put(self.lbaas_pool_path % (lbaas_pool),
+ body=body)
+
+ def delete_lbaas_pool(self, lbaas_pool):
+ """Deletes the specified lbaas_pool."""
+ return self.delete(self.lbaas_pool_path % (lbaas_pool))
+
+ def list_lbaas_healthmonitors(self, retrieve_all=True, **_params):
+ """Fetches a list of all lbaas_healthmonitors for a project."""
+ return self.list('healthmonitors', self.lbaas_healthmonitors_path,
+ retrieve_all, **_params)
+
+ def show_lbaas_healthmonitor(self, lbaas_healthmonitor, **_params):
+ """Fetches information for a lbaas_healthmonitor."""
+ return self.get(self.lbaas_healthmonitor_path % (lbaas_healthmonitor),
+ params=_params)
+
+ def create_lbaas_healthmonitor(self, body=None):
+ """Creates a new lbaas_healthmonitor."""
+ return self.post(self.lbaas_healthmonitors_path, body=body)
+
+ def update_lbaas_healthmonitor(self, lbaas_healthmonitor, body=None):
+ """Updates a lbaas_healthmonitor."""
+ return self.put(self.lbaas_healthmonitor_path % (lbaas_healthmonitor),
+ body=body)
+
+ def delete_lbaas_healthmonitor(self, lbaas_healthmonitor):
+ """Deletes the specified lbaas_healthmonitor."""
+ return self.delete(self.lbaas_healthmonitor_path %
+ (lbaas_healthmonitor))
+
+ def list_lbaas_loadbalancers(self, retrieve_all=True, **_params):
+ """Fetches a list of all lbaas_loadbalancers for a project."""
+ return self.list('loadbalancers', self.lbaas_loadbalancers_path,
+ retrieve_all, **_params)
+
+ def list_lbaas_members(self, lbaas_pool, retrieve_all=True, **_params):
+ """Fetches a list of all lbaas_members for a project."""
+ return self.list('members', self.lbaas_members_path % lbaas_pool,
+ retrieve_all, **_params)
+
+ def show_lbaas_member(self, lbaas_member, lbaas_pool, **_params):
+ """Fetches information of a certain lbaas_member."""
+ return self.get(self.lbaas_member_path % (lbaas_pool, lbaas_member),
+ params=_params)
+
+ def create_lbaas_member(self, lbaas_pool, body=None):
+ """Creates a lbaas_member."""
+ return self.post(self.lbaas_members_path % lbaas_pool, body=body)
+
+ def update_lbaas_member(self, lbaas_member, lbaas_pool, body=None):
+ """Updates a lbaas_member."""
+ return self.put(self.lbaas_member_path % (lbaas_pool, lbaas_member),
+ body=body)
+
+ def delete_lbaas_member(self, lbaas_member, lbaas_pool):
+ """Deletes the specified lbaas_member."""
+ return self.delete(self.lbaas_member_path % (lbaas_pool, lbaas_member))
+
+ def list_vips(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer vips for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('vips', self.vips_path, retrieve_all,
+ **_params)
+
+ def show_vip(self, vip, **_params):
+ """Fetches information of a certain load balancer vip."""
+ return self.get(self.vip_path % (vip), params=_params)
+
+ def create_vip(self, body=None):
+ """Creates a new load balancer vip."""
+ return self.post(self.vips_path, body=body)
+
+ def update_vip(self, vip, body=None):
+ """Updates a load balancer vip."""
+ return self.put(self.vip_path % (vip), body=body)
+
+ def delete_vip(self, vip):
+ """Deletes the specified load balancer vip."""
+ return self.delete(self.vip_path % (vip))
+
+ def list_pools(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer pools for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('pools', self.pools_path, retrieve_all,
+ **_params)
+
+ def show_pool(self, pool, **_params):
+ """Fetches information of a certain load balancer pool."""
+ return self.get(self.pool_path % (pool), params=_params)
+
+ def create_pool(self, body=None):
+ """Creates a new load balancer pool."""
+ return self.post(self.pools_path, body=body)
+
+ def update_pool(self, pool, body=None):
+ """Updates a load balancer pool."""
+ return self.put(self.pool_path % (pool), body=body)
+
+ def delete_pool(self, pool):
+ """Deletes the specified load balancer pool."""
+ return self.delete(self.pool_path % (pool))
+
+ def retrieve_pool_stats(self, pool, **_params):
+ """Retrieves stats for a certain load balancer pool."""
+ return self.get(self.pool_path_stats % (pool), params=_params)
+
+ def list_members(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer members for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('members', self.members_path, retrieve_all,
+ **_params)
+
+ def show_member(self, member, **_params):
+ """Fetches information of a certain load balancer member."""
+ return self.get(self.member_path % (member), params=_params)
+
+ def create_member(self, body=None):
+ """Creates a new load balancer member."""
+ return self.post(self.members_path, body=body)
+
+ def update_member(self, member, body=None):
+ """Updates a load balancer member."""
+ return self.put(self.member_path % (member), body=body)
+
+ def delete_member(self, member):
+ """Deletes the specified load balancer member."""
+ return self.delete(self.member_path % (member))
+
+ def list_health_monitors(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer health monitors for a project.
+
+ """
+ # Pass filters in "params" argument to do_request
+ return self.list('health_monitors', self.health_monitors_path,
+ retrieve_all, **_params)
+
+ def show_health_monitor(self, health_monitor, **_params):
+ """Fetches information of a certain load balancer health monitor."""
+ return self.get(self.health_monitor_path % (health_monitor),
+ params=_params)
+
+ def create_health_monitor(self, body=None):
+ """Creates a new load balancer health monitor."""
+ return self.post(self.health_monitors_path, body=body)
+
+ def update_health_monitor(self, health_monitor, body=None):
+ """Updates a load balancer health monitor."""
+ return self.put(self.health_monitor_path % (health_monitor), body=body)
+
+ def delete_health_monitor(self, health_monitor):
+ """Deletes the specified load balancer health monitor."""
+ return self.delete(self.health_monitor_path % (health_monitor))
+
+ def associate_health_monitor(self, pool, body):
+ """Associate specified load balancer health monitor and pool."""
+ return self.post(self.associate_pool_health_monitors_path % (pool),
+ body=body)
+
+ def disassociate_health_monitor(self, pool, health_monitor):
+ """Disassociate specified load balancer health monitor and pool."""
+ path = (self.disassociate_pool_health_monitors_path %
+ {'pool': pool, 'health_monitor': health_monitor})
+ return self.delete(path)
+
+ def create_qos_queue(self, body=None):
+ """Creates a new queue."""
+ return self.post(self.qos_queues_path, body=body)
+
+ def list_qos_queues(self, **_params):
+ """Fetches a list of all queues for a project."""
+ return self.get(self.qos_queues_path, params=_params)
+
+ def show_qos_queue(self, queue, **_params):
+ """Fetches information of a certain queue."""
+ return self.get(self.qos_queue_path % (queue),
+ params=_params)
+
+ def delete_qos_queue(self, queue):
+ """Deletes the specified queue."""
+ return self.delete(self.qos_queue_path % (queue))
+
+ def list_agents(self, **_params):
+ """Fetches agents."""
+ # Pass filters in "params" argument to do_request
+ return self.get(self.agents_path, params=_params)
+
+ def show_agent(self, agent, **_params):
+ """Fetches information of a certain agent."""
+ return self.get(self.agent_path % (agent), params=_params)
+
+ def update_agent(self, agent, body=None):
+ """Updates an agent."""
+ return self.put(self.agent_path % (agent), body=body)
+
+ def delete_agent(self, agent):
+ """Deletes the specified agent."""
+ return self.delete(self.agent_path % (agent))
+
+ def list_network_gateways(self, **_params):
+ """Retrieve network gateways."""
+ return self.get(self.network_gateways_path, params=_params)
+
+ def show_network_gateway(self, gateway_id, **_params):
+ """Fetch a network gateway."""
+ return self.get(self.network_gateway_path % gateway_id, params=_params)
+
+ def create_network_gateway(self, body=None):
+ """Create a new network gateway."""
+ return self.post(self.network_gateways_path, body=body)
+
+ def update_network_gateway(self, gateway_id, body=None):
+ """Update a network gateway."""
+ return self.put(self.network_gateway_path % gateway_id, body=body)
+
+ def delete_network_gateway(self, gateway_id):
+ """Delete the specified network gateway."""
+ return self.delete(self.network_gateway_path % gateway_id)
+
+ def connect_network_gateway(self, gateway_id, body=None):
+ """Connect a network gateway to the specified network."""
+ base_uri = self.network_gateway_path % gateway_id
+ return self.put("%s/connect_network" % base_uri, body=body)
+
+ def disconnect_network_gateway(self, gateway_id, body=None):
+ """Disconnect a network from the specified gateway."""
+ base_uri = self.network_gateway_path % gateway_id
+ return self.put("%s/disconnect_network" % base_uri, body=body)
+
+ def list_gateway_devices(self, **_params):
+ """Retrieve gateway devices."""
+ return self.get(self.gateway_devices_path, params=_params)
+
+ def show_gateway_device(self, gateway_device_id, **_params):
+ """Fetch a gateway device."""
+ return self.get(self.gateway_device_path % gateway_device_id,
+ params=_params)
+
+ def create_gateway_device(self, body=None):
+ """Create a new gateway device."""
+ return self.post(self.gateway_devices_path, body=body)
+
+ def update_gateway_device(self, gateway_device_id, body=None):
+ """Updates a new gateway device."""
+ return self.put(self.gateway_device_path % gateway_device_id,
+ body=body)
+
+ def delete_gateway_device(self, gateway_device_id):
+ """Delete the specified gateway device."""
+ return self.delete(self.gateway_device_path % gateway_device_id)
+
+ def list_dhcp_agent_hosting_networks(self, network, **_params):
+ """Fetches a list of dhcp agents hosting a network."""
+ return self.get((self.network_path + self.DHCP_AGENTS) % network,
+ params=_params)
+
+ def list_networks_on_dhcp_agent(self, dhcp_agent, **_params):
+ """Fetches a list of networks hosted on a DHCP agent."""
+ return self.get((self.agent_path + self.DHCP_NETS) % dhcp_agent,
+ params=_params)
+
+ def add_network_to_dhcp_agent(self, dhcp_agent, body=None):
+ """Adds a network to dhcp agent."""
+ return self.post((self.agent_path + self.DHCP_NETS) % dhcp_agent,
+ body=body)
+
+ def remove_network_from_dhcp_agent(self, dhcp_agent, network_id):
+ """Remove a network from dhcp agent."""
+ return self.delete((self.agent_path + self.DHCP_NETS + "/%s") % (
+ dhcp_agent, network_id))
+
+ def list_l3_agent_hosting_routers(self, router, **_params):
+ """Fetches a list of L3 agents hosting a router."""
+ return self.get((self.router_path + self.L3_AGENTS) % router,
+ params=_params)
+
+ def list_routers_on_l3_agent(self, l3_agent, **_params):
+ """Fetches a list of routers hosted on an L3 agent."""
+ return self.get((self.agent_path + self.L3_ROUTERS) % l3_agent,
+ params=_params)
+
+ def add_router_to_l3_agent(self, l3_agent, body):
+ """Adds a router to L3 agent."""
+ return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent,
+ body=body)
+
+ 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)
+
+ 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)
+
+ 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))
+
+ 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)
+
+ def list_firewall_rules(self, retrieve_all=True, **_params):
+ """Fetches a list of all firewall rules for a project."""
+ # Pass filters in "params" argument to do_request
+
+ return self.list('firewall_rules', self.firewall_rules_path,
+ retrieve_all, **_params)
+
+ def show_firewall_rule(self, firewall_rule, **_params):
+ """Fetches information of a certain firewall rule."""
+ return self.get(self.firewall_rule_path % (firewall_rule),
+ params=_params)
+
+ def create_firewall_rule(self, body=None):
+ """Creates a new firewall rule."""
+ return self.post(self.firewall_rules_path, body=body)
+
+ def update_firewall_rule(self, firewall_rule, body=None):
+ """Updates a firewall rule."""
+ return self.put(self.firewall_rule_path % (firewall_rule), body=body)
+
+ def delete_firewall_rule(self, firewall_rule):
+ """Deletes the specified firewall rule."""
+ return self.delete(self.firewall_rule_path % (firewall_rule))
+
+ def list_firewall_policies(self, retrieve_all=True, **_params):
+ """Fetches a list of all firewall policies for a project."""
+ # Pass filters in "params" argument to do_request
+
+ return self.list('firewall_policies', self.firewall_policies_path,
+ retrieve_all, **_params)
+
+ def show_firewall_policy(self, firewall_policy, **_params):
+ """Fetches information of a certain firewall policy."""
+ return self.get(self.firewall_policy_path % (firewall_policy),
+ params=_params)
+
+ def create_firewall_policy(self, body=None):
+ """Creates a new firewall policy."""
+ return self.post(self.firewall_policies_path, body=body)
+
+ def update_firewall_policy(self, firewall_policy, body=None):
+ """Updates a firewall policy."""
+ return self.put(self.firewall_policy_path % (firewall_policy),
+ body=body)
+
+ def delete_firewall_policy(self, firewall_policy):
+ """Deletes the specified firewall policy."""
+ return self.delete(self.firewall_policy_path % (firewall_policy))
+
+ def firewall_policy_insert_rule(self, firewall_policy, body=None):
+ """Inserts specified rule into firewall policy."""
+ return self.put(self.firewall_policy_insert_path % (firewall_policy),
+ body=body)
+
+ def firewall_policy_remove_rule(self, firewall_policy, body=None):
+ """Removes specified rule from firewall policy."""
+ return self.put(self.firewall_policy_remove_path % (firewall_policy),
+ body=body)
+
+ def list_firewalls(self, retrieve_all=True, **_params):
+ """Fetches a list of all firewalls for a project."""
+ # Pass filters in "params" argument to do_request
+
+ return self.list('firewalls', self.firewalls_path, retrieve_all,
+ **_params)
+
+ def show_firewall(self, firewall, **_params):
+ """Fetches information of a certain firewall."""
+ return self.get(self.firewall_path % (firewall), params=_params)
+
+ def create_firewall(self, body=None):
+ """Creates a new firewall."""
+ return self.post(self.firewalls_path, body=body)
+
+ def update_firewall(self, firewall, body=None):
+ """Updates a firewall."""
+ return self.put(self.firewall_path % (firewall), body=body)
+
+ def delete_firewall(self, firewall):
+ """Deletes the specified firewall."""
+ return self.delete(self.firewall_path % (firewall))
+
+ def list_fwaas_firewall_groups(self, retrieve_all=True, **_params):
+ """Fetches a list of all firewall groups for a project"""
+ return self.list('firewall_groups', self.fwaas_firewall_groups_path,
+ retrieve_all, **_params)
+
+ def show_fwaas_firewall_group(self, fwg, **_params):
+ """Fetches information of a certain firewall group"""
+ return self.get(self.fwaas_firewall_group_path % (fwg), params=_params)
+
+ def create_fwaas_firewall_group(self, body=None):
+ """Creates a new firewall group"""
+ return self.post(self.fwaas_firewall_groups_path, body=body)
+
+ def update_fwaas_firewall_group(self, fwg, body=None):
+ """Updates a firewall group"""
+ return self.put(self.fwaas_firewall_group_path % (fwg), body=body)
+
+ def delete_fwaas_firewall_group(self, fwg):
+ """Deletes the specified firewall group"""
+ return self.delete(self.fwaas_firewall_group_path % (fwg))
+
+ def list_fwaas_firewall_rules(self, retrieve_all=True, **_params):
+ """Fetches a list of all firewall rules for a project"""
+ # Pass filters in "params" argument to do_request
+ return self.list('firewall_rules', self.fwaas_firewall_rules_path,
+ retrieve_all, **_params)
+
+ def show_fwaas_firewall_rule(self, firewall_rule, **_params):
+ """Fetches information of a certain firewall rule"""
+ return self.get(self.fwaas_firewall_rule_path % (firewall_rule),
+ params=_params)
+
+ def create_fwaas_firewall_rule(self, body=None):
+ """Creates a new firewall rule"""
+ return self.post(self.fwaas_firewall_rules_path, body=body)
+
+ def update_fwaas_firewall_rule(self, firewall_rule, body=None):
+ """Updates a firewall rule"""
+ return self.put(self.fwaas_firewall_rule_path % (firewall_rule),
+ body=body)
+
+ def delete_fwaas_firewall_rule(self, firewall_rule):
+ """Deletes the specified firewall rule"""
+ return self.delete(self.fwaas_firewall_rule_path % (firewall_rule))
+
+ def list_fwaas_firewall_policies(self, retrieve_all=True, **_params):
+ """Fetches a list of all firewall policies for a project"""
+ # Pass filters in "params" argument to do_request
+
+ return self.list('firewall_policies',
+ self.fwaas_firewall_policies_path,
+ retrieve_all, **_params)
+
+ def show_fwaas_firewall_policy(self, firewall_policy, **_params):
+ """Fetches information of a certain firewall policy"""
+ return self.get(self.fwaas_firewall_policy_path % (firewall_policy),
+ params=_params)
+
+ def create_fwaas_firewall_policy(self, body=None):
+ """Creates a new firewall policy"""
+ return self.post(self.fwaas_firewall_policies_path, body=body)
+
+ def update_fwaas_firewall_policy(self, firewall_policy, body=None):
+ """Updates a firewall policy"""
+ return self.put(self.fwaas_firewall_policy_path % (firewall_policy),
+ body=body)
+
+ def delete_fwaas_firewall_policy(self, firewall_policy):
+ """Deletes the specified firewall policy"""
+ return self.delete(self.fwaas_firewall_policy_path % (firewall_policy))
+
+ def insert_rule_fwaas_firewall_policy(self, firewall_policy, body=None):
+ """Inserts specified rule into firewall policy"""
+ return self.put((self.fwaas_firewall_policy_insert_path %
+ (firewall_policy)), body=body)
+
+ def remove_rule_fwaas_firewall_policy(self, firewall_policy, body=None):
+ """Removes specified rule from firewall policy"""
+ return self.put((self.fwaas_firewall_policy_remove_path %
+ (firewall_policy)), body=body)
+
+ def remove_router_from_l3_agent(self, l3_agent, router_id):
+ """Remove a router from l3 agent."""
+ return self.delete((self.agent_path + self.L3_ROUTERS + "/%s") % (
+ l3_agent, router_id))
+
+ def get_lbaas_agent_hosting_pool(self, pool, **_params):
+ """Fetches a loadbalancer agent hosting a pool."""
+ return self.get((self.pool_path + self.LOADBALANCER_AGENT) % pool,
+ params=_params)
+
+ def list_pools_on_lbaas_agent(self, lbaas_agent, **_params):
+ """Fetches a list of pools hosted by the loadbalancer agent."""
+ return self.get((self.agent_path + self.LOADBALANCER_POOLS) %
+ lbaas_agent, params=_params)
+
+ def get_lbaas_agent_hosting_loadbalancer(self, loadbalancer, **_params):
+ """Fetches a loadbalancer agent hosting a loadbalancer."""
+ return self.get((self.lbaas_loadbalancer_path +
+ self.LOADBALANCER_HOSTING_AGENT) % loadbalancer,
+ params=_params)
+
+ def list_loadbalancers_on_lbaas_agent(self, lbaas_agent, **_params):
+ """Fetches a list of loadbalancers hosted by the loadbalancer agent."""
+ return self.get((self.agent_path + self.AGENT_LOADBALANCERS) %
+ lbaas_agent, params=_params)
+
+ def list_service_providers(self, retrieve_all=True, **_params):
+ """Fetches service providers."""
+ # Pass filters in "params" argument to do_request
+ return self.list('service_providers', self.service_providers_path,
+ retrieve_all, **_params)
+
+ def create_metering_label(self, body=None):
+ """Creates a metering label."""
+ return self.post(self.metering_labels_path, body=body)
+
+ def delete_metering_label(self, label):
+ """Deletes the specified metering label."""
+ return self.delete(self.metering_label_path % (label))
+
+ def list_metering_labels(self, retrieve_all=True, **_params):
+ """Fetches a list of all metering labels for a project."""
+ return self.list('metering_labels', self.metering_labels_path,
+ retrieve_all, **_params)
+
+ def show_metering_label(self, metering_label, **_params):
+ """Fetches information of a certain metering label."""
+ return self.get(self.metering_label_path %
+ (metering_label), params=_params)
+
+ def create_metering_label_rule(self, body=None):
+ """Creates a metering label rule."""
+ return self.post(self.metering_label_rules_path, body=body)
+
+ def delete_metering_label_rule(self, rule):
+ """Deletes the specified metering label rule."""
+ return self.delete(self.metering_label_rule_path % (rule))
+
+ def list_metering_label_rules(self, retrieve_all=True, **_params):
+ """Fetches a list of all metering label rules for a label."""
+ return self.list('metering_label_rules',
+ self.metering_label_rules_path, retrieve_all,
+ **_params)
+
+ def show_metering_label_rule(self, metering_label_rule, **_params):
+ """Fetches information of a certain metering label rule."""
+ return self.get(self.metering_label_rule_path %
+ (metering_label_rule), params=_params)
+
+ def create_rbac_policy(self, body=None):
+ """Create a new RBAC policy."""
+ return self.post(self.rbac_policies_path, body=body)
+
+ def update_rbac_policy(self, rbac_policy_id, body=None):
+ """Update a RBAC policy."""
+ return self.put(self.rbac_policy_path % rbac_policy_id, body=body)
+
+ def list_rbac_policies(self, retrieve_all=True, **_params):
+ """Fetch a list of all RBAC policies for a project."""
+ return self.list('rbac_policies', self.rbac_policies_path,
+ retrieve_all, **_params)
+
+ def show_rbac_policy(self, rbac_policy_id, **_params):
+ """Fetch information of a certain RBAC policy."""
+ return self.get(self.rbac_policy_path % rbac_policy_id,
+ params=_params)
+
+ def delete_rbac_policy(self, rbac_policy_id):
+ """Delete the specified RBAC policy."""
+ return self.delete(self.rbac_policy_path % rbac_policy_id)
+
+ def list_qos_policies(self, retrieve_all=True, **_params):
+ """Fetches a list of all qos policies for a project."""
+ # Pass filters in "params" argument to do_request
+ return self.list('policies', self.qos_policies_path,
+ retrieve_all, **_params)
+
+ def show_qos_policy(self, qos_policy, **_params):
+ """Fetches information of a certain qos policy."""
+ return self.get(self.qos_policy_path % qos_policy,
+ params=_params)
+
+ def create_qos_policy(self, body=None):
+ """Creates a new qos policy."""
+ return self.post(self.qos_policies_path, body=body)
+
+ def update_qos_policy(self, qos_policy, body=None, revision_number=None):
+ """Updates a qos policy."""
+ return self._update_resource(self.qos_policy_path % qos_policy,
+ body=body,
+ revision_number=revision_number)
+
+ def delete_qos_policy(self, qos_policy):
+ """Deletes the specified qos policy."""
+ return self.delete(self.qos_policy_path % qos_policy)
+
+ def list_qos_rule_types(self, retrieve_all=True, **_params):
+ """List available qos rule types."""
+ return self.list('rule_types', self.qos_rule_types_path,
+ retrieve_all, **_params)
+
+ def list_bandwidth_limit_rules(self, policy_id,
+ retrieve_all=True, **_params):
+ """Fetches a list of all bandwidth limit rules for the given policy."""
+ return self.list('bandwidth_limit_rules',
+ self.qos_bandwidth_limit_rules_path % policy_id,
+ retrieve_all, **_params)
+
+ def show_bandwidth_limit_rule(self, rule, policy, **_params):
+ """Fetches information of a certain bandwidth limit rule."""
+ return self.get(self.qos_bandwidth_limit_rule_path %
+ (policy, rule), params=_params)
+
+ def create_bandwidth_limit_rule(self, policy, body=None):
+ """Creates a new bandwidth limit rule."""
+ return self.post(self.qos_bandwidth_limit_rules_path % policy,
+ body=body)
+
+ def update_bandwidth_limit_rule(self, rule, policy, body=None):
+ """Updates a bandwidth limit rule."""
+ return self.put(self.qos_bandwidth_limit_rule_path %
+ (policy, rule), body=body)
+
+ def delete_bandwidth_limit_rule(self, rule, policy):
+ """Deletes a bandwidth limit rule."""
+ return self.delete(self.qos_bandwidth_limit_rule_path %
+ (policy, rule))
+
+ def list_dscp_marking_rules(self, policy_id,
+ retrieve_all=True, **_params):
+ """Fetches a list of all DSCP marking rules for the given policy."""
+ return self.list('dscp_marking_rules',
+ self.qos_dscp_marking_rules_path % policy_id,
+ retrieve_all, **_params)
+
+ def show_dscp_marking_rule(self, rule, policy, **_params):
+ """Shows information of a certain DSCP marking rule."""
+ return self.get(self.qos_dscp_marking_rule_path %
+ (policy, rule), params=_params)
+
+ def create_dscp_marking_rule(self, policy, body=None):
+ """Creates a new DSCP marking rule."""
+ return self.post(self.qos_dscp_marking_rules_path % policy,
+ body=body)
+
+ def update_dscp_marking_rule(self, rule, policy, body=None):
+ """Updates a DSCP marking rule."""
+ return self.put(self.qos_dscp_marking_rule_path %
+ (policy, rule), body=body)
+
+ def delete_dscp_marking_rule(self, rule, policy):
+ """Deletes a DSCP marking rule."""
+ return self.delete(self.qos_dscp_marking_rule_path %
+ (policy, rule))
+
+ def list_minimum_bandwidth_rules(self, policy_id, retrieve_all=True,
+ **_params):
+ """Fetches a list of all minimum bandwidth rules for the given policy.
+
+ """
+ return self.list('minimum_bandwidth_rules',
+ self.qos_minimum_bandwidth_rules_path %
+ policy_id, retrieve_all, **_params)
+
+ def show_minimum_bandwidth_rule(self, rule, policy, body=None):
+ """Fetches information of a certain minimum bandwidth rule."""
+ return self.get(self.qos_minimum_bandwidth_rule_path %
+ (policy, rule), body=body)
+
+ def create_minimum_bandwidth_rule(self, policy, body=None):
+ """Creates a new minimum bandwidth rule."""
+ return self.post(self.qos_minimum_bandwidth_rules_path % policy,
+ body=body)
+
+ def list_packet_rate_limit_rules(self, policy_id, retrieve_all=True,
+ **_params):
+ """Fetches a list of all packet rate limit rules for the given policy
+
+ """
+ return self.list('packet_rate_limit_rules',
+ self.qos_packet_rate_limit_rules_path %
+ policy_id, retrieve_all, **_params)
+
+ def show_packet_rate_limit_rule(self, rule, policy, body=None):
+ """Fetches information of a certain packet rate limit rule."""
+ return self.get(self.qos_packet_rate_limit_rule_path %
+ (policy, rule), body=body)
+
+ def create_packet_rate_limit_rule(self, policy, body=None):
+ """Creates a new packet rate limit rule."""
+ return self.post(self.qos_packet_rate_limit_rules_path % policy,
+ body=body)
+
+ def update_packet_rate_limit_rule(self, rule, policy, body=None):
+ """Updates a packet rate limit rule."""
+ return self.put(self.qos_packet_rate_limit_rule_path %
+ (policy, rule), body=body)
+
+ def delete_packet_rate_limit_rule(self, rule, policy):
+ """Deletes a packet rate limit rule."""
+ return self.delete(self.qos_packet_rate_limit_rule_path %
+ (policy, rule))
+
+ def update_minimum_bandwidth_rule(self, rule, policy, body=None):
+ """Updates a minimum bandwidth rule."""
+ return self.put(self.qos_minimum_bandwidth_rule_path %
+ (policy, rule), body=body)
+
+ def delete_minimum_bandwidth_rule(self, rule, policy):
+ """Deletes a minimum bandwidth rule."""
+ return self.delete(self.qos_minimum_bandwidth_rule_path %
+ (policy, rule))
+
+ def list_minimum_packet_rate_rules(self, policy_id, retrieve_all=True,
+ **_params):
+ """Fetches a list of all minimum packet rate rules for the given policy
+
+ """
+ return self.list('minimum_packet_rate_rules',
+ self.qos_minimum_packet_rate_rules_path %
+ policy_id, retrieve_all, **_params)
+
+ def show_minimum_packet_rate_rule(self, rule, policy, body=None):
+ """Fetches information of a certain minimum packet rate rule."""
+ return self.get(self.qos_minimum_packet_rate_rule_path %
+ (policy, rule), body=body)
+
+ def create_minimum_packet_rate_rule(self, policy, body=None):
+ """Creates a new minimum packet rate rule."""
+ return self.post(self.qos_minimum_packet_rate_rules_path % policy,
+ body=body)
+
+ def update_minimum_packet_rate_rule(self, rule, policy, body=None):
+ """Updates a minimum packet rate rule."""
+ return self.put(self.qos_minimum_packet_rate_rule_path %
+ (policy, rule), body=body)
+
+ def delete_minimum_packet_rate_rule(self, rule, policy):
+ """Deletes a minimum packet rate rule."""
+ return self.delete(self.qos_minimum_packet_rate_rule_path %
+ (policy, rule))
+
+ def create_flavor(self, body=None):
+ """Creates a new Neutron service flavor."""
+ return self.post(self.flavors_path, body=body)
+
+ def delete_flavor(self, flavor):
+ """Deletes the specified Neutron service flavor."""
+ return self.delete(self.flavor_path % (flavor))
+
+ def list_flavors(self, retrieve_all=True, **_params):
+ """Fetches a list of all Neutron service flavors for a project."""
+ return self.list('flavors', self.flavors_path, retrieve_all,
+ **_params)
+
+ def show_flavor(self, flavor, **_params):
+ """Fetches information for a certain Neutron service flavor."""
+ return self.get(self.flavor_path % (flavor), params=_params)
+
+ def update_flavor(self, flavor, body):
+ """Update a Neutron service flavor."""
+ return self.put(self.flavor_path % (flavor), body=body)
+
+ def associate_flavor(self, flavor, body):
+ """Associate a Neutron service flavor with a profile."""
+ return self.post(self.flavor_profile_bindings_path %
+ (flavor), body=body)
+
+ def disassociate_flavor(self, flavor, flavor_profile):
+ """Disassociate a Neutron service flavor with a profile."""
+ return self.delete(self.flavor_profile_binding_path %
+ (flavor, flavor_profile))
+
+ def create_service_profile(self, body=None):
+ """Creates a new Neutron service flavor profile."""
+ return self.post(self.service_profiles_path, body=body)
+
+ def delete_service_profile(self, flavor_profile):
+ """Deletes the specified Neutron service flavor profile."""
+ return self.delete(self.service_profile_path % (flavor_profile))
+
+ def list_service_profiles(self, retrieve_all=True, **_params):
+ """Fetches a list of all Neutron service flavor profiles."""
+ return self.list('service_profiles', self.service_profiles_path,
+ retrieve_all, **_params)
+
+ def show_service_profile(self, flavor_profile, **_params):
+ """Fetches information for a certain Neutron service flavor profile."""
+ return self.get(self.service_profile_path % (flavor_profile),
+ params=_params)
+
+ def update_service_profile(self, service_profile, body):
+ """Update a Neutron service profile."""
+ return self.put(self.service_profile_path % (service_profile),
+ body=body)
+
+ def list_availability_zones(self, retrieve_all=True, **_params):
+ """Fetches a list of all availability zones."""
+ return self.list('availability_zones', self.availability_zones_path,
+ retrieve_all, **_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def get_auto_allocated_topology(self, project_id, **_params):
+ """Fetch information about a project's auto-allocated topology."""
+ return self.get(
+ self.auto_allocated_topology_path % project_id,
+ params=_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def delete_auto_allocated_topology(self, project_id, **_params):
+ """Delete a project's auto-allocated topology."""
+ return self.delete(
+ self.auto_allocated_topology_path % project_id,
+ params=_params)
+
+ @debtcollector.renames.renamed_kwarg(
+ 'tenant_id', 'project_id', replace=True)
+ def validate_auto_allocated_topology_requirements(self, project_id):
+ """Validate requirements for getting an auto-allocated topology."""
+ return self.get_auto_allocated_topology(project_id, fields=['dry-run'])
+
+ def list_bgp_speakers(self, retrieve_all=True, **_params):
+ """Fetches a list of all BGP speakers for a project."""
+ return self.list('bgp_speakers', self.bgp_speakers_path, retrieve_all,
+ **_params)
+
+ 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)
+
+ def create_bgp_speaker(self, body=None):
+ """Creates a new BGP speaker."""
+ return self.post(self.bgp_speakers_path, body=body)
+
+ 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)
+
+ def delete_bgp_speaker(self, speaker_id):
+ """Deletes the specified BGP speaker."""
+ return self.delete(self.bgp_speaker_path % (speaker_id))
+
+ 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)
+
+ 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)
+
+ 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)
+
+ 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)
+
+ 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)
+
+ def list_bgp_peers(self, **_params):
+ """Fetches a list of all BGP peers."""
+ return self.get(self.bgp_peers_path, params=_params)
+
+ 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)
+
+ def create_bgp_peer(self, body=None):
+ """Create a new BGP peer."""
+ return self.post(self.bgp_peers_path, body=body)
+
+ 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)
+
+ def delete_bgp_peer(self, peer_id):
+ """Deletes the specified BGP peer."""
+ return self.delete(self.bgp_peer_path % peer_id)
+
+ def list_network_ip_availabilities(self, retrieve_all=True, **_params):
+ """Fetches IP availability information for all networks"""
+ return self.list('network_ip_availabilities',
+ self.network_ip_availabilities_path,
+ retrieve_all, **_params)
+
+ 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)
+
+ 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))
+
+ 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)
+
+ 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))
+
+ 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 create_trunk(self, body=None):
+ """Create a trunk port."""
+ return self.post(self.trunks_path, body=body)
+
+ def update_trunk(self, trunk, body=None, revision_number=None):
+ """Update a trunk port."""
+ return self._update_resource(self.trunk_path % trunk, body=body,
+ revision_number=revision_number)
+
+ def delete_trunk(self, trunk):
+ """Delete a trunk port."""
+ return self.delete(self.trunk_path % (trunk))
+
+ def list_trunks(self, retrieve_all=True, **_params):
+ """Fetch a list of all trunk ports."""
+ return self.list('trunks', self.trunks_path, retrieve_all,
+ **_params)
+
+ def show_trunk(self, trunk, **_params):
+ """Fetch information for a certain trunk port."""
+ return self.get(self.trunk_path % (trunk), params=_params)
+
+ def trunk_add_subports(self, trunk, body=None):
+ """Add specified subports to the trunk."""
+ return self.put(self.subports_add_path % (trunk), body=body)
+
+ def trunk_remove_subports(self, trunk, body=None):
+ """Removes specified subports from the trunk."""
+ return self.put(self.subports_remove_path % (trunk), body=body)
+
+ def trunk_get_subports(self, trunk, **_params):
+ """Fetch a list of all subports attached to given trunk."""
+ return self.get(self.subports_path % (trunk), params=_params)
+
+ def list_bgpvpns(self, retrieve_all=True, **_params):
+ """Fetches a list of all BGP VPNs for a project"""
+ return self.list('bgpvpns', self.bgpvpns_path, retrieve_all, **_params)
+
+ def show_bgpvpn(self, bgpvpn, **_params):
+ """Fetches information of a certain BGP VPN"""
+ return self.get(self.bgpvpn_path % bgpvpn, params=_params)
+
+ def create_bgpvpn(self, body=None):
+ """Creates a new BGP VPN"""
+ return self.post(self.bgpvpns_path, body=body)
+
+ def update_bgpvpn(self, bgpvpn, body=None):
+ """Updates a BGP VPN"""
+ return self.put(self.bgpvpn_path % bgpvpn, body=body)
+
+ def delete_bgpvpn(self, bgpvpn):
+ """Deletes the specified BGP VPN"""
+ return self.delete(self.bgpvpn_path % bgpvpn)
+
+ def list_bgpvpn_network_assocs(self, bgpvpn, retrieve_all=True, **_params):
+ """Fetches a list of network associations for a given BGP VPN."""
+ return self.list('network_associations',
+ self.bgpvpn_network_associations_path % bgpvpn,
+ retrieve_all, **_params)
+
+ def show_bgpvpn_network_assoc(self, bgpvpn, net_assoc, **_params):
+ """Fetches information of a certain BGP VPN's network association"""
+ return self.get(
+ self.bgpvpn_network_association_path % (bgpvpn, net_assoc),
+ params=_params)
+
+ def create_bgpvpn_network_assoc(self, bgpvpn, body=None):
+ """Creates a new BGP VPN network association"""
+ return self.post(self.bgpvpn_network_associations_path % bgpvpn,
+ body=body)
+
+ def update_bgpvpn_network_assoc(self, bgpvpn, net_assoc, body=None):
+ """Updates a BGP VPN network association"""
+ return self.put(
+ self.bgpvpn_network_association_path % (bgpvpn, net_assoc),
+ body=body)
+
+ def delete_bgpvpn_network_assoc(self, bgpvpn, net_assoc):
+ """Deletes the specified BGP VPN network association"""
+ return self.delete(
+ self.bgpvpn_network_association_path % (bgpvpn, net_assoc))
+
+ def list_bgpvpn_router_assocs(self, bgpvpn, retrieve_all=True, **_params):
+ """Fetches a list of router associations for a given BGP VPN."""
+ return self.list('router_associations',
+ self.bgpvpn_router_associations_path % bgpvpn,
+ retrieve_all, **_params)
+
+ def show_bgpvpn_router_assoc(self, bgpvpn, router_assoc, **_params):
+ """Fetches information of a certain BGP VPN's router association"""
+ return self.get(
+ self.bgpvpn_router_association_path % (bgpvpn, router_assoc),
+ params=_params)
+
+ def create_bgpvpn_router_assoc(self, bgpvpn, body=None):
+ """Creates a new BGP VPN router association"""
+ return self.post(self.bgpvpn_router_associations_path % bgpvpn,
+ body=body)
+
+ def update_bgpvpn_router_assoc(self, bgpvpn, router_assoc, body=None):
+ """Updates a BGP VPN router association"""
+ return self.put(
+ self.bgpvpn_router_association_path % (bgpvpn, router_assoc),
+ body=body)
+
+ def delete_bgpvpn_router_assoc(self, bgpvpn, router_assoc):
+ """Deletes the specified BGP VPN router association"""
+ return self.delete(
+ self.bgpvpn_router_association_path % (bgpvpn, router_assoc))
+
+ def list_bgpvpn_port_assocs(self, bgpvpn, retrieve_all=True, **_params):
+ """Fetches a list of port associations for a given BGP VPN."""
+ return self.list('port_associations',
+ self.bgpvpn_port_associations_path % bgpvpn,
+ retrieve_all, **_params)
+
+ def show_bgpvpn_port_assoc(self, bgpvpn, port_assoc, **_params):
+ """Fetches information of a certain BGP VPN's port association"""
+ return self.get(
+ self.bgpvpn_port_association_path % (bgpvpn, port_assoc),
+ params=_params)
+
+ def create_bgpvpn_port_assoc(self, bgpvpn, body=None):
+ """Creates a new BGP VPN port association"""
+ return self.post(self.bgpvpn_port_associations_path % bgpvpn,
+ body=body)
+
+ def update_bgpvpn_port_assoc(self, bgpvpn, port_assoc, body=None):
+ """Updates a BGP VPN port association"""
+ return self.put(
+ self.bgpvpn_port_association_path % (bgpvpn, port_assoc),
+ body=body)
+
+ def delete_bgpvpn_port_assoc(self, bgpvpn, port_assoc):
+ """Deletes the specified BGP VPN port association"""
+ return self.delete(
+ self.bgpvpn_port_association_path % (bgpvpn, port_assoc))
+
+ def create_sfc_port_pair(self, body=None):
+ """Creates a new Port Pair."""
+ return self.post(self.sfc_port_pairs_path, body=body)
+
+ def update_sfc_port_pair(self, port_pair, body=None):
+ """Update a Port Pair."""
+ return self.put(self.sfc_port_pair_path % port_pair, body=body)
+
+ def delete_sfc_port_pair(self, port_pair):
+ """Deletes the specified Port Pair."""
+ return self.delete(self.sfc_port_pair_path % (port_pair))
+
+ def list_sfc_port_pairs(self, retrieve_all=True, **_params):
+ """Fetches a list of all Port Pairs."""
+ return self.list('port_pairs', self.sfc_port_pairs_path, retrieve_all,
+ **_params)
+
+ def show_sfc_port_pair(self, port_pair, **_params):
+ """Fetches information of a certain Port Pair."""
+ return self.get(self.sfc_port_pair_path % (port_pair), params=_params)
+
+ def create_sfc_port_pair_group(self, body=None):
+ """Creates a new Port Pair Group."""
+ return self.post(self.sfc_port_pair_groups_path, body=body)
+
+ def update_sfc_port_pair_group(self, port_pair_group, body=None):
+ """Update a Port Pair Group."""
+ return self.put(self.sfc_port_pair_group_path % port_pair_group,
+ body=body)
+
+ def delete_sfc_port_pair_group(self, port_pair_group):
+ """Deletes the specified Port Pair Group."""
+ return self.delete(self.sfc_port_pair_group_path % (port_pair_group))
+
+ def list_sfc_port_pair_groups(self, retrieve_all=True, **_params):
+ """Fetches a list of all Port Pair Groups."""
+ return self.list('port_pair_groups', self.sfc_port_pair_groups_path,
+ retrieve_all, **_params)
+
+ def show_sfc_port_pair_group(self, port_pair_group, **_params):
+ """Fetches information of a certain Port Pair Group."""
+ return self.get(self.sfc_port_pair_group_path % (port_pair_group),
+ params=_params)
+
+ def create_sfc_port_chain(self, body=None):
+ """Creates a new Port Chain."""
+ return self.post(self.sfc_port_chains_path, body=body)
+
+ def update_sfc_port_chain(self, port_chain, body=None):
+ """Update a Port Chain."""
+ return self.put(self.sfc_port_chain_path % port_chain, body=body)
+
+ def delete_sfc_port_chain(self, port_chain):
+ """Deletes the specified Port Chain."""
+ return self.delete(self.sfc_port_chain_path % (port_chain))
+
+ def list_sfc_port_chains(self, retrieve_all=True, **_params):
+ """Fetches a list of all Port Chains."""
+ return self.list('port_chains', self.sfc_port_chains_path,
+ retrieve_all, **_params)
+
+ def show_sfc_port_chain(self, port_chain, **_params):
+ """Fetches information of a certain Port Chain."""
+ return self.get(self.sfc_port_chain_path % (port_chain),
+ params=_params)
+
+ def create_sfc_flow_classifier(self, body=None):
+ """Creates a new Flow Classifier."""
+ return self.post(self.sfc_flow_classifiers_path, body=body)
+
+ def update_sfc_flow_classifier(self, flow_classifier, body=None):
+ """Update a Flow Classifier."""
+ return self.put(self.sfc_flow_classifier_path % flow_classifier,
+ body=body)
+
+ def delete_sfc_flow_classifier(self, flow_classifier):
+ """Deletes the specified Flow Classifier."""
+ return self.delete(self.sfc_flow_classifier_path % (flow_classifier))
+
+ def list_sfc_flow_classifiers(self, retrieve_all=True, **_params):
+ """Fetches a list of all Flow Classifiers."""
+ return self.list('flow_classifiers', self.sfc_flow_classifiers_path,
+ retrieve_all, **_params)
+
+ def show_sfc_flow_classifier(self, flow_classifier, **_params):
+ """Fetches information of a certain Flow Classifier."""
+ return self.get(self.sfc_flow_classifier_path % (flow_classifier),
+ params=_params)
+
+ def create_sfc_service_graph(self, body=None):
+ """Create the specified Service Graph."""
+ return self.post(self.sfc_service_graphs_path, body=body)
+
+ def update_sfc_service_graph(self, service_graph, body=None):
+ """Update a Service Graph."""
+ return self.put(self.sfc_service_graph_path % service_graph,
+ body=body)
+
+ def delete_sfc_service_graph(self, service_graph):
+ """Deletes the specified Service Graph."""
+ return self.delete(self.sfc_service_graph_path % service_graph)
+
+ def list_sfc_service_graphs(self, retrieve_all=True, **_params):
+ """Fetches a list of all Service Graphs."""
+ return self.list('service_graphs', self.sfc_service_graphs_path,
+ retrieve_all, **_params)
+
+ def show_sfc_service_graph(self, service_graph, **_params):
+ """Fetches information of a certain Service Graph."""
+ return self.get(self.sfc_service_graph_path % service_graph,
+ params=_params)
+
+ def create_network_log(self, body=None):
+ """Create a network log."""
+ return self.post(self.network_logs_path, body=body)
+
+ def delete_network_log(self, net_log):
+ """Delete a network log."""
+ return self.delete(self.network_log_path % net_log)
+
+ def list_network_logs(self, retrieve_all=True, **_params):
+ """Fetch a list of all network logs."""
+ return self.list(
+ 'logs', self.network_logs_path, retrieve_all, **_params)
+
+ def show_network_log(self, net_log, **_params):
+ """Fetch information for a certain network log."""
+ return self.get(self.network_log_path % net_log, params=_params)
+
+ def update_network_log(self, net_log, body=None):
+ """Update a network log."""
+ return self.put(self.network_log_path % net_log, body=body)
+
+ def list_network_loggable_resources(self, retrieve_all=True, **_params):
+ """Fetch a list of supported resource types for network log."""
+ return self.list('loggable_resources', self.network_loggables_path,
+ retrieve_all, **_params)
+
+ def onboard_network_subnets(self, subnetpool, body=None):
+ """Onboard the specified network's subnets into a subnet pool."""
+ return self.put(self.onboard_network_subnets_path % (subnetpool),
+ body=body)
+
+ def __init__(self, **kwargs):
+ """Initialize a new client for the Neutron v2.0 API."""
+ super(Client, self).__init__(**kwargs)
+ self._register_extensions(self.version)
+
+ def _update_resource(self, path, **kwargs):
+ revision_number = kwargs.pop('revision_number', None)
+ if revision_number:
+ headers = kwargs.setdefault('headers', {})
+ headers['If-Match'] = 'revision_number=%s' % revision_number
+ return self.put(path, **kwargs)
+
+ def extend_show(self, resource_singular, path, parent_resource):
+ def _fx(obj, **_params):
+ return self.show_ext(path, obj, **_params)
+
+ def _parent_fx(obj, parent_id, **_params):
+ return self.show_ext(path % parent_id, obj, **_params)
+ fn = _fx if not parent_resource else _parent_fx
+ setattr(self, "show_%s" % resource_singular, fn)
+
+ def extend_list(self, resource_plural, path, parent_resource):
+ def _fx(retrieve_all=True, **_params):
+ return self.list_ext(resource_plural, path,
+ retrieve_all, **_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)
+
+ def extend_create(self, resource_singular, path, parent_resource):
+ def _fx(body=None):
+ return self.create_ext(path, body)
+
+ def _parent_fx(parent_id, body=None):
+ return self.create_ext(path % parent_id, body)
+ fn = _fx if not parent_resource else _parent_fx
+ setattr(self, "create_%s" % resource_singular, fn)
+
+ def extend_delete(self, resource_singular, path, parent_resource):
+ def _fx(obj):
+ return self.delete_ext(path, obj)
+
+ def _parent_fx(obj, parent_id):
+ return self.delete_ext(path % parent_id, obj)
+ fn = _fx if not parent_resource else _parent_fx
+ setattr(self, "delete_%s" % resource_singular, fn)
+
+ def extend_update(self, resource_singular, path, parent_resource):
+ def _fx(obj, body=None):
+ return self.update_ext(path, obj, body)
+
+ def _parent_fx(obj, parent_id, body=None):
+ return self.update_ext(path % parent_id, obj, body)
+ fn = _fx if not parent_resource else _parent_fx
+ setattr(self, "update_%s" % resource_singular, fn)
+
+ def _extend_client_with_module(self, module, version):
+ classes = inspect.getmembers(module, inspect.isclass)
+ for cls_name, cls in classes:
+ if hasattr(cls, 'versions'):
+ if version not in cls.versions:
+ continue
+ parent_resource = getattr(cls, 'parent_resource', None)
+ if issubclass(cls, client_extension.ClientExtensionList):
+ self.extend_list(cls.resource_plural, cls.object_path,
+ parent_resource)
+ elif issubclass(cls, client_extension.ClientExtensionCreate):
+ self.extend_create(cls.resource, cls.object_path,
+ parent_resource)
+ elif issubclass(cls, client_extension.ClientExtensionUpdate):
+ self.extend_update(cls.resource, cls.resource_path,
+ parent_resource)
+ elif issubclass(cls, client_extension.ClientExtensionDelete):
+ self.extend_delete(cls.resource, cls.resource_path,
+ parent_resource)
+ elif issubclass(cls, client_extension.ClientExtensionShow):
+ self.extend_show(cls.resource, cls.resource_path,
+ parent_resource)
+ elif issubclass(cls, client_extension.NeutronClientExtension):
+ setattr(self, "%s_path" % cls.resource_plural,
+ cls.object_path)
+ setattr(self, "%s_path" % cls.resource, cls.resource_path)
+ self.EXTED_PLURALS.update({cls.resource_plural: cls.resource})
+
+ def _register_extensions(self, version):
+ for name, module in itertools.chain(
+ client_extension._discover_via_entry_points()):
+ self._extend_client_with_module(module, version)
diff --git a/neutronclient/version.py b/neutronclient/version.py
new file mode 100644
index 000000000..d597b5c91
--- /dev/null
+++ b/neutronclient/version.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# 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 pbr.version
+
+
+version_info = pbr.version.VersionInfo('python-neutronclient')
+__version__ = version_info.version_string()
diff --git a/quantumclient/__init__.py b/quantumclient/__init__.py
deleted file mode 100644
index 461f9b853..000000000
--- a/quantumclient/__init__.py
+++ /dev/null
@@ -1,563 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 Citrix 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.
-# @author: Tyler Smith, Cisco Systems
-
-import gettext
-import httplib
-import logging
-import socket
-import time
-import urllib
-
-
-# gettext must be initialized before any quantumclient imports
-gettext.install('quantumclient', unicode=1)
-
-
-from quantumclient.common import exceptions
-from quantumclient.common.serializer import Serializer
-
-
-LOG = logging.getLogger('quantumclient')
-
-
-AUTH_TOKEN_HEADER = "X-Auth-Token"
-
-
-def exception_handler_v10(status_code, error_content):
- """ Exception handler for API v1.0 client
-
- This routine generates the appropriate
- Quantum exception according to the contents of the
- response body
-
- :param status_code: HTTP error status code
- :param error_content: deserialized body of error response
- """
-
- quantum_error_types = {
- 420: 'networkNotFound',
- 421: 'networkInUse',
- 430: 'portNotFound',
- 431: 'requestedStateInvalid',
- 432: 'portInUse',
- 440: 'alreadyAttached',
- }
-
- quantum_errors = {
- 400: exceptions.BadInputError,
- 401: exceptions.NotAuthorized,
- 404: exceptions.NotFound,
- 420: exceptions.NetworkNotFoundClient,
- 421: exceptions.NetworkInUseClient,
- 430: exceptions.PortNotFoundClient,
- 431: exceptions.StateInvalidClient,
- 432: exceptions.PortInUseClient,
- 440: exceptions.AlreadyAttachedClient,
- 501: NotImplementedError,
- }
-
- # Find real error type
- error_type = None
- if isinstance(error_content, dict):
- error_type = quantum_error_types.get(status_code)
- if error_type:
- error_dict = error_content[error_type]
- error_message = error_dict['message'] + "\n" + error_dict['detail']
- else:
- error_message = error_content
- # raise the appropriate error!
- ex = quantum_errors[status_code](message=error_message)
- ex.args = ([dict(status_code=status_code,
- message=error_message)],)
- raise ex
-
-
-def exception_handler_v11(status_code, error_content):
- """ Exception handler for API v1.1 client
-
- This routine generates the appropriate
- Quantum exception according to the contents of the
- response body
-
- :param status_code: HTTP error status code
- :param error_content: deserialized body of error response
- """
-
- quantum_errors = {
- 'NetworkNotFound': exceptions.NetworkNotFoundClient,
- 'NetworkInUse': exceptions.NetworkInUseClient,
- 'PortNotFound': exceptions.PortNotFoundClient,
- 'RequestedStateInvalid': exceptions.StateInvalidClient,
- 'PortInUse': exceptions.PortInUseClient,
- 'AlreadyAttached': exceptions.AlreadyAttachedClient,
- }
-
- error_dict = None
- if isinstance(error_content, dict):
- error_dict = error_content.get('QuantumError')
- # Find real error type
- if error_dict:
- # If QuantumError key is found, it will definitely contain
- # a 'message' and 'type' keys
- error_type = error_dict['type']
- error_message = (error_dict['message'] + "\n" +
- error_dict['detail'])
- # raise the appropriate error!
- ex = quantum_errors[error_type](message=error_message)
- ex.args = ([dict(status_code=status_code,
- message=error_message)],)
- raise ex
- # If we end up here the exception was not a quantum error
- msg = "%s-%s" % (status_code, error_content)
- raise exceptions.QuantumClientException(message=msg)
-
-
-EXCEPTION_HANDLERS = {
- '1.0': exception_handler_v10,
- '1.1': exception_handler_v11,
-}
-
-
-class ApiCall(object):
- """A Decorator to add support for format and tenant overriding"""
- def __init__(self, function):
- self.function = function
-
- def __get__(self, instance, owner):
- def with_params(*args, **kwargs):
- """
- Temporarily sets the format and tenant for this request
- """
- (format, tenant) = (instance.format, instance.tenant)
-
- if 'format' in kwargs:
- instance.format = kwargs['format']
- if 'tenant' in kwargs:
- instance.tenant = kwargs['tenant']
-
- ret = self.function(instance, *args)
- (instance.format, instance.tenant) = (format, tenant)
- return ret
- return with_params
-
-
-class Client(object):
-
- """A base client class - derived from Glance.BaseClient"""
-
- #Metadata for deserializing xml
- _serialization_metadata = {
- "application/xml": {
- "attributes": {
- "network": ["id", "name"],
- "port": ["id", "state"],
- "attachment": ["id"]},
- "plurals": {
- "networks": "network",
- "ports": "port",
- },
- },
- }
-
- # Action query strings
- networks_path = "/networks"
- network_path = "/networks/%s"
- ports_path = "/networks/%s/ports"
- port_path = "/networks/%s/ports/%s"
- attachment_path = "/networks/%s/ports/%s/attachment"
- detail_path = "/detail"
-
- def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
- format="xml", testingStub=None, key_file=None, cert_file=None,
- auth_token=None, logger=None,
- version="1.1",
- uri_prefix="/tenants/{tenant_id}",
- retries=0,
- retry_interval=1):
- """
- Creates a new client to some service.
-
- :param host: The host where service resides
- :param port: The port where service resides
- :param use_ssl: True to use SSL, False to use HTTP
- :param tenant: The tenant ID to make requests with
- :param format: The format to query the server with
- :param testingStub: A class that stubs basic server methods for tests
- :param key_file: The SSL key file to use if use_ssl is true
- :param cert_file: The SSL cert file to use if use_ssl is true
- :param auth_token: authentication token to be passed to server
- :param logger: Logger object for the client library
- :param action_prefix: prefix for request URIs
- :param retries: How many times to retry a failed connection attempt
- :param retry_interval: The # of seconds between connection attempts
- """
- self.host = host
- self.port = port
- self.use_ssl = use_ssl
- self.tenant = tenant
- self.format = format
- self.connection = None
- self.testingStub = testingStub
- self.key_file = key_file
- self.cert_file = cert_file
- self.logger = logger
- if not self.logger:
- self.logger = LOG
- self.auth_token = auth_token
- self.version = version
- self.action_prefix = "/v%s%s" % (version, uri_prefix)
- self.retries = retries
- self.retry_interval = retry_interval
-
- def _handle_fault_response(self, status_code, response_body):
- # Create exception with HTTP status code and message
- error_message = response_body
- LOG.debug("Server returned error: %s", status_code)
- LOG.debug("Error message: %s", error_message)
- # Add deserialized error message to exception arguments
- try:
- des_error_body = Serializer().deserialize(error_message,
- self.content_type())
- except:
- # If unable to deserialized body it is probably not a
- # Quantum error
- des_error_body = {'message': error_message}
- # Raise the appropriate exception
- EXCEPTION_HANDLERS[self.version](status_code, des_error_body)
-
- def get_connection_type(self):
- """
- Returns the proper connection type
- """
- if self.testingStub:
- return self.testingStub
- if self.use_ssl:
- return httplib.HTTPSConnection
- else:
- return httplib.HTTPConnection
-
- def _send_request(self, conn, method, action, body, headers):
- # Salvatore: Isolating this piece of code in its own method to
- # facilitate stubout for testing
- if self.logger:
- self.logger.debug("Quantum Client Request:\n"
- + method + " " + action + "\n")
- if body:
- self.logger.debug(body)
- conn.request(method, action, body, headers)
- return conn.getresponse()
-
- def do_request(self, method, action, body=None,
- headers=None, params=None):
- """
- Connects to the server and issues a request.
- Returns the result data, or raises an appropriate exception if
- HTTP status code is not 2xx
-
- :param method: HTTP method ("GET", "POST", "PUT", etc...)
- :param body: string of data to send, or None (default)
- :param headers: mapping of key/value pairs to add as headers
- :param params: dictionary of key/value pairs to add to append
- to action
- :raises: ConnectionFailed
- """
- LOG.debug("Client issuing request: %s", action)
- # Ensure we have a tenant id
- if not self.tenant:
- raise Exception("Tenant ID not set")
- # Add format and tenant_id
- action += ".%s" % self.format
- action = self.action_prefix + action
- action = action.replace('{tenant_id}', self.tenant)
-
- if type(params) is dict:
- action += '?' + urllib.urlencode(params)
- if body:
- body = self.serialize(body)
- try:
- connection_type = self.get_connection_type()
- headers = headers or {"Content-Type":
- "application/%s" % self.format}
- # if available, add authentication token
- if self.auth_token:
- headers[AUTH_TOKEN_HEADER] = self.auth_token
- # Open connection and send request, handling SSL certs
- certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
- certs = dict((x, certs[x]) for x in certs if certs[x] != None)
- if self.use_ssl and len(certs):
- conn = connection_type(self.host, self.port, **certs)
- else:
- conn = connection_type(self.host, self.port)
- # besides HTTP(s)Connection, we still have testConnection
- if (LOG.isEnabledFor(logging.DEBUG) and
- isinstance(conn, httplib.HTTPConnection)):
- conn.set_debuglevel(1)
-
- res = self._send_request(conn, method, action, body, headers)
- status_code = self.get_status_code(res)
- data = res.read()
- if self.logger:
- self.logger.debug("Quantum Client Reply (code = %s) :\n %s" %
- (str(status_code), data))
- if status_code in (httplib.OK,
- httplib.CREATED,
- httplib.ACCEPTED,
- httplib.NO_CONTENT):
- return self.deserialize(data, status_code)
- else:
- self._handle_fault_response(status_code, data)
- except (socket.error, IOError), e:
- exc = exceptions.ConnectionFailed(reason=str(e))
- LOG.exception(exc.message)
- raise exc
-
- def get_status_code(self, response):
- """
- Returns the integer status code from the response, which
- can be either a Webob.Response (used in testing) or httplib.Response
- """
- if hasattr(response, 'status_int'):
- return response.status_int
- else:
- return response.status
-
- def serialize(self, data):
- """
- Serializes a dictionary with a single key (which can contain any
- structure) into either xml or json
- """
- if data is None:
- return None
- elif type(data) is dict:
- return Serializer().serialize(data, self.content_type())
- else:
- raise Exception("unable to serialize object of type = '%s'" %
- type(data))
-
- def deserialize(self, data, status_code):
- """
- Deserializes a an xml or json string into a dictionary
- """
- if status_code == 204:
- return data
- return Serializer(self._serialization_metadata).deserialize(
- data, self.content_type())
-
- def content_type(self, format=None):
- """
- Returns the mime-type for either 'xml' or 'json'. Defaults to the
- currently set format
- """
- if not format:
- format = self.format
- return "application/%s" % (format)
-
- def retry_request(self, method, action, body=None,
- headers=None, params=None):
- """
- Call do_request with the default retry configuration. Only
- idempotent requests should retry failed connection attempts.
-
- :raises: ConnectionFailed if the maximum # of retries is exceeded
- """
- max_attempts = self.retries + 1
- for i in xrange(max_attempts):
- try:
- return self.do_request(method, action, body=body,
- headers=headers, params=params)
- except exceptions.ConnectionFailed:
- # Exception has already been logged by do_request()
- if i < self.retries:
- LOG.debug(_('Retrying connection to quantum service'))
- time.sleep(self.retry_interval)
-
- raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached"))
-
- def delete(self, action, body=None, headers=None, params=None):
- return self.retry_request("DELETE", action, body=body,
- headers=headers, params=params)
-
- def get(self, action, body=None, headers=None, params=None):
- return self.retry_request("GET", action, body=body,
- headers=headers, params=params)
-
- def post(self, action, body=None, headers=None, params=None):
- # Do not retry POST requests to avoid the orphan objects problem.
- return self.do_request("POST", action, body=body,
- headers=headers, params=params)
-
- def put(self, action, body=None, headers=None, params=None):
- return self.retry_request("PUT", action, body=body,
- headers=headers, params=params)
-
- @ApiCall
- def list_networks(self):
- """
- Fetches a list of all networks for a tenant
- """
- return self.get(self.networks_path)
-
- @ApiCall
- def list_networks_details(self):
- """
- Fetches a detailed list of all networks for a tenant
- """
- return self.get(self.networks_path + self.detail_path)
-
- @ApiCall
- def show_network(self, network):
- """
- Fetches information of a certain network
- """
- return self.get(self.network_path % (network))
-
- @ApiCall
- def show_network_details(self, network):
- """
- Fetches the details of a certain network
- """
- return self.get((self.network_path + self.detail_path) % (network))
-
- @ApiCall
- def create_network(self, body=None):
- """
- Creates a new network
- """
- return self.post(self.networks_path, body=body)
-
- @ApiCall
- def update_network(self, network, body=None):
- """
- Updates a network
- """
- return self.put(self.network_path % (network), body=body)
-
- @ApiCall
- def delete_network(self, network):
- """
- Deletes the specified network
- """
- return self.delete(self.network_path % (network))
-
- @ApiCall
- def list_ports(self, network):
- """
- Fetches a list of ports on a given network
- """
- return self.get(self.ports_path % (network))
-
- @ApiCall
- def list_ports_details(self, network):
- """
- Fetches a detailed list of ports on a given network
- """
- return self.get((self.ports_path + self.detail_path) % (network))
-
- @ApiCall
- def show_port(self, network, port):
- """
- Fetches the information of a certain port
- """
- return self.get(self.port_path % (network, port))
-
- @ApiCall
- def show_port_details(self, network, port):
- """
- Fetches the details of a certain port
- """
- return self.get((self.port_path + self.detail_path) % (network, port))
-
- @ApiCall
- def create_port(self, network, body=None):
- """
- Creates a new port on a given network
- """
- return self.post(self.ports_path % (network), body=body)
-
- @ApiCall
- def delete_port(self, network, port):
- """
- Deletes the specified port from a network
- """
- return self.delete(self.port_path % (network, port))
-
- @ApiCall
- def update_port(self, network, port, body=None):
- """
- Sets the attributes of the specified port
- """
- return self.put(self.port_path % (network, port), body=body)
-
- @ApiCall
- def show_port_attachment(self, network, port):
- """
- Fetches the attachment-id associated with the specified port
- """
- return self.get(self.attachment_path % (network, port))
-
- @ApiCall
- def attach_resource(self, network, port, body=None):
- """
- Sets the attachment-id of the specified port
- """
- return self.put(self.attachment_path % (network, port), body=body)
-
- @ApiCall
- def detach_resource(self, network, port):
- """
- Removes the attachment-id of the specified port
- """
- return self.delete(self.attachment_path % (network, port))
-
-
-class ClientV11(Client):
- """
- This class overiddes some methods of the Client class in order to deal with
- features specific to API v1.1 such as filters
- """
-
- @ApiCall
- def list_networks(self, **filters):
- """
- Fetches a list of all networks for a tenant
- """
- # Pass filters in "params" argument to do_request
- return self.get(self.networks_path, params=filters)
-
- @ApiCall
- def list_networks_details(self, **filters):
- """
- Fetches a detailed list of all networks for a tenant
- """
- return self.get(self.networks_path + self.detail_path, params=filters)
-
- @ApiCall
- def list_ports(self, network, **filters):
- """
- Fetches a list of ports on a given network
- """
- # Pass filters in "params" argument to do_request
- return self.get(self.ports_path % (network), params=filters)
-
- @ApiCall
- def list_ports_details(self, network, **filters):
- """
- Fetches a detailed list of ports on a given network
- """
- return self.get((self.ports_path + self.detail_path) % (network),
- params=filters)
diff --git a/quantumclient/cli.py b/quantumclient/cli.py
deleted file mode 100755
index 6c14f632e..000000000
--- a/quantumclient/cli.py
+++ /dev/null
@@ -1,325 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 Nicira Networks, Inc.
-# Copyright 2012 Citrix Systems
-#
-# 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.
-# @author: Somik Behera, Nicira Networks, Inc.
-# @author: Brad Hall, Nicira Networks, Inc.
-# @author: Salvatore Orlando, Citrix
-
-import logging
-import logging.handlers
-from optparse import OptionParser
-import os
-import sys
-
-from quantumclient import cli_lib
-from quantumclient import Client
-from quantumclient import ClientV11
-from quantumclient.common import exceptions
-from quantumclient.common import utils
-
-
-# Configure logger for client - cli logger is a child of it
-# NOTE(salvatore-orlando): logger name does not map to package
-# this is deliberate. Simplifies logger configuration
-logging.basicConfig()
-LOG = logging.getLogger('quantumclient')
-
-
-DEFAULT_QUANTUM_VERSION = '1.1'
-FORMAT = 'json'
-commands_v10 = {
- "list_nets": {
- "func": cli_lib.list_nets,
- "args": ["tenant-id"],
- },
- "list_nets_detail": {
- "func": cli_lib.list_nets_detail,
- "args": ["tenant-id"],
- },
- "create_net": {
- "func": cli_lib.create_net,
- "args": ["tenant-id", "net-name"],
- },
- "delete_net": {
- "func": cli_lib.delete_net,
- "args": ["tenant-id", "net-id"],
- },
- "show_net": {
- "func": cli_lib.show_net,
- "args": ["tenant-id", "net-id"],
- },
- "show_net_detail": {
- "func": cli_lib.show_net_detail,
- "args": ["tenant-id", "net-id"],
- },
- "update_net": {
- "func": cli_lib.update_net,
- "args": ["tenant-id", "net-id", "new-name"],
- },
- "list_ports": {
- "func": cli_lib.list_ports,
- "args": ["tenant-id", "net-id"],
- },
- "list_ports_detail": {
- "func": cli_lib.list_ports_detail,
- "args": ["tenant-id", "net-id"],
- },
- "create_port": {
- "func": cli_lib.create_port,
- "args": ["tenant-id", "net-id"],
- },
- "delete_port": {
- "func": cli_lib.delete_port,
- "args": ["tenant-id", "net-id", "port-id"],
- },
- "update_port": {
- "func": cli_lib.update_port,
- "args": ["tenant-id", "net-id", "port-id", "params"],
- },
- "show_port": {
- "func": cli_lib.show_port,
- "args": ["tenant-id", "net-id", "port-id"],
- },
- "show_port_detail": {
- "func": cli_lib.show_port_detail,
- "args": ["tenant-id", "net-id", "port-id"],
- },
- "plug_iface": {
- "func": cli_lib.plug_iface,
- "args": ["tenant-id", "net-id", "port-id", "iface-id"],
- },
- "unplug_iface": {
- "func": cli_lib.unplug_iface,
- "args": ["tenant-id", "net-id", "port-id"],
- },
- "show_iface": {
- "func": cli_lib.show_iface,
- "args": ["tenant-id", "net-id", "port-id"],
- },
- }
-
-commands_v11 = commands_v10.copy()
-commands_v11.update({
- "list_nets": {
- "func": cli_lib.list_nets_v11,
- "args": ["tenant-id"],
- "filters": ["name", "op-status", "port-op-status", "port-state",
- "has-attachment", "attachment", "port"],
- },
- "list_nets_detail": {
- "func": cli_lib.list_nets_detail_v11,
- "args": ["tenant-id"],
- "filters": ["name", "op-status", "port-op-status", "port-state",
- "has-attachment", "attachment", "port"],
- },
- "list_ports": {
- "func": cli_lib.list_ports_v11,
- "args": ["tenant-id", "net-id"],
- "filters": ["name", "op-status", "has-attachment", "attachment"],
- },
- "list_ports_detail": {
- "func": cli_lib.list_ports_detail_v11,
- "args": ["tenant-id", "net-id"],
- "filters": ["name", "op-status", "has-attachment", "attachment"],
- },
- })
-commands = {
- '1.0': commands_v10,
- '1.1': commands_v11,
- }
-clients = {
- '1.0': Client,
- '1.1': ClientV11,
- }
-
-
-def help(version):
- print "\nCommands:"
- cmds = commands[version]
- for k in cmds.keys():
- print " %s %s %s" % (
- k,
- " ".join(["<%s>" % y for y in cmds[k]["args"]]),
- 'filters' in cmds[k] and "[filterspec ...]" or "")
-
-
-def print_usage(cmd, version):
- cmds = commands[version]
- print "Usage:\n %s %s" % (
- cmd, " ".join(["<%s>" % y for y in cmds[cmd]["args"]]))
-
-
-def build_args(cmd, cmdargs, arglist):
- arglist_len = len(arglist)
- cmdargs_len = len(cmdargs)
- if arglist_len < cmdargs_len:
- message = ("Not enough arguments for \"%s\" (expected: %d, got: %d)" %
- (cmd, len(cmdargs), arglist_len))
- raise exceptions.QuantumCLIError(message=message)
- args = arglist[:cmdargs_len]
- return args
-
-
-def build_filters(cmd, cmd_filters, filter_list, version):
- filters = {}
- # Each filter is expected to be in the = format
- for flt in filter_list:
- split_filter = flt.split("=")
- if len(split_filter) != 2:
- message = "Invalid filter argument detected (%s)" % flt
- raise exceptions.QuantumCLIError(message=message)
- filter_key, filter_value = split_filter
- # Ensure the filter is allowed
- if not filter_key in cmd_filters:
- message = "Invalid filter key (%s)" % filter_key
- raise exceptions.QuantumCLIError(message=message)
- filters[filter_key] = filter_value
- return filters
-
-
-def build_cmd(cmd, cmd_args, cmd_filters, arglist, version):
- """
- Builds arguments and filters to be passed to the cli library routines
-
- :param cmd: Command to be executed
- :param cmd_args: List of arguments required by the command
- :param cmd_filters: List of filters allowed by the command
- :param arglist: Command line arguments (includes both arguments and
- filter specifications)
- :param version: API version
- """
- arglist_len = len(arglist)
- try:
- # Parse arguments
- args = build_args(cmd, cmd_args, arglist)
- # Parse filters
- filters = None
- if cmd_filters:
- # Pop consumed arguments
- arglist = arglist[len(args):]
- filters = build_filters(cmd, cmd_filters, arglist, version)
- except exceptions.QuantumCLIError as cli_ex:
- LOG.error(cli_ex.message)
- print " Error in command line:%s" % cli_ex.message
- print_usage(cmd, version)
- return None, None
- filter_len = (filters is not None) and len(filters) or 0
- if len(arglist) - len(args) - filter_len > 0:
- message = ("Too many arguments for \"%s\" (expected: %d, got: %d)" %
- (cmd, len(cmd_args), arglist_len))
- LOG.error(message)
- print "Error in command line: %s " % message
- print "Usage:\n %s %s" % (
- cmd,
- " ".join(["<%s>" % y for y in commands[version][cmd]["args"]]))
- return None, None
- # Append version to arguments for cli functions
- args.append(version)
- return args, filters
-
-
-def instantiate_client(host, port, ssl, tenant, token, version):
- client = clients[version](host,
- port,
- ssl,
- tenant,
- FORMAT,
- auth_token=token,
- version=version)
- return client
-
-
-def main():
- usagestr = "Usage: %prog [OPTIONS] [args]"
- parser = OptionParser(usage=usagestr)
- parser.add_option("-H", "--host", dest="host",
- type="string", default="127.0.0.1",
- help="ip address of api host")
- parser.add_option("-p", "--port", dest="port",
- type="int", default=9696, help="api poort")
- parser.add_option("-s", "--ssl", dest="ssl",
- action="store_true", default=False, help="use ssl")
- parser.add_option("--debug", dest="debug",
- action="store_true", default=False,
- help="print debugging output")
- parser.add_option("-f", "--logfile", dest="logfile",
- type="string", default="syslog", help="log file path")
- parser.add_option("-t", "--token", dest="token",
- type="string", default=None, help="authentication token")
- parser.add_option(
- '--version',
- default=utils.env('QUANTUM_VERSION', default=DEFAULT_QUANTUM_VERSION),
- help='Accepts 1.1 and 1.0, defaults to env[QUANTUM_VERSION].')
- options, args = parser.parse_args()
-
- if options.debug:
- LOG.setLevel(logging.DEBUG)
- else:
- LOG.setLevel(logging.WARN)
-
- if options.logfile == "syslog":
- LOG.addHandler(logging.handlers.SysLogHandler(address='/dev/log'))
- else:
- LOG.addHandler(logging.handlers.WatchedFileHandler(options.logfile))
- # Set permissions on log file
- os.chmod(options.logfile, 0644)
-
- version = options.version
- if not version in commands:
- LOG.error("Unknown API version specified:%s", version)
- parser.print_help()
- sys.exit(1)
-
- if len(args) < 1:
- parser.print_help()
- help(version)
- sys.exit(1)
-
- cmd = args[0]
- if cmd not in commands[version].keys():
- LOG.error("Unknown command: %s" % cmd)
- help(version)
- sys.exit(1)
-
- # Build argument list for CLI command
- # The argument list will include the version number as well
- args, filters = build_cmd(cmd,
- commands[version][cmd]["args"],
- commands[version][cmd].get("filters", None),
- args[1:],
- options.version)
- if not args:
- sys.exit(1)
- LOG.info("Executing command \"%s\" with args: %s" % (cmd, args))
-
- client = instantiate_client(options.host,
- options.port,
- options.ssl,
- args[0],
- options.token,
- options.version)
- # append filters to arguments
- # this will allow for using the same prototype for v10 and v11
- # TODO: Use **kwargs instead of *args (keyword is better than positional)
- if filters:
- args.append(filters)
- commands[version][cmd]["func"](client, *args)
-
- LOG.info("Command execution completed")
- sys.exit(0)
-
-if __name__ == '__main__':
- main()
diff --git a/quantumclient/cli_lib.py b/quantumclient/cli_lib.py
deleted file mode 100755
index 6111bcb3d..000000000
--- a/quantumclient/cli_lib.py
+++ /dev/null
@@ -1,594 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 Nicira Networks, Inc.
-# Copyright 2011 Citrix Systems
-#
-# 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.
-# @author: Somik Behera, Nicira Networks, Inc.
-# @author: Brad Hall, Nicira Networks, Inc.
-# @author: Salvatore Orlando, Citrix
-
-""" Functions providing implementation for CLI commands. """
-
-import logging
-import traceback
-
-
-LOG = logging.getLogger('quantumclient.cli_lib')
-
-
-FORMAT = "json"
-
-
-class OutputTemplate(object):
- """ A class for generating simple templated output.
- Based on Python templating mechanism.
- Templates can also express attributes on objects, such as network.id;
- templates can also be nested, thus allowing for iteration on inner
- templates.
-
- Examples:
- 1) template with class attributes
- Name: %(person.name)s \n
- Surname: %(person.surname)s \n
- 2) template with iteration
- Telephone numbers: \n
- %(phone_numbers|Telephone number:%(number)s)
- 3) template with iteration and class attributes
- Addresses: \n
- %(Addresses|Street:%(address.street)s\nNumber%(address.number))
-
- Instances of this class are initialized with a template string and
- the dictionary for performing substitution. The class implements the
- __str__ method, so it can be directly printed.
- """
-
- def __init__(self, template, data):
- self._template = template
- self.data = data
-
- def __str__(self):
- return self._template % self
-
- def __getitem__(self, key):
- items = key.split("|")
- # Note(madhav-puri): for now the logic supports only 0 or 1 list
- # indicators (|) in a template.
- if len(items) > 2:
- raise Exception("Found more than one list indicator (|).")
- if len(items) == 1:
- return self._make_attribute(key)
- else:
- # Note(salvatore-orlando): items[0] must be subscriptable
- return self._make_list(self._make_attribute(items[0]), items[1])
-
- def _make_attribute(self, item):
- """ Renders an entity attribute key in the template.
- e.g.: entity.attribute
- """
- items = item.split('.')
- attr = self.data[items[0]]
- for _item in items[1:]:
- attr = attr[_item]
- return attr
-
- def _make_list(self, items, inner_template):
- """ Renders a list key in the template.
- e.g.: %(list|item data:%(item))
- """
- #make sure list is subscriptable
- if not hasattr(items, '__getitem__'):
- raise Exception("Element is not iterable")
- return "\n".join([str(OutputTemplate(inner_template, item))
- for item in items])
-
-
-class CmdOutputTemplate(OutputTemplate):
- """ This class provides templated output for CLI commands.
- Extends OutputTemplate loading a different template for each command.
- """
-
- _templates_v10 = dict(
- list_nets="""
-Virtual Networks for Tenant %(tenant_id)s
-%(networks|\tNetwork ID: %(id)s)s
-""".strip(),
-
- list_nets_detail="""
-Virtual Networks for Tenant %(tenant_id)s
-%(networks|\tNetwork %(name)s with ID: %(id)s)s
-""".strip(),
-
- show_net="""
-Network ID: %(network.id)s
-Network Name: %(network.name)s
-""".strip(),
-
- show_net_detail="""
-Network ID: %(network.id)s
-Network Name: %(network.name)s
-Ports: %(network.ports|\tID: %(id)s
-\t\tadministrative state: %(state)s
-\t\tinterface: %(attachment.id)s)s
-""".strip(),
-
- create_net="""
-Created a new Virtual Network with ID: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- update_net="""
-Updated Virtual Network with ID: %(network.id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- delete_net="""
-Deleted Virtual Network with ID: %(network_id)s
-for Tenant %(tenant_id)s
-""".strip(),
-
- list_ports="""
-Ports on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-%(ports|\tLogical Port: %(id)s)s
-""".strip(),
-
- list_ports_detail="""
-Ports on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-%(ports|\tLogical Port: %(id)s
-\t\tadministrative State: %(state)s)s
-""".strip(),
-
- create_port="""
-Created new Logical Port with ID: %(port_id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- show_port="""
-Logical Port ID: %(port.id)s
-administrative State: %(port.state)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- show_port_detail="""
-Logical Port ID: %(port.id)s
-administrative State: %(port.state)s
-interface: %(port.attachment.id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- update_port="""
-Updated Logical Port with ID: %(port.id)s
-on Virtual Network: %(network_id)s
-for tenant: %(tenant_id)s
-""".strip(),
-
- delete_port="""
-Deleted Logical Port with ID: %(port_id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- plug_iface="""
-Plugged interface %(attachment)s
-into Logical Port: %(port_id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- unplug_iface="""
-Unplugged interface from Logical Port: %(port_id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- show_iface="""
-interface: %(iface.id)s
-plugged in Logical Port ID: %(port_id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
- )
-
- _templates_v11 = _templates_v10.copy()
- _templates_v11.update(dict(
- show_net="""
-Network ID: %(network.id)s
-network Name: %(network.name)s
-operational Status: %(network.op-status)s
-""".strip(),
-
- show_net_detail="""
-Network ID: %(network.id)s
-Network Name: %(network.name)s
-operational Status: %(network.op-status)s
-Ports: %(network.ports|\tID: %(id)s
-\t\tadministrative state: %(state)s
-\t\tinterface: %(attachment.id)s)s
-""".strip(),
-
- show_port="""
-Logical Port ID: %(port.id)s
-administrative state: %(port.state)s
-operational status: %(port.op-status)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
-
- show_port_detail="""
-Logical Port ID: %(port.id)s
-administrative State: %(port.state)s
-operational status: %(port.op-status)s
-interface: %(port.attachment.id)s
-on Virtual Network: %(network_id)s
-for Tenant: %(tenant_id)s
-""".strip(),
- ))
-
- _templates = {
- '1.0': _templates_v10,
- '1.1': _templates_v11
- }
-
- def __init__(self, cmd, data, version):
- super(CmdOutputTemplate, self).__init__(
- self._templates[version][cmd], data)
-
-
-def _handle_exception(ex):
- status_code = None
- message = None
- # Retrieve dict at 1st element of tuple at last argument
- if ex.args and isinstance(ex.args[-1][0], dict):
- status_code = ex.args[-1][0].get('status_code', None)
- message = ex.args[-1][0].get('message', None)
- msg_1 = ("Command failed with error code: %s" %
- (status_code or ''))
- msg_2 = "Error message:%s" % (message or '')
- print msg_1
- print msg_2
- else:
- traceback.print_exc()
-
-
-def prepare_output(cmd, tenant_id, response, version):
- LOG.debug("Preparing output for response:%s, version:%s" %
- (response, version))
- response['tenant_id'] = tenant_id
- output = str(CmdOutputTemplate(cmd, response, version))
- LOG.debug("Finished preparing output for command:%s", cmd)
- return output
-
-
-def list_nets(client, *args):
- tenant_id, version = args
- try:
- res = client.list_networks()
- LOG.debug("Operation 'list_networks' executed.")
- output = prepare_output("list_nets", tenant_id, res, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_nets_v11(client, *args):
- filters = {}
- tenant_id, version = args[:2]
- if len(args) > 2:
- filters = args[2]
- try:
- res = client.list_networks(**filters)
- LOG.debug("Operation 'list_networks' executed.")
- output = prepare_output("list_nets", tenant_id, res, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_nets_detail(client, *args):
- tenant_id, version = args
- try:
- res = client.list_networks_details()
- LOG.debug("Operation 'list_networks_details' executed.")
- output = prepare_output("list_nets_detail", tenant_id, res, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_nets_detail_v11(client, *args):
- filters = {}
- tenant_id, version = args[:2]
- if len(args) > 2:
- filters = args[2]
- try:
- res = client.list_networks_details(**filters)
- LOG.debug("Operation 'list_networks_details' executed.")
- output = prepare_output("list_nets_detail", tenant_id, res, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def create_net(client, *args):
- tenant_id, name, version = args
- data = {'network': {'name': name}}
- new_net_id = None
- try:
- res = client.create_network(data)
- new_net_id = res["network"]["id"]
- LOG.debug("Operation 'create_network' executed.")
- output = prepare_output("create_net",
- tenant_id,
- dict(network_id=new_net_id),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def delete_net(client, *args):
- tenant_id, network_id, version = args
- try:
- client.delete_network(network_id)
- LOG.debug("Operation 'delete_network' executed.")
- output = prepare_output("delete_net",
- tenant_id,
- dict(network_id=network_id),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def show_net(client, *args):
- tenant_id, network_id, version = args
- try:
- res = client.show_network(network_id)["network"]
- LOG.debug("Operation 'show_network' executed.")
- output = prepare_output("show_net", tenant_id,
- dict(network=res),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def show_net_detail(client, *args):
- tenant_id, network_id, version = args
- try:
- res = client.show_network_details(network_id)["network"]
- LOG.debug("Operation 'show_network_details' executed.")
- if not len(res["ports"]):
- res["ports"] = [{"id": "",
- "state": "",
- "attachment": {"id": ""}}]
- else:
- for port in res["ports"]:
- if "attachment" not in port:
- port["attachment"] = {"id": ""}
-
- output = prepare_output("show_net_detail",
- tenant_id,
- dict(network=res),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def update_net(client, *args):
- tenant_id, network_id, param_data, version = args
- data = {'network': {}}
- for kv in param_data.split(","):
- k, v = kv.split("=")
- data['network'][k] = v
- data['network']['id'] = network_id
- try:
- client.update_network(network_id, data)
- LOG.debug("Operation 'update_network' executed.")
- # Response has no body. Use data for populating output
- output = prepare_output("update_net", tenant_id, data, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_ports(client, *args):
- tenant_id, network_id, version = args
- try:
- ports = client.list_ports(network_id)
- LOG.debug("Operation 'list_ports' executed.")
- data = ports
- data['network_id'] = network_id
- output = prepare_output("list_ports", tenant_id, data, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_ports_v11(client, *args):
- filters = {}
- tenant_id, network_id, version = args[:3]
- if len(args) > 3:
- filters = args[3]
- try:
- ports = client.list_ports(network_id, **filters)
- LOG.debug("Operation 'list_ports' executed.")
- data = ports
- data['network_id'] = network_id
- output = prepare_output("list_ports", tenant_id, data, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_ports_detail(client, *args):
- tenant_id, network_id, version = args
- try:
- ports = client.list_ports_details(network_id)
- LOG.debug("Operation 'list_ports_details' executed.")
- data = ports
- data['network_id'] = network_id
- output = prepare_output("list_ports_detail", tenant_id, data, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def list_ports_detail_v11(client, *args):
- filters = {}
- tenant_id, network_id, version = args[:3]
- if len(args) > 3:
- filters = args[3]
- try:
- ports = client.list_ports_details(network_id, **filters)
- LOG.debug("Operation 'list_ports' executed.")
- data = ports
- data['network_id'] = network_id
- output = prepare_output("list_ports_detail", tenant_id, data, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def create_port(client, *args):
- tenant_id, network_id, version = args
- try:
- res = client.create_port(network_id)
- LOG.debug("Operation 'create_port' executed.")
- new_port_id = res["port"]["id"]
- output = prepare_output("create_port",
- tenant_id,
- dict(network_id=network_id,
- port_id=new_port_id),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def delete_port(client, *args):
- tenant_id, network_id, port_id, version = args
- try:
- client.delete_port(network_id, port_id)
- LOG.debug("Operation 'delete_port' executed.")
- output = prepare_output("delete_port",
- tenant_id,
- dict(network_id=network_id,
- port_id=port_id),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
- return
-
-
-def show_port(client, *args):
- tenant_id, network_id, port_id, version = args
- try:
- port = client.show_port(network_id, port_id)["port"]
- LOG.debug("Operation 'show_port' executed.")
- output = prepare_output("show_port", tenant_id,
- dict(network_id=network_id,
- port=port),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def show_port_detail(client, *args):
- tenant_id, network_id, port_id, version = args
- try:
- port = client.show_port_details(network_id, port_id)["port"]
- LOG.debug("Operation 'show_port_details' executed.")
- if 'attachment' not in port:
- port['attachment'] = {'id': ''}
- output = prepare_output("show_port_detail", tenant_id,
- dict(network_id=network_id,
- port=port),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def update_port(client, *args):
- tenant_id, network_id, port_id, param_data, version = args
- data = {'port': {}}
- for kv in param_data.split(","):
- k, v = kv.split("=")
- data['port'][k] = v
- data['network_id'] = network_id
- data['port']['id'] = port_id
- try:
- client.update_port(network_id, port_id, data)
- LOG.debug("Operation 'udpate_port' executed.")
- # Response has no body. Use data for populating output
- output = prepare_output("update_port", tenant_id, data, version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def plug_iface(client, *args):
- tenant_id, network_id, port_id, attachment, version = args
- try:
- data = {'attachment': {'id': '%s' % attachment}}
- client.attach_resource(network_id, port_id, data)
- LOG.debug("Operation 'attach_resource' executed.")
- output = prepare_output("plug_iface", tenant_id,
- dict(network_id=network_id,
- port_id=port_id,
- attachment=attachment),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def unplug_iface(client, *args):
- tenant_id, network_id, port_id, version = args
- try:
- client.detach_resource(network_id, port_id)
- LOG.debug("Operation 'detach_resource' executed.")
- output = prepare_output("unplug_iface",
- tenant_id,
- dict(network_id=network_id,
- port_id=port_id),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
-
-
-def show_iface(client, *args):
- tenant_id, network_id, port_id, version = args
- try:
- iface = client.show_port_attachment(network_id, port_id)['attachment']
- if 'id' not in iface:
- iface['id'] = ''
- LOG.debug("Operation 'show_port_attachment' executed.")
- output = prepare_output("show_iface", tenant_id,
- dict(network_id=network_id,
- port_id=port_id,
- iface=iface),
- version)
- print output
- except Exception as ex:
- _handle_exception(ex)
diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py
deleted file mode 100644
index 33526c2d0..000000000
--- a/quantumclient/common/exceptions.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 Nicira Networks, Inc
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""
-Quantum base exception handling.
-"""
-
-
-class QuantumException(Exception):
- """Base Quantum Exception
-
- Taken from nova.exception.NovaException
- To correctly use this class, inherit from it and define
- a 'message' property. That message will get printf'd
- with the keyword arguments provided to the constructor.
-
- """
- message = _("An unknown exception occurred.")
-
- def __init__(self, **kwargs):
- try:
- self._error_string = self.message % kwargs
-
- except Exception:
- # at least get the core message out if something happened
- self._error_string = self.message
-
- def __str__(self):
- return self._error_string
-
-
-class NotFound(QuantumException):
- pass
-
-
-class QuantumClientException(QuantumException):
-
- def __init__(self, **kwargs):
- message = kwargs.get('message')
- if message:
- self.message = message
- super(QuantumClientException, self).__init__(**kwargs)
-
-
-# NOTE: on the client side, we use different exception types in order
-# to allow client library users to handle server exceptions in try...except
-# blocks. The actual error message is the one generated on the server side
-class NetworkNotFoundClient(QuantumClientException):
- pass
-
-
-class PortNotFoundClient(QuantumClientException):
- pass
-
-
-class MalformedResponseBody(QuantumException):
- message = _("Malformed response body: %(reason)s")
-
-
-class StateInvalidClient(QuantumClientException):
- pass
-
-
-class NetworkInUseClient(QuantumClientException):
- pass
-
-
-class PortInUseClient(QuantumClientException):
- pass
-
-
-class AlreadyAttachedClient(QuantumClientException):
- pass
-
-
-class NotAuthorized(QuantumClientException):
- pass
-
-
-class QuantumCLIError(QuantumClientException):
- """ Exception raised when command line parsing fails """
- pass
-
-
-class ConnectionFailed(QuantumClientException):
- message = _("Connection to quantum failed: %(reason)s")
-
-
-class BadInputError(Exception):
- """Error resulting from a client sending bad input to a server"""
- pass
-
-
-class Error(Exception):
- def __init__(self, message=None):
- super(Error, self).__init__(message)
-
-
-class MalformedRequestBody(QuantumException):
- message = _("Malformed request body: %(reason)s")
-
-
-class Invalid(Error):
- pass
-
-
-class InvalidContentType(Invalid):
- message = _("Invalid content type %(content_type)s.")
diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py
deleted file mode 100644
index 6e914673a..000000000
--- a/quantumclient/common/serializer.py
+++ /dev/null
@@ -1,155 +0,0 @@
-from xml.dom import minidom
-
-from quantumclient.common import exceptions as exception
-from quantumclient.common import utils
-
-
-# NOTE(maru): this class is duplicated from quantum.wsgi
-class Serializer(object):
- """Serializes and deserializes dictionaries to certain MIME types."""
-
- def __init__(self, metadata=None, default_xmlns=None):
- """Create a serializer based on the given WSGI environment.
-
- 'metadata' is an optional dict mapping MIME types to information
- needed to serialize a dictionary to that type.
-
- """
- self.metadata = metadata or {}
- self.default_xmlns = default_xmlns
-
- def _get_serialize_handler(self, content_type):
- handlers = {
- 'application/json': self._to_json,
- 'application/xml': self._to_xml,
- }
-
- try:
- return handlers[content_type]
- except Exception:
- raise exception.InvalidContentType(content_type=content_type)
-
- def serialize(self, data, content_type):
- """Serialize a dictionary into the specified content type."""
- return self._get_serialize_handler(content_type)(data)
-
- def deserialize(self, datastring, content_type):
- """Deserialize a string to a dictionary.
-
- The string must be in the format of a supported MIME type.
-
- """
- try:
- return self.get_deserialize_handler(content_type)(datastring)
- except Exception:
- raise exception.MalformedResponseBody(
- reason="Unable to deserialize response body")
-
- def get_deserialize_handler(self, content_type):
- handlers = {
- 'application/json': self._from_json,
- 'application/xml': self._from_xml,
- }
-
- try:
- return handlers[content_type]
- except Exception:
- raise exception.InvalidContentType(content_type=content_type)
-
- def _from_json(self, datastring):
- return utils.loads(datastring)
-
- def _from_xml(self, datastring):
- xmldata = self.metadata.get('application/xml', {})
- plurals = set(xmldata.get('plurals', {}))
- node = minidom.parseString(datastring).childNodes[0]
- return {node.nodeName: self._from_xml_node(node, plurals)}
-
- def _from_xml_node(self, node, listnames):
- """Convert a minidom node to a simple Python type.
-
- listnames is a collection of names of XML nodes whose subnodes should
- be considered list items.
-
- """
- if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
- return node.childNodes[0].nodeValue
- elif node.nodeName in listnames:
- return [self._from_xml_node(n, listnames)
- for n in node.childNodes if n.nodeType != node.TEXT_NODE]
- else:
- result = dict()
- for attr in node.attributes.keys():
- result[attr] = node.attributes[attr].nodeValue
- for child in node.childNodes:
- if child.nodeType != node.TEXT_NODE:
- result[child.nodeName] = self._from_xml_node(child,
- listnames)
- return result
-
- def _to_json(self, data):
- return utils.dumps(data)
-
- def _to_xml(self, data):
- metadata = self.metadata.get('application/xml', {})
- # We expect data to contain a single key which is the XML root.
- root_key = data.keys()[0]
- doc = minidom.Document()
- node = self._to_xml_node(doc, metadata, root_key, data[root_key])
-
- xmlns = node.getAttribute('xmlns')
- if not xmlns and self.default_xmlns:
- node.setAttribute('xmlns', self.default_xmlns)
-
- return node.toprettyxml(indent='', newl='')
-
- def _to_xml_node(self, doc, metadata, nodename, data):
- """Recursive method to convert data members to XML nodes."""
- result = doc.createElement(nodename)
-
- # Set the xml namespace if one is specified
- # TODO(justinsb): We could also use prefixes on the keys
- xmlns = metadata.get('xmlns', None)
- if xmlns:
- result.setAttribute('xmlns', xmlns)
- if type(data) is list:
- collections = metadata.get('list_collections', {})
- if nodename in collections:
- metadata = collections[nodename]
- for item in data:
- node = doc.createElement(metadata['item_name'])
- node.setAttribute(metadata['item_key'], str(item))
- result.appendChild(node)
- return result
- singular = metadata.get('plurals', {}).get(nodename, None)
- if singular is None:
- if nodename.endswith('s'):
- singular = nodename[:-1]
- else:
- singular = 'item'
- for item in data:
- node = self._to_xml_node(doc, metadata, singular, item)
- result.appendChild(node)
- elif type(data) is dict:
- collections = metadata.get('dict_collections', {})
- if nodename in collections:
- metadata = collections[nodename]
- for k, v in data.items():
- node = doc.createElement(metadata['item_name'])
- node.setAttribute(metadata['item_key'], str(k))
- text = doc.createTextNode(str(v))
- node.appendChild(text)
- result.appendChild(node)
- return result
- attrs = metadata.get('attributes', {}).get(nodename, {})
- for k, v in data.items():
- if k in attrs:
- result.setAttribute(k, str(v))
- else:
- node = self._to_xml_node(doc, metadata, k, v)
- result.appendChild(node)
- else:
- # Type is atom.
- node = doc.createTextNode(str(data))
- result.appendChild(node)
- return result
diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py
deleted file mode 100644
index afb554e26..000000000
--- a/quantumclient/common/utils.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011, Nicira Networks, Inc.
-#
-# 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.
-#
-# Borrowed from nova code base, more utilities will be added/borrowed as and
-# when needed.
-# @author: Somik Behera, Nicira Networks, Inc.
-
-"""Utilities and helper functions."""
-
-import datetime
-import json
-import os
-
-
-def env(*vars, **kwargs):
- """
- returns the first environment variable set
- if none are non-empty, defaults to '' or keyword arg default
- """
- for v in vars:
- value = os.environ.get(v)
- if value:
- return value
- return kwargs.get('default', '')
-
-
-def to_primitive(value):
- if type(value) is type([]) or type(value) is type((None,)):
- o = []
- for v in value:
- o.append(to_primitive(v))
- return o
- elif type(value) is type({}):
- o = {}
- for k, v in value.iteritems():
- o[k] = to_primitive(v)
- return o
- elif isinstance(value, datetime.datetime):
- return str(value)
- elif hasattr(value, 'iteritems'):
- return to_primitive(dict(value.iteritems()))
- elif hasattr(value, '__iter__'):
- return to_primitive(list(value))
- else:
- return value
-
-
-def dumps(value):
- try:
- return json.dumps(value)
- except TypeError:
- pass
- return json.dumps(to_primitive(value))
-
-
-def loads(s):
- return json.loads(s)
diff --git a/quantumclient/tests/unit/stubs.py b/quantumclient/tests/unit/stubs.py
deleted file mode 100644
index 3295753fb..000000000
--- a/quantumclient/tests/unit/stubs.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 OpenStack LLC
-#
-# 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.
-""" Stubs for client tools unit tests """
-
-
-from quantum import api as server
-from quantum.tests.unit import testlib_api
-
-
-class FakeStdout:
-
- def __init__(self):
- self.content = []
-
- def write(self, text):
- self.content.append(text)
-
- def make_string(self):
- result = ''
- for line in self.content:
- result = result + line
- return result
-
-
-class FakeHTTPConnection:
- """ stub HTTP connection class for CLI testing """
- def __init__(self, _1, _2):
- # Ignore host and port parameters
- self._req = None
- plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin'
- options = dict(plugin_provider=plugin)
- self._api = server.APIRouterV11(options)
-
- def request(self, method, action, body, headers):
- # TODO: remove version prefix from action!
- parts = action.split('/', 2)
- path = '/' + parts[2]
- self._req = testlib_api.create_request(path, body, "application/json",
- method)
-
- def getresponse(self):
- res = self._req.get_response(self._api)
-
- def _fake_read():
- """ Trick for making a webob.Response look like a
- httplib.Response
-
- """
- return res.body
-
- setattr(res, 'read', _fake_read)
- return res
diff --git a/quantumclient/tests/unit/test_cli.py b/quantumclient/tests/unit/test_cli.py
deleted file mode 100644
index 6cf618c47..000000000
--- a/quantumclient/tests/unit/test_cli.py
+++ /dev/null
@@ -1,825 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010-2011 ????
-# 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.
-# @author: Salvatore Orlando, Citrix Systems
-
-""" Module containing unit tests for Quantum
- command line interface
-
-"""
-
-import logging
-import sys
-import unittest
-
-from quantum import api as server
-from quantum.db import api as db
-from quantumclient import cli_lib as cli
-from quantumclient import Client
-from quantumclient.tests.unit import stubs as client_stubs
-
-
-LOG = logging.getLogger('quantumclient.tests.test_cli')
-API_VERSION = "1.1"
-FORMAT = 'json'
-
-
-class CLITest(unittest.TestCase):
-
- def setUp(self):
- """Prepare the test environment"""
- options = {}
- options['plugin_provider'] = (
- 'quantum.plugins.sample.SamplePlugin.FakePlugin')
- #TODO: make the version of the API router configurable
- self.api = server.APIRouterV11(options)
-
- self.tenant_id = "test_tenant"
- self.network_name_1 = "test_network_1"
- self.network_name_2 = "test_network_2"
- self.version = API_VERSION
- # Prepare client and plugin manager
- self.client = Client(tenant=self.tenant_id,
- format=FORMAT,
- testingStub=client_stubs.FakeHTTPConnection,
- version=self.version)
- # Redirect stdout
- self.fake_stdout = client_stubs.FakeStdout()
- sys.stdout = self.fake_stdout
-
- def tearDown(self):
- """Clear the test environment"""
- db.clear_db()
- sys.stdout = sys.__stdout__
-
- def _verify_list_networks(self):
- # Verification - get raw result from db
- nw_list = db.network_list(self.tenant_id)
- networks = [{'id': nw.uuid, 'name': nw.name}
- for nw in nw_list]
- # Fill CLI template
- output = cli.prepare_output('list_nets',
- self.tenant_id,
- dict(networks=networks),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_list_networks_details(self):
- # Verification - get raw result from db
- nw_list = db.network_list(self.tenant_id)
- networks = [dict(id=nw.uuid, name=nw.name) for nw in nw_list]
- # Fill CLI template
- output = cli.prepare_output('list_nets_detail',
- self.tenant_id,
- dict(networks=networks),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_create_network(self):
- # Verification - get raw result from db
- nw_list = db.network_list(self.tenant_id)
- if len(nw_list) != 1:
- self.fail("No network created")
- network_id = nw_list[0].uuid
- # Fill CLI template
- output = cli.prepare_output('create_net',
- self.tenant_id,
- dict(network_id=network_id),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_delete_network(self, network_id):
- # Verification - get raw result from db
- nw_list = db.network_list(self.tenant_id)
- if len(nw_list) != 0:
- self.fail("DB should not contain any network")
- # Fill CLI template
- output = cli.prepare_output('delete_net',
- self.tenant_id,
- dict(network_id=network_id),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_update_network(self):
- # Verification - get raw result from db
- nw_list = db.network_list(self.tenant_id)
- network_data = {'id': nw_list[0].uuid,
- 'name': nw_list[0].name,
- 'op-status': nw_list[0].op_status}
- # Fill CLI template
- output = cli.prepare_output('update_net',
- self.tenant_id,
- dict(network=network_data),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_show_network(self):
- # Verification - get raw result from db
- nw = db.network_list(self.tenant_id)[0]
- network = {'id': nw.uuid,
- 'name': nw.name,
- 'op-status': nw.op_status}
- # Fill CLI template
- output = cli.prepare_output('show_net',
- self.tenant_id,
- dict(network=network),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_show_network_details(self):
- # Verification - get raw result from db
- nw = db.network_list(self.tenant_id)[0]
- network = {'id': nw.uuid,
- 'name': nw.name,
- 'op-status': nw.op_status}
- port_list = db.port_list(nw.uuid)
- if not port_list:
- network['ports'] = [
- {
- 'id': '',
- 'state': '',
- 'attachment': {
- 'id': '',
- },
- },
- ]
- else:
- network['ports'] = []
- for port in port_list:
- network['ports'].append({
- 'id': port.uuid,
- 'state': port.state,
- 'attachment': {
- 'id': port.interface_id or '',
- },
- })
-
- # Fill CLI template
- output = cli.prepare_output('show_net_detail',
- self.tenant_id,
- dict(network=network),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_list_ports(self, network_id):
- # Verification - get raw result from db
- port_list = db.port_list(network_id)
- ports = [dict(id=port.uuid, state=port.state)
- for port in port_list]
- # Fill CLI template
- output = cli.prepare_output('list_ports',
- self.tenant_id,
- dict(network_id=network_id,
- ports=ports),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_list_ports_details(self, network_id):
- # Verification - get raw result from db
- port_list = db.port_list(network_id)
- ports = [dict(id=port.uuid, state=port.state)
- for port in port_list]
- # Fill CLI template
- output = cli.prepare_output('list_ports_detail',
- self.tenant_id,
- dict(network_id=network_id,
- ports=ports),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_create_port(self, network_id):
- # Verification - get raw result from db
- port_list = db.port_list(network_id)
- if len(port_list) != 1:
- self.fail("No port created")
- port_id = port_list[0].uuid
- # Fill CLI template
- output = cli.prepare_output('create_port',
- self.tenant_id,
- dict(network_id=network_id,
- port_id=port_id),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_delete_port(self, network_id, port_id):
- # Verification - get raw result from db
- port_list = db.port_list(network_id)
- if len(port_list) != 0:
- self.fail("DB should not contain any port")
- # Fill CLI template
- output = cli.prepare_output('delete_port',
- self.tenant_id,
- dict(network_id=network_id,
- port_id=port_id),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_update_port(self, network_id, port_id):
- # Verification - get raw result from db
- port = db.port_get(port_id, network_id)
- port_data = {'id': port.uuid,
- 'state': port.state,
- 'op-status': port.op_status}
- # Fill CLI template
- output = cli.prepare_output('update_port',
- self.tenant_id,
- dict(network_id=network_id,
- port=port_data),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_show_port(self, network_id, port_id):
- # Verification - get raw result from db
- port = db.port_get(port_id, network_id)
- port_data = {'id': port.uuid,
- 'state': port.state,
- 'attachment': {'id': port.interface_id or ''},
- 'op-status': port.op_status}
-
- # Fill CLI template
- output = cli.prepare_output('show_port',
- self.tenant_id,
- dict(network_id=network_id,
- port=port_data),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_show_port_details(self, network_id, port_id):
- # Verification - get raw result from db
- # TODO(salvatore-orlando): Must resolve this issue with
- # attachment in separate bug fix.
- port = db.port_get(port_id, network_id)
- port_data = {'id': port.uuid,
- 'state': port.state,
- 'attachment': {'id': port.interface_id or ''},
- 'op-status': port.op_status}
-
- # Fill CLI template
- output = cli.prepare_output('show_port_detail',
- self.tenant_id,
- dict(network_id=network_id,
- port=port_data),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_plug_iface(self, network_id, port_id):
- # Verification - get raw result from db
- port = db.port_get(port_id, network_id)
- # Fill CLI template
- output = cli.prepare_output("plug_iface",
- self.tenant_id,
- dict(network_id=network_id,
- port_id=port['uuid'],
- attachment=port['interface_id']),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_unplug_iface(self, network_id, port_id):
- # Verification - get raw result from db
- port = db.port_get(port_id, network_id)
- # Fill CLI template
- output = cli.prepare_output("unplug_iface",
- self.tenant_id,
- dict(network_id=network_id,
- port_id=port['uuid']),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def _verify_show_iface(self, network_id, port_id):
- # Verification - get raw result from db
- port = db.port_get(port_id, network_id)
- iface = {'id': port.interface_id or ''}
-
- # Fill CLI template
- output = cli.prepare_output('show_iface',
- self.tenant_id,
- dict(network_id=network_id,
- port_id=port.uuid,
- iface=iface),
- self.version)
- # Verify!
- # Must add newline at the end to match effect of print call
- self.assertEquals(self.fake_stdout.make_string(), output + '\n')
-
- def test_list_networks_v10(self):
- try:
- # Pre-populate data for testing using db api
- db.network_create(self.tenant_id, self.network_name_1)
- db.network_create(self.tenant_id, self.network_name_2)
-
- cli.list_nets(self.client,
- self.tenant_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_networks_v10 failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_networks()
-
- def test_list_networks_details_v10(self):
- try:
- # Pre-populate data for testing using db api
- db.network_create(self.tenant_id, self.network_name_1)
- db.network_create(self.tenant_id, self.network_name_2)
-
- cli.list_nets_detail(self.client,
- self.tenant_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_networks_details_v10 failed due to" +
- " an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_networks_details()
-
- def test_list_networks_v11(self):
- try:
- # Pre-populate data for testing using db api
- db.network_create(self.tenant_id, self.network_name_1)
- db.network_create(self.tenant_id, self.network_name_2)
- #TODO: test filters
- cli.list_nets_v11(self.client,
- self.tenant_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_networks_v11 failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_networks()
-
- def test_list_networks_details_v11(self):
- try:
- # Pre-populate data for testing using db api
- db.network_create(self.tenant_id, self.network_name_1)
- db.network_create(self.tenant_id, self.network_name_2)
- #TODO: test filters
- cli.list_nets_detail_v11(self.client,
- self.tenant_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_networks_details_v11 failed due to " +
- "an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_networks_details()
-
- def test_create_network(self):
- try:
- cli.create_net(self.client,
- self.tenant_id,
- "test",
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_create_network failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_create_network()
-
- def test_delete_network(self):
- try:
- db.network_create(self.tenant_id, self.network_name_1)
- network_id = db.network_list(self.tenant_id)[0]['uuid']
- cli.delete_net(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_delete_network failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_delete_network(network_id)
-
- def test_show_network(self):
- try:
- # Load some data into the datbase
- net = db.network_create(self.tenant_id, self.network_name_1)
- cli.show_net(self.client,
- self.tenant_id,
- net['uuid'],
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_detail_network failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_network()
-
- def test_show_network_details_no_ports(self):
- try:
- # Load some data into the datbase
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- cli.show_net_detail(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_network_details_no_ports failed due to" +
- " an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_network_details()
-
- def test_show_network_details(self):
- iface_id = "flavor crystals"
- try:
- # Load some data into the datbase
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- db.port_set_attachment(port_id, network_id, iface_id)
- port = db.port_create(network_id)
- cli.show_net_detail(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_network_details failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_network_details()
-
- def test_update_network(self):
- try:
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- cli.update_net(self.client,
- self.tenant_id,
- network_id,
- 'name=%s' % self.network_name_2,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_update_network failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_update_network()
-
- def test_list_ports_v10(self):
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- db.port_create(network_id)
- db.port_create(network_id)
- cli.list_ports(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_ports failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_ports(network_id)
-
- def test_list_ports_details_v10(self):
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- db.port_create(network_id)
- db.port_create(network_id)
- cli.list_ports_detail(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_ports_details_v10 failed due to" +
- " an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_ports_details(network_id)
-
- def test_list_ports_v11(self):
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- db.port_create(network_id)
- db.port_create(network_id)
- #TODO: test filters
- cli.list_ports_v11(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_ports_v11 failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_ports(network_id)
-
- def test_list_ports_details_v11(self):
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- db.port_create(network_id)
- db.port_create(network_id)
- #TODO: test filters
- cli.list_ports_detail_v11(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_list_ports_details_v11 failed due to " +
- "an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_list_ports_details(network_id)
-
- def test_create_port(self):
- network_id = None
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- cli.create_port(self.client,
- self.tenant_id,
- network_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_create_port failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_create_port(network_id)
-
- def test_delete_port(self):
- network_id = None
- port_id = None
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- cli.delete_port(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_delete_port failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_delete_port(network_id, port_id)
-
- def test_update_port(self):
- try:
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- # Default state is DOWN - change to ACTIVE.
- cli.update_port(self.client,
- self.tenant_id,
- network_id,
- port_id,
- 'state=ACTIVE',
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_update_port failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_update_port(network_id, port_id)
-
- def test_show_port(self):
- network_id = None
- port_id = None
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- cli.show_port(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_port failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_port(network_id, port_id)
-
- def test_show_port_details_no_attach(self):
- network_id = None
- port_id = None
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- cli.show_port_detail(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_port_details_no_attach failed due to" +
- " an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_port_details(network_id, port_id)
-
- def test_show_port_details_with_attach(self):
- network_id = None
- port_id = None
- iface_id = "flavor crystals"
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- db.port_set_attachment(port_id, network_id, iface_id)
- cli.show_port_detail(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_port_details_with_attach failed due" +
- " to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_port_details(network_id, port_id)
-
- def test_plug_iface(self):
- network_id = None
- port_id = None
- try:
- # Load some data into the datbase
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(net['uuid'])
- port_id = port['uuid']
- cli.plug_iface(self.client,
- self.tenant_id,
- network_id,
- port_id,
- "test_iface_id",
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_plug_iface failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_plug_iface(network_id, port_id)
-
- def test_unplug_iface(self):
- network_id = None
- port_id = None
- try:
- # Load some data into the datbase
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(net['uuid'])
- port_id = port['uuid']
- db.port_set_attachment(port_id, network_id, "test_iface_id")
- cli.unplug_iface(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_plug_iface failed due to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_unplug_iface(network_id, port_id)
-
- def test_show_iface_no_attach(self):
- network_id = None
- port_id = None
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- cli.show_iface(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_iface_no_attach failed due to" +
- " an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_iface(network_id, port_id)
-
- def test_show_iface_with_attach(self):
- network_id = None
- port_id = None
- iface_id = "flavor crystals"
- try:
- # Pre-populate data for testing using db api
- net = db.network_create(self.tenant_id, self.network_name_1)
- network_id = net['uuid']
- port = db.port_create(network_id)
- port_id = port['uuid']
- db.port_set_attachment(port_id, network_id, iface_id)
- cli.show_iface(self.client,
- self.tenant_id,
- network_id,
- port_id,
- self.version)
- except:
- LOG.exception("Exception caught: %s", sys.exc_info())
- self.fail("test_show_iface_with_attach failed due" +
- " to an exception")
-
- LOG.debug("Operation completed. Verifying result")
- LOG.debug(self.fake_stdout.content)
- self._verify_show_iface(network_id, port_id)
diff --git a/quantumclient/tests/unit/test_clientlib.py b/quantumclient/tests/unit/test_clientlib.py
deleted file mode 100644
index 3b88f1521..000000000
--- a/quantumclient/tests/unit/test_clientlib.py
+++ /dev/null
@@ -1,770 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 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.
-# @author: Tyler Smith, Cisco Systems
-
-import logging
-import re
-import unittest
-
-from quantumclient.common import exceptions
-from quantumclient.common.serializer import Serializer
-from quantumclient import Client
-
-
-LOG = logging.getLogger('quantumclient.tests.test_api')
-
-
-# Set a couple tenants to use for testing
-TENANT_1 = 'totore'
-TENANT_2 = 'totore2'
-
-
-class ServerStub():
- """This class stubs a basic server for the API client to talk to"""
-
- class Response(object):
- """This class stubs a basic response to send the API client"""
- def __init__(self, content=None, status=None):
- self.content = content
- self.status = status
-
- def read(self):
- return self.content
-
- def status(self):
- return self.status
-
- # To test error codes, set the host to 10.0.0.1, and the port to the code
- def __init__(self, host, port=9696, key_file="", cert_file=""):
- self.host = host
- self.port = port
- self.key_file = key_file
- self.cert_file = cert_file
-
- def request(self, method, action, body, headers):
- self.method = method
- self.action = action
- self.body = body
-
- def status(self, status=None):
- return status or 200
-
- def getresponse(self):
- res = self.Response(status=self.status())
-
- # If the host is 10.0.0.1, return the port as an error code
- if self.host == "10.0.0.1":
- res.status = self.port
- return res
-
- # Extract important information from the action string to assure sanity
- match = re.search('tenants/(.+?)/(.+)\.(json|xml)$', self.action)
-
- tenant = match.group(1)
- path = match.group(2)
- format = match.group(3)
-
- data = {'data': {'method': self.method, 'action': self.action,
- 'body': self.body, 'tenant': tenant, 'path': path,
- 'format': format, 'key_file': self.key_file,
- 'cert_file': self.cert_file}}
-
- # Serialize it to the proper format so the API client can handle it
- if data['data']['format'] == 'json':
- res.content = Serializer().serialize(data, "application/json")
- else:
- res.content = Serializer().serialize(data, "application/xml")
- return res
-
-
-class APITest(unittest.TestCase):
-
- def setUp(self):
- """ Setups a test environment for the API client """
- HOST = '127.0.0.1'
- PORT = 9696
- USE_SSL = False
-
- self.client = Client(HOST, PORT, USE_SSL, TENANT_1, 'json', ServerStub)
-
- def _assert_sanity(self, call, status, method, path, data=[], params={}):
- """ Perform common assertions to test the sanity of client requests """
-
- # Handle an error case first
- if status != 200:
- (self.client.host, self.client.port) = ("10.0.0.1", status)
- self.assertRaises(Exception, call, *data, **params)
- return
-
- # Make the call, then get the data from the root node and assert it
- data = call(*data, **params)['data']
-
- self.assertEqual(data['method'], method)
- self.assertEqual(data['format'], params['format'])
- self.assertEqual(data['tenant'], params['tenant'])
- self.assertEqual(data['path'], path)
-
- return data
-
- def _test_list_networks(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_list_networks - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.list_networks,
- status,
- "GET",
- "networks",
- data=[],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_list_networks - tenant:%s - format:%s - END",
- format, tenant)
-
- def _test_list_networks_details(self,
- tenant=TENANT_1, format='json',
- status=200):
- LOG.debug("_test_list_networks_details - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.list_networks_details,
- status,
- "GET",
- "networks/detail",
- data=[],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_list_networks_details - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_show_network(self,
- tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_show_network - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.show_network,
- status,
- "GET",
- "networks/001",
- data=["001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_show_network - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_show_network_details(self,
- tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_show_network_details - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.show_network_details,
- status,
- "GET",
- "networks/001/detail",
- data=["001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_show_network_details - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_create_network(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_create_network - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.create_network,
- status,
- "POST",
- "networks",
- data=[{'network': {'net-name': 'testNetwork'}}],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_create_network - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_update_network(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_update_network - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.update_network,
- status,
- "PUT",
- "networks/001",
- data=["001",
- {'network': {'net-name': 'newName'}}],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_update_network - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_delete_network(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_delete_network - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.delete_network,
- status,
- "DELETE",
- "networks/001",
- data=["001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_delete_network - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_list_ports(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_list_ports - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.list_ports,
- status,
- "GET",
- "networks/001/ports",
- data=["001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_list_ports - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_list_ports_details(self,
- tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_list_ports_details - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.list_ports_details,
- status,
- "GET",
- "networks/001/ports/detail",
- data=["001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_list_ports_details - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_show_port(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_show_port - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.show_port,
- status,
- "GET",
- "networks/001/ports/001",
- data=["001", "001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_show_port - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_show_port_details(self,
- tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_show_port_details - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.show_port_details,
- status,
- "GET",
- "networks/001/ports/001/detail",
- data=["001", "001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_show_port_details - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_create_port(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_create_port - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.create_port,
- status,
- "POST",
- "networks/001/ports",
- data=["001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_create_port - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_delete_port(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_delete_port - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.delete_port,
- status,
- "DELETE",
- "networks/001/ports/001",
- data=["001", "001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_delete_port - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_update_port(self, tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_update_port - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.update_port,
- status,
- "PUT",
- "networks/001/ports/001",
- data=["001", "001",
- {'port': {'state': 'ACTIVE'}}],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_update_port - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_show_port_attachment(self,
- tenant=TENANT_1, format='json', status=200):
- LOG.debug("_test_show_port_attachment - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.show_port_attachment,
- status,
- "GET",
- "networks/001/ports/001/attachment",
- data=["001", "001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_show_port_attachment - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_attach_resource(self, tenant=TENANT_1,
- format='json', status=200):
- LOG.debug("_test_attach_resource - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.attach_resource,
- status,
- "PUT",
- "networks/001/ports/001/attachment",
- data=["001", "001",
- {'resource': {'id': '1234'}}],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_attach_resource - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_detach_resource(self, tenant=TENANT_1,
- format='json', status=200):
- LOG.debug("_test_detach_resource - tenant:%s "
- "- format:%s - START", format, tenant)
-
- self._assert_sanity(self.client.detach_resource,
- status,
- "DELETE",
- "networks/001/ports/001/attachment",
- data=["001", "001"],
- params={'tenant': tenant, 'format': format})
-
- LOG.debug("_test_detach_resource - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def _test_ssl_certificates(self, tenant=TENANT_1,
- format='json', status=200):
- LOG.debug("_test_ssl_certificates - tenant:%s "
- "- format:%s - START", format, tenant)
-
- # Set SSL, and our cert file
- self.client.use_ssl = True
- cert_file = "/fake.cert"
- self.client.key_file = self.client.cert_file = cert_file
-
- data = self._assert_sanity(self.client.list_networks,
- status,
- "GET",
- "networks",
- data=[],
- params={'tenant': tenant, 'format': format})
-
- self.assertEquals(data["key_file"], cert_file)
- self.assertEquals(data["cert_file"], cert_file)
-
- LOG.debug("_test_ssl_certificates - tenant:%s "
- "- format:%s - END", format, tenant)
-
- def test_list_networks_json(self):
- self._test_list_networks(format='json')
-
- def test_list_networks_xml(self):
- self._test_list_networks(format='xml')
-
- def test_list_networks_alt_tenant(self):
- self._test_list_networks(tenant=TENANT_2)
-
- def test_list_networks_error_470(self):
- self._test_list_networks(status=470)
-
- def test_list_networks_error_401(self):
- self._test_list_networks(status=401)
-
- def test_list_networks_details_json(self):
- self._test_list_networks_details(format='json')
-
- def test_list_networks_details_xml(self):
- self._test_list_networks_details(format='xml')
-
- def test_list_networks_details_alt_tenant(self):
- self._test_list_networks_details(tenant=TENANT_2)
-
- def test_list_networks_details_error_470(self):
- self._test_list_networks_details(status=470)
-
- def test_list_networks_details_error_401(self):
- self._test_list_networks_details(status=401)
-
- def test_show_network_json(self):
- self._test_show_network(format='json')
-
- def test_show_network__xml(self):
- self._test_show_network(format='xml')
-
- def test_show_network_alt_tenant(self):
- self._test_show_network(tenant=TENANT_2)
-
- def test_show_network_error_470(self):
- self._test_show_network(status=470)
-
- def test_show_network_error_401(self):
- self._test_show_network(status=401)
-
- def test_show_network_error_420(self):
- self._test_show_network(status=420)
-
- def test_show_network_details_json(self):
- self._test_show_network_details(format='json')
-
- def test_show_network_details_xml(self):
- self._test_show_network_details(format='xml')
-
- def test_show_network_details_alt_tenant(self):
- self._test_show_network_details(tenant=TENANT_2)
-
- def test_show_network_details_error_470(self):
- self._test_show_network_details(status=470)
-
- def test_show_network_details_error_401(self):
- self._test_show_network_details(status=401)
-
- def test_show_network_details_error_420(self):
- self._test_show_network_details(status=420)
-
- def test_create_network_json(self):
- self._test_create_network(format='json')
-
- def test_create_network_xml(self):
- self._test_create_network(format='xml')
-
- def test_create_network_alt_tenant(self):
- self._test_create_network(tenant=TENANT_2)
-
- def test_create_network_error_470(self):
- self._test_create_network(status=470)
-
- def test_create_network_error_401(self):
- self._test_create_network(status=401)
-
- def test_create_network_error_400(self):
- self._test_create_network(status=400)
-
- def test_create_network_error_422(self):
- self._test_create_network(status=422)
-
- def test_update_network_json(self):
- self._test_update_network(format='json')
-
- def test_update_network_xml(self):
- self._test_update_network(format='xml')
-
- def test_update_network_alt_tenant(self):
- self._test_update_network(tenant=TENANT_2)
-
- def test_update_network_error_470(self):
- self._test_update_network(status=470)
-
- def test_update_network_error_401(self):
- self._test_update_network(status=401)
-
- def test_update_network_error_400(self):
- self._test_update_network(status=400)
-
- def test_update_network_error_420(self):
- self._test_update_network(status=420)
-
- def test_update_network_error_422(self):
- self._test_update_network(status=422)
-
- def test_delete_network_json(self):
- self._test_delete_network(format='json')
-
- def test_delete_network_xml(self):
- self._test_delete_network(format='xml')
-
- def test_delete_network_alt_tenant(self):
- self._test_delete_network(tenant=TENANT_2)
-
- def test_delete_network_error_470(self):
- self._test_delete_network(status=470)
-
- def test_delete_network_error_401(self):
- self._test_delete_network(status=401)
-
- def test_delete_network_error_420(self):
- self._test_delete_network(status=420)
-
- def test_delete_network_error_421(self):
- self._test_delete_network(status=421)
-
- def test_list_ports_json(self):
- self._test_list_ports(format='json')
-
- def test_list_ports_xml(self):
- self._test_list_ports(format='xml')
-
- def test_list_ports_alt_tenant(self):
- self._test_list_ports(tenant=TENANT_2)
-
- def test_list_ports_error_470(self):
- self._test_list_ports(status=470)
-
- def test_list_ports_error_401(self):
- self._test_list_ports(status=401)
-
- def test_list_ports_error_420(self):
- self._test_list_ports(status=420)
-
- def test_list_ports_details_json(self):
- self._test_list_ports_details(format='json')
-
- def test_list_ports_details_xml(self):
- self._test_list_ports_details(format='xml')
-
- def test_list_ports_details_alt_tenant(self):
- self._test_list_ports_details(tenant=TENANT_2)
-
- def test_list_ports_details_error_470(self):
- self._test_list_ports_details(status=470)
-
- def test_list_ports_details_error_401(self):
- self._test_list_ports_details(status=401)
-
- def test_list_ports_details_error_420(self):
- self._test_list_ports_details(status=420)
-
- def test_show_port_json(self):
- self._test_show_port(format='json')
-
- def test_show_port_xml(self):
- self._test_show_port(format='xml')
-
- def test_show_port_alt_tenant(self):
- self._test_show_port(tenant=TENANT_2)
-
- def test_show_port_error_470(self):
- self._test_show_port(status=470)
-
- def test_show_port_error_401(self):
- self._test_show_port(status=401)
-
- def test_show_port_error_420(self):
- self._test_show_port(status=420)
-
- def test_show_port_error_430(self):
- self._test_show_port(status=430)
-
- def test_show_port_details_json(self):
- self._test_show_port_details(format='json')
-
- def test_show_port_details_xml(self):
- self._test_show_port_details(format='xml')
-
- def test_show_port_details_alt_tenant(self):
- self._test_show_port_details(tenant=TENANT_2)
-
- def test_show_port_details_error_470(self):
- self._test_show_port_details(status=470)
-
- def test_show_port_details_error_401(self):
- self._test_show_port_details(status=401)
-
- def test_show_port_details_error_420(self):
- self._test_show_port_details(status=420)
-
- def test_show_port_details_error_430(self):
- self._test_show_port_details(status=430)
-
- def test_create_port_json(self):
- self._test_create_port(format='json')
-
- def test_create_port_xml(self):
- self._test_create_port(format='xml')
-
- def test_create_port_alt_tenant(self):
- self._test_create_port(tenant=TENANT_2)
-
- def test_create_port_error_470(self):
- self._test_create_port(status=470)
-
- def test_create_port_error_401(self):
- self._test_create_port(status=401)
-
- def test_create_port_error_400(self):
- self._test_create_port(status=400)
-
- def test_create_port_error_420(self):
- self._test_create_port(status=420)
-
- def test_create_port_error_430(self):
- self._test_create_port(status=430)
-
- def test_create_port_error_431(self):
- self._test_create_port(status=431)
-
- def test_delete_port_json(self):
- self._test_delete_port(format='json')
-
- def test_delete_port_xml(self):
- self._test_delete_port(format='xml')
-
- def test_delete_port_alt_tenant(self):
- self._test_delete_port(tenant=TENANT_2)
-
- def test_delete_port_error_470(self):
- self._test_delete_port(status=470)
-
- def test_delete_port_error_401(self):
- self._test_delete_port(status=401)
-
- def test_delete_port_error_420(self):
- self._test_delete_port(status=420)
-
- def test_delete_port_error_430(self):
- self._test_delete_port(status=430)
-
- def test_delete_port_error_432(self):
- self._test_delete_port(status=432)
-
- def test_update_port_json(self):
- self._test_update_port(format='json')
-
- def test_update_port_xml(self):
- self._test_update_port(format='xml')
-
- def test_update_port_alt_tenant(self):
- self._test_update_port(tenant=TENANT_2)
-
- def test_update_port_error_470(self):
- self._test_update_port(status=470)
-
- def test_update_port_error_401(self):
- self._test_update_port(status=401)
-
- def test_update_port_error_400(self):
- self._test_update_port(status=400)
-
- def test_update_port_error_420(self):
- self._test_update_port(status=420)
-
- def test_update_port_error_430(self):
- self._test_update_port(status=430)
-
- def test_update_port_error_431(self):
- self._test_update_port(status=431)
-
- def test_show_port_attachment_json(self):
- self._test_show_port_attachment(format='json')
-
- def test_show_port_attachment_xml(self):
- self._test_show_port_attachment(format='xml')
-
- def test_show_port_attachment_alt_tenant(self):
- self._test_show_port_attachment(tenant=TENANT_2)
-
- def test_show_port_attachment_error_470(self):
- self._test_show_port_attachment(status=470)
-
- def test_show_port_attachment_error_401(self):
- self._test_show_port_attachment(status=401)
-
- def test_show_port_attachment_error_400(self):
- self._test_show_port_attachment(status=400)
-
- def test_show_port_attachment_error_420(self):
- self._test_show_port_attachment(status=420)
-
- def test_show_port_attachment_error_430(self):
- self._test_show_port_attachment(status=430)
-
- def test_attach_resource_json(self):
- self._test_attach_resource(format='json')
-
- def test_attach_resource_xml(self):
- self._test_attach_resource(format='xml')
-
- def test_attach_resource_alt_tenant(self):
- self._test_attach_resource(tenant=TENANT_2)
-
- def test_attach_resource_error_470(self):
- self._test_attach_resource(status=470)
-
- def test_attach_resource_error_401(self):
- self._test_attach_resource(status=401)
-
- def test_attach_resource_error_400(self):
- self._test_attach_resource(status=400)
-
- def test_attach_resource_error_420(self):
- self._test_attach_resource(status=420)
-
- def test_attach_resource_error_430(self):
- self._test_attach_resource(status=430)
-
- def test_attach_resource_error_432(self):
- self._test_attach_resource(status=432)
-
- def test_attach_resource_error_440(self):
- self._test_attach_resource(status=440)
-
- def test_detach_resource_json(self):
- self._test_detach_resource(format='json')
-
- def test_detach_resource_xml(self):
- self._test_detach_resource(format='xml')
-
- def test_detach_resource_alt_tenant(self):
- self._test_detach_resource(tenant=TENANT_2)
-
- def test_detach_resource_error_470(self):
- self._test_detach_resource(status=470)
-
- def test_detach_resource_error_401(self):
- self._test_detach_resource(status=401)
-
- def test_detach_resource_error_420(self):
- self._test_detach_resource(status=420)
-
- def test_detach_resource_error_430(self):
- self._test_detach_resource(status=430)
-
- def test_ssl_certificates(self):
- self._test_ssl_certificates()
-
- def test_connection_retry_failure(self):
- self.client = Client(port=55555, tenant=TENANT_1, retries=1,
- retry_interval=0)
- try:
- self.client.list_networks()
- except exceptions.ConnectionFailed as exc:
- self.assertTrue('Maximum attempts reached' in str(exc))
- else:
- self.fail('ConnectionFailed not raised')
diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder
new file mode 100644
index 000000000..e69de29bb
diff --git a/releasenotes/notes/Define-IpAddressAlreadyAllocatedClient-exception-e8600ca5ba1c7f45.yaml b/releasenotes/notes/Define-IpAddressAlreadyAllocatedClient-exception-e8600ca5ba1c7f45.yaml
new file mode 100644
index 000000000..4182585e0
--- /dev/null
+++ b/releasenotes/notes/Define-IpAddressAlreadyAllocatedClient-exception-e8600ca5ba1c7f45.yaml
@@ -0,0 +1,6 @@
+---
+other:
+ - |
+ Define a new exception type ``IpAddressAlreadyAllocatedClient``.
+ Users can catch this specific exception instead of the generic
+ ``NeutronClientException``.
diff --git a/releasenotes/notes/add-aggressive-negotiation-mode-5218b1baff930eb8.yaml b/releasenotes/notes/add-aggressive-negotiation-mode-5218b1baff930eb8.yaml
new file mode 100644
index 000000000..ae1b6672f
--- /dev/null
+++ b/releasenotes/notes/add-aggressive-negotiation-mode-5218b1baff930eb8.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ The ``--phase1-negotiation-mode`` option supports ``aggressive`` mode
+ in VPNaaS ikepolicy commands.
diff --git a/releasenotes/notes/add-auto-allocated-topology-delete-aaccd60bd0f2e7b2.yaml b/releasenotes/notes/add-auto-allocated-topology-delete-aaccd60bd0f2e7b2.yaml
new file mode 100644
index 000000000..84c395236
--- /dev/null
+++ b/releasenotes/notes/add-auto-allocated-topology-delete-aaccd60bd0f2e7b2.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - The ``auto-allocated-topology-delete`` command allows users to
+ delete the auto allocated topology.
diff --git a/releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml b/releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml
new file mode 100644
index 000000000..51dd37f41
--- /dev/null
+++ b/releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml
@@ -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.
diff --git a/releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml b/releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml
new file mode 100644
index 000000000..e413df7ec
--- /dev/null
+++ b/releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml
@@ -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.
diff --git a/releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml b/releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml
new file mode 100644
index 000000000..8f48d2863
--- /dev/null
+++ b/releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml
@@ -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.
\ No newline at end of file
diff --git a/releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml b/releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml
new file mode 100644
index 000000000..a689b89ed
--- /dev/null
+++ b/releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml
@@ -0,0 +1,16 @@
+---
+features:
+ - |
+ New command 'neutron purge ' 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
diff --git a/releasenotes/notes/add-new-session-clear-option-3c0b78ebc133a10c.yaml b/releasenotes/notes/add-new-session-clear-option-3c0b78ebc133a10c.yaml
new file mode 100644
index 000000000..2782efe4f
--- /dev/null
+++ b/releasenotes/notes/add-new-session-clear-option-3c0b78ebc133a10c.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ A new option ``--no-session-persistence`` has been added to
+ the ``neutron lbaas-pool-update`` CLI to clear the session persistence
+ with which the current pool is associated.
diff --git a/releasenotes/notes/add-no-shared-option-to-qos-policy-update-56ac41fb3af7e309.yaml b/releasenotes/notes/add-no-shared-option-to-qos-policy-update-56ac41fb3af7e309.yaml
new file mode 100644
index 000000000..0c34f5eeb
--- /dev/null
+++ b/releasenotes/notes/add-no-shared-option-to-qos-policy-update-56ac41fb3af7e309.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ CLI support to set QoS policy as not shared if it was shared before.
+ The ``qos-policy-update`` command include a ``--no-shared`` option.
+ Closes `bug 1590942 `_.
diff --git a/releasenotes/notes/add-osc-dynamic-routing-support-11130b2f440c0ac2.yaml b/releasenotes/notes/add-osc-dynamic-routing-support-11130b2f440c0ac2.yaml
new file mode 100644
index 000000000..58cd1ae30
--- /dev/null
+++ b/releasenotes/notes/add-osc-dynamic-routing-support-11130b2f440c0ac2.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add OSC plugin to support "Neutron Dynamic Routing"
diff --git a/releasenotes/notes/add-osc-trunk-commands-7e77283a369729c5.yaml b/releasenotes/notes/add-osc-trunk-commands-7e77283a369729c5.yaml
new file mode 100644
index 000000000..684ae3102
--- /dev/null
+++ b/releasenotes/notes/add-osc-trunk-commands-7e77283a369729c5.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Add ``network trunk create``, ``network trunk list``,
+ ``network trunk set``, ``network trunk unset``, ``network trunk delete``
+ and ``network subport list`` OSC commands for trunk resource along with
+ client bindings.
+ [Blueprint `vlan-aware-vms `_]
diff --git a/releasenotes/notes/add-quota-default-show-c2ab35b791dcdcbc.yaml b/releasenotes/notes/add-quota-default-show-c2ab35b791dcdcbc.yaml
new file mode 100644
index 000000000..3aececa35
--- /dev/null
+++ b/releasenotes/notes/add-quota-default-show-c2ab35b791dcdcbc.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ CLI support to display the default quota reserved for a tenant.
+
+ * The ``quota-default-show`` command outputs the default quota
+ of resources for a given tenant.
diff --git a/releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml b/releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml
new file mode 100644
index 000000000..9dff77c75
--- /dev/null
+++ b/releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml
@@ -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.
diff --git a/releasenotes/notes/add-service-graph-ce4a25b3e32d70a6.yaml b/releasenotes/notes/add-service-graph-ce4a25b3e32d70a6.yaml
new file mode 100644
index 000000000..f0b52abe7
--- /dev/null
+++ b/releasenotes/notes/add-service-graph-ce4a25b3e32d70a6.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added support for SFC Service Graph resource.
+ Related RFE: https://bugs.launchpad.net/networking-sfc/+bug/1587486.
diff --git a/releasenotes/notes/add-sfc-commands.yaml b/releasenotes/notes/add-sfc-commands.yaml
new file mode 100644
index 000000000..c081f50f9
--- /dev/null
+++ b/releasenotes/notes/add-sfc-commands.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add OSC plugin support for the “Networking Service Function Chaining” feature commands along with client bindings.
+ [Blueprint `openstackclient-cli-porting `_]
\ No newline at end of file
diff --git a/releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml b/releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml
new file mode 100644
index 000000000..1be2e0ed1
--- /dev/null
+++ b/releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml
@@ -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.
diff --git a/releasenotes/notes/add-subnet-onboard-e60772bc4984f698.yaml b/releasenotes/notes/add-subnet-onboard-e60772bc4984f698.yaml
new file mode 100644
index 000000000..358fdf006
--- /dev/null
+++ b/releasenotes/notes/add-subnet-onboard-e60772bc4984f698.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add ``network onboard subnets`` OSC command to enable subnet onboard support from the CLI
+ [Blueprint `subnet-onboard `_]
diff --git a/releasenotes/notes/add-support-to-floating-ip-port-forwardings-9dc838a5c5727eb7.yaml b/releasenotes/notes/add-support-to-floating-ip-port-forwardings-9dc838a5c5727eb7.yaml
new file mode 100644
index 000000000..3ba168ec9
--- /dev/null
+++ b/releasenotes/notes/add-support-to-floating-ip-port-forwardings-9dc838a5c5727eb7.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add support to floating ip port forwarding.
\ No newline at end of file
diff --git a/releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml b/releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml
new file mode 100644
index 000000000..cc5b6eec2
--- /dev/null
+++ b/releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml
@@ -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.
\ No newline at end of file
diff --git a/releasenotes/notes/availability-zone-support-8e66f55e46b7ef9a.yaml b/releasenotes/notes/availability-zone-support-8e66f55e46b7ef9a.yaml
new file mode 100644
index 000000000..5551084f1
--- /dev/null
+++ b/releasenotes/notes/availability-zone-support-8e66f55e46b7ef9a.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ CLI support for availability zones.
+
+ * The ``availability-zone-list`` command provides a list of
+ availability zones.
+ * The ``net-create`` and ``router-create`` commands include a
+ ``--availability-zone-hint`` option.
+ * The ``agent-list`` command output includes availability zones.
diff --git a/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml b/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml
new file mode 100644
index 000000000..2fa2e8e6e
--- /dev/null
+++ b/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml
@@ -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.
\ No newline at end of file
diff --git a/releasenotes/notes/bug-1676922-81341b70bc6f055a.yaml b/releasenotes/notes/bug-1676922-81341b70bc6f055a.yaml
new file mode 100644
index 000000000..9dc6aef41
--- /dev/null
+++ b/releasenotes/notes/bug-1676922-81341b70bc6f055a.yaml
@@ -0,0 +1,19 @@
+---
+deprecations:
+ - |
+ The ``--public`` and ``--private`` attribute of Firewall-as-a-Service v2
+ have been deprecated. While the ``--public`` attribute will now be replaced
+ by ``--share``, the ``--private`` attribute will be replaced by
+ ``--no-share``. This is because of the similarity between the behavior of
+ ``--public`` attribute in FireWall-as-a-Service and the ``--share``
+ attribute used in OpenStack. This deprecation affects the following CLIs.
+
+ * openstack firewall group create
+ * openstack firewall group set
+ * openstack firewall group unset
+ * openstack firewall policy create
+ * openstack firewall policy set
+ * openstack firewall policy unset
+ * openstack firewall rule create
+ * openstack firewall rule set
+ * openstack firewall rule unset
diff --git a/releasenotes/notes/bulk-delete-support-94a353db08efec8d.yaml b/releasenotes/notes/bulk-delete-support-94a353db08efec8d.yaml
new file mode 100644
index 000000000..4d2575d9d
--- /dev/null
+++ b/releasenotes/notes/bulk-delete-support-94a353db08efec8d.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ CLI support for bulk delete.
+
+ * By using this feature, multiple resource
+ can be deleted using a single command.
+ * Example: ``neutron router-delete router_a router_b``
+ deletes both router_a and router_b.
diff --git a/releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml b/releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml
new file mode 100644
index 000000000..2a571d6d8
--- /dev/null
+++ b/releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml
@@ -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.
diff --git a/releasenotes/notes/deprecate-bgp-speaker-show-dragents-2fcce99cf6bb5b60.yaml b/releasenotes/notes/deprecate-bgp-speaker-show-dragents-2fcce99cf6bb5b60.yaml
new file mode 100644
index 000000000..6233c6f4f
--- /dev/null
+++ b/releasenotes/notes/deprecate-bgp-speaker-show-dragents-2fcce99cf6bb5b60.yaml
@@ -0,0 +1,10 @@
+---
+deprecations:
+ - |
+ The ``openstack bgp speaker show dragents`` CLI is deprecated and
+ will be removed in the future. Use ``openstack bgp dragent list
+ --bgp-speaker `` CLI instead.
+features:
+ - |
+ The ``openstack bgp dragent list`` CLI is added to support showing
+ the list of dynamic routing agents.
diff --git a/releasenotes/notes/deprecate-cli-7be1123817969439.yaml b/releasenotes/notes/deprecate-cli-7be1123817969439.yaml
new file mode 100644
index 000000000..4833ddda6
--- /dev/null
+++ b/releasenotes/notes/deprecate-cli-7be1123817969439.yaml
@@ -0,0 +1,8 @@
+---
+deprecations:
+ - The neutron CLI is now deprecated. This is the signal that it is
+ time to start using the openstack CLI. No new features will be
+ added to the neutron CLI, though fixes to the CLI will be assessed
+ on a case by case basis. Fixes to the API bindings, as well as
+ development of new API bindings as well as OSC plugins are exempt
+ from this deprecation.
diff --git a/releasenotes/notes/direct-physical-vnic-port-create-736d8b2600faf22b.yaml b/releasenotes/notes/direct-physical-vnic-port-create-736d8b2600faf22b.yaml
new file mode 100644
index 000000000..911377496
--- /dev/null
+++ b/releasenotes/notes/direct-physical-vnic-port-create-736d8b2600faf22b.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Added new 'direct-physical' vnic-type option for port-create CLI command.
+ Passing this particular value allows for a port to be create with the
+ vnic-type used for assigning SR-IOV physical functions to
+ instances.
diff --git a/releasenotes/notes/docs-improvements-17e31babe38e2962.yaml b/releasenotes/notes/docs-improvements-17e31babe38e2962.yaml
new file mode 100644
index 000000000..f00dc742d
--- /dev/null
+++ b/releasenotes/notes/docs-improvements-17e31babe38e2962.yaml
@@ -0,0 +1,5 @@
+---
+other:
+ - |
+ Addition of CLI user documentation including output filters, extra
+ options, and operation using os-client-config.
diff --git a/releasenotes/notes/drop-nuage-commands-df10aab6ccd77ed2.yaml b/releasenotes/notes/drop-nuage-commands-df10aab6ccd77ed2.yaml
new file mode 100644
index 000000000..b76f40f05
--- /dev/null
+++ b/releasenotes/notes/drop-nuage-commands-df10aab6ccd77ed2.yaml
@@ -0,0 +1,3 @@
+---
+upgrade:
+ - Remove Nuage-specific commands.
diff --git a/releasenotes/notes/drop-python-2.7-f615ebae463b2143.yaml b/releasenotes/notes/drop-python-2.7-f615ebae463b2143.yaml
new file mode 100644
index 000000000..6fb4d22ab
--- /dev/null
+++ b/releasenotes/notes/drop-python-2.7-f615ebae463b2143.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ Python 2.7 support has been dropped. The minimum version of Python now
+ supported by python-neutronclient is Python 3.6.
diff --git a/releasenotes/notes/drop-python-3-6-and-3-7-73767fa0bbe89a6e.yaml b/releasenotes/notes/drop-python-3-6-and-3-7-73767fa0bbe89a6e.yaml
new file mode 100644
index 000000000..db420d739
--- /dev/null
+++ b/releasenotes/notes/drop-python-3-6-and-3-7-73767fa0bbe89a6e.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ Python 3.6 & 3.7 support has been dropped. The minimum version of Python now
+ supported is Python 3.8.
diff --git a/releasenotes/notes/drop-xml-support-41babecb1784d996.yaml b/releasenotes/notes/drop-xml-support-41babecb1784d996.yaml
new file mode 100644
index 000000000..23c95e688
--- /dev/null
+++ b/releasenotes/notes/drop-xml-support-41babecb1784d996.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - XML request format support has been removed.
+deprecations:
+ - request-format option is deprecated.
diff --git a/releasenotes/notes/dscp_qos-4a26d3c0363624b0.yaml b/releasenotes/notes/dscp_qos-4a26d3c0363624b0.yaml
new file mode 100644
index 000000000..37770cd51
--- /dev/null
+++ b/releasenotes/notes/dscp_qos-4a26d3c0363624b0.yaml
@@ -0,0 +1,6 @@
+---
+prelude: >
+ Adding new QoS DSCP marking rule commands.
+features:
+ - New create, update, list, show, and delete commands are added for QoS
+ DSCP marking rule functionality.
diff --git a/releasenotes/notes/extraroute-atomic-b11919d8e33b0d92.yaml b/releasenotes/notes/extraroute-atomic-b11919d8e33b0d92.yaml
new file mode 100644
index 000000000..fed4a1e10
--- /dev/null
+++ b/releasenotes/notes/extraroute-atomic-b11919d8e33b0d92.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ New client methods: ``add_extra_routes_to_router`` and
+ ``remove_extra_routes_from_router``.
diff --git a/releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml b/releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml
new file mode 100644
index 000000000..a21eebbeb
--- /dev/null
+++ b/releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml
@@ -0,0 +1,5 @@
+---
+critical:
+ - Fix a critical bug that when lazy translation is enabled
+ NeutronClientException raises a TypeError
+ (`bug 1552760 `_).
diff --git a/releasenotes/notes/fix-quota-update-zero-args-d596c4169c2d2e30.yaml b/releasenotes/notes/fix-quota-update-zero-args-d596c4169c2d2e30.yaml
new file mode 100644
index 000000000..eb70752a3
--- /dev/null
+++ b/releasenotes/notes/fix-quota-update-zero-args-d596c4169c2d2e30.yaml
@@ -0,0 +1,9 @@
+---
+fixes:
+ - |
+ Fix CLI quota-update to return an error message for no args
+
+ * ``quota-update`` CLI will return an error message
+ ``Must specify a valid resource with new quota value`` if no
+ argument is provided while executing it. If arguments are
+ provided with CLI, no existing behavior is changed.
diff --git a/releasenotes/notes/fix-rbac-create-command-dd40a474f0f092db.yaml b/releasenotes/notes/fix-rbac-create-command-dd40a474f0f092db.yaml
new file mode 100644
index 000000000..7ae438abf
--- /dev/null
+++ b/releasenotes/notes/fix-rbac-create-command-dd40a474f0f092db.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - Fix 'bug 1596750 '
+ that using 'rbac-create' without specifying 'target-tenant' will
+ return 'Request Failed internal server error while processing your
+ request'.
+ Update the default value of the argument '--target-tenant' to '*'
diff --git a/releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml b/releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml
new file mode 100644
index 000000000..9f8b29040
--- /dev/null
+++ b/releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - Fix `bug 1450414 `_
+ that authentication with via ``--os-token`` and ``--os-url`` options
+ (or corresponding environment variables) does not work
+ after keystone v3 API support.
diff --git a/releasenotes/notes/global_request_id-56856a93b982a6b3.yaml b/releasenotes/notes/global_request_id-56856a93b982a6b3.yaml
new file mode 100644
index 000000000..168987613
--- /dev/null
+++ b/releasenotes/notes/global_request_id-56856a93b982a6b3.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Adds a new ``global_request_id`` parameter to the Client
+ constructors, which will pass that id on all requests as the
+ ``X-OpenStack-Request-ID`` header.
diff --git a/releasenotes/notes/keystonev3-7f9ede9c21b30841.yaml b/releasenotes/notes/keystonev3-7f9ede9c21b30841.yaml
new file mode 100644
index 000000000..507259663
--- /dev/null
+++ b/releasenotes/notes/keystonev3-7f9ede9c21b30841.yaml
@@ -0,0 +1,7 @@
+---
+deprecations:
+ - |
+ Keystone v3 support for CLI
+
+ * Using 'tenant_id' and 'tenant_name' arguments in API bindings is
+ deprecated. Use 'project_id' and 'project_name' arguments instead.
diff --git a/releasenotes/notes/log-request-id-64bef955b8292c18.yaml b/releasenotes/notes/log-request-id-64bef955b8292c18.yaml
new file mode 100644
index 000000000..62c73fa2e
--- /dev/null
+++ b/releasenotes/notes/log-request-id-64bef955b8292c18.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Added support to log 'x-openstack-request-id' for each api call.
\ No newline at end of file
diff --git a/releasenotes/notes/minimum-packet-rate-34576b8fd98a3034.yaml b/releasenotes/notes/minimum-packet-rate-34576b8fd98a3034.yaml
new file mode 100644
index 000000000..7ce33f65e
--- /dev/null
+++ b/releasenotes/notes/minimum-packet-rate-34576b8fd98a3034.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Added new client methods for QoS minimum packet rate rule:
+ ``list_minimum_packet_rate_rules``, ``show_minimum_packet_rate_rule``,
+ ``create_minimum_packet_rate_rule``, ``update_minimum_packet_rate_rule``,
+ ``delete_minimum_packet_rate_rule``.
diff --git a/releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml b/releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml
new file mode 100644
index 000000000..7a120fd7e
--- /dev/null
+++ b/releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml
@@ -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.
diff --git a/releasenotes/notes/neutron-cli-deprecation-398823c87270a296.yaml b/releasenotes/notes/neutron-cli-deprecation-398823c87270a296.yaml
new file mode 100644
index 000000000..a8065940e
--- /dev/null
+++ b/releasenotes/notes/neutron-cli-deprecation-398823c87270a296.yaml
@@ -0,0 +1,10 @@
+---
+deprecations:
+ - |
+ ``neutron`` CLI will be removed in 'Z' release.
+ While it has been marked as deprecated for removal for long,
+ all features in ``neutron`` CLI have been supported in ``openstack`` CLI
+ (OpenStackClient) as of Xena release and the neutron team plans to
+ remove it in 'Z' release. Consider using ``openstack`` CLI and
+ `Mapping Guide `__
+ in the OSC documentation would help you.
diff --git a/releasenotes/notes/no-new-binding-code-b03c9abbcaf2839e.yaml b/releasenotes/notes/no-new-binding-code-b03c9abbcaf2839e.yaml
new file mode 100644
index 000000000..bc41e7438
--- /dev/null
+++ b/releasenotes/notes/no-new-binding-code-b03c9abbcaf2839e.yaml
@@ -0,0 +1,7 @@
+---
+prelude: >
+ Openstack community decided to use one SDK for its services, that is
+ in ``openstacksdk`` repository. To avoid duplication, sooner or later the
+ python binding code in ``python-neutronclient`` will be deprecated, and
+ ``Neutron`` team decided on the ``2023.1 (Antelope)`` PTG to not allow
+ new features\' bindings implemented here.
diff --git a/releasenotes/notes/osprofiler-support-9ba539761ae437e9.yaml b/releasenotes/notes/osprofiler-support-9ba539761ae437e9.yaml
new file mode 100644
index 000000000..98e2bb168
--- /dev/null
+++ b/releasenotes/notes/osprofiler-support-9ba539761ae437e9.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add osprofiler support to the neutronclient python binding.
+ If osprofiler is initiated, neutronclient sends a special HTTP
+ header that contains trace info.
diff --git a/releasenotes/notes/paket_rate_limit-1266a2a30f18727f.yaml b/releasenotes/notes/paket_rate_limit-1266a2a30f18727f.yaml
new file mode 100644
index 000000000..9fdfc1e62
--- /dev/null
+++ b/releasenotes/notes/paket_rate_limit-1266a2a30f18727f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Added new client methods for QoS packet rate limit rule:
+ ``list_packet_rate_limit_rules``, ``show_packet_rate_limit_rule``,
+ ``create_packet_rate_limit_rule``, ``update_packet_rate_limit_rule``,
+ ``delete_packet_rate_limit_rule``.
diff --git a/releasenotes/notes/port-bindings-c3f36bd76ece0a71.yaml b/releasenotes/notes/port-bindings-c3f36bd76ece0a71.yaml
new file mode 100644
index 000000000..7d2ef725e
--- /dev/null
+++ b/releasenotes/notes/port-bindings-c3f36bd76ece0a71.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ New client methods: ``create_port_binding``, ``delete_port_binding``,
+ ``show_port_binding``, ``list_port_bindings`` and ``activate_port_binding``.
diff --git a/releasenotes/notes/qos_minimum_bandwidth-dc4adb23c51de30b.yaml b/releasenotes/notes/qos_minimum_bandwidth-dc4adb23c51de30b.yaml
new file mode 100644
index 000000000..2d1002f07
--- /dev/null
+++ b/releasenotes/notes/qos_minimum_bandwidth-dc4adb23c51de30b.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - New create, update, list, show, and delete commands are added for the QoS
+ minimum bandwidth rule.
diff --git a/releasenotes/notes/quota-update-for-LB-b21e7bc9e4a10f3e.yaml b/releasenotes/notes/quota-update-for-LB-b21e7bc9e4a10f3e.yaml
new file mode 100644
index 000000000..9d6321db3
--- /dev/null
+++ b/releasenotes/notes/quota-update-for-LB-b21e7bc9e4a10f3e.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Quota of Loadbalancers and listeners can now be updated.
diff --git a/releasenotes/notes/quota-update-for-rbac-192a8e65bf481941.yaml b/releasenotes/notes/quota-update-for-rbac-192a8e65bf481941.yaml
new file mode 100644
index 000000000..572ce35c2
--- /dev/null
+++ b/releasenotes/notes/quota-update-for-rbac-192a8e65bf481941.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Quota for RBAC policies can now be set.
diff --git a/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml b/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml
new file mode 100644
index 000000000..2bf4d5ab0
--- /dev/null
+++ b/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml
@@ -0,0 +1,26 @@
+---
+features:
+ - Support os-client-config. OS_CLOUD environment variable is used for
+ selecting named cloud configuration.
+ - Support keystoneauth1 library which brings us better keystone v3 support.
+ - Client command extension now supports a child resource.
+ - New CLI for VPNaaS multiple local subnets.
+ - New CLI for VPNaaS endpoint group API.
+ - New CLI for flavor argument to loadbalancer v2 create.
+ - New CLI for Neutron flavor framework.
+ - Support creating floating IP on a specific subnet ID.
+ - NSX gateway extension adds new transport type values (ipsec_gre and ipsec_stt).
+ - New router-update option to update static routes (--route and --no-routes).
+ - New allowed-address-pairs option to port-update
+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
+ the server was always returning all entries since the
+ name filter was ignored. (bug 1517818)
+other:
+ - cliff-tablib has been removed from test dependencies.
diff --git a/releasenotes/notes/remote_fwg-0f5362e5be8b2e84.yaml b/releasenotes/notes/remote_fwg-0f5362e5be8b2e84.yaml
new file mode 100644
index 000000000..5f5073aee
--- /dev/null
+++ b/releasenotes/notes/remote_fwg-0f5362e5be8b2e84.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Adds the remote source firewall group and the remote destination
+ firewall group field to the firewall rules.
diff --git a/releasenotes/notes/remove-bgp-speaker-show-dragents-0a0db4b72b2feffc.yaml b/releasenotes/notes/remove-bgp-speaker-show-dragents-0a0db4b72b2feffc.yaml
new file mode 100644
index 000000000..5d4fdda2c
--- /dev/null
+++ b/releasenotes/notes/remove-bgp-speaker-show-dragents-0a0db4b72b2feffc.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ The ``openstack bgp speaker show dragents`` CLI is removed. It was
+ deprecated in the 7.1.0 release (Ussuri). Use ``openstack bgp dragent list
+ --bgp-speaker `` CLI instead.
diff --git a/releasenotes/notes/remove-case-dependency-773ccb3237c38e81.yaml b/releasenotes/notes/remove-case-dependency-773ccb3237c38e81.yaml
new file mode 100644
index 000000000..d32f96b4f
--- /dev/null
+++ b/releasenotes/notes/remove-case-dependency-773ccb3237c38e81.yaml
@@ -0,0 +1,7 @@
+---
+other:
+ - |
+ This patch provides user the support to use
+ any form of casing, thus removing the specific
+ UPPER/lower case inputs required by different
+ neutron CLIs.
diff --git a/releasenotes/notes/remove-cli-code-53969e9aa927e530.yaml b/releasenotes/notes/remove-cli-code-53969e9aa927e530.yaml
new file mode 100644
index 000000000..92ab5423c
--- /dev/null
+++ b/releasenotes/notes/remove-cli-code-53969e9aa927e530.yaml
@@ -0,0 +1,12 @@
+prelude: >
+ |
+ This new version of ``python-neutronclient`` does not include the
+ command line interface code. The "neutron" script is no longer
+ supported. This project only contains the OpenStack Client bindings
+ that are currently being moved to OpenStack SDK
+ _.
+deprecations:
+ - |
+ This project no longer provides CLI support. All code has been removed.
+ Please use openstack CLI instead. See `openstack CLI command list
+ `_.
diff --git a/releasenotes/notes/remove-deprecated-option-b53f5d7e6a16ce95.yaml b/releasenotes/notes/remove-deprecated-option-b53f5d7e6a16ce95.yaml
new file mode 100644
index 000000000..fb304fbda
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-option-b53f5d7e6a16ce95.yaml
@@ -0,0 +1,4 @@
+---
+deprecations:
+ - |
+ "admin-state-down" option was deprecated in Mitaka and has been removed in Newton.
diff --git a/releasenotes/notes/remove-fwaas-osc-plugin-a1b2c3d4e5f6g7h8.yaml b/releasenotes/notes/remove-fwaas-osc-plugin-a1b2c3d4e5f6g7h8.yaml
new file mode 100644
index 000000000..171b3eb62
--- /dev/null
+++ b/releasenotes/notes/remove-fwaas-osc-plugin-a1b2c3d4e5f6g7h8.yaml
@@ -0,0 +1,11 @@
+---
+deprecations:
+ - |
+ The Firewall as a Service (FWaaS) v2 OSC plugin commands have been moved
+ to ``python-openstackclient``. The FWaaS commands are no longer provided
+ by ``python-neutronclient``. Please use ``python-openstackclient`` for
+ the following commands:
+
+ * ``firewall group create/delete/list/set/show/unset``
+ * ``firewall group policy create/delete/list/set/show/unset/add rule/remove rule``
+ * ``firewall group rule create/delete/list/set/show/unset``
diff --git a/releasenotes/notes/remove-public-and-private-parameters-d683e7c30ecedc3b.yaml b/releasenotes/notes/remove-public-and-private-parameters-d683e7c30ecedc3b.yaml
new file mode 100644
index 000000000..d340c1763
--- /dev/null
+++ b/releasenotes/notes/remove-public-and-private-parameters-d683e7c30ecedc3b.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ The deprecated options ``--public`` and ``--private`` were
+ dropped in FWaaS v2 related commands. Use ``--share`` and
+ ``--no-share`` instead.
diff --git a/releasenotes/notes/remove-py38-26a1befde3f44b82.yaml b/releasenotes/notes/remove-py38-26a1befde3f44b82.yaml
new file mode 100644
index 000000000..a0440dc80
--- /dev/null
+++ b/releasenotes/notes/remove-py38-26a1befde3f44b82.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ Python 3.8 support was dropped. The minimum version of Python now supported
+ is Python 3.9.
diff --git a/releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml b/releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml
new file mode 100644
index 000000000..da7c5f817
--- /dev/null
+++ b/releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Neutron client returns 'x-openstack-request-id'.
diff --git a/releasenotes/notes/segments-8557f5b0caa5ee26.yaml b/releasenotes/notes/segments-8557f5b0caa5ee26.yaml
new file mode 100644
index 000000000..292adf85e
--- /dev/null
+++ b/releasenotes/notes/segments-8557f5b0caa5ee26.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ New client methods: ``create_segment``, ``update_segment``,
+ ``list_segments``, ``show_segment`` and ``delete_segment``.
diff --git a/releasenotes/notes/sfc-tap-service-function-support-a05242f25f79066b.yaml b/releasenotes/notes/sfc-tap-service-function-support-a05242f25f79066b.yaml
new file mode 100644
index 000000000..ccfcc8276
--- /dev/null
+++ b/releasenotes/notes/sfc-tap-service-function-support-a05242f25f79066b.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add OSC support to create Port pair group for Tap service functions.
diff --git a/releasenotes/notes/show-tenant-id-admin-listing-dc13ee7eb889d418.yaml b/releasenotes/notes/show-tenant-id-admin-listing-dc13ee7eb889d418.yaml
new file mode 100644
index 000000000..6b84e1843
--- /dev/null
+++ b/releasenotes/notes/show-tenant-id-admin-listing-dc13ee7eb889d418.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Show tenant_id when ``*-list`` command is run by admin. In neutron
+ the list operations by admin retrieve all resources from all tenants.
+ It is not easy to distinguish resources without tenant_id.
+ This feature is useful for admin operations.
diff --git a/releasenotes/notes/start-using-reno-9081b3e4c1951fdb.yaml b/releasenotes/notes/start-using-reno-9081b3e4c1951fdb.yaml
new file mode 100644
index 000000000..873a30fe6
--- /dev/null
+++ b/releasenotes/notes/start-using-reno-9081b3e4c1951fdb.yaml
@@ -0,0 +1,3 @@
+---
+other:
+ - Start using reno to manage release notes.
diff --git a/releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml b/releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml
new file mode 100644
index 000000000..2f7ed1720
--- /dev/null
+++ b/releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add BGP VPN `port association `_
+ support to the CLI, which are introduced for BGP VPN interconnections by the
+ ``bgpvpn-routes-control`` API extension.
diff --git a/releasenotes/notes/support-firewall-group-resource-type-5ad1b69cabcb4aa6.yaml b/releasenotes/notes/support-firewall-group-resource-type-5ad1b69cabcb4aa6.yaml
new file mode 100644
index 000000000..78fbff8fd
--- /dev/null
+++ b/releasenotes/notes/support-firewall-group-resource-type-5ad1b69cabcb4aa6.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ CLI support for the "firewal_group" as a loggable resource type for logging
+ feature, which is enhanced FWaaS functionality, as OSC plugin commands.
diff --git a/releasenotes/notes/support-fwaasv2-cli-7f21676c551f8ae0.yaml b/releasenotes/notes/support-fwaasv2-cli-7f21676c551f8ae0.yaml
new file mode 100644
index 000000000..2032bfa63
--- /dev/null
+++ b/releasenotes/notes/support-fwaasv2-cli-7f21676c551f8ae0.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - CLI support for the "Firewall as a Service v2" feature, which is enhanced
+ FWaaS functionality, as OSC plugin commands.
diff --git a/releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml b/releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml
new file mode 100644
index 000000000..415c30cd7
--- /dev/null
+++ b/releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ CLI support for 'Logging' feature, which enable to collect packet logs
+ for specified resource. Currently, only security-group can be logged.
diff --git a/releasenotes/notes/support-networking-bgpvpn-cli-fdd0cc3a5b14983d.yaml b/releasenotes/notes/support-networking-bgpvpn-cli-fdd0cc3a5b14983d.yaml
new file mode 100644
index 000000000..287e0427c
--- /dev/null
+++ b/releasenotes/notes/support-networking-bgpvpn-cli-fdd0cc3a5b14983d.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - CLI support for the "Neutron BGP VPN Interconnection" feature,
+ which is an API extension to support inter-connection between
+ L3VPNs/E-VPNs and Neutron resources, as OSC plugin commands.
diff --git a/releasenotes/notes/support-routes-advertise-9356a38cf3e2fe5a.yaml b/releasenotes/notes/support-routes-advertise-9356a38cf3e2fe5a.yaml
new file mode 100644
index 000000000..f1dbd5ed0
--- /dev/null
+++ b/releasenotes/notes/support-routes-advertise-9356a38cf3e2fe5a.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add optional flag to control the advertisement in BGPVPNs
+ of the routes defined on a Router resource
+ (``bgpvpn-routes-control`` API extension).
diff --git a/releasenotes/notes/support-vni-in-networking-bgpvpn-cli-d284b73b40b79495.yaml b/releasenotes/notes/support-vni-in-networking-bgpvpn-cli-d284b73b40b79495.yaml
new file mode 100644
index 000000000..3c3e51c28
--- /dev/null
+++ b/releasenotes/notes/support-vni-in-networking-bgpvpn-cli-d284b73b40b79495.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ CLI support for VXLAN VNI ID attribute in bgpvpn.
+ An optional argument ``--vni`` is added to ``openstack bgpvpn``
+ commands to configure VXLAN Network Identifier when VXLAN
+ encapsulation is used for the bgpvpn.
diff --git a/releasenotes/notes/support-vpnaas-cli-9478fb7cfe603e26.yaml b/releasenotes/notes/support-vpnaas-cli-9478fb7cfe603e26.yaml
new file mode 100644
index 000000000..3b5e6e491
--- /dev/null
+++ b/releasenotes/notes/support-vpnaas-cli-9478fb7cfe603e26.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ CLI support for the "VPN as a Service" feature, which is enhanced
+ VPNaaS functionality, as OSC plugin commands.
diff --git a/releasenotes/notes/tag-support-subnet-port-subnetpool-router-6250ec4714ee8690.yaml b/releasenotes/notes/tag-support-subnet-port-subnetpool-router-6250ec4714ee8690.yaml
new file mode 100644
index 000000000..1bc27aeb8
--- /dev/null
+++ b/releasenotes/notes/tag-support-subnet-port-subnetpool-router-6250ec4714ee8690.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Tag operation for subnet, port, subnetpool and router resources
+ are now supported.
diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst
new file mode 100644
index 000000000..2c9a36fae
--- /dev/null
+++ b/releasenotes/source/2023.1.rst
@@ -0,0 +1,6 @@
+===========================
+2023.1 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: unmaintained/2023.1
diff --git a/releasenotes/source/2023.2.rst b/releasenotes/source/2023.2.rst
new file mode 100644
index 000000000..a4838d7d0
--- /dev/null
+++ b/releasenotes/source/2023.2.rst
@@ -0,0 +1,6 @@
+===========================
+2023.2 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2023.2
diff --git a/releasenotes/source/2024.1.rst b/releasenotes/source/2024.1.rst
new file mode 100644
index 000000000..6896656be
--- /dev/null
+++ b/releasenotes/source/2024.1.rst
@@ -0,0 +1,6 @@
+===========================
+2024.1 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: unmaintained/2024.1
diff --git a/releasenotes/source/2024.2.rst b/releasenotes/source/2024.2.rst
new file mode 100644
index 000000000..aaebcbc8c
--- /dev/null
+++ b/releasenotes/source/2024.2.rst
@@ -0,0 +1,6 @@
+===========================
+2024.2 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2024.2
diff --git a/releasenotes/source/2025.1.rst b/releasenotes/source/2025.1.rst
new file mode 100644
index 000000000..3add0e53a
--- /dev/null
+++ b/releasenotes/source/2025.1.rst
@@ -0,0 +1,6 @@
+===========================
+2025.1 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2025.1
diff --git a/releasenotes/source/2025.2.rst b/releasenotes/source/2025.2.rst
new file mode 100644
index 000000000..4dae18d86
--- /dev/null
+++ b/releasenotes/source/2025.2.rst
@@ -0,0 +1,6 @@
+===========================
+2025.2 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2025.2
diff --git a/releasenotes/source/2026.1.rst b/releasenotes/source/2026.1.rst
new file mode 100644
index 000000000..3d2861580
--- /dev/null
+++ b/releasenotes/source/2026.1.rst
@@ -0,0 +1,6 @@
+===========================
+2026.1 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2026.1
diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder
new file mode 100644
index 000000000..e69de29bb
diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder
new file mode 100644
index 000000000..e69de29bb
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 000000000..498287605
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+# Neutron Client Release Notes documentation build configuration file,
+# created by sphinx-quickstart on Tue Nov 3 17:40:50 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'reno.sphinxext',
+ 'openstackdocstheme',
+]
+
+# openstackdocstheme options
+openstackdocs_repo_name = 'openstack/python-neutronclient'
+openstackdocs_bug_project = 'python-neutronclient'
+openstackdocs_bug_tag = 'doc'
+openstackdocs_auto_name = False
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'Neutron Client Release Notes'
+copyright = '2015, Neutron Developers'
+
+# Release notes are version independent.
+# The full version, including alpha/beta/rc tags.
+release = ''
+# The short X.Y version.
+version = ''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'openstackdocs'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'NeutronReleaseNotesdoc'
+
+# -- Options for Internationalization output ------------------------------
+locale_dirs = ['locale/']
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 000000000..7fe1c4a53
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,30 @@
+==============================
+ Neutron Client Release Notes
+==============================
+
+.. toctree::
+ :maxdepth: 1
+
+ unreleased
+ 2026.1
+ 2025.2
+ 2025.1
+ 2024.2
+ 2024.1
+ 2023.2
+ 2023.1
+ zed
+ yoga
+ xena
+ wallaby
+ victoria
+ ussuri
+ train
+ stein
+ rocky
+ queens
+ pike
+ ocata
+ newton
+ mitaka
+ old_relnotes
diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
new file mode 100644
index 000000000..cb1dcb6bd
--- /dev/null
+++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,400 @@
+# Andi Chandler , 2022. #zanata
+# Andi Chandler , 2023. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Neutron Client Release Notes\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-09-08 20:18+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2023-07-28 12:44+0000\n"
+"Last-Translator: Andi Chandler \n"
+"Language-Team: English (United Kingdom)\n"
+"Language: en_GB\n"
+"X-Generator: Zanata 4.3.3\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ""
+"\"admin-state-down\" option was deprecated in Mitaka and has been removed in "
+"Newton."
+msgstr ""
+"\"admin-state-down\" option was deprecated in Mitaka and has been removed in "
+"Newton."
+
+msgid "10.0.0"
+msgstr "10.0.0"
+
+msgid "2.0"
+msgstr "2.0"
+
+msgid "2.2.0"
+msgstr "2.2.0"
+
+msgid "2.2.2"
+msgstr "2.2.2"
+
+msgid "2023.1 Series Release Notes"
+msgstr "2023.1 Series Release Notes"
+
+msgid "4.0.0"
+msgstr "4.0.0"
+
+msgid "4.1.0"
+msgstr "4.1.0"
+
+msgid "4.1.1"
+msgstr "4.1.1"
+
+msgid "4.1.2-15"
+msgstr "4.1.2-15"
+
+msgid "4.2.0"
+msgstr "4.2.0"
+
+msgid "5.0.0"
+msgstr "5.0.0"
+
+msgid "5.1.0"
+msgstr "5.1.0"
+
+msgid "6.0.0"
+msgstr "6.0.0"
+
+msgid "6.1.0"
+msgstr "6.1.0"
+
+msgid "6.1.1"
+msgstr "6.1.1"
+
+msgid "6.10.0"
+msgstr "6.10.0"
+
+msgid "6.11.0"
+msgstr "6.11.0"
+
+msgid "6.12.0"
+msgstr "6.12.0"
+
+msgid "6.14.0"
+msgstr "6.14.0"
+
+msgid "6.2.0"
+msgstr "6.2.0"
+
+msgid "6.3.0"
+msgstr "6.3.0"
+
+msgid "6.4.0"
+msgstr "6.4.0"
+
+msgid "6.5.0"
+msgstr "6.5.0"
+
+msgid "6.6.0"
+msgstr "6.6.0"
+
+msgid "6.7.0"
+msgstr "6.7.0"
+
+msgid "7.0.0"
+msgstr "7.0.0"
+
+msgid "7.1.0"
+msgstr "7.1.0"
+
+msgid "7.2.0"
+msgstr "7.2.0"
+
+msgid "7.2.1"
+msgstr "7.2.1"
+
+msgid "7.5.0"
+msgstr "7.5.0"
+
+msgid "7.7.0"
+msgstr "7.7.0"
+
+msgid "7.8.0"
+msgstr "7.8.0"
+
+msgid "8.0.0"
+msgstr "8.0.0"
+
+msgid "8.1.0"
+msgstr "8.1.0"
+
+msgid "8.2.0"
+msgstr "8.2.0"
+
+msgid ""
+"A new option ``--no-session-persistence`` has been added to the ``neutron "
+"lbaas-pool-update`` CLI to clear the session persistence with which the "
+"current pool is associated."
+msgstr ""
+"A new option ``--no-session-persistence`` has been added to the ``neutron "
+"lbaas-pool-update`` CLI to clear the session persistence with which the "
+"current pool is associated."
+
+msgid ""
+"Add BGP VPN `port association `_ support to the CLI, which are "
+"introduced for BGP VPN interconnections by the ``bgpvpn-routes-control`` API "
+"extension."
+msgstr ""
+"Add BGP VPN `port association `_ support to the CLI, which are "
+"introduced for BGP VPN interconnections by the ``bgpvpn-routes-control`` API "
+"extension."
+
+msgid ""
+"Add OSC plugin support for the “Networking Service Function Chaining” "
+"feature commands along with client bindings. [Blueprint `openstackclient-cli-"
+"porting `_]"
+msgstr ""
+"Add OSC plugin support for the “Networking Service Function Chaining” "
+"feature commands along with client bindings. [Blueprint `openstackclient-cli-"
+"porting `_]"
+
+msgid "Add OSC plugin to support \"Neutron Dynamic Routing\""
+msgstr "Add OSC plugin to support \"Neutron Dynamic Routing\""
+
+msgid "Add OSC support to create Port pair group for Tap service functions."
+msgstr "Add OSC support to create Port pair group for Tap service functions."
+
+msgid ""
+"Add ``network onboard subnets`` OSC command to enable subnet onboard support "
+"from the CLI [Blueprint `subnet-onboard `_]"
+msgstr ""
+"Add ``network onboard subnets`` OSC command to enable subnet onboard support "
+"from the CLI [Blueprint `subnet-onboard `_]"
+
+msgid ""
+"Add ``network trunk create``, ``network trunk list``, ``network trunk set``, "
+"``network trunk unset``, ``network trunk delete`` and ``network subport "
+"list`` OSC commands for trunk resource along with client bindings. "
+"[Blueprint `vlan-aware-vms `_]"
+msgstr ""
+"Add ``network trunk create``, ``network trunk list``, ``network trunk set``, "
+"``network trunk unset``, ``network trunk delete`` and ``network subport "
+"list`` OSC commands for trunk resource along with client bindings. "
+"[Blueprint `vlan-aware-vms `_]"
+
+msgid ""
+"Add optional flag to control the advertisement in BGPVPNs of the routes "
+"defined on a Router resource (``bgpvpn-routes-control`` API extension)."
+msgstr ""
+"Add optional flag to control the advertisement in BGPVPNs of the routes "
+"defined on a Router resource (``bgpvpn-routes-control`` API extension)."
+
+msgid ""
+"Add osprofiler support to the neutronclient python binding. If osprofiler is "
+"initiated, neutronclient sends a special HTTP header that contains trace "
+"info."
+msgstr ""
+"Add osprofiler support to the neutronclient python binding. If osprofiler is "
+"initiated, neutronclient sends a special HTTP header that contains trace "
+"info."
+
+msgid "Add support to floating ip port forwarding."
+msgstr "Add support to floating ip port forwarding."
+
+msgid ""
+"By using this feature, multiple resource can be deleted using a single "
+"command."
+msgstr ""
+"By using this feature, multiple resource can be deleted using a single "
+"command."
+
+msgid ""
+"CLI support for 'Logging' feature, which enable to collect packet logs for "
+"specified resource. Currently, only security-group can be logged."
+msgstr ""
+"CLI support for 'Logging' feature, which enable to collect packet logs for "
+"specified resource. Currently, only security-group can be logged."
+
+msgid "CLI support for Layer 7 content policies and rules."
+msgstr "CLI support for Layer 7 content policies and rules."
+
+msgid "CLI support for Neutron-LBaaS v2 shared pools added."
+msgstr "CLI support for Neutron-LBaaS v2 shared pools added."
+
+msgid "CLI support for QoS policy RBAC."
+msgstr "CLI support for QoS policy RBAC."
+
+msgid "Current Series Release Notes"
+msgstr "Current Series Release Notes"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka Series Release Notes"
+
+msgid "Neutron Client Release Notes"
+msgstr "Neutron Client Release Notes"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton Series Release Notes"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata Series Release Notes"
+
+msgid "Old Release Notes"
+msgstr "Old Release Notes"
+
+msgid "Pike Series Release Notes"
+msgstr "Pike Series Release Notes"
+
+msgid "Queens Series Release Notes"
+msgstr "Queens Series Release Notes"
+
+msgid "Rocky Series Release Notes"
+msgstr "Rocky Series Release Notes"
+
+msgid "Stein Series Release Notes"
+msgstr "Stein Series Release Notes"
+
+msgid "The ``rbac-create`` command include a --type qos-policy option."
+msgstr "The ``rbac-create`` command include a --type qos-policy option."
+
+msgid "The ``rbac-list`` command output includes a new 'type' column."
+msgstr "The ``rbac-list`` command output includes a new 'type' column."
+
+msgid ""
+"The ``subnetpool-create`` and ``subnetpool-update`` commands include a ``--"
+"is-default`` option."
+msgstr ""
+"The ``subnetpool-create`` and ``subnetpool-update`` commands include a ``--"
+"is-default`` option."
+
+msgid ""
+"The ``subnetpool-list`` and ``subnetpool-show`` command output includes the "
+"``is_default`` field."
+msgstr ""
+"The ``subnetpool-list`` and ``subnetpool-show`` command output includes the "
+"``is_default`` field."
+
+msgid ""
+"The ``tag-add`` command sets a tag on the network resource. It also includes "
+"``--resource-type``, ``--resource`` and ``--tag`` options."
+msgstr ""
+"The ``tag-add`` command sets a tag on the network resource. It also includes "
+"``--resource-type``, ``--resource`` and ``--tag`` options."
+
+msgid ""
+"This patch provides user the support to use any form of casing, thus "
+"removing the specific UPPER/lower case inputs required by different neutron "
+"CLIs."
+msgstr ""
+"This patch provides users the support to use any form of casing, thus "
+"removing the specific UPPER/lower case inputs required by different Neutron "
+"CLIs."
+
+msgid ""
+"This project no longer provides CLI support. All code has been removed. "
+"Please use openstack CLI instead. See `openstack CLI command list `_."
+msgstr ""
+"This project no longer provides CLI support. All code has been removed. "
+"Please use OpenStack CLI instead. See `openstack CLI command list `_."
+
+msgid "Train Series Release Notes"
+msgstr "Train Series Release Notes"
+
+msgid "Upgrade Notes"
+msgstr "Upgrade Notes"
+
+msgid ""
+"Using 'tenant_id' and 'tenant_name' arguments in API bindings is deprecated. "
+"Use 'project_id' and 'project_name' arguments instead."
+msgstr ""
+"Using 'tenant_id' and 'tenant_name' arguments in API bindings is deprecated. "
+"Use 'project_id' and 'project_name' arguments instead."
+
+msgid "Ussuri Series Release Notes"
+msgstr "Ussuri Series Release Notes"
+
+msgid "Victoria Series Release Notes"
+msgstr "Victoria Series Release Notes"
+
+msgid "Wallaby Series Release Notes"
+msgstr "Wallaby Series Release Notes"
+
+msgid "XML request format support has been removed."
+msgstr "XML request format support has been removed."
+
+msgid "Xena Series Release Notes"
+msgstr "Xena Series Release Notes"
+
+msgid "Yoga Series Release Notes"
+msgstr "Yoga Series Release Notes"
+
+msgid "Zed Series Release Notes"
+msgstr "Zed Series Release Notes"
+
+msgid "add --endpoint-type and OS_ENDPOINT_TYPE to shell client"
+msgstr "add --endpoint-type and OS_ENDPOINT_TYPE to shell client"
+
+msgid "add Lbaas commands"
+msgstr "add Lbaas commands"
+
+msgid "add NVP queue and net gateway commands"
+msgstr "add NVP queue and net gateway commands"
+
+msgid ""
+"add ability to update security group name (requires 2013.2-Havana or later)"
+msgstr ""
+"add ability to update security group name (requires 2013.2-Havana or later)"
+
+msgid "add commands for DHCP and L3 agents scheduling"
+msgstr "add commands for DHCP and L3 agents scheduling"
+
+msgid "add commands for agent management extensions"
+msgstr "add commands for agent management extensions"
+
+msgid "add flake8 and pbr support for testing and building"
+msgstr "add flake8 and pbr support for testing and building"
+
+msgid "add security group commands"
+msgstr "add security group commands"
+
+msgid "allow options put after positional arguments"
+msgstr "allow options put after positional arguments"
+
+msgid "improved support for listing a large number of filtered subnets"
+msgstr "improved support for listing a large number of filtered subnets"
+
+msgid "made the publicURL the default endpoint instead of adminURL"
+msgstr "made the publicURL the default endpoint instead of adminURL"
+
+msgid "openstack firewall rule create"
+msgstr "openstack firewall rule create"
+
+msgid "openstack firewall rule set"
+msgstr "openstack firewall rule set"
+
+msgid "openstack firewall rule unset"
+msgstr "openstack firewall rule unset"
+
+msgid "py26 support has been dropped."
+msgstr "py26 support has been dropped."
+
+msgid "py33 support has been dropped."
+msgstr "py33 support has been dropped."
+
+msgid "request-format option is deprecated."
+msgstr "request-format option is deprecated."
+
+msgid "support Neutron API 2.0"
+msgstr "support Neutron API 2.0"
+
+msgid "support XML request format"
+msgstr "support XML request format"
+
+msgid "support pagination options"
+msgstr "support pagination options"
diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst
new file mode 100644
index 000000000..e54560965
--- /dev/null
+++ b/releasenotes/source/mitaka.rst
@@ -0,0 +1,6 @@
+===================================
+ Mitaka Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: origin/stable/mitaka
diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst
new file mode 100644
index 000000000..97036ed25
--- /dev/null
+++ b/releasenotes/source/newton.rst
@@ -0,0 +1,6 @@
+===================================
+ Newton Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: origin/stable/newton
diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst
new file mode 100644
index 000000000..ebe62f42e
--- /dev/null
+++ b/releasenotes/source/ocata.rst
@@ -0,0 +1,6 @@
+===================================
+ Ocata Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: origin/stable/ocata
diff --git a/releasenotes/source/old_relnotes.rst b/releasenotes/source/old_relnotes.rst
new file mode 100644
index 000000000..6a571a7b4
--- /dev/null
+++ b/releasenotes/source/old_relnotes.rst
@@ -0,0 +1,29 @@
+=================
+Old Release Notes
+=================
+
+2.2.2
+-----
+
+* improved support for listing a large number of filtered subnets
+* add --endpoint-type and OS_ENDPOINT_TYPE to shell client
+* 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
+
+2.2.0
+-----
+
+* add security group commands
+* add Lbaas commands
+* allow options put after positional arguments
+* add NVP queue and net gateway commands
+* add commands for agent management extensions
+* add commands for DHCP and L3 agents scheduling
+* support XML request format
+* support pagination options
+
+2.0
+-----
+
+* support Neutron API 2.0
diff --git a/releasenotes/source/pike.rst b/releasenotes/source/pike.rst
new file mode 100644
index 000000000..e43bfc0ce
--- /dev/null
+++ b/releasenotes/source/pike.rst
@@ -0,0 +1,6 @@
+===================================
+ Pike Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: stable/pike
diff --git a/releasenotes/source/queens.rst b/releasenotes/source/queens.rst
new file mode 100644
index 000000000..36ac6160c
--- /dev/null
+++ b/releasenotes/source/queens.rst
@@ -0,0 +1,6 @@
+===================================
+ Queens Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: stable/queens
diff --git a/releasenotes/source/rocky.rst b/releasenotes/source/rocky.rst
new file mode 100644
index 000000000..40dd517b7
--- /dev/null
+++ b/releasenotes/source/rocky.rst
@@ -0,0 +1,6 @@
+===================================
+ Rocky Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: stable/rocky
diff --git a/releasenotes/source/stein.rst b/releasenotes/source/stein.rst
new file mode 100644
index 000000000..efaceb667
--- /dev/null
+++ b/releasenotes/source/stein.rst
@@ -0,0 +1,6 @@
+===================================
+ Stein Series Release Notes
+===================================
+
+.. release-notes::
+ :branch: stable/stein
diff --git a/releasenotes/source/train.rst b/releasenotes/source/train.rst
new file mode 100644
index 000000000..583900393
--- /dev/null
+++ b/releasenotes/source/train.rst
@@ -0,0 +1,6 @@
+==========================
+Train Series Release Notes
+==========================
+
+.. release-notes::
+ :branch: stable/train
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 000000000..cd22aabcc
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+==============================
+ Current Series Release Notes
+==============================
+
+.. release-notes::
diff --git a/releasenotes/source/ussuri.rst b/releasenotes/source/ussuri.rst
new file mode 100644
index 000000000..e21e50e0c
--- /dev/null
+++ b/releasenotes/source/ussuri.rst
@@ -0,0 +1,6 @@
+===========================
+Ussuri Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/ussuri
diff --git a/releasenotes/source/victoria.rst b/releasenotes/source/victoria.rst
new file mode 100644
index 000000000..8ce933419
--- /dev/null
+++ b/releasenotes/source/victoria.rst
@@ -0,0 +1,6 @@
+=============================
+Victoria Series Release Notes
+=============================
+
+.. release-notes::
+ :branch: unmaintained/victoria
diff --git a/releasenotes/source/wallaby.rst b/releasenotes/source/wallaby.rst
new file mode 100644
index 000000000..bcf35c5f8
--- /dev/null
+++ b/releasenotes/source/wallaby.rst
@@ -0,0 +1,6 @@
+============================
+Wallaby Series Release Notes
+============================
+
+.. release-notes::
+ :branch: unmaintained/wallaby
diff --git a/releasenotes/source/xena.rst b/releasenotes/source/xena.rst
new file mode 100644
index 000000000..d19eda488
--- /dev/null
+++ b/releasenotes/source/xena.rst
@@ -0,0 +1,6 @@
+=========================
+Xena Series Release Notes
+=========================
+
+.. release-notes::
+ :branch: unmaintained/xena
diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst
new file mode 100644
index 000000000..43cafdea8
--- /dev/null
+++ b/releasenotes/source/yoga.rst
@@ -0,0 +1,6 @@
+=========================
+Yoga Series Release Notes
+=========================
+
+.. release-notes::
+ :branch: unmaintained/yoga
diff --git a/releasenotes/source/zed.rst b/releasenotes/source/zed.rst
new file mode 100644
index 000000000..6cc2b1554
--- /dev/null
+++ b/releasenotes/source/zed.rst
@@ -0,0 +1,6 @@
+========================
+Zed Series Release Notes
+========================
+
+.. release-notes::
+ :branch: unmaintained/zed
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 000000000..41ca9d7ef
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,18 @@
+# Requirements lower bounds listed here are our best effort to keep them up to
+# date but we do not test them so no guarantee of having them all correct. If
+# you find any incorrect lower bounds, let us know or propose a fix.
+pbr!=2.1.0,>=2.0.0 # Apache-2.0
+cliff>=3.4.0 # Apache-2.0
+debtcollector>=1.2.0 # Apache-2.0
+netaddr>=0.7.18 # BSD
+openstacksdk>=1.5.0 # Apache-2.0
+osc-lib>=1.12.0 # Apache-2.0
+oslo.i18n>=3.15.3 # Apache-2.0
+oslo.log>=3.36.0 # Apache-2.0
+oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
+oslo.utils>=3.33.0 # Apache-2.0
+keystoneauth1>=3.8.0 # Apache-2.0
+# keystoneclient is used only by neutronclient.osc.utils
+# TODO(amotoki): Drop this after osc.utils has no dependency on keystoneclient
+python-keystoneclient>=3.8.0 # Apache-2.0
+requests>=2.14.2 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 48000a777..a995bae67 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,120 @@
-[nosetests]
-# NOTE(jkoelker) To run the test suite under nose install the following
-# coverage http://pypi.python.org/pypi/coverage
-# tissue http://pypi.python.org/pypi/tissue (pep8 checker)
-# openstack-nose https://github.com/jkoelker/openstack-nose
-verbosity=2
-detailed-errors=1
+[metadata]
+name = python-neutronclient
+summary = CLI and Client Library for OpenStack Networking
+description_file =
+ README.rst
+author = OpenStack Networking Project
+author_email = openstack-discuss@lists.openstack.org
+home_page = https://docs.openstack.org/python-neutronclient/latest/
+python_requires = >=3.9
+classifier =
+ Environment :: OpenStack
+ Intended Audience :: Developers
+ Intended Audience :: Information Technology
+ Intended Audience :: System Administrators
+ License :: OSI Approved :: Apache Software License
+ Operating System :: POSIX :: Linux
+ Programming Language :: Python
+ Programming Language :: Python :: Implementation :: CPython
+ Programming Language :: Python :: 3 :: Only
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.9
+ Programming Language :: Python :: 3.10
+ Programming Language :: Python :: 3.11
+ Programming Language :: Python :: 3.12
+
+[files]
+packages =
+ neutronclient
+
+[entry_points]
+openstack.cli.extension =
+ neutronclient = neutronclient.osc.plugin
+
+openstack.neutronclient.v2 =
+ sfc_flow_classifier_create = neutronclient.osc.v2.sfc.sfc_flow_classifier:CreateSfcFlowClassifier
+ sfc_flow_classifier_delete = neutronclient.osc.v2.sfc.sfc_flow_classifier:DeleteSfcFlowClassifier
+ sfc_flow_classifier_list = neutronclient.osc.v2.sfc.sfc_flow_classifier:ListSfcFlowClassifier
+ sfc_flow_classifier_set = neutronclient.osc.v2.sfc.sfc_flow_classifier:SetSfcFlowClassifier
+ sfc_flow_classifier_show = neutronclient.osc.v2.sfc.sfc_flow_classifier:ShowSfcFlowClassifier
+ sfc_port_chain_create = neutronclient.osc.v2.sfc.sfc_port_chain:CreateSfcPortChain
+ sfc_port_chain_delete = neutronclient.osc.v2.sfc.sfc_port_chain:DeleteSfcPortChain
+ sfc_port_chain_list = neutronclient.osc.v2.sfc.sfc_port_chain:ListSfcPortChain
+ sfc_port_chain_set = neutronclient.osc.v2.sfc.sfc_port_chain:SetSfcPortChain
+ sfc_port_chain_show = neutronclient.osc.v2.sfc.sfc_port_chain:ShowSfcPortChain
+ sfc_port_chain_unset = neutronclient.osc.v2.sfc.sfc_port_chain:UnsetSfcPortChain
+ sfc_port_pair_create = neutronclient.osc.v2.sfc.sfc_port_pair:CreateSfcPortPair
+ sfc_port_pair_delete = neutronclient.osc.v2.sfc.sfc_port_pair:DeleteSfcPortPair
+ sfc_port_pair_list = neutronclient.osc.v2.sfc.sfc_port_pair:ListSfcPortPair
+ sfc_port_pair_set = neutronclient.osc.v2.sfc.sfc_port_pair:SetSfcPortPair
+ sfc_port_pair_show = neutronclient.osc.v2.sfc.sfc_port_pair:ShowSfcPortPair
+ sfc_port_pair_group_create = neutronclient.osc.v2.sfc.sfc_port_pair_group:CreateSfcPortPairGroup
+ sfc_port_pair_group_delete = neutronclient.osc.v2.sfc.sfc_port_pair_group:DeleteSfcPortPairGroup
+ sfc_port_pair_group_list = neutronclient.osc.v2.sfc.sfc_port_pair_group:ListSfcPortPairGroup
+ sfc_port_pair_group_set = neutronclient.osc.v2.sfc.sfc_port_pair_group:SetSfcPortPairGroup
+ sfc_port_pair_group_show = neutronclient.osc.v2.sfc.sfc_port_pair_group:ShowSfcPortPairGroup
+ sfc_port_pair_group_unset = neutronclient.osc.v2.sfc.sfc_port_pair_group:UnsetSfcPortPairGroup
+ sfc_service_graph_create = neutronclient.osc.v2.sfc.sfc_service_graph:CreateSfcServiceGraph
+ sfc_service_graph_delete = neutronclient.osc.v2.sfc.sfc_service_graph:DeleteSfcServiceGraph
+ sfc_service_graph_set = neutronclient.osc.v2.sfc.sfc_service_graph:SetSfcServiceGraph
+ sfc_service_graph_list = neutronclient.osc.v2.sfc.sfc_service_graph:ListSfcServiceGraph
+ sfc_service_graph_show = neutronclient.osc.v2.sfc.sfc_service_graph:ShowSfcServiceGraph
+
+ bgp_dragent_add_speaker = neutronclient.osc.v2.dynamic_routing.bgp_dragent:AddBgpSpeakerToDRAgent
+ bgp_dragent_list = neutronclient.osc.v2.dynamic_routing.bgp_dragent:ListDRAgent
+ bgp_dragent_remove_speaker = neutronclient.osc.v2.dynamic_routing.bgp_dragent:RemoveBgpSpeakerFromDRAgent
+ bgp_peer_create = neutronclient.osc.v2.dynamic_routing.bgp_peer:CreateBgpPeer
+ bgp_peer_delete = neutronclient.osc.v2.dynamic_routing.bgp_peer:DeleteBgpPeer
+ bgp_peer_list = neutronclient.osc.v2.dynamic_routing.bgp_peer:ListBgpPeer
+ bgp_peer_show = neutronclient.osc.v2.dynamic_routing.bgp_peer:ShowBgpPeer
+ bgp_peer_set = neutronclient.osc.v2.dynamic_routing.bgp_peer:SetBgpPeer
+ bgp_speaker_list_advertised_routes = neutronclient.osc.v2.dynamic_routing.bgp_speaker:ListRoutesAdvertisedBySpeaker
+ bgp_speaker_create = neutronclient.osc.v2.dynamic_routing.bgp_speaker:CreateBgpSpeaker
+ bgp_speaker_delete = neutronclient.osc.v2.dynamic_routing.bgp_speaker:DeleteBgpSpeaker
+ bgp_speaker_list = neutronclient.osc.v2.dynamic_routing.bgp_speaker:ListBgpSpeaker
+ bgp_speaker_add_network = neutronclient.osc.v2.dynamic_routing.bgp_speaker:AddNetworkToSpeaker
+ bgp_speaker_remove_network = neutronclient.osc.v2.dynamic_routing.bgp_speaker:RemoveNetworkFromSpeaker
+ bgp_speaker_add_peer = neutronclient.osc.v2.dynamic_routing.bgp_speaker:AddPeerToSpeaker
+ bgp_speaker_remove_peer = neutronclient.osc.v2.dynamic_routing.bgp_speaker:RemovePeerFromSpeaker
+ bgp_speaker_set = neutronclient.osc.v2.dynamic_routing.bgp_speaker:SetBgpSpeaker
+ bgp_speaker_show = neutronclient.osc.v2.dynamic_routing.bgp_speaker:ShowBgpSpeaker
+
+
+ network_loggable_resources_list = neutronclient.osc.v2.logging.network_log:ListLoggableResource
+ network_log_create = neutronclient.osc.v2.logging.network_log:CreateNetworkLog
+ network_log_delete = neutronclient.osc.v2.logging.network_log:DeleteNetworkLog
+ network_log_list = neutronclient.osc.v2.logging.network_log:ListNetworkLog
+ network_log_set = neutronclient.osc.v2.logging.network_log:SetNetworkLog
+ network_log_show = neutronclient.osc.v2.logging.network_log:ShowNetworkLog
+
+ vpn_endpoint_group_create = neutronclient.osc.v2.vpnaas.endpoint_group:CreateEndpointGroup
+ vpn_endpoint_group_delete = neutronclient.osc.v2.vpnaas.endpoint_group:DeleteEndpointGroup
+ vpn_endpoint_group_list = neutronclient.osc.v2.vpnaas.endpoint_group:ListEndpointGroup
+ vpn_endpoint_group_set = neutronclient.osc.v2.vpnaas.endpoint_group:SetEndpointGroup
+ vpn_endpoint_group_show = neutronclient.osc.v2.vpnaas.endpoint_group:ShowEndpointGroup
+
+ vpn_ike_policy_create = neutronclient.osc.v2.vpnaas.ikepolicy:CreateIKEPolicy
+ vpn_ike_policy_delete = neutronclient.osc.v2.vpnaas.ikepolicy:DeleteIKEPolicy
+ vpn_ike_policy_list = neutronclient.osc.v2.vpnaas.ikepolicy:ListIKEPolicy
+ vpn_ike_policy_set = neutronclient.osc.v2.vpnaas.ikepolicy:SetIKEPolicy
+ vpn_ike_policy_show = neutronclient.osc.v2.vpnaas.ikepolicy:ShowIKEPolicy
+
+ vpn_ipsec_policy_create = neutronclient.osc.v2.vpnaas.ipsecpolicy:CreateIPsecPolicy
+ vpn_ipsec_policy_delete = neutronclient.osc.v2.vpnaas.ipsecpolicy:DeleteIPsecPolicy
+ vpn_ipsec_policy_list = neutronclient.osc.v2.vpnaas.ipsecpolicy:ListIPsecPolicy
+ vpn_ipsec_policy_set = neutronclient.osc.v2.vpnaas.ipsecpolicy:SetIPsecPolicy
+ vpn_ipsec_policy_show = neutronclient.osc.v2.vpnaas.ipsecpolicy:ShowIPsecPolicy
+
+ vpn_service_create = neutronclient.osc.v2.vpnaas.vpnservice:CreateVPNService
+ vpn_service_delete = neutronclient.osc.v2.vpnaas.vpnservice:DeleteVPNService
+ vpn_service_list = neutronclient.osc.v2.vpnaas.vpnservice:ListVPNService
+ vpn_service_set = neutronclient.osc.v2.vpnaas.vpnservice:SetVPNSercice
+ vpn_service_show = neutronclient.osc.v2.vpnaas.vpnservice:ShowVPNService
+
+ vpn_ipsec_site_connection_create = neutronclient.osc.v2.vpnaas.ipsec_site_connection:CreateIPsecSiteConnection
+ vpn_ipsec_site_connection_delete = neutronclient.osc.v2.vpnaas.ipsec_site_connection:DeleteIPsecSiteConnection
+ vpn_ipsec_site_connection_list = neutronclient.osc.v2.vpnaas.ipsec_site_connection:ListIPsecSiteConnection
+ vpn_ipsec_site_connection_set = neutronclient.osc.v2.vpnaas.ipsec_site_connection:SetIPsecSiteConnection
+ vpn_ipsec_site_connection_show = neutronclient.osc.v2.vpnaas.ipsec_site_connection:ShowIPsecSiteConnection
+
+ network_onboard_subnets = neutronclient.osc.v2.subnet_onboard.subnet_onboard:NetworkOnboardSubnets
diff --git a/setup.py b/setup.py
index 087e1546b..cd35c3c35 100644
--- a/setup.py
+++ b/setup.py
@@ -1,71 +1,20 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 Citrix Systems
-# All Rights Reserved.
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
-# 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
+# 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
+# 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.
-
-try:
- from setuptools import setup, find_packages
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup, find_packages
-
-import version
-
-Name = 'python-quantumclient'
-Url = "https://launchpad.net/quantum"
-Version = version.canonical_version_string()
-License = 'Apache License 2.0'
-Author = 'Netstack'
-AuthorEmail = 'netstack@lists.launchpad.net'
-Maintainer = ''
-Summary = 'Client functionalities for Quantum'
-ShortDescription = Summary
-Description = Summary
-
-requires = [
-]
-
-EagerResources = [
-]
-
-ProjectScripts = [
-]
-
-PackageData = {
-}
-
-
-setup(
- name=Name,
- version=Version,
- url=Url,
- author=Author,
- author_email=AuthorEmail,
- description=ShortDescription,
- long_description=Description,
- license=License,
- scripts=ProjectScripts,
- install_requires=requires,
- include_package_data=False,
- packages=["quantumclient", "quantumclient.common"],
- package_data=PackageData,
- eager_resources=EagerResources,
- entry_points={
- 'console_scripts': [
- 'quantum = quantumclient.cli:main'
- ]
- },
-)
+# 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 setuptools
+
+setuptools.setup(
+ setup_requires=['pbr>=2.0.0'],
+ pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 000000000..d6a160c26
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,12 @@
+hacking>=6.1.0,<6.2.0 # Apache-2.0
+
+bandit!=1.6.0,>=1.1.0 # Apache-2.0
+coverage!=4.4,>=4.0 # Apache-2.0
+fixtures>=3.0.0 # Apache-2.0/BSD
+flake8-import-order>=0.19.0,<0.20.0 # LGPLv3
+oslotest>=3.2.0 # Apache-2.0
+osprofiler>=2.3.0 # Apache-2.0
+python-openstackclient>=3.12.0 # Apache-2.0
+requests-mock>=1.2.0 # Apache-2.0
+stestr>=2.0.0 # Apache-2.0
+testtools>=2.2.0 # MIT
diff --git a/tools/test-requires b/tools/test-requires
deleted file mode 100644
index a048ab095..000000000
--- a/tools/test-requires
+++ /dev/null
@@ -1,11 +0,0 @@
-distribute>=0.6.24
-
-mox
-nose
-nose-exclude
-nosexcover
-openstack.nose_plugin
-pep8==0.6.1
-sphinx>=1.1.2
-
--e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev
diff --git a/tox.ini b/tox.ini
index a5feeffaa..a939f8b65 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,57 +1,94 @@
[tox]
-envlist = py26,py27,pep8
+envlist = py39,pep8
+minversion = 3.18.0
+skipsdist = False
+ignore_basepython_conflict = True
[testenv]
-setenv =
- VIRTUAL_ENV={envdir}
- NOSE_WITH_OPENSTACK=1
- NOSE_OPENSTACK_COLOR=1
- NOSE_OPENSTACK_RED=0.05
- NOSE_OPENSTACK_YELLOW=0.025
- NOSE_OPENSTACK_SHOW_ELAPSED=1
-deps =
- -r{toxinidir}/tools/test-requires
-commands = nosetests
+basepython = python3
+setenv = VIRTUAL_ENV={envdir}
+ LANG=en_US.UTF-8
+ LANGUAGE=en_US:en
+ LC_ALL=C
+ PYTHONWARNINGS=default::DeprecationWarning
+usedevelop = True
+deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
+# 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 = bash -c "find . -type d -name '.?*' -prune -o \
+ \( -type d -name '__pycache__' -o -type f -name '*.py[co]' \) \
+ -print0 | xargs -0 rm -rf"
+ stestr run {posargs}
+allowlist_externals = bash
[testenv:pep8]
commands =
- pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \
- setup.py version.py
+ flake8
+ {[testenv:bandit]commands}
+distribute = false
[testenv:venv]
commands = {posargs}
[testenv:cover]
+setenv =
+ {[testenv]setenv}
+ PYTHON=coverage run --source neutronclient --parallel-mode
commands =
- nosetests --with-coverage --cover-html --cover-erase \
- --cover-package=quantumclient
+ stestr run {posargs}
+ coverage combine
+ coverage html -d cover
+ coverage xml -o cover/coverage.xml
+ coverage report
-[tox:jenkins]
-downloadcache = ~/cache/pip
+[testenv:docs]
+deps =
+ -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+ -r{toxinidir}/doc/requirements.txt
+commands = sphinx-build -W -b html doc/source doc/build/html
-[testenv:jenkins26]
-basepython = python2.6
-setenv = NOSE_WITH_XUNIT=1
-deps = file://{toxinidir}/.cache.bundle
+[testenv:pdf-docs]
+deps = {[testenv:docs]deps}
+allowlist_externals =
+ make
+commands =
+ sphinx-build -W -b latex doc/source doc/build/pdf
+ make -C doc/build/pdf
-[testenv:jenkins27]
-basepython = python2.7
-setenv = NOSE_WITH_XUNIT=1
-deps = file://{toxinidir}/.cache.bundle
+[testenv:releasenotes]
+deps =
+ -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+ -r{toxinidir}/doc/requirements.txt
+commands = sphinx-build -a -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
-[testenv:jenkinspep8]
-deps = file://{toxinidir}/.cache.bundle
-commands =
- pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \
- setup.py version.py
+[flake8]
+# E126 continuation line over-indented for hanging indent
+# E128 continuation line under-indented for visual indent
+# H405 multi line docstring summary not separated with an empty line
+# I202 Additional newline in a group of imports
+# N530 direct neutron imports not allowed
+# TODO(amotoki) check the following new rules should be fixed or ignored
+# E731 do not assign a lambda expression, use a def
+# W504 line break after binary operator
+ignore = E126,E128,E731,I202,H405,N530,W504
+show-source = true
+exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools
+import-order-style = pep8
-[testenv:jenkinscover]
-deps = file://{toxinidir}/.cache.bundle
-setenv = NOSE_WITH_XUNIT=1
-commands =
- nosetests --cover-erase --cover-package=quantumclient --with-xcoverage
+# H106: Don't put vim configuration in source files
+# H203: Use assertIs(Not)None to check for None
+# H204: Use assert(Not)Equal to check for equality
+# H205: Use assert(Greater|Less)(Equal) for comparison
+# H904: Delay string interpolations at logging calls
+enable-extensions=H106,H203,H204,H205,H904
-[testenv:jenkinsvenv]
-deps = file://{toxinidir}/.cache.bundle
-setenv = NOSE_WITH_XUNIT=1
-commands = {posargs}
+[testenv:bandit]
+# B303: blacklist calls: md5, sha1
+# B105: The software contains a hard-coded password, which it uses for its own
+# inbound authentication or for outbound communication to external
+# components.
+deps = -r{toxinidir}/test-requirements.txt
+commands = bandit -r neutronclient -x tests -n5 -s B303,B105
diff --git a/version.py b/version.py
deleted file mode 100644
index fae32acd3..000000000
--- a/version.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 OpenStack LLC
-#
-# 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.
-
-try:
- from quantumclient.vcsversion import version_info
-except ImportError:
- version_info = {'branch_nick': u'LOCALBRANCH',
- 'revision_id': 'LOCALREVISION',
- 'revno': 0}
-
-QUANTUM_VERSION = ['2012', '2', None]
-YEAR, COUNT, REVSISION = QUANTUM_VERSION
-
-FINAL = False # This becomes true at Release Candidate time
-
-
-def canonical_version_string():
- return '.'.join(filter(None, QUANTUM_VERSION))
-
-
-def version_string():
- if FINAL:
- return canonical_version_string()
- else:
- return '%s-dev' % (canonical_version_string(),)
-
-
-def vcs_version_string():
- return "%s:%s" % (version_info['branch_nick'], version_info['revision_id'])
-
-
-def version_string_with_vcs():
- return "%s-%s" % (canonical_version_string(), vcs_version_string())