From e577c5c2f4a7ebeed23745fa206a170fc8c2f990 Mon Sep 17 00:00:00 2001 From: Artem Roma Date: Tue, 15 Jul 2014 16:28:24 +0300 Subject: [PATCH] requests module plugged What is done: * urllib2 code changed to corresponding from requests * error handling for APIclient updated in order to exploit requests exceptions classes. Partially implements: blueprint refactoring-for-fuelclient Change-Id: I7f1c0b390828f4a77daeaca370cfb16a5df3839d --- fuelclient/cli/error.py | 58 ++++++++++++++--------------- fuelclient/client.py | 81 ++++++++++++++++++++--------------------- requirements.txt | 1 + tests/test_client.py | 18 +++++---- 4 files changed, 78 insertions(+), 80 deletions(-) diff --git a/fuelclient/cli/error.py b/fuelclient/cli/error.py index 62b4c837..a5f923d6 100644 --- a/fuelclient/cli/error.py +++ b/fuelclient/cli/error.py @@ -14,8 +14,8 @@ from functools import wraps from keystoneclient.exceptions import Unauthorized +import requests import sys -import urllib2 def exit_with_error(message): @@ -67,40 +67,36 @@ class ParserException(FuelClientException): """ -def handle_exceptions(exc): - """handle_exceptions - exception handling manager. - """ - if isinstance(exc, urllib2.HTTPError): - error_body = exc.read() - exit_with_error("{0} {1}".format( - exc, - "({0})".format(error_body or "") - )) - elif isinstance(exc, urllib2.URLError): - exit_with_error(""" - Can't connect to Nailgun server! - Please modify "SERVER_ADDRESS" and "LISTEN_PORT" - in the file /etc/fuel/client/config.yaml""") - elif isinstance(exc, Unauthorized): - exit_with_error(""" - Unauthorized: need authentication! - Please provide user and password via client --os-username --os-password - or modify "KEYSTONE_USER" and "KEYSTONE_PASS" in - /etc/fuel/client/config.yaml""") - elif isinstance(exc, FuelClientException): - exit_with_error(exc.message) - else: - raise - - def exceptions_decorator(func): - """exceptions_decorator - is decorator which intercepts exceptions and - redirects them to handle_exceptions. + """Handles HTTP errors and expected exceptions that may occur + in methods of APIClient class """ @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) - except Exception as exc: - handle_exceptions(exc) + + # when server returns to us bad request check that + # and print meaningful reason + except requests.HTTPError as exc: + error_body = exc.response.text + exit_with_error("{0} ({1})".format(exc, error_body)) + except requests.ConnectionError: + exit_with_error(""" + Can't connect to Nailgun server! + Please modify "SERVER_ADDRESS" and "LISTEN_PORT" + in the file /etc/fuel/client/config.yaml""") + except Unauthorized: + exit_with_error(""" + Unauthorized: need authentication! + Please provide user and password via client + --os-username --os-password + or modify "KEYSTONE_USER" and "KEYSTONE_PASS" in + /etc/fuel/client/config.yaml""") + except FuelClientException as exc: + exit_with_error(exc.message) + # not all responses return data + except ValueError: + return {} + return wrapper diff --git a/fuelclient/client.py b/fuelclient/client.py index 75b3c4a9..3c2ebb82 100644 --- a/fuelclient/client.py +++ b/fuelclient/client.py @@ -15,7 +15,7 @@ import json import logging import os -import urllib2 +import requests import yaml @@ -71,11 +71,14 @@ class Client(object): return '' @property + @exceptions_decorator def auth_required(self): if self._auth_required is None: - request = urllib2.urlopen(''.join([self.api_root, 'version'])) - self._auth_required = json.loads( - request.read()).get('auth_required', False) + url = self.api_root + 'version' + resp = requests.get(url) + resp.raise_for_status() + + self._auth_required = resp.json().get('auth_required', False) return self._auth_required @property @@ -107,37 +110,41 @@ class Client(object): if self.debug: print(message) + @exceptions_decorator def delete_request(self, api): """Make DELETE request to specific API with some data """ + url = self.api_root + api self.print_debug( "DELETE {0}".format(self.api_root + api) ) - opener = urllib2.build_opener(urllib2.HTTPHandler) - request = urllib2.Request(self.api_root + api) - request.add_header('Content-Type', 'application/json') - request.add_header('X-Auth-Token', self.auth_token) - request.get_method = lambda: 'DELETE' - opener.open(request) - return {} + headers = {'content-type': 'application/json', + 'x-auth-token': self.auth_token} + resp = requests.delete(url, headers=headers) + resp.raise_for_status() + + return resp.json() + + @exceptions_decorator def put_request(self, api, data): """Make PUT request to specific API with some data """ + url = self.api_root + api data_json = json.dumps(data) self.print_debug( "PUT {0} data={1}" .format(self.api_root + api, data_json) ) - opener = urllib2.build_opener(urllib2.HTTPHandler) - request = urllib2.Request(self.api_root + api, data=data_json) - request.add_header('Content-Type', 'application/json') - request.add_header('X-Auth-Token', self.auth_token) - request.get_method = lambda: 'PUT' - return json.loads( - opener.open(request).read() - ) + headers = {'content-type': 'application/json', + 'x-auth-token': self.auth_token} + resp = requests.put(url, data=data_json, headers=headers) + resp.raise_for_status() + + return resp.json() + + @exceptions_decorator def get_request(self, api, ostf=False): """Make GET request to specific API """ @@ -146,13 +153,14 @@ class Client(object): "GET {0}" .format(url) ) - opener = urllib2.build_opener(urllib2.HTTPHandler) - request = urllib2.Request(url) - request.add_header('X-Auth-Token', self.auth_token) - return json.loads( - opener.open(request).read() - ) + headers = {'x-auth-token': self.auth_token} + resp = requests.get(url, headers=headers) + resp.raise_for_status() + + return resp.json() + + @exceptions_decorator def post_request(self, api, data, ostf=False): """Make POST request to specific API with some data """ @@ -162,22 +170,13 @@ class Client(object): "POST {0} data={1}" .format(url, data_json) ) - request = urllib2.Request( - url=url, - data=data_json, - headers={ - 'Content-Type': 'application/json' - } - ) - request.add_header('X-Auth-Token', self.auth_token) - try: - response = json.loads( - urllib2.urlopen(request) - .read() - ) - except ValueError: - response = {} - return response + + headers = {'content-type': 'application/json', + 'x-auth-token': self.auth_token} + resp = requests.post(url, data=data_json, headers=headers) + resp.raise_for_status() + + return resp.json() @exceptions_decorator def get_fuel_version(self): diff --git a/requirements.txt b/requirements.txt index b8b57fc8..afd348bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Mako==0.9.1 MarkupSafe==0.18 Paste==1.7.5.1 PyYAML==3.10 +requests>=1.2.3 SQLAlchemy==0.7.9 Shotgun==0.1.0 alembic==0.6.2 diff --git a/tests/test_client.py b/tests/test_client.py index 2e76f954..ea2176f9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -102,11 +102,13 @@ class TestHandlers(BaseTestCase): def test_wrong_credentials(self): result = self.run_cli_command("--os-username=a --os-password=a node", check_errors=True) - self.assertEqual(result.stderr, - '\n Unauthorized: need authentication!\n' - ' Please provide user and password via client --os-username ' - '--os-password\n or modify "KEYSTONE_USER" and "KEYSTONE_PASS" ' - 'in\n /etc/fuel/client/config.yaml\n') + must_be = ('\n Unauthorized: need authentication!\n ' + ' Please provide user and password via client\n ' + ' --os-username --os-password\n or modify ' + '"KEYSTONE_USER" and "KEYSTONE_PASS" in\n ' + '/etc/fuel/client/config.yaml\n' + ) + self.assertEqual(result.stderr, must_be) def test_destroy_node(self): self.load_data_to_nailgun_server() @@ -117,9 +119,9 @@ class TestHandlers(BaseTestCase): msg = ("Nodes with id [1] has been deleted from fuel db.\n" "You should still delete node from cobbler\n") self.check_for_stdout( - "node --node 1 --delete-from-db", - msg - ) + "node --node 1 --delete-from-db", + msg + ) def test_for_examples_in_action_help(self): actions = (