Merge lp:~bcsaller/charmtester/ssh-redux into lp:charmtester

Proposed by Benjamin Saller
Status: Merged
Merged at revision: 15
Proposed branch: lp:~bcsaller/charmtester/ssh-redux
Merge into: lp:charmtester
Diff against target: 325 lines (+116/-88)
2 files modified
setup.py (+4/-3)
substrateDispatcher/jlxc.py (+112/-85)
To merge this branch: bzr merge lp:~bcsaller/charmtester/ssh-redux
Reviewer Review Type Date Requested Status
Benjamin Saller Pending
Review via email: mp+203626@code.launchpad.net

Description of the change

more verbose output, better ssh interactions

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote :

*** Submitted:

more verbose output, better ssh interactions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'setup.py'
--- setup.py 2014-01-18 08:52:42 +0000
+++ setup.py 2014-01-28 19:43:54 +0000
@@ -18,12 +18,13 @@
18 license="MIT",18 license="MIT",
19 keywords="testing substates juju",19 keywords="testing substates juju",
20 description=README,20 description=README,
21 entry_points = {21 entry_points={
22 "console_scripts": [22 "console_scripts": [
23 'substrate_dispatch = substrateDispatcher.dispatcher:main',23 'substrate_dispatch = substrateDispatcher.dispatcher:main',
24 'jssh = substrateDispatcher.jlxc:main',24 'jssh = substrateDispatcher.jlxc:ssh',
25 'jhere = substrateDispatcher.jlxc:here',25 'jhere = substrateDispatcher.jlxc:here',
26 'jscp = substrateDispatcher.jlxc:scp'26 'jscp = substrateDispatcher.jlxc:scp',
27 'jstart = substrateDispatcher.jlxc:start'
27 ]28 ]
28 }29 }
29)30)
3031
=== modified file 'substrateDispatcher/jlxc.py'
--- substrateDispatcher/jlxc.py 2014-01-27 21:22:38 +0000
+++ substrateDispatcher/jlxc.py 2014-01-28 19:43:54 +0000
@@ -9,8 +9,6 @@
9import tempfile9import tempfile
10import time10import time
1111
12from multiprocessing import Process
13
14from .identity import Identity12from .identity import Identity
1513
16log = default_log = logging.getLogger("jlxc")14log = default_log = logging.getLogger("jlxc")
@@ -21,36 +19,35 @@
21 return self[key]19 return self[key]
2220
2321
24def concurrent(fn):
25 """Simple concurrency decorator"""
26 def run(*args, **kwargs):
27 c = Process(target=fn, args=args, kwargs=kwargs)
28 c.start()
29 c.join()
30 return c
31 return run
32
33
34def run(cmd, **kwargs):22def run(cmd, **kwargs):
35 if isinstance(cmd, str):23 if isinstance(cmd, str):
36 cmd = shlex.split(cmd)24 cmd = shlex.split(cmd)
37 pipe = subprocess.PIPE25 pipe = subprocess.PIPE
38 fatal = kwargs.pop('fatal', False)26 fatal = kwargs.pop('fatal', False)
27 inline = kwargs.pop('inline', False)
28 on_failure = kwargs.pop('on_failure', None)
29 stdout = kwargs.pop('stdout', inline and sys.stdout or pipe)
30
39 cmdstr = ' '.join(cmd)31 cmdstr = ' '.join(cmd)
40 log.debug("Exec Command: {}".format(cmdstr))32 log.debug("Exec Command: {}".format(cmdstr))
33
41 process = subprocess.Popen(34 process = subprocess.Popen(
42 cmd, stdout=kwargs.pop('stdout', pipe),35 cmd,
36 stdout=stdout,
43 stderr=kwargs.pop('stderr', pipe),37 stderr=kwargs.pop('stderr', pipe),
44 close_fds=kwargs.pop('close_fds', True), **kwargs)38 close_fds=kwargs.pop('close_fds', True), **kwargs)
45 stdout, stderr = process.communicate()39 stdout, stderr = process.communicate()
46 if process.returncode != 0:40 if process.returncode != 0:
47 log.debug('Error invoking cmd: {} -> {}'.format(41 log.debug('Error invoking cmd: {} -> {}'.format(
48 cmdstr, process.returncode))42 cmdstr, process.returncode))
49 if fatal:43 if fatal or on_failure:
50 exception = subprocess.CalledProcessError(44 exception = subprocess.CalledProcessError(
51 process.returncode, cmdstr)45 process.returncode, cmdstr)
52 exception.output = ''.join(filter(None, [stdout, stderr]))46 exception.output = ''.join(filter(None, [stdout, stderr]))
53 raise exception47 if on_failure:
48 on_failure(cmd, process, exception)
49 if fatal:
50 raise exception
54 return O(returncode=process.returncode,51 return O(returncode=process.returncode,
55 stdout=stdout, stderr=stderr)52 stdout=stdout, stderr=stderr)
5653
@@ -165,7 +162,7 @@
165 # they will skip so it makes sense to do this in the base image.162 # they will skip so it makes sense to do this in the base image.
166 with LXCContainer(series_container) as c:163 with LXCContainer(series_container) as c:
167 c.set_identity(self.identity)164 c.set_identity(self.identity)
168 c.start(True)165 c.start()
169 c.run_template('customize_container',166 c.run_template('customize_container',
170 fatal=True)167 fatal=True)
171 c.stop()168 c.stop()
@@ -234,11 +231,24 @@
234 def start(self, wait=False):231 def start(self, wait=False):
235 if self.running:232 if self.running:
236 return233 return
237 params = ["sudo", "lxc-start", '-d']234 log_path = os.path.join(self.container_root,
235 "%s-container.log" % self.name)
236
237 def dump_log(cmd, process, exc):
238 with open(log_path, 'r') as fp:
239 output = fp.read()
240 exc_output = getattr(exc, 'output')
241 if exc_output:
242 output += exc_output
243 log.error("Command failure: %s\n%s",
244 cmd, output, exc_info=exc)
245
246 params = ["sudo", "lxc-start", '-d',
247 '-o', log_path]
238 if self.container_root:248 if self.container_root:
239 params.extend(["-P", self.container_root])249 params.extend(["-P", self.container_root])
240 params.extend(["--name", self.name])250 params.extend(["--name", self.name])
241 run(params, fatal=True)251 run(params, fatal=True, on_failure=dump_log)
242252
243 if wait:253 if wait:
244 self.wait_for_running(wait)254 self.wait_for_running(wait)
@@ -343,7 +353,7 @@
343 return result353 return result
344 return self.ssh(dest, **kwargs)354 return self.ssh(dest, **kwargs)
345355
346 def run_template(self, template, inline=False, **kwargs):356 def run_template(self, template, **kwargs):
347 if not os.path.exists(template):357 if not os.path.exists(template):
348 template = os.path.join('templates', template)358 template = os.path.join('templates', template)
349 if not os.path.exists(template):359 if not os.path.exists(template):
@@ -352,11 +362,9 @@
352 script = open(template, 'r').read()362 script = open(template, 'r').read()
353 script = script.format(**kwargs)363 script = script.format(**kwargs)
354 logging.debug("Executing HERE document::\n{}".format(script))364 logging.debug("Executing HERE document::\n{}".format(script))
355 return self.here(script,365 return self.here(script, fatal=fatal)
356 stdout=sys.stdout if inline else None,
357 fatal=fatal)
358366
359 def wait_for(self, interval, timeout, *callbacks):367 def wait_for(self, timeout, interval, *callbacks):
360 """368 """
361 Repeatedly try callbacks until all return True369 Repeatedly try callbacks until all return True
362370
@@ -376,12 +384,13 @@
376 if passes is True:384 if passes is True:
377 break385 break
378 current = time.time()386 current = time.time()
379 if current - start >= timeout:387 if current - start >= timeout or \
380 raise OSError("Timeout exceeded in waitFor")388 current + interval > timeout:
389 raise OSError("Timeout exceeded in wait_for")
381 time.sleep(interval)390 time.sleep(interval)
382391
383 def wait_for_running(self, interval=5, timeout=30):392 def wait_for_running(self, timeout=30, interval=2):
384 self.wait_for(interval, timeout, valid_ip, running_ssh)393 self.wait_for(timeout, interval, valid_ip, running_ssh)
385394
386395
387# wait_for callbacks396# wait_for callbacks
@@ -398,7 +407,7 @@
398 s = socket.socket()407 s = socket.socket()
399 # check ssh port408 # check ssh port
400 addr = (ip, 22)409 addr = (ip, 22)
401 logging.info('Waiting on ssh: %s', addr)410 logging.debug('Waiting on ssh: %s', addr)
402 s.connect(addr)411 s.connect(addr)
403 s.close()412 s.close()
404 return True413 return True
@@ -407,34 +416,40 @@
407 return False416 return False
408417
409418
419def _normalize_container(value):
420 if value.endswith('/'):
421 value = value[:-1]
422 name, path = (os.path.basename(value),
423 os.path.dirname(value))
424 return name, path
425
426
427def _start_parser():
428 parser = argparse.ArgumentParser()
429 parser.add_argument('-l', '--log-level',
430 dest="log_level", default=logging.INFO)
431 parser.add_argument('--timeout', type=int, default=35)
432 return parser
433
434
410def here():435def here():
411 parser = argparse.ArgumentParser()436 parser = _start_parser()
412 parser.add_argument('-l', '--log-level',
413 dest="log_level", default=logging.INFO)
414 parser.add_argument('container')437 parser.add_argument('container')
415 parser.add_argument('script', type=file)438 parser.add_argument('script', type=file)
416 options = parser.parse_args()439 options = parser.parse_args()
417
418 logging.basicConfig(level=options.log_level)440 logging.basicConfig(level=options.log_level)
419 if options.container.endswith('/'):441 name, path = _normalize_container(options.container)
420 options.container = options.container[:-1]442 identity = Identity.find()
421443
422 with LXCManager(container_root=os.path.dirname(options.container),444 with LXCContainer(name, path) as c:
423 identity=Identity.find(),445 c.set_identity(identity)
424 cleanup=False,446 if not c.running:
425 destroy=False) as m:447 c.start(options.timeout)
426 with m.container(os.path.basename(options.container)) as c:448 c.here(options.script)
427 if not c.running:
428 c.start()
429 c.here(options.script,
430 user=options.user,
431 identity=options.ssh_identity)
432449
433450
434def scp():451def scp():
435 parser = argparse.ArgumentParser()452 parser = _start_parser()
436 parser.add_argument('-l', '--log-level', dest="log_level",
437 default=logging.INFO)
438 parser.add_argument('-r', '--recursive', action="store_true")453 parser.add_argument('-r', '--recursive', action="store_true")
439 parser.add_argument('container')454 parser.add_argument('container')
440 parser.add_argument('source')455 parser.add_argument('source')
@@ -442,46 +457,58 @@
442 options = parser.parse_args()457 options = parser.parse_args()
443458
444 logging.basicConfig(level=options.log_level)459 logging.basicConfig(level=options.log_level)
445 if options.container.endswith('/'):460 name, path = _normalize_container(options.container)
446 options.container = options.container[:-1]461 identity = Identity.find()
447462
448 with LXCManager(container_root=os.path.dirname(options.container),463 with LXCContainer(name, path) as c:
449 identity=Identity.find(),464 c.set_identity(identity)
450 cleanup=False,465 if not c.running:
451 destroy=False) as m:466 c.start(options.timeout)
452 with m.container(os.path.basename(options.container)) as c:467 c.scp(options.source, options.dest,
453 if not c.running:468 recursive=options.recursive)
454 c.start()469
455 c.scp(options.source, options.dest,470
456 recursive=options.recursive,471def ssh():
457 user=options.user, identity=options.ssh_identity)472 parser = _start_parser()
458
459
460def main():
461 parser = argparse.ArgumentParser()
462 parser.add_argument('-l', '--log-level',
463 dest="log_level", default=logging.INFO)
464 parser.add_argument('container')473 parser.add_argument('container')
465 parser.add_argument('cmd', nargs="+")474 parser.add_argument('cmd', nargs="+")
466 options = parser.parse_args()475 options = parser.parse_args()
467476
468 logging.basicConfig(level=options.log_level)477 logging.basicConfig(level=options.log_level)
469 if options.container.endswith('/'):478 name, path = _normalize_container(options.container)
470 options.container = options.container[:-1]479 identity = Identity.find()
471480
472 with LXCManager(container_root=os.path.dirname(options.container),481 with LXCContainer(name, path) as c:
473 identity=Identity.find(),482 c.set_identity(identity)
474 cleanup=False,483 if not c.running:
475 destroy=False) as m:484 c.start(options.timeout)
476 with m.container(os.path.basename(options.container)) as c:485 result = c.ssh(options.cmd)
477 if not c.running:486
478 c.start()487 if result.stdout:
479 result = c.ssh(options.cmd, user=options.user,488 print(result.stdout)
480 identity=options.ssh_identity)489 if result.stderr:
481 if result.stdout:490 print(result.stderr)
482 print(result.stdout)491
483 if result.stderr:492
484 print(result.stderr)493def start():
485494 parser = _start_parser()
486if __name__ == '__main__':495 parser.add_argument('container')
487 main()496 options = parser.parse_args()
497
498 logging.basicConfig(level=options.log_level)
499 name, path = _normalize_container(options.container)
500 identity = Identity.find()
501
502 c = LXCContainer(name, path)
503 c.set_identity(identity)
504 if not c.running:
505 c.start(options.timeout)
506 print('Container will be left running, to stop:\n'
507 'sudo lxc-stop -P {} -n {}'.format(
508 c.container_root, c.name))
509 sys.stdout.flush()
510
511 ssh_args = ["ssh", "-o", "StrictHostKeyChecking=no",
512 "-o", "UserKnownHostsFile=/dev/null",
513 "ubuntu@{}".format(c.info()['ipv4'])]
514 os.execvp('ssh', ssh_args)

Subscribers

People subscribed via source and target branches

to all changes: