diff options
-rw-r--r-- | neutronclient/client.py | 62 | ||||
-rw-r--r-- | neutronclient/neutron/v2_0/fw/firewallrule.py | 2 | ||||
-rw-r--r-- | neutronclient/neutron/v2_0/networkprofile.py | 12 | ||||
-rw-r--r-- | neutronclient/shell.py | 6 | ||||
-rw-r--r-- | neutronclient/tests/unit/test_http.py | 82 | ||||
-rw-r--r-- | neutronclient/v2_0/client.py | 7 |
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, |