summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--neutronclient/client.py62
-rw-r--r--neutronclient/neutron/v2_0/fw/firewallrule.py2
-rw-r--r--neutronclient/neutron/v2_0/networkprofile.py12
-rw-r--r--neutronclient/shell.py6
-rw-r--r--neutronclient/tests/unit/test_http.py82
-rw-r--r--neutronclient/v2_0/client.py7
6 files changed, 120 insertions, 51 deletions
diff --git a/neutronclient/client.py b/neutronclient/client.py
index 02a198d..d2f3685 100644
--- a/neutronclient/client.py
+++ b/neutronclient/client.py
@@ -14,6 +14,7 @@
# under the License.
#
+import abc
try:
import json
except ImportError:
@@ -24,6 +25,7 @@ import os
from keystoneclient import access
from keystoneclient.auth.identity.base import BaseIdentityPlugin
import requests
+import six
from neutronclient.common import exceptions
from neutronclient.common import utils
@@ -42,12 +44,34 @@ else:
logging.getLogger("requests").setLevel(_requests_log_level)
-class NeutronClientMixin(object):
+@six.add_metaclass(abc.ABCMeta)
+class AbstractHTTPClient(object):
USER_AGENT = 'python-neutronclient'
+ CONTENT_TYPE = 'application/json'
+ def request(self, url, method, body=None, content_type=None, headers=None,
+ **kwargs):
+ """Request without authentication."""
-class HTTPClient(NeutronClientMixin):
+ headers = headers or {}
+ content_type = content_type or self.CONTENT_TYPE
+ headers.setdefault('Accept', content_type)
+ if body:
+ headers.setdefault('Content-Type', content_type)
+
+ return self._request(url, method, body=body, headers=headers, **kwargs)
+
+ @abc.abstractmethod
+ def do_request(self, url, method, **kwargs):
+ """Request with authentication."""
+
+ @abc.abstractmethod
+ def _request(self, url, method, body=None, headers=None, **kwargs):
+ """Request without authentication nor headers population."""
+
+
+class HTTPClient(AbstractHTTPClient):
"""Handles the REST calls and responses, include authentication."""
def __init__(self, username=None, user_id=None,
@@ -73,7 +97,6 @@ class HTTPClient(NeutronClientMixin):
self.auth_token = token
self.auth_tenant_id = None
self.auth_user_id = None
- self.content_type = 'application/json'
self.endpoint_url = endpoint_url
self.auth_strategy = auth_strategy
self.log_credentials = log_credentials
@@ -87,13 +110,6 @@ class HTTPClient(NeutronClientMixin):
kargs.setdefault('headers', kwargs.get('headers', {}))
kargs['headers']['User-Agent'] = self.USER_AGENT
- if 'content_type' in kwargs:
- kargs['headers']['Content-Type'] = kwargs['content_type']
- kargs['headers']['Accept'] = kwargs['content_type']
- else:
- kargs['headers']['Content-Type'] = self.content_type
- kargs['headers']['Accept'] = self.content_type
-
if 'body' in kwargs:
kargs['body'] = kwargs['body']
args = utils.safe_encode_list(args)
@@ -135,17 +151,15 @@ class HTTPClient(NeutronClientMixin):
elif not self.endpoint_url:
self.endpoint_url = self._get_endpoint_url()
- def request(self, url, method, **kwargs):
- kwargs.setdefault('headers', kwargs.get('headers', {}))
- kwargs['headers']['User-Agent'] = self.USER_AGENT
- kwargs['headers']['Accept'] = 'application/json'
- if 'body' in kwargs:
- kwargs['headers']['Content-Type'] = 'application/json'
- kwargs['data'] = kwargs['body']
- del kwargs['body']
+ def _request(self, url, method, body=None, headers=None, **kwargs):
+ headers = headers or {}
+ headers['User-Agent'] = self.USER_AGENT
+
resp = requests.request(
method,
url,
+ data=body,
+ headers=headers,
verify=self.verify_cert,
timeout=self.timeout,
**kwargs)
@@ -268,7 +282,7 @@ class HTTPClient(NeutronClientMixin):
'endpoint_url': self.endpoint_url}
-class SessionClient(NeutronClientMixin):
+class SessionClient(AbstractHTTPClient):
def __init__(self,
session,
@@ -285,23 +299,19 @@ class SessionClient(NeutronClientMixin):
self.auth_token = None
self.endpoint_url = None
- def request(self, url, method, **kwargs):
+ def _request(self, url, method, body=None, headers=None, **kwargs):
kwargs.setdefault('user_agent', self.USER_AGENT)
kwargs.setdefault('auth', self.auth)
kwargs.setdefault('authenticated', False)
- try:
- kwargs['data'] = kwargs.pop('body')
- except KeyError:
- pass
-
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('interface', self.interface)
endpoint_filter.setdefault('service_type', self.service_type)
endpoint_filter.setdefault('region_name', self.region_name)
kwargs = utils.safe_encode_dict(kwargs)
- resp = self.session.request(url, method, **kwargs)
+ resp = self.session.request(url, method, data=body, headers=headers,
+ **kwargs)
return resp, resp.text
def do_request(self, url, method, **kwargs):
diff --git a/neutronclient/neutron/v2_0/fw/firewallrule.py b/neutronclient/neutron/v2_0/fw/firewallrule.py
index 88f47e9..aa4b90c 100644
--- a/neutronclient/neutron/v2_0/fw/firewallrule.py
+++ b/neutronclient/neutron/v2_0/fw/firewallrule.py
@@ -98,7 +98,7 @@ class CreateFirewallRule(neutronv20.CreateCommand):
parser.add_argument(
'--enabled',
dest='enabled', choices=['True', 'False'],
- help=_('To enable or disable this rule'),
+ help=_('Whether to enable or disable this rule.'),
default=argparse.SUPPRESS)
parser.add_argument(
'--protocol', choices=['tcp', 'udp', 'icmp', 'any'],
diff --git a/neutronclient/neutron/v2_0/networkprofile.py b/neutronclient/neutron/v2_0/networkprofile.py
index 8a6054e..def7441 100644
--- a/neutronclient/neutron/v2_0/networkprofile.py
+++ b/neutronclient/neutron/v2_0/networkprofile.py
@@ -65,8 +65,8 @@ class CreateNetworkProfile(neutronV20.CreateCommand):
help=_('Multicast IPv4 range.'))
parser.add_argument("--add-tenant",
action='append', dest='add_tenants',
- help=_("Add tenant to the network profile "
- "(This option can be repeated)."))
+ help=_("Add tenant to the network profile. "
+ "You can repeat this option."))
def args2body(self, parsed_args):
body = {'network_profile': {'name': parsed_args.name}}
@@ -106,12 +106,12 @@ class UpdateNetworkProfile(neutronV20.UpdateCommand):
def add_known_arguments(self, parser):
parser.add_argument("--remove-tenant",
action='append', dest='remove_tenants',
- help=_("Remove tenant from the network profile "
- "(This option can be repeated)"))
+ help=_("Remove tenant from the network profile. "
+ "You can repeat this option."))
parser.add_argument("--add-tenant",
action='append', dest='add_tenants',
- help=_("Add tenant to the network profile "
- "(This option can be repeated)"))
+ help=_("Add tenant to the network profile. "
+ "You can repeat this option."))
def args2body(self, parsed_args):
body = {'network_profile': {}}
diff --git a/neutronclient/shell.py b/neutronclient/shell.py
index 8dd2b3a..51804e9 100644
--- a/neutronclient/shell.py
+++ b/neutronclient/shell.py
@@ -549,7 +549,7 @@ class NeutronShell(app.App):
help=_("Path of certificate file to use in SSL "
"connection. This file can optionally be "
"prepended with the private key. Defaults "
- "to env[OS_CERT]"))
+ "to env[OS_CERT]."))
parser.add_argument(
'--os-cacert',
@@ -557,7 +557,7 @@ class NeutronShell(app.App):
default=env('OS_CACERT', default=None),
help=_("Specify a CA bundle file to use in "
"verifying a TLS (https) server certificate. "
- "Defaults to env[OS_CACERT]"))
+ "Defaults to env[OS_CACERT]."))
parser.add_argument(
'--os-key',
@@ -566,7 +566,7 @@ class NeutronShell(app.App):
help=_("Path of client key to use in SSL "
"connection. This option is not necessary "
"if your key is prepended to your certificate "
- "file. Defaults to env[OS_KEY]"))
+ "file. Defaults to env[OS_KEY]."))
parser.add_argument(
'--os-password', metavar='<auth-password>',
diff --git a/neutronclient/tests/unit/test_http.py b/neutronclient/tests/unit/test_http.py
index f2a6675..5595eb8 100644
--- a/neutronclient/tests/unit/test_http.py
+++ b/neutronclient/tests/unit/test_http.py
@@ -13,11 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import abc
+
from mox3 import mox
+import six
import testtools
-from neutronclient.client import HTTPClient
+from neutronclient import client
from neutronclient.common import exceptions
+from neutronclient.tests.unit import test_auth
from neutronclient.tests.unit.test_cli20 import MyResp
@@ -25,21 +29,73 @@ AUTH_TOKEN = 'test_token'
END_URL = 'test_url'
METHOD = 'GET'
URL = 'http://test.test:1234/v2.0/test'
+BODY = 'IAMFAKE'
+
+@six.add_metaclass(abc.ABCMeta)
+class TestHTTPClientMixin(object):
-class TestHTTPClient(testtools.TestCase):
def setUp(self):
- super(TestHTTPClient, self).setUp()
+ super(TestHTTPClientMixin, self).setUp()
+ self.clazz, self.http = self.initialize()
self.mox = mox.Mox()
- self.mox.StubOutWithMock(HTTPClient, 'request')
self.addCleanup(self.mox.UnsetStubs)
+ self.mox.StubOutWithMock(self.clazz, '_request')
+
+ @abc.abstractmethod
+ def initialize(self):
+ """Return client class, instance."""
+
+ def _test_headers(self, expected_headers, **kwargs):
+ """Test headers."""
+ self.clazz._request(URL, METHOD,
+ body=kwargs.get('body'),
+ headers=expected_headers)
+ self.mox.ReplayAll()
+ self.http.request(URL, METHOD, **kwargs)
+ self.mox.VerifyAll()
+
+ 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/xml'}
+ self._test_headers(headers, content_type='application/xml')
+
+ def test_headers_with_body_with_content_type(self):
+ headers = {'Accept': 'application/xml',
+ 'Content-Type': 'application/xml'}
+ self._test_headers(headers, body=BODY, content_type='application/xml')
+
+ def test_headers_defined_in_headers(self):
+ headers = {'Accept': 'application/xml',
+ 'Content-Type': 'application/xml'}
+ self._test_headers(headers, body=BODY, headers=headers)
+
+
+class TestSessionClient(TestHTTPClientMixin, testtools.TestCase):
+
+ def initialize(self):
+ session, auth = test_auth.setup_keystone_v2()
+ return [client.SessionClient,
+ client.SessionClient(session=session, auth=auth)]
+
+
+class TestHTTPClient(TestHTTPClientMixin, testtools.TestCase):
- self.http = HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL)
+ def initialize(self):
+ return [client.HTTPClient,
+ client.HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL)]
def test_request_error(self):
- HTTPClient.request(
- URL, METHOD, headers=mox.IgnoreArg()
+ self.clazz._request(
+ URL, METHOD, body=None, headers=mox.IgnoreArg()
).AndRaise(Exception('error msg'))
self.mox.ReplayAll()
@@ -53,8 +109,8 @@ class TestHTTPClient(testtools.TestCase):
def test_request_success(self):
rv_should_be = MyResp(200), 'test content'
- HTTPClient.request(
- URL, METHOD, headers=mox.IgnoreArg()
+ self.clazz._request(
+ URL, METHOD, body=None, headers=mox.IgnoreArg()
).AndReturn(rv_should_be)
self.mox.ReplayAll()
@@ -63,8 +119,8 @@ class TestHTTPClient(testtools.TestCase):
def test_request_unauthorized(self):
rv_should_be = MyResp(401), 'unauthorized message'
- HTTPClient.request(
- URL, METHOD, headers=mox.IgnoreArg()
+ self.clazz._request(
+ URL, METHOD, body=None, headers=mox.IgnoreArg()
).AndReturn(rv_should_be)
self.mox.ReplayAll()
@@ -75,8 +131,8 @@ class TestHTTPClient(testtools.TestCase):
def test_request_forbidden_is_returned_to_caller(self):
rv_should_be = MyResp(403), 'forbidden message'
- HTTPClient.request(
- URL, METHOD, headers=mox.IgnoreArg()
+ self.clazz._request(
+ URL, METHOD, body=None, headers=mox.IgnoreArg()
).AndReturn(rv_should_be)
self.mox.ReplayAll()
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
index 20b5626..fcce028 100644
--- a/neutronclient/v2_0/client.py
+++ b/neutronclient/v2_0/client.py
@@ -1234,8 +1234,11 @@ class Client(object):
if body:
body = self.serialize(body)
- self.httpclient.content_type = self.content_type()
- resp, replybody = self.httpclient.do_request(action, method, body=body)
+
+ resp, replybody = self.httpclient.do_request(
+ action, method, body=body,
+ content_type=self.content_type())
+
status_code = resp.status_code
if status_code in (requests.codes.ok,
requests.codes.created,