summaryrefslogtreecommitdiff
path: root/neutronclient
diff options
context:
space:
mode:
Diffstat (limited to 'neutronclient')
-rw-r--r--neutronclient/client.py10
-rw-r--r--neutronclient/common/exceptions.py6
-rw-r--r--neutronclient/common/extension.py86
-rw-r--r--neutronclient/neutron/v2_0/__init__.py6
-rw-r--r--neutronclient/neutron/v2_0/contrib/__init__.py0
-rw-r--r--neutronclient/neutron/v2_0/contrib/_fox_sockets.py85
-rw-r--r--neutronclient/neutron/v2_0/credential.py4
-rw-r--r--neutronclient/neutron/v2_0/lb/v2/healthmonitor.py8
-rw-r--r--neutronclient/neutron/v2_0/lb/v2/listener.py15
-rw-r--r--neutronclient/neutron/v2_0/lb/v2/pool.py11
-rw-r--r--neutronclient/neutron/v2_0/networkprofile.py2
-rw-r--r--neutronclient/shell.py24
-rw-r--r--neutronclient/tests/functional/__init__.py0
-rw-r--r--neutronclient/tests/functional/base.py44
-rwxr-xr-xneutronclient/tests/functional/hooks/post_test_hook.sh50
-rw-r--r--neutronclient/tests/functional/test_readonly_neutron.py199
-rw-r--r--neutronclient/tests/unit/lb/v2/test_cli20_healthmonitor.py14
-rw-r--r--neutronclient/tests/unit/lb/v2/test_cli20_listener.py11
-rw-r--r--neutronclient/tests/unit/lb/v2/test_cli20_pool.py18
-rw-r--r--neutronclient/tests/unit/test_cli20.py20
-rw-r--r--neutronclient/tests/unit/test_cli20_network.py6
-rw-r--r--neutronclient/tests/unit/test_cli20_securitygroup.py6
-rw-r--r--neutronclient/tests/unit/test_client_extension.py87
-rw-r--r--neutronclient/v2_0/client.py100
24 files changed, 754 insertions, 58 deletions
diff --git a/neutronclient/client.py b/neutronclient/client.py
index 0a68ff0..bc645f1 100644
--- a/neutronclient/client.py
+++ b/neutronclient/client.py
@@ -48,9 +48,6 @@ class HTTPClient(object):
USER_AGENT = 'python-neutronclient'
CONTENT_TYPE = 'application/json'
- # 8192 Is the default max URI len for eventlet.wsgi.server
- MAX_URI_LEN = 8192
-
def __init__(self, username=None, user_id=None,
tenant_name=None, tenant_id=None,
password=None, auth_url=None,
@@ -149,16 +146,9 @@ class HTTPClient(object):
return resp, resp.text
- def _check_uri_length(self, action):
- uri_len = len(self.endpoint_url) + len(action)
- if uri_len > self.MAX_URI_LEN:
- raise exceptions.RequestURITooLong(
- excess=uri_len - self.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
diff --git a/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py
index 9728fbd..9a12f19 100644
--- a/neutronclient/common/exceptions.py
+++ b/neutronclient/common/exceptions.py
@@ -148,12 +148,6 @@ class OverQuotaClient(Conflict):
pass
-# TODO(amotoki): It is unused in Neutron, but it is referred to
-# in Horizon code. After Horizon code is updated, remove it.
-class AlreadyAttachedClient(Conflict):
- pass
-
-
class IpAddressGenerationFailureClient(Conflict):
pass
diff --git a/neutronclient/common/extension.py b/neutronclient/common/extension.py
new file mode 100644
index 0000000..2ff8d34
--- /dev/null
+++ b/neutronclient/common/extension.py
@@ -0,0 +1,86 @@
+# 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 get_data(self, parsed_args):
+ # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
+ # for any implementers adding extensions with
+ # regard to any other extension verb.
+ return self.execute(parsed_args)
+
+ def execute(self, parsed_args):
+ return super(ClientExtensionShow, self).get_data(parsed_args)
+
+
+class ClientExtensionList(NeutronClientExtension, neutronV20.ListCommand):
+
+ def get_data(self, parsed_args):
+ # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
+ # for any implementers adding extensions with
+ # regard to any other extension verb.
+ return self.execute(parsed_args)
+
+ def execute(self, parsed_args):
+ return super(ClientExtensionList, self).get_data(parsed_args)
+
+
+class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand):
+ def run(self, parsed_args):
+ # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
+ # for any implementers adding extensions with
+ # regard to any other extension verb.
+ return self.execute(parsed_args)
+
+ def execute(self, parsed_args):
+ return super(ClientExtensionDelete, self).run(parsed_args)
+
+
+class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand):
+ def get_data(self, parsed_args):
+ # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
+ # for any implementers adding extensions with
+ # regard to any other extension verb.
+ return self.execute(parsed_args)
+
+ def execute(self, parsed_args):
+ return super(ClientExtensionCreate, self).get_data(parsed_args)
+
+
+class ClientExtensionUpdate(NeutronClientExtension, neutronV20.UpdateCommand):
+ def run(self, parsed_args):
+ # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
+ # for any implementers adding extensions with
+ # regard to any other extension verb.
+ return self.execute(parsed_args)
+
+ def execute(self, parsed_args):
+ return super(ClientExtensionUpdate, self).run(parsed_args)
diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py
index 1ca26e2..2fca3b3 100644
--- a/neutronclient/neutron/v2_0/__init__.py
+++ b/neutronclient/neutron/v2_0/__init__.py
@@ -492,9 +492,13 @@ class UpdateCommand(NeutronCommand):
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.')
parser.add_argument(
'id', metavar=self.resource.upper(),
- help=_('ID or name of %s to update.') % self.resource)
+ help=help_str % self.resource)
self.add_known_arguments(parser)
return parser
diff --git a/neutronclient/neutron/v2_0/contrib/__init__.py b/neutronclient/neutron/v2_0/contrib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutronclient/neutron/v2_0/contrib/__init__.py
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 0000000..da88eb1
--- /dev/null
+++ b/neutronclient/neutron/v2_0/contrib/_fox_sockets.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 neutronclient.common import extension
+from neutronclient.i18n import _
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+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['fox_socket'].update({'name': parsed_args.name})
+
+
+class FoxInSocket(extension.NeutronClientExtension):
+ 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):
+ shell_command = 'fox-sockets-list'
+ list_columns = ['id', 'name']
+ pagination_support = True
+ sorting_support = True
+
+
+class FoxInSocketsCreate(extension.ClientExtensionCreate, FoxInSocket):
+ 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 = {'fox_socket': {}}
+ client = self.get_client()
+ _updatable_args2body(parsed_args, body, client)
+ neutronV20.update_dict(parsed_args, body['fox_socket'], [])
+ return body
+
+
+class FoxInSocketsUpdate(extension.ClientExtensionUpdate, FoxInSocket):
+ 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 = {'fox_socket': {
+ 'name': parsed_args.name}, }
+ neutronV20.update_dict(parsed_args, body['fox_socket'], [])
+ return body
+
+
+class FoxInSocketsDelete(extension.ClientExtensionDelete, FoxInSocket):
+ shell_command = 'fox-sockets-delete'
+
+
+class FoxInSocketsShow(extension.ClientExtensionShow, FoxInSocket):
+ shell_command = 'fox-sockets-show'
diff --git a/neutronclient/neutron/v2_0/credential.py b/neutronclient/neutron/v2_0/credential.py
index ffec5ee..c397627 100644
--- a/neutronclient/neutron/v2_0/credential.py
+++ b/neutronclient/neutron/v2_0/credential.py
@@ -32,7 +32,7 @@ class ShowCredential(neutronV20.ShowCommand):
class CreateCredential(neutronV20.CreateCommand):
- """Creates a credential."""
+ """Create a credential."""
resource = 'credential'
@@ -67,7 +67,7 @@ class CreateCredential(neutronV20.CreateCommand):
class DeleteCredential(neutronV20.DeleteCommand):
- """Delete a given credential."""
+ """Delete a given credential."""
resource = 'credential'
allow_names = False
diff --git a/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py b/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py
index 23136f7..ff1c11e 100644
--- a/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py
+++ b/neutronclient/neutron/v2_0/lb/v2/healthmonitor.py
@@ -86,8 +86,15 @@ class CreateHealthMonitor(neutronV20.CreateCommand):
'--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 = {
self.resource: {
'admin_state_up': parsed_args.admin_state,
@@ -95,6 +102,7 @@ class CreateHealthMonitor(neutronV20.CreateCommand):
'max_retries': parsed_args.max_retries,
'timeout': parsed_args.timeout,
'type': parsed_args.type,
+ 'pool_id': pool_id
},
}
neutronV20.update_dict(parsed_args, body[self.resource],
diff --git a/neutronclient/neutron/v2_0/lb/v2/listener.py b/neutronclient/neutron/v2_0/lb/v2/listener.py
index 6b398ff..6ce9944 100644
--- a/neutronclient/neutron/v2_0/lb/v2/listener.py
+++ b/neutronclient/neutron/v2_0/lb/v2/listener.py
@@ -66,6 +66,15 @@ class CreateListener(neutronV20.CreateCommand):
'--name',
help=_('The name of the listener.'))
parser.add_argument(
+ '--default-tls-container-id',
+ dest='default_tls_container_id',
+ help=_('Default TLS container ID to retrieve TLS information.'))
+ parser.add_argument(
+ '--sni-container-ids',
+ dest='sni_container_ids',
+ nargs='+',
+ help=_('List of TLS container IDs for SNI.'))
+ parser.add_argument(
'--loadbalancer',
required=True,
metavar='LOADBALANCER',
@@ -73,7 +82,7 @@ class CreateListener(neutronV20.CreateCommand):
parser.add_argument(
'--protocol',
required=True,
- choices=['TCP', 'HTTP', 'HTTPS'],
+ choices=['TCP', 'HTTP', 'HTTPS', 'TERMINATED_HTTPS'],
help=_('Protocol for the listener.'))
parser.add_argument(
'--protocol-port',
@@ -97,7 +106,9 @@ class CreateListener(neutronV20.CreateCommand):
neutronV20.update_dict(parsed_args, body[self.resource],
['connection-limit', 'description',
- 'loadbalancer_id', 'name'])
+ 'loadbalancer_id', 'name',
+ 'default_tls_container_id',
+ 'sni_container_ids'])
return body
diff --git a/neutronclient/neutron/v2_0/lb/v2/pool.py b/neutronclient/neutron/v2_0/lb/v2/pool.py
index 11644be..7182171 100644
--- a/neutronclient/neutron/v2_0/lb/v2/pool.py
+++ b/neutronclient/neutron/v2_0/lb/v2/pool.py
@@ -76,12 +76,11 @@ class CreatePool(neutronV20.CreateCommand):
'--description',
help=_('Description of the pool.'))
parser.add_argument(
- '--healthmonitor-id',
- help=_('ID of the health monitor to use.'))
- parser.add_argument(
'--session-persistence', metavar='TYPE:VALUE',
help=_('The type of session persistence to use.'))
parser.add_argument(
+ '--name', help=_('The name of the pool.'))
+ parser.add_argument(
'--lb-algorithm',
required=True,
choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'],
@@ -96,9 +95,6 @@ class CreatePool(neutronV20.CreateCommand):
required=True,
choices=['HTTP', 'HTTPS', 'TCP'],
help=_('Protocol for balancing.'))
- parser.add_argument(
- 'name', metavar='NAME',
- help=_('The name of the pool.'))
def args2body(self, parsed_args):
if parsed_args.session_persistence:
@@ -107,7 +103,6 @@ class CreatePool(neutronV20.CreateCommand):
self.get_client(), 'listener', parsed_args.listener)
body = {
self.resource: {
- 'name': parsed_args.name,
'admin_state_up': parsed_args.admin_state,
'protocol': parsed_args.protocol,
'lb_algorithm': parsed_args.lb_algorithm,
@@ -115,7 +110,7 @@ class CreatePool(neutronV20.CreateCommand):
},
}
neutronV20.update_dict(parsed_args, body[self.resource],
- ['description', 'healthmonitor_id',
+ ['description', 'name',
'session_persistence'])
return body
diff --git a/neutronclient/neutron/v2_0/networkprofile.py b/neutronclient/neutron/v2_0/networkprofile.py
index 03fc497..3450b32 100644
--- a/neutronclient/neutron/v2_0/networkprofile.py
+++ b/neutronclient/neutron/v2_0/networkprofile.py
@@ -42,7 +42,7 @@ class ShowNetworkProfile(neutronV20.ShowCommand):
class CreateNetworkProfile(neutronV20.CreateCommand):
- """Creates a network profile."""
+ """Create a network profile."""
resource = RESOURCE
diff --git a/neutronclient/shell.py b/neutronclient/shell.py
index 96f675c..177de52 100644
--- a/neutronclient/shell.py
+++ b/neutronclient/shell.py
@@ -22,6 +22,8 @@ from __future__ import print_function
import argparse
import getpass
+import inspect
+import itertools
import logging
import os
import sys
@@ -40,6 +42,7 @@ from cliff import commandmanager
from neutronclient.common import clientmanager
from neutronclient.common import command as openstack_command
from neutronclient.common import exceptions as exc
+from neutronclient.common import extension as client_extension
from neutronclient.common import utils
from neutronclient.i18n import _
from neutronclient.neutron.v2_0 import agent
@@ -385,6 +388,8 @@ class NeutronShell(app.App):
for k, v in self.commands[apiversion].items():
self.command_manager.add_command(k, v)
+ self._register_extensions(VERSION)
+
# Pop the 'complete' to correct the outputs of 'neutron help'.
self.command_manager.commands.pop('complete')
@@ -675,6 +680,25 @@ class NeutronShell(app.App):
options.add(option)
print(' '.join(commands | options))
+ def _register_extensions(self, version):
+ for name, module in itertools.chain(
+ client_extension._discover_via_entry_points()):
+ self._extend_shell_commands(module, version)
+
+ def _extend_shell_commands(self, module, version):
+ classes = inspect.getmembers(module, inspect.isclass)
+ for cls_name, cls in classes:
+ if (issubclass(cls, client_extension.NeutronClientExtension) and
+ hasattr(cls, 'shell_command')):
+ cmd = cls.shell_command
+ if hasattr(cls, 'versions'):
+ if version not in cls.versions:
+ continue
+ try:
+ self.command_manager.add_command(cmd, cls)
+ except TypeError:
+ pass
+
def run(self, argv):
"""Equivalent to the main program for the application.
diff --git a/neutronclient/tests/functional/__init__.py b/neutronclient/tests/functional/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutronclient/tests/functional/__init__.py
diff --git a/neutronclient/tests/functional/base.py b/neutronclient/tests/functional/base.py
new file mode 100644
index 0000000..48561d6
--- /dev/null
+++ b/neutronclient/tests/functional/base.py
@@ -0,0 +1,44 @@
+# 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 tempest_lib.cli import base
+
+
+class ClientTestBase(base.ClientTestBase):
+ """This is a first pass at a simple read only python-neutronclient test.
+ This only exercises client commands that are read only.
+
+ This should test commands:
+ * as a regular user
+ * as a admin user
+ * with and without optional parameters
+ * initially just check return codes, and later test command outputs
+
+ """
+
+ def _get_clients(self):
+ cli_dir = os.environ.get(
+ 'OS_NEUTRONCLIENT_EXEC_DIR',
+ os.path.join(os.path.abspath('.'), '.tox/functional/bin'))
+
+ return base.CLIClient(
+ username=os.environ.get('OS_USERNAME'),
+ password=os.environ.get('OS_PASSWORD'),
+ tenant_name=os.environ.get('OS_TENANT_NAME'),
+ uri=os.environ.get('OS_AUTH_URL'),
+ cli_dir=cli_dir)
+
+ def neutron(self, *args, **kwargs):
+ return self.clients.neutron(*args,
+ **kwargs)
diff --git a/neutronclient/tests/functional/hooks/post_test_hook.sh b/neutronclient/tests/functional/hooks/post_test_hook.sh
new file mode 100755
index 0000000..e0c9669
--- /dev/null
+++ b/neutronclient/tests/functional/hooks/post_test_hook.sh
@@ -0,0 +1,50 @@
+#!/bin/bash -xe
+
+# 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 script is executed inside post_test_hook function in devstack gate.
+
+function generate_testr_results {
+ if [ -f .testrepository/0 ]; then
+ sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit
+ sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
+ sudo .tox/functional/bin/python /usr/local/jenkins/slave_scripts/subunit2html.py $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
+ sudo gzip -9 $BASE/logs/testrepository.subunit
+ sudo gzip -9 $BASE/logs/testr_results.html
+ sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
+ sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
+ fi
+}
+
+export NEUTRONCLIENT_DIR="$BASE/new/python-neutronclient"
+
+# Get admin credentials
+cd $BASE/new/devstack
+source openrc admin admin
+
+# Go to the neutronclient dir
+cd $NEUTRONCLIENT_DIR
+
+sudo chown -R jenkins:stack $NEUTRONCLIENT_DIR
+
+# Run tests
+echo "Running neutronclient functional test suite"
+set +e
+# Preserve env for OS_ credentials
+sudo -E -H -u jenkins tox -efunctional
+EXIT_CODE=$?
+set -e
+
+# Collect and parse result
+generate_testr_results
+exit $EXIT_CODE
diff --git a/neutronclient/tests/functional/test_readonly_neutron.py b/neutronclient/tests/functional/test_readonly_neutron.py
new file mode 100644
index 0000000..a552ef7
--- /dev/null
+++ b/neutronclient/tests/functional/test_readonly_neutron.py
@@ -0,0 +1,199 @@
+# 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 re
+
+from tempest_lib import exceptions
+
+from neutronclient.tests.functional import base
+
+
+class SimpleReadOnlyNeutronClientTest(base.ClientTestBase):
+
+ """This is a first pass at a simple read only python-neutronclient test.
+ This only exercises client commands that are read only.
+
+ This should test commands:
+ * as a regular user
+ * as a admin user
+ * with and without optional parameters
+ * initially just check return codes, and later test command outputs
+
+ """
+
+ def test_admin_fake_action(self):
+ self.assertRaises(exceptions.CommandFailed,
+ self.neutron,
+ 'this-does-neutron-exist')
+
+ # NOTE(mestery): Commands in order listed in 'neutron help'
+
+ # Optional arguments:
+
+ def test_neutron_fake_action(self):
+ self.assertRaises(exceptions.CommandFailed,
+ self.neutron,
+ 'this-does-not-exist')
+
+ def test_neutron_net_list(self):
+ net_list = self.parser.listing(self.neutron('net-list'))
+ self.assertTableStruct(net_list, ['id', 'name', 'subnets'])
+
+ def test_neutron_ext_list(self):
+ ext = self.parser.listing(self.neutron('ext-list'))
+ self.assertTableStruct(ext, ['alias', 'name'])
+
+ def test_neutron_dhcp_agent_list_hosting_net(self):
+ self.neutron('dhcp-agent-list-hosting-net',
+ params='private')
+
+ def test_neutron_agent_list(self):
+ agents = self.parser.listing(self.neutron('agent-list'))
+ field_names = ['id', 'agent_type', 'host', 'alive', 'admin_state_up']
+ self.assertTableStruct(agents, field_names)
+
+ def test_neutron_floatingip_list(self):
+ self.neutron('floatingip-list')
+
+ def test_neutron_meter_label_list(self):
+ self.neutron('meter-label-list')
+
+ def test_neutron_meter_label_rule_list(self):
+ self.neutron('meter-label-rule-list')
+
+ def _test_neutron_lbaas_command(self, command):
+ try:
+ self.neutron(command)
+ except exceptions.CommandFailed as e:
+ if '404 Not Found' not in e.stderr:
+ self.fail('%s: Unexpected failure.' % command)
+
+ def test_neutron_lb_healthmonitor_list(self):
+ self._test_neutron_lbaas_command('lb-healthmonitor-list')
+
+ def test_neutron_lb_member_list(self):
+ self._test_neutron_lbaas_command('lb-member-list')
+
+ def test_neutron_lb_pool_list(self):
+ self._test_neutron_lbaas_command('lb-pool-list')
+
+ def test_neutron_lb_vip_list(self):
+ self._test_neutron_lbaas_command('lb-vip-list')
+
+ def test_neutron_net_external_list(self):
+ net_ext_list = self.parser.listing(self.neutron('net-external-list'))
+ self.assertTableStruct(net_ext_list, ['id', 'name', 'subnets'])
+
+ def test_neutron_port_list(self):
+ port_list = self.parser.listing(self.neutron('port-list'))
+ self.assertTableStruct(port_list, ['id', 'name', 'mac_address',
+ 'fixed_ips'])
+
+ def test_neutron_quota_list(self):
+ self.neutron('quota-list')
+
+ def test_neutron_router_list(self):
+ router_list = self.parser.listing(self.neutron('router-list'))
+ self.assertTableStruct(router_list, ['id', 'name',
+ 'external_gateway_info'])
+
+ def test_neutron_security_group_list(self):
+ security_grp = self.parser.listing(self.neutron('security-group-list'))
+ self.assertTableStruct(security_grp, ['id', 'name', 'description'])
+
+ def test_neutron_security_group_rule_list(self):
+ security_grp = self.parser.listing(self.neutron
+ ('security-group-rule-list'))
+ self.assertTableStruct(security_grp, ['id', 'security_group',
+ 'direction', 'protocol',
+ 'remote_ip_prefix',
+ 'remote_group'])
+
+ def test_neutron_subnet_list(self):
+ subnet_list = self.parser.listing(self.neutron('subnet-list'))
+ self.assertTableStruct(subnet_list, ['id', 'name', 'cidr',
+ 'allocation_pools'])
+
+ def test_neutron_vpn_ikepolicy_list(self):
+ ikepolicy = self.parser.listing(self.neutron('vpn-ikepolicy-list'))
+ self.assertTableStruct(ikepolicy, ['id', 'name',
+ 'auth_algorithm',
+ 'encryption_algorithm',
+ 'ike_version', 'pfs'])
+
+ def test_neutron_vpn_ipsecpolicy_list(self):
+ ipsecpolicy = self.parser.listing(self.neutron('vpn-ipsecpolicy-list'))
+ self.assertTableStruct(ipsecpolicy, ['id', 'name',
+ 'auth_algorithm',
+ 'encryption_algorithm',
+ 'pfs'])
+
+ def test_neutron_vpn_service_list(self):
+ vpn_list = self.parser.listing(self.neutron('vpn-service-list'))
+ self.assertTableStruct(vpn_list, ['id', 'name',
+ 'router_id', 'status'])
+
+ def test_neutron_ipsec_site_connection_list(self):
+ ipsec_site = self.parser.listing(self.neutron
+ ('ipsec-site-connection-list'))
+ self.assertTableStruct(ipsec_site, ['id', 'name',
+ 'peer_address',
+ 'peer_cidrs',
+ 'route_mode',
+ 'auth_mode', 'status'])
+
+ def test_neutron_firewall_list(self):
+ firewall_list = self.parser.listing(self.neutron
+ ('firewall-list'))
+ self.assertTableStruct(firewall_list, ['id', 'name',
+ 'firewall_policy_id'])
+
+ def test_neutron_firewall_policy_list(self):
+ firewall_policy = self.parser.listing(self.neutron
+ ('firewall-policy-list'))
+ self.assertTableStruct(firewall_policy, ['id', 'name',
+ 'firewall_rules'])
+
+ def test_neutron_firewall_rule_list(self):
+ firewall_rule = self.parser.listing(self.neutron
+ ('firewall-rule-list'))
+ self.assertTableStruct(firewall_rule, ['id', 'name',
+ 'firewall_policy_id',
+ 'summary', 'enabled'])
+
+ def test_neutron_help(self):
+ help_text = self.neutron('help')
+ lines = help_text.split('\n')
+ self.assertFirstLineStartsWith(lines, 'usage: neutron')
+
+ commands = []
+ cmds_start = lines.index('Commands for API v2.0:')
+ command_pattern = re.compile('^ {2}([a-z0-9\-\_]+)')
+ for line in lines[cmds_start:]:
+ match = command_pattern.match(line)
+ if match:
+ commands.append(match.group(1))
+ commands = set(commands)
+ wanted_commands = set(('net-create', 'subnet-list', 'port-delete',
+ 'router-show', 'agent-update', 'help'))
+ self.assertFalse(wanted_commands - commands)
+
+ # Optional arguments:
+
+ def test_neutron_version(self):
+ self.neutron('', flags='--version')
+
+ def test_neutron_debug_net_list(self):
+ self.neutron('net-list', flags='--debug')
+
+ def test_neutron_quiet_net_list(self):
+ self.neutron('net-list', flags='--quiet')
diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_healthmonitor.py b/neutronclient/tests/unit/lb/v2/test_cli20_healthmonitor.py
index 20af127..96f96a6 100644
--- a/neutronclient/tests/unit/lb/v2/test_cli20_healthmonitor.py
+++ b/neutronclient/tests/unit/lb/v2/test_cli20_healthmonitor.py
@@ -35,10 +35,11 @@ class CLITestV20LbHealthMonitorJSON(test_cli20.CLITestV20Base):
max_retries = '3'
delay = '10'
timeout = '60'
+ pool = 'pool1'
args = ['--type', type, '--max-retries', max_retries,
- '--delay', delay, '--timeout', timeout]
- position_names = ['type', 'max_retries', 'delay', 'timeout']
- position_values = [type, max_retries, delay, timeout]
+ '--delay', delay, '--timeout', timeout, '--pool', pool]
+ position_names = ['type', 'max_retries', 'delay', 'timeout', 'pool_id']
+ position_values = [type, max_retries, delay, timeout, pool]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
@@ -57,15 +58,16 @@ class CLITestV20LbHealthMonitorJSON(test_cli20.CLITestV20Base):
http_method = 'GET'
expected_codes = '201'
url_path = '/somepath'
+ pool = 'pool1'
args = ['--admin-state-down', '--http-method', http_method,
'--expected-codes', expected_codes, '--url-path', url_path,
'--type', type, '--max-retries', max_retries,
- '--delay', delay, '--timeout', timeout]
+ '--delay', delay, '--timeout', timeout, '--pool', pool]
position_names = ['admin_state_up', 'http_method', 'expected_codes',
'url_path', 'type', 'max_retries', 'delay',
- 'timeout']
+ 'timeout', 'pool_id']
position_values = [False, http_method, expected_codes, url_path,
- type, max_retries, delay, timeout]
+ type, max_retries, delay, timeout, pool]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_listener.py b/neutronclient/tests/unit/lb/v2/test_cli20_listener.py
index fc0a062..ea421a8 100644
--- a/neutronclient/tests/unit/lb/v2/test_cli20_listener.py
+++ b/neutronclient/tests/unit/lb/v2/test_cli20_listener.py
@@ -51,12 +51,17 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base):
loadbalancer = 'loadbalancer'
protocol = 'TCP'
protocol_port = '80'
+ def_tls_cont_id = '11111'
args = ['--admin-state-down',
'--protocol', protocol, '--protocol-port', protocol_port,
- '--loadbalancer', loadbalancer]
+ '--loadbalancer', loadbalancer,
+ '--default-tls-container-id', def_tls_cont_id,
+ '--sni-container-ids', '1111', '2222', '3333']
position_names = ['admin_state_up',
- 'protocol', 'protocol_port', 'loadbalancer_id']
- position_values = [False, protocol, protocol_port, loadbalancer]
+ 'protocol', 'protocol_port', 'loadbalancer_id',
+ 'default_tls_container_id', 'sni_container_ids']
+ position_values = [False, protocol, protocol_port, loadbalancer,
+ def_tls_cont_id, ['1111', '2222', '3333']]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_pool.py b/neutronclient/tests/unit/lb/v2/test_cli20_pool.py
index daf3e52..77b5655 100644
--- a/neutronclient/tests/unit/lb/v2/test_cli20_pool.py
+++ b/neutronclient/tests/unit/lb/v2/test_cli20_pool.py
@@ -33,12 +33,11 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base):
lb_algorithm = 'ROUND_ROBIN'
listener = 'listener'
protocol = 'TCP'
- name = 'my-pool'
args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol,
- '--listener', listener, name]
+ '--listener', listener]
position_names = ['admin_state_up', 'lb_algorithm', 'protocol',
- 'listener_id', 'name']
- position_values = [True, lb_algorithm, protocol, listener, name]
+ 'listener_id']
+ position_values = [True, lb_algorithm, protocol, listener]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
@@ -56,19 +55,16 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base):
session_persistence_str = 'HTTP_COOKIE:1234'
session_persistence = {'type': 'HTTP_COOKIE',
'cookie_name': '1234'}
- healthmon_id = 'healthmon-id'
name = 'my-pool'
args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol,
'--description', description, '--session-persistence',
- session_persistence_str, '--healthmonitor-id',
- healthmon_id, '--admin-state-down', name,
+ session_persistence_str, '--admin-state-down', '--name', name,
'--listener', listener]
position_names = ['lb_algorithm', 'protocol', 'description',
- 'session_persistence', 'healthmonitor_id',
- 'admin_state_up', 'listener_id', 'name']
+ 'session_persistence', 'admin_state_up', 'name',
+ 'listener_id']
position_values = [lb_algorithm, protocol, description,
- session_persistence, healthmon_id,
- False, listener, name]
+ session_persistence, False, name, listener]
self._test_create_resource(resource, cmd, '', my_id, args,
position_names, position_values,
cmd_resource=cmd_resource)
diff --git a/neutronclient/tests/unit/test_cli20.py b/neutronclient/tests/unit/test_cli20.py
index 5f5e06a..657b15e 100644
--- a/neutronclient/tests/unit/test_cli20.py
+++ b/neutronclient/tests/unit/test_cli20.py
@@ -91,10 +91,14 @@ class MyUrlComparator(mox.Comparator):
lhsp = urlparse.urlparse(self.lhs)
rhsp = urlparse.urlparse(rhs)
+ lhs_qs = urlparse.parse_qsl(lhsp.query)
+ rhs_qs = urlparse.parse_qsl(rhsp.query)
+
return (lhsp.scheme == rhsp.scheme and
lhsp.netloc == rhsp.netloc and
lhsp.path == rhsp.path and
- urlparse.parse_qs(lhsp.query) == urlparse.parse_qs(rhsp.query))
+ len(lhs_qs) == len(rhs_qs) and
+ set(lhs_qs) == set(rhs_qs))
def __str__(self):
if self.client and self.client.format != FORMAT:
@@ -217,7 +221,8 @@ class CLITestV20Base(base.BaseTestCase):
'credential', 'network_profile',
'policy_profile', 'ikepolicy',
'ipsecpolicy', 'metering_label',
- 'metering_label_rule', 'net_partition']
+ 'metering_label_rule', 'net_partition',
+ 'fox_socket']
if not cmd_resource:
cmd_resource = resource
if (resource in non_admin_status_resources):
@@ -617,6 +622,17 @@ class ClientV2TestJson(CLITestV20Base):
self.mox.VerifyAll()
self.mox.UnsetStubs()
+ def test_do_request_with_long_uri_exception(self):
+ long_string = 'x' * 8200 # 8200 > MAX_URI_LEN:8192
+ params = {'id': long_string}
+
+ try:
+ self.client.do_request('GET', '/test', body='', params=params)
+ except exceptions.RequestURITooLong as cm:
+ self.assertNotEqual(cm.excess, 0)
+ else:
+ self.fail('Expected exception NOT raised')
+
class ClientV2UnicodeTestXML(ClientV2TestJson):
format = 'xml'
diff --git a/neutronclient/tests/unit/test_cli20_network.py b/neutronclient/tests/unit/test_cli20_network.py
index 1ac49b6..31754be 100644
--- a/neutronclient/tests/unit/test_cli20_network.py
+++ b/neutronclient/tests/unit/test_cli20_network.py
@@ -551,14 +551,14 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base):
filters, response = self._build_test_data(data)
# 1 char of extra URI len will cause a split in 2 requests
- self.mox.StubOutWithMock(self.client.httpclient,
+ self.mox.StubOutWithMock(self.client,
"_check_uri_length")
- self.client.httpclient._check_uri_length(mox.IgnoreArg()).AndRaise(
+ self.client._check_uri_length(mox.IgnoreArg()).AndRaise(
exceptions.RequestURITooLong(excess=1))
for data in sub_data_lists:
filters, response = self._build_test_data(data)
- self.client.httpclient._check_uri_length(
+ self.client._check_uri_length(
mox.IgnoreArg()).AndReturn(None)
self.client.httpclient.request(
test_cli20.MyUrlComparator(
diff --git a/neutronclient/tests/unit/test_cli20_securitygroup.py b/neutronclient/tests/unit/test_cli20_securitygroup.py
index ec18fb8..8032e03 100644
--- a/neutronclient/tests/unit/test_cli20_securitygroup.py
+++ b/neutronclient/tests/unit/test_cli20_securitygroup.py
@@ -265,14 +265,14 @@ class CLITestV20SecurityGroupsJSON(test_cli20.CLITestV20Base):
def test_extend_list_exceed_max_uri_len(self):
def mox_calls(path, data):
# 1 char of extra URI len will cause a split in 2 requests
- self.mox.StubOutWithMock(self.client.httpclient,
+ self.mox.StubOutWithMock(self.client,
'_check_uri_length')
- self.client.httpclient._check_uri_length(mox.IgnoreArg()).AndRaise(
+ self.client._check_uri_length(mox.IgnoreArg()).AndRaise(
exceptions.RequestURITooLong(excess=1))
responses = self._build_test_data(data, excess=1)
for item in responses:
- self.client.httpclient._check_uri_length(
+ self.client._check_uri_length(
mox.IgnoreArg()).AndReturn(None)
self.client.httpclient.request(
test_cli20.end_url(path, item['filter']),
diff --git a/neutronclient/tests/unit/test_client_extension.py b/neutronclient/tests/unit/test_client_extension.py
new file mode 100644
index 0000000..fe1712b
--- /dev/null
+++ b/neutronclient/tests/unit/test_client_extension.py
@@ -0,0 +1,87 @@
+# 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.
+#
+
+import sys
+
+import mock
+
+from neutronclient.neutron.v2_0.contrib import _fox_sockets as fox_sockets
+from neutronclient.tests.unit import test_cli20
+
+
+class CLITestV20ExtensionJSON(test_cli20.CLITestV20Base):
+ def setUp(self):
+ # need to mock before super because extensions loaded on instantiation
+ self._mock_extension_loading()
+ super(CLITestV20ExtensionJSON, self).setUp(plurals={'tags': 'tag'})
+
+ def _create_patch(self, name, func=None):
+ patcher = mock.patch(name)
+ thing = patcher.start()
+ self.addCleanup(patcher.stop)
+ return thing
+
+ def _mock_extension_loading(self):
+ ext_pkg = 'neutronclient.common.extension'
+ contrib = self._create_patch(ext_pkg + '._discover_via_entry_points')
+ iterator = iter([("_fox_sockets", fox_sockets)])
+ contrib.return_value.__iter__.return_value = iterator
+ return contrib
+
+ def test_delete_fox_socket(self):
+ """Delete fox socket: myid."""
+ resource = 'fox_socket'
+ cmd = fox_sockets.FoxInSocketsDelete(test_cli20.MyApp(sys.stdout),
+ None)
+ myid = 'myid'
+ args = [myid]
+ self._test_delete_resource(resource, cmd, myid, args)
+
+ def test_update_fox_socket(self):
+ """Update fox_socket: myid --name myname."""
+ resource = 'fox_socket'
+ cmd = fox_sockets.FoxInSocketsUpdate(test_cli20.MyApp(sys.stdout),
+ None)
+ self._test_update_resource(resource, cmd, 'myid',
+ ['myid', '--name', 'myname'],
+ {'name': 'myname'})
+
+ def test_create_fox_socket(self):
+ """Create fox_socket: myname."""
+ resource = 'fox_socket'
+ cmd = fox_sockets.FoxInSocketsCreate(test_cli20.MyApp(sys.stdout),
+ None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, ]
+ position_names = ['name', ]
+ position_values = [name, ]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_list_fox_sockets(self):
+ """List fox_sockets."""
+ resources = 'fox_sockets'
+ cmd = fox_sockets.FoxInSocketsList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, True)
+
+ def test_show_fox_socket(self):
+ """Show fox_socket: --fields id --fields name myid."""
+ resource = 'fox_socket'
+ cmd = fox_sockets.FoxInSocketsShow(test_cli20.MyApp(sys.stdout), None)
+ args = ['--fields', 'id', '--fields', 'name', self.test_id]
+ self._test_show_resource(resource, cmd, self.test_id,
+ args, ['id', 'name'])
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
index 87fca7c..6c430a9 100644
--- a/neutronclient/v2_0/client.py
+++ b/neutronclient/v2_0/client.py
@@ -15,6 +15,8 @@
# under the License.
#
+import inspect
+import itertools
import logging
import time
@@ -24,6 +26,7 @@ import six.moves.urllib.parse as urlparse
from neutronclient import client
from neutronclient.common import constants
from neutronclient.common import exceptions
+from neutronclient.common import extension as client_extension
from neutronclient.common import serializer
from neutronclient.common import utils
from neutronclient.i18n import _
@@ -181,6 +184,12 @@ class ClientBase(object):
# Raise the appropriate exception
exception_handler_v20(status_code, des_error_body)
+ def _check_uri_length(self, action):
+ uri_len = len(self.httpclient.endpoint_url) + len(action)
+ if uri_len > self.MAX_URI_LEN:
+ raise exceptions.RequestURITooLong(
+ excess=uri_len - self.MAX_URI_LEN)
+
def do_request(self, method, action, body=None, headers=None, params=None):
# Add format and tenant_id
action += ".%s" % self.format
@@ -189,6 +198,8 @@ class ClientBase(object):
params = utils.safe_encode_dict(params)
action += '?' + urlparse.urlencode(params, doseq=1)
+ self._check_uri_length(action)
+
if body:
body = self.serialize(body)
@@ -455,6 +466,39 @@ class Client(ClientBase):
'healthmonitors': 'healthmonitor',
}
+ # 8192 Is the default max URI len for eventlet.wsgi.server
+ MAX_URI_LEN = 8192
+
+ @APIParamsCall
+ def list_ext(self, path, **_params):
+ """Client extension hook for lists.
+ """
+ return self.get(path, params=_params)
+
+ @APIParamsCall
+ def show_ext(self, path, id, **_params):
+ """Client extension hook for shows.
+ """
+ return self.get(path % id, params=_params)
+
+ @APIParamsCall
+ def create_ext(self, path, body=None):
+ """Client extension hook for creates.
+ """
+ return self.post(path, body=body)
+
+ @APIParamsCall
+ def update_ext(self, path, id, body=None):
+ """Client extension hook for updates.
+ """
+ return self.put(path % id, body=body)
+
+ @APIParamsCall
+ def delete_ext(self, path, id):
+ """Client extension hook for deletes.
+ """
+ return self.delete(path % id)
+
@APIParamsCall
def get_quotas_tenant(self, **_params):
"""Fetch tenant info in server's context for following quota operation.
@@ -1538,3 +1582,59 @@ class Client(ClientBase):
def delete_packet_filter(self, packet_filter_id):
"""Delete the specified packet filter."""
return self.delete(self.packet_filter_path % packet_filter_id)
+
+ 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 extend_show(self, resource_plural, path):
+ def _fx(obj, **_params):
+ return self.show_ext(path, obj, **_params)
+ setattr(self, "show_%s" % resource_plural, _fx)
+
+ def extend_list(self, resource_plural, path):
+ def _fx(**_params):
+ return self.list_ext(path, **_params)
+ setattr(self, "list_%s" % resource_plural, _fx)
+
+ def extend_create(self, resource_singular, path):
+ def _fx(body=None):
+ return self.create_ext(path, body)
+ setattr(self, "create_%s" % resource_singular, _fx)
+
+ def extend_delete(self, resource_singular, path):
+ def _fx(obj):
+ return self.delete_ext(path, obj)
+ setattr(self, "delete_%s" % resource_singular, _fx)
+
+ def extend_update(self, resource_singular, path):
+ def _fx(obj, body=None):
+ return self.update_ext(path, obj, body)
+ setattr(self, "update_%s" % resource_singular, _fx)
+
+ 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
+ if issubclass(cls, client_extension.ClientExtensionList):
+ self.extend_list(cls.resource_plural, cls.object_path)
+ elif issubclass(cls, client_extension.ClientExtensionCreate):
+ self.extend_create(cls.resource, cls.object_path)
+ elif issubclass(cls, client_extension.ClientExtensionUpdate):
+ self.extend_update(cls.resource, cls.resource_path)
+ elif issubclass(cls, client_extension.ClientExtensionDelete):
+ self.extend_delete(cls.resource, cls.resource_path)
+ elif issubclass(cls, client_extension.ClientExtensionShow):
+ self.extend_show(cls.resource, cls.resource_path)
+ 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)
+
+ def _register_extensions(self, version):
+ for name, module in itertools.chain(
+ client_extension._discover_via_entry_points()):
+ self._extend_client_with_module(module, version)