From 9b2dbf2a01afbcaa1b23b2d38515255ffa580cc4 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Thu, 31 Oct 2013 23:21:20 +0000 Subject: Do not reconnect on getting relocation errors --- diff --git a/sugar_network/client/__init__.py b/sugar_network/client/__init__.py index d8eefcb..c863e98 100644 --- a/sugar_network/client/__init__.py +++ b/sugar_network/client/__init__.py @@ -21,6 +21,25 @@ from os.path import join, expanduser, exists from sugar_network.toolkit import http, Option +def profile_path(*args): + """Path within sugar profile directory. + + Missed directories will be created. + + :param args: + path parts that will be added to the resulting path + :returns: + full path with directory part existed + + """ + if os.geteuid(): + root_dir = join(os.environ['HOME'], '.sugar', + os.environ.get('SUGAR_PROFILE', 'default')) + else: + root_dir = '/var/sugar-network' + return join(root_dir, *args) + + api_url = Option( 'url to connect to Sugar Network server API', default='http://node-devel.sugarlabs.org', short_option='-a', @@ -36,7 +55,7 @@ no_check_certificate = Option( local_root = Option( 'path to the directory to keep all local data', - default=lambda: profile_path('network'), name='local_root') + default=profile_path('network'), name='local_root') server_mode = Option( 'start server to share local documents', @@ -113,25 +132,6 @@ keyfile = Option( _logger = logging.getLogger('client') -def profile_path(*args): - """Path within sugar profile directory. - - Missed directories will be created. - - :param args: - path parts that will be added to the resulting path - :returns: - full path with directory part existed - - """ - if os.geteuid(): - root_dir = join(os.environ['HOME'], '.sugar', - os.environ.get('SUGAR_PROFILE', 'default')) - else: - root_dir = '/var/sugar-network' - return join(root_dir, *args) - - def path(*args): """Calculate a path from the root. diff --git a/sugar_network/client/routes.py b/sugar_network/client/routes.py index 1feedec..bcc6a60 100644 --- a/sugar_network/client/routes.py +++ b/sugar_network/client/routes.py @@ -15,8 +15,8 @@ import os import logging -import httplib from base64 import b64encode +from httplib import IncompleteRead from zipfile import ZipFile, ZIP_DEFLATED from os.path import join, basename @@ -207,10 +207,15 @@ class ClientRoutes(model.FrontRoutes, implementations.Routes, journal.Routes): try: reply = self._node.call(request, response) if hasattr(reply, 'read'): - return _ResponseStream(reply, self._restart_online) + if response.relocations: + return reply + else: + return _ResponseStream(reply, self._restart_online) else: return reply - except (http.ConnectionError, httplib.IncompleteRead): + except (http.ConnectionError, IncompleteRead): + if response.relocations: + raise self._restart_online() return self._local.call(request, response) else: @@ -518,16 +523,16 @@ class _ResponseStream(object): self._stream = stream self._on_fail_cb = on_fail_cb - def __hasattr__(self, key): - return hasattr(self._stream, key) + def __hasattr__(self, name): + return hasattr(self._stream, name) - def __getattr__(self, key): - return getattr(self._stream, key) + def __getattr__(self, name): + return getattr(self._stream, name) def read(self, size=None): try: return self._stream.read(size) - except (http.ConnectionError, httplib.IncompleteRead): + except (http.ConnectionError, IncompleteRead): self._on_fail_cb() raise diff --git a/sugar_network/toolkit/http.py b/sugar_network/toolkit/http.py index 0a25c57..2506873 100644 --- a/sugar_network/toolkit/http.py +++ b/sugar_network/toolkit/http.py @@ -102,10 +102,15 @@ class GatewayTimeout(Status): status_code = 504 +class _ConnectionError(Status): + + status = '999 For testing purpose only' + status_code = 999 + + class Connection(object): _Session = None - _ConnectionError = None def __init__(self, api_url='', auth=None, max_retries=0, **session_args): self.api_url = api_url @@ -203,12 +208,8 @@ class Connection(object): try_ = 0 while True: try_ += 1 - try: - reply = self._session.request(method, path, data=data, - headers=headers, params=params, **kwargs) - except Connection._ConnectionError, error: - raise ConnectionError, error, sys.exc_info()[2] - + reply = self._session.request(method, path, data=data, + headers=headers, params=params, **kwargs) if reply.status_code == Unauthorized.status_code: enforce(self.auth is not None, Unauthorized, 'No credentials') self._authenticate(reply.headers.get('www-authenticate')) @@ -229,7 +230,8 @@ class Connection(object): continue error = content or reply.headers.get('x-sn-error') or \ 'No error message provided' - cls = _FORWARD_STATUSES.get(reply.status_code, RuntimeError) + cls = _FORWARD_STATUSES.get(reply.status_code, RuntimeError) \ + or ConnectionError raise cls(error) return reply @@ -277,6 +279,8 @@ class Connection(object): response.meta[key[5:]] = json.loads(value) elif not resend: response[key] = value + if resend: + response.relocations += 1 if not resend: break path = reply.headers['location'] @@ -306,7 +310,8 @@ class Connection(object): sys.path.insert(0, sys_path) from requests import Session, exceptions Connection._Session = Session - Connection._ConnectionError = exceptions.ConnectionError + global ConnectionError + ConnectionError = exceptions.ConnectionError self._session = Connection._Session() self._session.headers['accept-language'] = \ @@ -384,7 +389,7 @@ class SugarAuth(object): os.chmod(key_dir, 0700) _logger.info('Generate RSA private key at %r', self._key_path) - self._key = RSA.gen_key(2048, 65537, lambda *args: None) + self._key = RSA.gen_key(1024, 65537, lambda *args: None) self._key.save_key(self._key_path, cipher=None) os.chmod(self._key_path, 0600) @@ -466,4 +471,5 @@ _FORWARD_STATUSES = { BadGateway.status_code: BadGateway, ServiceUnavailable.status_code: ServiceUnavailable, GatewayTimeout.status_code: GatewayTimeout, + _ConnectionError.status_code: None, } diff --git a/sugar_network/toolkit/router.py b/sugar_network/toolkit/router.py index b6d4466..9dd11cd 100644 --- a/sugar_network/toolkit/router.py +++ b/sugar_network/toolkit/router.py @@ -349,6 +349,7 @@ class Request(dict): class Response(dict): status = '200 OK' + relocations = 0 def __init__(self, **kwargs): dict.__init__(self, kwargs) diff --git a/tests/units/client/online_routes.py b/tests/units/client/online_routes.py index 20bc2a1..46c18c1 100755 --- a/tests/units/client/online_routes.py +++ b/tests/units/client/online_routes.py @@ -1252,21 +1252,61 @@ Can't find all required implementations: ipc.get(['context', context], cmd='feed', layer='public')) def test_Redirects(self): - URL = 'http://sugarlabs.org' class Document(Resource): @db.blob_property() - def blob(self, value): - raise http.Redirect(URL) + def blob1(self, value): + raise http.Redirect(prefix + 'blob2') - self.start_online_client([User, Context, Implementation, Document]) + @db.blob_property() + def blob3(self, value): + raise http.Redirect(client.api_url.value + prefix + 'blob4') + + self.start_online_client([User, Document]) ipc = IPCConnection() guid = ipc.post(['document'], {}) + prefix = '/document/' + guid + '/' + + response = requests.request('GET', client.api_url.value + prefix + 'blob1', allow_redirects=False) + self.assertEqual(303, response.status_code) + self.assertEqual(prefix + 'blob2', response.headers['Location']) - response = requests.request('GET', client.api_url.value + '/document/' + guid + '/blob', allow_redirects=False) + response = requests.request('GET', client.api_url.value + prefix + 'blob3', allow_redirects=False) self.assertEqual(303, response.status_code) - self.assertEqual(URL, response.headers['Location']) + self.assertEqual(client.api_url.value + prefix + 'blob4', response.headers['Location']) + + def test_DoNotSwitchToOfflineOnRedirectFails(self): + + class Document(Resource): + + @db.blob_property() + def blob1(self, value): + raise http.Redirect(prefix + '/blob2') + + @db.blob_property() + def blob2(self, value): + raise http._ConnectionError() + + local_volume = self.start_online_client([User, Document]) + ipc = IPCConnection() + guid = ipc.post(['document'], {}) + prefix = client.api_url.value + '/document/' + guid + '/' + local_volume['document'].create({'guid': guid}) + + trigger = self.wait_for_events(ipc, event='inline', state='connecting') + try: + ipc.get(['document', guid, 'blob1']) + except Exception: + pass + assert trigger.wait(.1) is None + + trigger = self.wait_for_events(ipc, event='inline', state='connecting') + try: + ipc.get(['document', guid, 'blob2']) + except Exception: + pass + assert trigger.wait(.1) is not None def test_ContentDisposition(self): self.start_online_client([User, Context, Implementation, Artifact]) -- cgit v0.9.1