13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""RemoteTransport client for the smart-server.
62
60
RemoteTCPTransport, etc.
65
# When making a readv request, cap it at requesting 5MB of data
66
_max_readv_bytes = 5*1024*1024
68
63
# IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
69
64
# responsibilities: Put those on SmartClient or similar. This is vital for
70
65
# the ability to support multiple versions of the smart protocol over time:
71
# RemoteTransport is an adapter from the Transport object model to the
66
# RemoteTransport is an adapter from the Transport object model to the
72
67
# SmartClient model, not an encoder.
74
69
# FIXME: the medium parameter should be private, only the tests requires
91
86
should only be used for testing purposes; normally this is
92
87
determined from the medium.
94
super(RemoteTransport, self).__init__(
95
url, _from_transport=_from_transport)
89
super(RemoteTransport, self).__init__(url,
90
_from_transport=_from_transport)
97
92
# The medium is the connection, except when we need to share it with
98
93
# other objects (RemoteBzrDir, RemoteRepository etc). In these cases
99
94
# what we want to share is really the shared connection.
101
if (_from_transport is not None
102
and isinstance(_from_transport, RemoteTransport)):
103
_client = _from_transport._client
104
elif _from_transport is None:
96
if _from_transport is None:
105
97
# If no _from_transport is specified, we need to intialize the
107
99
credentials = None
167
152
def get_smart_medium(self):
168
153
return self._get_connection()
155
@deprecated_method(one_four)
156
def get_shared_medium(self):
157
return self._get_shared_connection()
170
159
def _remote_path(self, relpath):
171
160
"""Returns the Unicode version of the absolute path for relpath."""
172
return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
161
return self._combine_paths(self._path, relpath)
174
163
def _call(self, method, *args):
175
resp = self._call2(method, *args)
176
self._ensure_ok(resp)
165
resp = self._call2(method, *args)
166
except errors.ErrorFromSmartServer, err:
167
self._translate_error(err.error_tuple)
168
self._translate_error(resp)
178
170
def _call2(self, method, *args):
179
171
"""Call a method on the remote server."""
181
173
return self._client.call(method, *args)
182
174
except errors.ErrorFromSmartServer, err:
183
# The first argument, if present, is always a path.
185
context = {'relpath': args[0]}
188
self._translate_error(err, **context)
175
self._translate_error(err.error_tuple)
190
177
def _call_with_body_bytes(self, method, args, body):
191
178
"""Call a method on the remote server with body bytes."""
193
180
return self._client.call_with_body_bytes(method, args, body)
194
181
except errors.ErrorFromSmartServer, err:
195
# The first argument, if present, is always a path.
197
context = {'relpath': args[0]}
200
self._translate_error(err, **context)
182
self._translate_error(err.error_tuple)
202
184
def has(self, relpath):
203
185
"""Indicate whether a remote file of the given name exists or not.
247
230
transport._file_streams[self.abspath(relpath)] = result
250
def put_bytes(self, relpath, raw_bytes, mode=None):
251
if not isinstance(raw_bytes, str):
253
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
254
resp = self._call_with_body_bytes(
233
def put_bytes(self, relpath, upload_contents, mode=None):
234
# FIXME: upload_file is probably not safe for non-ascii characters -
235
# should probably just pass all parameters as length-delimited
237
if type(upload_contents) is unicode:
238
# Although not strictly correct, we raise UnicodeEncodeError to be
239
# compatible with other transports.
240
raise UnicodeEncodeError(
241
'undefined', upload_contents, 0, 1,
242
'put_bytes must be given bytes, not unicode.')
243
resp = self._call_with_body_bytes('put',
256
244
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
258
self._ensure_ok(resp)
259
return len(raw_bytes)
246
self._translate_error(resp)
247
return len(upload_contents)
261
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
249
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
262
250
create_parent_dir=False,
264
252
"""See Transport.put_bytes_non_atomic."""
326
314
sorted_offsets = sorted(offsets)
327
315
coalesced = list(self._coalesce_offsets(sorted_offsets,
328
316
limit=self._max_readv_combine,
329
fudge_factor=self._bytes_to_read_before_seek,
330
max_size=self._max_readv_bytes))
332
# now that we've coallesced things, avoid making enormous requests
337
if c.length + cur_len > self._max_readv_bytes:
338
requests.append(cur_request)
342
cur_request.append(c)
345
requests.append(cur_request)
346
if 'hpss' in debug.debug_flags:
347
trace.mutter('%s.readv %s offsets => %s coalesced'
348
' => %s requests (%s)',
349
self.__class__.__name__, len(offsets), len(coalesced),
350
len(requests), sum(map(len, requests)))
351
# Cache the results, but only until they have been fulfilled
353
# turn the list of offsets into a single stack to iterate
317
fudge_factor=self._bytes_to_read_before_seek))
320
result = self._client.call_with_body_readv_array(
321
('readv', self._remote_path(relpath),),
322
[(c.start, c.length) for c in coalesced])
323
resp, response_handler = result
324
except errors.ErrorFromSmartServer, err:
325
self._translate_error(err.error_tuple)
327
if resp[0] != 'readv':
328
# This should raise an exception
329
response_handler.cancel_read_body()
330
raise errors.UnexpectedSmartServerResponse(resp)
332
return self._handle_response(offsets, coalesced, response_handler)
334
def _handle_response(self, offsets, coalesced, response_handler):
335
# turn the list of offsets into a stack
354
336
offset_stack = iter(offsets)
355
# using a list so it can be modified when passing down and coming back
356
next_offset = [offset_stack.next()]
357
for cur_request in requests:
359
result = self._client.call_with_body_readv_array(
360
('readv', self._remote_path(relpath),),
361
[(c.start, c.length) for c in cur_request])
362
resp, response_handler = result
363
except errors.ErrorFromSmartServer, err:
364
self._translate_error(err, relpath)
366
if resp[0] != 'readv':
367
# This should raise an exception
368
response_handler.cancel_read_body()
369
raise errors.UnexpectedSmartServerResponse(resp)
371
for res in self._handle_response(offset_stack, cur_request,
377
def _handle_response(self, offset_stack, coalesced, response_handler,
378
data_map, next_offset):
379
cur_offset_and_size = next_offset[0]
337
cur_offset_and_size = offset_stack.next()
380
338
# FIXME: this should know how many bytes are needed, for clarity.
381
339
data = response_handler.read_body_bytes()
340
# Cache the results, but only until they have been fulfilled
383
343
for c_offset in coalesced:
384
344
if len(data) < c_offset.length:
421
381
def rmdir(self, relpath):
422
382
resp = self._call('rmdir', self._remote_path(relpath))
424
def _ensure_ok(self, resp):
426
raise errors.UnexpectedSmartServerResponse(resp)
428
def _translate_error(self, err, relpath=None):
429
remote._translate_error(err, path=relpath)
384
def _translate_error(self, resp, orig_path=None):
385
"""Raise an exception from a response"""
392
elif what == 'NoSuchFile':
393
if orig_path is not None:
394
error_path = orig_path
397
raise errors.NoSuchFile(error_path)
398
elif what == 'error':
399
raise errors.SmartProtocolError(unicode(resp[1]))
400
elif what == 'FileExists':
401
raise errors.FileExists(resp[1])
402
elif what == 'DirectoryNotEmpty':
403
raise errors.DirectoryNotEmpty(resp[1])
404
elif what == 'ShortReadvError':
405
raise errors.ShortReadvError(resp[1], int(resp[2]),
406
int(resp[3]), int(resp[4]))
407
elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
408
encoding = str(resp[1]) # encoding must always be a string
412
reason = str(resp[5]) # reason must always be a string
413
if val.startswith('u:'):
414
val = val[2:].decode('utf-8')
415
elif val.startswith('s:'):
416
val = val[2:].decode('base64')
417
if what == 'UnicodeDecodeError':
418
raise UnicodeDecodeError(encoding, val, start, end, reason)
419
elif what == 'UnicodeEncodeError':
420
raise UnicodeEncodeError(encoding, val, start, end, reason)
421
elif what == "ReadOnlyError":
422
raise errors.TransportNotPossible('readonly transport')
423
elif what == "ReadError":
424
if orig_path is not None:
425
error_path = orig_path
428
raise errors.ReadError(error_path)
429
elif what == "PermissionDenied":
430
if orig_path is not None:
431
error_path = orig_path
434
raise errors.PermissionDenied(error_path)
436
raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
431
438
def disconnect(self):
432
m = self.get_smart_medium()
439
self.get_smart_medium().disconnect()
436
441
def stat(self, relpath):
437
442
resp = self._call2('stat', self._remote_path(relpath))
438
443
if resp[0] == 'stat':
439
444
return _SmartStat(int(resp[1]), int(resp[2], 8))
440
raise errors.UnexpectedSmartServerResponse(resp)
446
self._translate_error(resp)
442
448
## def lock_read(self, relpath):
443
449
## """Lock the given file for shared (read) access.
459
465
resp = self._call2('list_dir', self._remote_path(relpath))
460
466
if resp[0] == 'names':
461
467
return [name.encode('ascii') for name in resp[1:]]
462
raise errors.UnexpectedSmartServerResponse(resp)
469
self._translate_error(resp)
464
471
def iter_files_recursive(self):
465
472
resp = self._call2('iter_files_recursive', self._remote_path(''))
466
473
if resp[0] == 'names':
468
raise errors.UnexpectedSmartServerResponse(resp)
476
self._translate_error(resp)
471
479
class RemoteTCPTransport(RemoteTransport):
472
480
"""Connection to smart server over plain tcp.
474
482
This is essentially just a factory to get 'RemoteTransport(url,
475
483
SmartTCPClientMedium).
478
486
def _build_medium(self):
479
487
client_medium = medium.SmartTCPClientMedium(
480
self._parsed_url.host, self._parsed_url.port, self.base)
488
self._host, self._port, self.base)
481
489
return client_medium, None
506
514
def _build_medium(self):
515
# ssh will prompt the user for a password if needed and if none is
516
# provided but it will not give it back, so no credentials can be
507
518
location_config = config.LocationConfig(self.base)
508
519
bzr_remote_path = location_config.get_bzr_remote_path()
509
user = self._parsed_url.user
511
auth = config.AuthenticationConfig()
512
user = auth.get_user('ssh', self._parsed_url.host,
513
self._parsed_url.port)
514
ssh_params = medium.SSHParams(self._parsed_url.host,
515
self._parsed_url.port, user, self._parsed_url.password,
517
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
518
return client_medium, (user, self._parsed_url.password)
520
client_medium = medium.SmartSSHClientMedium(self._host, self._port,
521
self._user, self._password, self.base,
522
bzr_remote_path=bzr_remote_path)
523
return client_medium, None
521
526
class RemoteHTTPTransport(RemoteTransport):
522
527
"""Just a way to connect between a bzr+http:// url and http://.
524
529
This connection operates slightly differently than the RemoteSSHTransport.
525
530
It uses a plain http:// transport underneath, which defines what remote
526
531
.bzr/smart URL we are connected to. From there, all paths that are sent are
575
580
_from_transport=self,
576
581
http_transport=self._http_transport)
578
def _redirected_to(self, source, target):
579
"""See transport._redirected_to"""
580
redirected = self._http_transport._redirected_to(source, target)
581
if (redirected is not None
582
and isinstance(redirected, type(self._http_transport))):
583
return RemoteHTTPTransport('bzr+' + redirected.external_url(),
584
http_transport=redirected)
586
# Either None or a transport for a different protocol
590
class HintingSSHTransport(transport.Transport):
591
"""Simple transport that handles ssh:// and points out bzr+ssh://."""
593
def __init__(self, url):
594
raise errors.UnsupportedProtocol(url,
595
'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
598
584
def get_test_permutations():
599
585
"""Return (transport, server) permutations for testing."""
600
586
### We may need a little more test framework support to construct an
601
587
### appropriate RemoteTransport in the future.
602
from bzrlib.tests import test_server
603
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]
588
from bzrlib.smart import server
589
return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]