Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(497)

Side by Side Diff: third_party/twisted_8_1/twisted/conch/test/test_conch.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.conch.test.test_conch -*-
2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 import os, sys
6
7 try:
8 import Crypto
9 except:
10 Crypto = None
11
12 from twisted.cred import portal
13 from twisted.internet import reactor, defer, protocol
14 from twisted.internet.error import ProcessExitedAlready
15 from twisted.python import log, runtime
16 from twisted.python.filepath import FilePath
17 from twisted.trial import unittest
18 from twisted.conch.error import ConchError
19 from twisted.conch.test.test_ssh import ConchTestRealm
20 from twisted.python.procutils import which
21
22 from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh
23 from twisted.conch.test.keydata import publicDSA_openssh, privateDSA_openssh
24
25
26
27 class Echo(protocol.Protocol):
28 def connectionMade(self):
29 log.msg('ECHO CONNECTION MADE')
30
31
32 def connectionLost(self, reason):
33 log.msg('ECHO CONNECTION DONE')
34
35
36 def dataReceived(self, data):
37 self.transport.write(data)
38 if '\n' in data:
39 self.transport.loseConnection()
40
41
42
43 class EchoFactory(protocol.Factory):
44 protocol = Echo
45
46
47
48 class ConchTestOpenSSHProcess(protocol.ProcessProtocol):
49 """
50 Test protocol for launching an OpenSSH client process.
51
52 @ivar deferred: Set by whatever uses this object. Accessed using
53 L{_getDeferred}, which destroys the value so the Deferred is not
54 fired twice. Fires when the process is terminated.
55 """
56
57 deferred = None
58 buf = ''
59
60 def _getDeferred(self):
61 d, self.deferred = self.deferred, None
62 return d
63
64
65 def outReceived(self, data):
66 self.buf += data
67
68
69 def processEnded(self, reason):
70 """
71 Called when the process has ended.
72
73 @param reason: a Failure giving the reason for the process' end.
74 """
75 if reason.value.exitCode != 0:
76 self._getDeferred().errback(
77 ConchError("exit code was not 0: %s" %
78 reason.value.exitCode))
79 else:
80 buf = self.buf.replace('\r\n', '\n')
81 self._getDeferred().callback(buf)
82
83
84
85 class ConchTestForwardingProcess(protocol.ProcessProtocol):
86 """
87 Manages a third-party process which launches a server.
88
89 Uses L{ConchTestForwardingPort} to connect to the third-party server.
90 Once L{ConchTestForwardingPort} has disconnected, kill the process and fire
91 a Deferred with the data received by the L{ConchTestForwardingPort}.
92
93 @ivar deferred: Set by whatever uses this object. Accessed using
94 L{_getDeferred}, which destroys the value so the Deferred is not
95 fired twice. Fires when the process is terminated.
96 """
97
98 deferred = None
99
100 def __init__(self, port, data):
101 """
102 @type port: C{int}
103 @param port: The port on which the third-party server is listening.
104 (it is assumed that the server is running on localhost).
105
106 @type data: C{str}
107 @param data: This is sent to the third-party server. Must end with '\n'
108 in order to trigger a disconnect.
109 """
110 self.port = port
111 self.buffer = None
112 self.data = data
113
114
115 def _getDeferred(self):
116 d, self.deferred = self.deferred, None
117 return d
118
119
120 def connectionMade(self):
121 self._connect()
122
123
124 def _connect(self):
125 """
126 Connect to the server, which is often a third-party process.
127 Tries to reconnect if it fails because we have no way of determining
128 exactly when the port becomes available for listening -- we can only
129 know when the process starts.
130 """
131 cc = protocol.ClientCreator(reactor, ConchTestForwardingPort, self,
132 self.data)
133 d = cc.connectTCP('127.0.0.1', self.port)
134 d.addErrback(self._ebConnect)
135 return d
136
137
138 def _ebConnect(self, f):
139 reactor.callLater(1, self._connect)
140
141
142 def forwardingPortDisconnected(self, buffer):
143 """
144 The network connection has died; save the buffer of output
145 from the network and attempt to quit the process gracefully,
146 and then (after the reactor has spun) send it a KILL signal.
147 """
148 self.buffer = buffer
149 self.transport.write('\x03')
150 self.transport.loseConnection()
151 reactor.callLater(0, self._reallyDie)
152
153
154 def _reallyDie(self):
155 try:
156 self.transport.signalProcess('KILL')
157 except ProcessExitedAlready:
158 pass
159
160
161 def processEnded(self, reason):
162 """
163 Fire the Deferred at self.deferred with the data collected
164 from the L{ConchTestForwardingPort} connection, if any.
165 """
166 self._getDeferred().callback(self.buffer)
167
168
169
170 class ConchTestForwardingPort(protocol.Protocol):
171 """
172 Connects to server launched by a third-party process (managed by
173 L{ConchTestForwardingProcess}) sends data, then reports whatever it
174 received back to the L{ConchTestForwardingProcess} once the connection
175 is ended.
176 """
177
178
179 def __init__(self, protocol, data):
180 """
181 @type protocol: L{ConchTestForwardingProcess}
182 @param protocol: The L{ProcessProtocol} which made this connection.
183
184 @type data: str
185 @param data: The data to be sent to the third-party server.
186 """
187 self.protocol = protocol
188 self.data = data
189
190
191 def connectionMade(self):
192 self.buffer = ''
193 self.transport.write(self.data)
194
195
196 def dataReceived(self, data):
197 self.buffer += data
198
199
200 def connectionLost(self, reason):
201 self.protocol.forwardingPortDisconnected(self.buffer)
202
203
204
205 if Crypto:
206 from twisted.conch.client import options, default, connect
207 from twisted.conch.ssh import forwarding
208 from twisted.conch.ssh import connection
209
210 from twisted.conch.test.test_ssh import ConchTestServerFactory
211 from twisted.conch.test.test_ssh import ConchTestPublicKeyChecker
212
213
214 class SSHTestConnectionForUnix(connection.SSHConnection):
215 """
216 @ivar stopDeferred: Deferred that will be fired when C{serviceStopped}
217 is called.
218 @type stopDeferred: C{defer.Deferred}
219 """
220
221 def __init__(self, p, exe=None, cmds=None):
222 connection.SSHConnection.__init__(self)
223 if p:
224 self.spawn = (p, exe, cmds)
225 else:
226 self.spawn = None
227 self.connected = 0
228 self.remoteForwards = {}
229 self.stopDeferred = defer.Deferred()
230
231 def serviceStopped(self):
232 self.stopDeferred.callback(None)
233
234 def serviceStarted(self):
235 if self.spawn:
236 env = os.environ.copy()
237 env['PYTHONPATH'] = os.pathsep.join(sys.path)
238 reactor.callLater(0,reactor.spawnProcess, env=env, *self.spawn)
239 self.connected = 1
240
241 def requestRemoteForwarding(self, remotePort, hostport):
242 data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
243 d = self.sendGlobalRequest('tcpip-forward', data,
244 wantReply=1)
245 log.msg('requesting remote forwarding %s:%s' %(remotePort, hostport) )
246 d.addCallback(self._cbRemoteForwarding, remotePort, hostport)
247 d.addErrback(self._ebRemoteForwarding, remotePort, hostport)
248
249 def _cbRemoteForwarding(self, result, remotePort, hostport):
250 log.msg('accepted remote forwarding %s:%s' % (remotePort, hostport))
251 self.remoteForwards[remotePort] = hostport
252 log.msg(repr(self.remoteForwards))
253
254 def _ebRemoteForwarding(self, f, remotePort, hostport):
255 log.msg('remote forwarding %s:%s failed' % (remotePort, hostport))
256 log.msg(f)
257
258 def cancelRemoteForwarding(self, remotePort):
259 data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
260 self.sendGlobalRequest('cancel-tcpip-forward', data)
261 log.msg('cancelling remote forwarding %s' % remotePort)
262 try:
263 del self.remoteForwards[remotePort]
264 except:
265 pass
266 log.msg(repr(self.remoteForwards))
267
268 def channel_forwarded_tcpip(self, windowSize, maxPacket, data):
269 log.msg('%s %s' % ('FTCP', repr(data)))
270 remoteHP, origHP = forwarding.unpackOpen_forwarded_tcpip(data)
271 log.msg(self.remoteForwards)
272 log.msg(remoteHP)
273 if self.remoteForwards.has_key(remoteHP[1]):
274 connectHP = self.remoteForwards[remoteHP[1]]
275 log.msg('connect forwarding %s' % (connectHP,))
276 return forwarding.SSHConnectForwardingChannel(connectHP,
277 remoteWindow = windowSize,
278 remoteMaxPacket = maxPacket,
279 conn = self)
280 else:
281 raise ConchError(connection.OPEN_CONNECT_FAILED, "don't know abo ut that port")
282
283
284
285 def _makeArgs(args, mod="conch"):
286 start = [sys.executable, '-c'
287 """
288 ### Twisted Preamble
289 import sys, os
290 path = os.path.abspath(sys.argv[0])
291 while os.path.dirname(path) != path:
292 if os.path.basename(path).startswith('Twisted'):
293 sys.path.insert(0, path)
294 break
295 path = os.path.dirname(path)
296
297 from twisted.conch.scripts.%s import run
298 run()""" % mod]
299 return start + list(args)
300
301
302
303 class ForwardingTestBase:
304 """
305 Template class for tests of the Conch server's ability to forward arbitrary
306 protocols over SSH.
307
308 These tests are integration tests, not unit tests. They launch a Conch
309 server, a custom TCP server (just an L{EchoProtocol}) and then call
310 L{execute}.
311
312 L{execute} is implemented by subclasses of L{ForwardingTestBase}. It should
313 cause an SSH client to connect to the Conch server, asking it to forward
314 data to the custom TCP server.
315 """
316
317 if not Crypto:
318 skip = "can't run w/o PyCrypto"
319
320 def _createFiles(self):
321 for f in ['rsa_test','rsa_test.pub','dsa_test','dsa_test.pub',
322 'kh_test']:
323 if os.path.exists(f):
324 os.remove(f)
325 open('rsa_test','w').write(privateRSA_openssh)
326 open('rsa_test.pub','w').write(publicRSA_openssh)
327 open('dsa_test.pub','w').write(publicDSA_openssh)
328 open('dsa_test','w').write(privateDSA_openssh)
329 os.chmod('dsa_test', 33152)
330 os.chmod('rsa_test', 33152)
331 open('kh_test','w').write('127.0.0.1 '+publicRSA_openssh)
332
333
334 def _getFreePort(self):
335 f = EchoFactory()
336 serv = reactor.listenTCP(0, f)
337 port = serv.getHost().port
338 serv.stopListening()
339 return port
340
341
342 def _makeConchFactory(self):
343 """
344 Make a L{ConchTestServerFactory}, which allows us to start a
345 L{ConchTestServer} -- i.e. an actually listening conch.
346 """
347 realm = ConchTestRealm()
348 p = portal.Portal(realm)
349 p.registerChecker(ConchTestPublicKeyChecker())
350 factory = ConchTestServerFactory()
351 factory.portal = p
352 return factory
353
354
355 def setUp(self):
356 self._createFiles()
357 self.conchFactory = self._makeConchFactory()
358 self.conchFactory.expectedLoseConnection = 1
359 self.conchServer = reactor.listenTCP(0, self.conchFactory,
360 interface="127.0.0.1")
361 self.echoServer = reactor.listenTCP(0, EchoFactory())
362 self.echoPort = self.echoServer.getHost().port
363
364
365 def tearDown(self):
366 try:
367 self.conchFactory.proto.done = 1
368 except AttributeError:
369 pass
370 else:
371 self.conchFactory.proto.transport.loseConnection()
372 return defer.gatherResults([
373 defer.maybeDeferred(self.conchServer.stopListening),
374 defer.maybeDeferred(self.echoServer.stopListening)])
375
376
377 def test_exec(self):
378 """
379 Test that we can use whatever client to send the command "echo goodbye"
380 to the Conch server. Make sure we receive "goodbye" back from the
381 server.
382 """
383 d = self.execute('echo goodbye', ConchTestOpenSSHProcess())
384 return d.addCallback(self.assertEquals, 'goodbye\n')
385
386
387 def test_localToRemoteForwarding(self):
388 """
389 Test that we can use whatever client to forward a local port to a
390 specified port on the server.
391 """
392 lport = self._getFreePort()
393 process = ConchTestForwardingProcess(lport, 'test\n')
394 d = self.execute('', process,
395 sshArgs='-N -L%i:127.0.0.1:%i'
396 % (lport, self.echoPort))
397 d.addCallback(self.assertEqual, 'test\n')
398 return d
399
400
401 def test_remoteToLocalForwarding(self):
402 """
403 Test that we can use whatever client to forward a port from the server
404 to a port locally.
405 """
406 localPort = self._getFreePort()
407 process = ConchTestForwardingProcess(localPort, 'test\n')
408 d = self.execute('', process,
409 sshArgs='-N -R %i:127.0.0.1:%i'
410 % (localPort, self.echoPort))
411 d.addCallback(self.assertEqual, 'test\n')
412 return d
413
414
415
416 class OpenSSHClientTestCase(ForwardingTestBase, unittest.TestCase):
417
418 def execute(self, remoteCommand, process, sshArgs=''):
419 """
420 Connects to the SSH server started in L{ForwardingTestBase.setUp} by
421 running the 'ssh' command line tool.
422
423 @type remoteCommand: str
424 @param remoteCommand: The command (with arguments) to run on the
425 remote end.
426
427 @type process: L{ConchTestOpenSSHProcess}
428
429 @type sshArgs: str
430 @param sshArgs: Arguments to pass to the 'ssh' process.
431
432 @return: L{defer.Deferred}
433 """
434 process.deferred = defer.Deferred()
435 cmdline = ('ssh -2 -l testuser -p %i '
436 '-oUserKnownHostsFile=kh_test '
437 '-oPasswordAuthentication=no '
438 # Always use the RSA key, since that's the one in kh_test.
439 '-oHostKeyAlgorithms=ssh-rsa '
440 '-a '
441 '-i dsa_test ') + sshArgs + \
442 ' 127.0.0.1 ' + remoteCommand
443 port = self.conchServer.getHost().port
444 cmds = (cmdline % port).split()
445 reactor.spawnProcess(process, "ssh", cmds)
446 return process.deferred
447
448
449
450 class CmdLineClientTestCase(ForwardingTestBase, unittest.TestCase):
451 def setUp(self):
452 if runtime.platformType == 'win32':
453 raise unittest.SkipTest("can't run cmdline client on win32")
454 ForwardingTestBase.setUp(self)
455
456
457 def execute(self, remoteCommand, process, sshArgs=''):
458 """
459 As for L{OpenSSHClientTestCase.execute}, except it runs the 'conch'
460 command line tool, not 'ssh'.
461 """
462 process.deferred = defer.Deferred()
463 port = self.conchServer.getHost().port
464 cmd = ('-p %i -l testuser '
465 '--known-hosts kh_test '
466 '--user-authentications publickey '
467 '--host-key-algorithms ssh-rsa '
468 '-a -I '
469 '-K direct '
470 '-i dsa_test '
471 '-v ') % port + sshArgs + \
472 ' 127.0.0.1 ' + remoteCommand
473 cmds = _makeArgs(cmd.split())
474 log.msg(str(cmds))
475 env = os.environ.copy()
476 env['PYTHONPATH'] = os.pathsep.join(sys.path)
477 reactor.spawnProcess(process, sys.executable, cmds, env=env)
478 return process.deferred
479
480
481
482 class _UnixFixHome(object):
483 """
484 Mixin class to fix the HOME environment variable to something usable.
485
486 @ivar home: FilePath pointing at C{homePath}.
487 @type home: L{FilePath}
488
489 @ivar homePath: relative path to the directory used as HOME during the
490 tests.
491 @type homePath: C{str}
492 """
493
494 def setUp(self):
495 path = self.mktemp()
496 self.home = FilePath(path)
497 self.homePath = os.path.join(*self.home.segmentsFrom(FilePath(".")))
498 if len(self.home.path) >= 70:
499 # UNIX_MAX_PATH is 108, and the socket file is generally of length
500 # 30, so we can't rely on mktemp...
501 self.homePath = "_tmp"
502 self.home = FilePath(self.homePath)
503 self.home.makedirs()
504 self.savedEnviron = os.environ.copy()
505 os.environ["HOME"] = self.homePath
506
507
508 def tearDown(self):
509 os.environ.clear()
510 os.environ.update(self.savedEnviron)
511 self.home.remove()
512
513
514
515 class UnixClientTestCase(_UnixFixHome, ForwardingTestBase, unittest.TestCase):
516 def setUp(self):
517 if runtime.platformType == 'win32':
518 raise unittest.SkipTest("can't run cmdline client on win32")
519 ForwardingTestBase.setUp(self)
520 _UnixFixHome.setUp(self)
521
522
523 def tearDown(self):
524 d1 = ForwardingTestBase.tearDown(self)
525 d2 = defer.maybeDeferred(self.conn.transport.transport.loseConnection)
526 d3 = self.conn.stopDeferred
527 def clean(ign):
528 _UnixFixHome.tearDown(self)
529 return ign
530 return defer.gatherResults([d1, d2, d3]).addBoth(clean)
531
532
533 def makeOptions(self):
534 o = options.ConchOptions()
535 def parseArgs(host, *args):
536 o['host'] = host
537 o.parseArgs = parseArgs
538 return o
539
540
541 def makeAuthClient(self, port, options):
542 cmds = (('-p %i -l testuser '
543 '--known-hosts kh_test '
544 '--user-authentications publickey '
545 '--host-key-algorithms ssh-rsa '
546 '-a '
547 '-K direct '
548 '-i dsa_test '
549 '127.0.0.1') % port).split()
550 options.parseOptions(cmds)
551 return default.SSHUserAuthClient(options['user'], options, self.conn)
552
553
554 def execute(self, remoteCommand, process, sshArgs=''):
555 """
556 Connect to the forwarding process using the 'unix' client found in
557 L{twisted.conch.client.unix.connect}. See
558 L{OpenSSHClientTestCase.execute}.
559 """
560 process.deferred = defer.Deferred()
561 port = self.conchServer.getHost().port
562 cmd = ('-p %i -l testuser '
563 '-K unix '
564 '-v ') % port + sshArgs + \
565 ' 127.0.0.1 ' + remoteCommand
566 cmds = _makeArgs(cmd.split())
567 options = self.makeOptions()
568 self.conn = SSHTestConnectionForUnix(process, sys.executable, cmds)
569 authClient = self.makeAuthClient(port, options)
570 d = connect.connect(options['host'], port, options,
571 default.verifyHostKey, authClient)
572 return d.addCallback(lambda x : process.deferred)
573
574
575 def test_noHome(self):
576 """
577 When setting the HOME environment variable to a path that doesn't
578 exist, L{connect.connect} should forward the failure, and the created
579 process should fail with a L{ConchError}.
580 """
581 path = self.mktemp()
582 # We override the HOME variable, and let tearDown restore the initial
583 # value
584 os.environ['HOME'] = path
585 process = ConchTestOpenSSHProcess()
586 d = self.execute('echo goodbye', process)
587 def cb(ign):
588 return self.assertFailure(process.deferred, ConchError)
589 return self.assertFailure(d, OSError).addCallback(cb)
590
591
592
593 if not which('ssh'):
594 OpenSSHClientTestCase.skip = "no ssh command-line client available"
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698