Merge lp:~bcsaller/charmtester/ssh-redux into lp:charmtester
- ssh-redux
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benjamin Saller | Pending | ||
Review via email:
|
Commit message
Description of the change
more verbose output, better ssh interactions
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'setup.py' | |||
2 | --- setup.py 2014-01-18 08:52:42 +0000 | |||
3 | +++ setup.py 2014-01-28 19:43:54 +0000 | |||
4 | @@ -18,12 +18,13 @@ | |||
5 | 18 | license="MIT", | 18 | license="MIT", |
6 | 19 | keywords="testing substates juju", | 19 | keywords="testing substates juju", |
7 | 20 | description=README, | 20 | description=README, |
9 | 21 | entry_points = { | 21 | entry_points={ |
10 | 22 | "console_scripts": [ | 22 | "console_scripts": [ |
11 | 23 | 'substrate_dispatch = substrateDispatcher.dispatcher:main', | 23 | 'substrate_dispatch = substrateDispatcher.dispatcher:main', |
13 | 24 | 'jssh = substrateDispatcher.jlxc:main', | 24 | 'jssh = substrateDispatcher.jlxc:ssh', |
14 | 25 | 'jhere = substrateDispatcher.jlxc:here', | 25 | 'jhere = substrateDispatcher.jlxc:here', |
16 | 26 | 'jscp = substrateDispatcher.jlxc:scp' | 26 | 'jscp = substrateDispatcher.jlxc:scp', |
17 | 27 | 'jstart = substrateDispatcher.jlxc:start' | ||
18 | 27 | ] | 28 | ] |
19 | 28 | } | 29 | } |
20 | 29 | ) | 30 | ) |
21 | 30 | 31 | ||
22 | === modified file 'substrateDispatcher/jlxc.py' | |||
23 | --- substrateDispatcher/jlxc.py 2014-01-27 21:22:38 +0000 | |||
24 | +++ substrateDispatcher/jlxc.py 2014-01-28 19:43:54 +0000 | |||
25 | @@ -9,8 +9,6 @@ | |||
26 | 9 | import tempfile | 9 | import tempfile |
27 | 10 | import time | 10 | import time |
28 | 11 | 11 | ||
29 | 12 | from multiprocessing import Process | ||
30 | 13 | |||
31 | 14 | from .identity import Identity | 12 | from .identity import Identity |
32 | 15 | 13 | ||
33 | 16 | log = default_log = logging.getLogger("jlxc") | 14 | log = default_log = logging.getLogger("jlxc") |
34 | @@ -21,36 +19,35 @@ | |||
35 | 21 | return self[key] | 19 | return self[key] |
36 | 22 | 20 | ||
37 | 23 | 21 | ||
38 | 24 | def concurrent(fn): | ||
39 | 25 | """Simple concurrency decorator""" | ||
40 | 26 | def run(*args, **kwargs): | ||
41 | 27 | c = Process(target=fn, args=args, kwargs=kwargs) | ||
42 | 28 | c.start() | ||
43 | 29 | c.join() | ||
44 | 30 | return c | ||
45 | 31 | return run | ||
46 | 32 | |||
47 | 33 | |||
48 | 34 | def run(cmd, **kwargs): | 22 | def run(cmd, **kwargs): |
49 | 35 | if isinstance(cmd, str): | 23 | if isinstance(cmd, str): |
50 | 36 | cmd = shlex.split(cmd) | 24 | cmd = shlex.split(cmd) |
51 | 37 | pipe = subprocess.PIPE | 25 | pipe = subprocess.PIPE |
52 | 38 | fatal = kwargs.pop('fatal', False) | 26 | fatal = kwargs.pop('fatal', False) |
53 | 27 | inline = kwargs.pop('inline', False) | ||
54 | 28 | on_failure = kwargs.pop('on_failure', None) | ||
55 | 29 | stdout = kwargs.pop('stdout', inline and sys.stdout or pipe) | ||
56 | 30 | |||
57 | 39 | cmdstr = ' '.join(cmd) | 31 | cmdstr = ' '.join(cmd) |
58 | 40 | log.debug("Exec Command: {}".format(cmdstr)) | 32 | log.debug("Exec Command: {}".format(cmdstr)) |
59 | 33 | |||
60 | 41 | process = subprocess.Popen( | 34 | process = subprocess.Popen( |
62 | 42 | cmd, stdout=kwargs.pop('stdout', pipe), | 35 | cmd, |
63 | 36 | stdout=stdout, | ||
64 | 43 | stderr=kwargs.pop('stderr', pipe), | 37 | stderr=kwargs.pop('stderr', pipe), |
65 | 44 | close_fds=kwargs.pop('close_fds', True), **kwargs) | 38 | close_fds=kwargs.pop('close_fds', True), **kwargs) |
66 | 45 | stdout, stderr = process.communicate() | 39 | stdout, stderr = process.communicate() |
67 | 46 | if process.returncode != 0: | 40 | if process.returncode != 0: |
68 | 47 | log.debug('Error invoking cmd: {} -> {}'.format( | 41 | log.debug('Error invoking cmd: {} -> {}'.format( |
69 | 48 | cmdstr, process.returncode)) | 42 | cmdstr, process.returncode)) |
71 | 49 | if fatal: | 43 | if fatal or on_failure: |
72 | 50 | exception = subprocess.CalledProcessError( | 44 | exception = subprocess.CalledProcessError( |
73 | 51 | process.returncode, cmdstr) | 45 | process.returncode, cmdstr) |
74 | 52 | exception.output = ''.join(filter(None, [stdout, stderr])) | 46 | exception.output = ''.join(filter(None, [stdout, stderr])) |
76 | 53 | raise exception | 47 | if on_failure: |
77 | 48 | on_failure(cmd, process, exception) | ||
78 | 49 | if fatal: | ||
79 | 50 | raise exception | ||
80 | 54 | return O(returncode=process.returncode, | 51 | return O(returncode=process.returncode, |
81 | 55 | stdout=stdout, stderr=stderr) | 52 | stdout=stdout, stderr=stderr) |
82 | 56 | 53 | ||
83 | @@ -165,7 +162,7 @@ | |||
84 | 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. |
85 | 166 | with LXCContainer(series_container) as c: | 163 | with LXCContainer(series_container) as c: |
86 | 167 | c.set_identity(self.identity) | 164 | c.set_identity(self.identity) |
88 | 168 | c.start(True) | 165 | c.start() |
89 | 169 | c.run_template('customize_container', | 166 | c.run_template('customize_container', |
90 | 170 | fatal=True) | 167 | fatal=True) |
91 | 171 | c.stop() | 168 | c.stop() |
92 | @@ -234,11 +231,24 @@ | |||
93 | 234 | def start(self, wait=False): | 231 | def start(self, wait=False): |
94 | 235 | if self.running: | 232 | if self.running: |
95 | 236 | return | 233 | return |
97 | 237 | params = ["sudo", "lxc-start", '-d'] | 234 | log_path = os.path.join(self.container_root, |
98 | 235 | "%s-container.log" % self.name) | ||
99 | 236 | |||
100 | 237 | def dump_log(cmd, process, exc): | ||
101 | 238 | with open(log_path, 'r') as fp: | ||
102 | 239 | output = fp.read() | ||
103 | 240 | exc_output = getattr(exc, 'output') | ||
104 | 241 | if exc_output: | ||
105 | 242 | output += exc_output | ||
106 | 243 | log.error("Command failure: %s\n%s", | ||
107 | 244 | cmd, output, exc_info=exc) | ||
108 | 245 | |||
109 | 246 | params = ["sudo", "lxc-start", '-d', | ||
110 | 247 | '-o', log_path] | ||
111 | 238 | if self.container_root: | 248 | if self.container_root: |
112 | 239 | params.extend(["-P", self.container_root]) | 249 | params.extend(["-P", self.container_root]) |
113 | 240 | params.extend(["--name", self.name]) | 250 | params.extend(["--name", self.name]) |
115 | 241 | run(params, fatal=True) | 251 | run(params, fatal=True, on_failure=dump_log) |
116 | 242 | 252 | ||
117 | 243 | if wait: | 253 | if wait: |
118 | 244 | self.wait_for_running(wait) | 254 | self.wait_for_running(wait) |
119 | @@ -343,7 +353,7 @@ | |||
120 | 343 | return result | 353 | return result |
121 | 344 | return self.ssh(dest, **kwargs) | 354 | return self.ssh(dest, **kwargs) |
122 | 345 | 355 | ||
124 | 346 | def run_template(self, template, inline=False, **kwargs): | 356 | def run_template(self, template, **kwargs): |
125 | 347 | if not os.path.exists(template): | 357 | if not os.path.exists(template): |
126 | 348 | template = os.path.join('templates', template) | 358 | template = os.path.join('templates', template) |
127 | 349 | if not os.path.exists(template): | 359 | if not os.path.exists(template): |
128 | @@ -352,11 +362,9 @@ | |||
129 | 352 | script = open(template, 'r').read() | 362 | script = open(template, 'r').read() |
130 | 353 | script = script.format(**kwargs) | 363 | script = script.format(**kwargs) |
131 | 354 | logging.debug("Executing HERE document::\n{}".format(script)) | 364 | logging.debug("Executing HERE document::\n{}".format(script)) |
135 | 355 | return self.here(script, | 365 | return self.here(script, fatal=fatal) |
133 | 356 | stdout=sys.stdout if inline else None, | ||
134 | 357 | fatal=fatal) | ||
136 | 358 | 366 | ||
138 | 359 | def wait_for(self, interval, timeout, *callbacks): | 367 | def wait_for(self, timeout, interval, *callbacks): |
139 | 360 | """ | 368 | """ |
140 | 361 | Repeatedly try callbacks until all return True | 369 | Repeatedly try callbacks until all return True |
141 | 362 | 370 | ||
142 | @@ -376,12 +384,13 @@ | |||
143 | 376 | if passes is True: | 384 | if passes is True: |
144 | 377 | break | 385 | break |
145 | 378 | current = time.time() | 386 | current = time.time() |
148 | 379 | if current - start >= timeout: | 387 | if current - start >= timeout or \ |
149 | 380 | raise OSError("Timeout exceeded in waitFor") | 388 | current + interval > timeout: |
150 | 389 | raise OSError("Timeout exceeded in wait_for") | ||
151 | 381 | time.sleep(interval) | 390 | time.sleep(interval) |
152 | 382 | 391 | ||
155 | 383 | def wait_for_running(self, interval=5, timeout=30): | 392 | def wait_for_running(self, timeout=30, interval=2): |
156 | 384 | self.wait_for(interval, timeout, valid_ip, running_ssh) | 393 | self.wait_for(timeout, interval, valid_ip, running_ssh) |
157 | 385 | 394 | ||
158 | 386 | 395 | ||
159 | 387 | # wait_for callbacks | 396 | # wait_for callbacks |
160 | @@ -398,7 +407,7 @@ | |||
161 | 398 | s = socket.socket() | 407 | s = socket.socket() |
162 | 399 | # check ssh port | 408 | # check ssh port |
163 | 400 | addr = (ip, 22) | 409 | addr = (ip, 22) |
165 | 401 | logging.info('Waiting on ssh: %s', addr) | 410 | logging.debug('Waiting on ssh: %s', addr) |
166 | 402 | s.connect(addr) | 411 | s.connect(addr) |
167 | 403 | s.close() | 412 | s.close() |
168 | 404 | return True | 413 | return True |
169 | @@ -407,34 +416,40 @@ | |||
170 | 407 | return False | 416 | return False |
171 | 408 | 417 | ||
172 | 409 | 418 | ||
173 | 419 | def _normalize_container(value): | ||
174 | 420 | if value.endswith('/'): | ||
175 | 421 | value = value[:-1] | ||
176 | 422 | name, path = (os.path.basename(value), | ||
177 | 423 | os.path.dirname(value)) | ||
178 | 424 | return name, path | ||
179 | 425 | |||
180 | 426 | |||
181 | 427 | def _start_parser(): | ||
182 | 428 | parser = argparse.ArgumentParser() | ||
183 | 429 | parser.add_argument('-l', '--log-level', | ||
184 | 430 | dest="log_level", default=logging.INFO) | ||
185 | 431 | parser.add_argument('--timeout', type=int, default=35) | ||
186 | 432 | return parser | ||
187 | 433 | |||
188 | 434 | |||
189 | 410 | def here(): | 435 | def here(): |
193 | 411 | parser = argparse.ArgumentParser() | 436 | parser = _start_parser() |
191 | 412 | parser.add_argument('-l', '--log-level', | ||
192 | 413 | dest="log_level", default=logging.INFO) | ||
194 | 414 | parser.add_argument('container') | 437 | parser.add_argument('container') |
195 | 415 | parser.add_argument('script', type=file) | 438 | parser.add_argument('script', type=file) |
196 | 416 | options = parser.parse_args() | 439 | options = parser.parse_args() |
197 | 417 | |||
198 | 418 | logging.basicConfig(level=options.log_level) | 440 | logging.basicConfig(level=options.log_level) |
201 | 419 | if options.container.endswith('/'): | 441 | name, path = _normalize_container(options.container) |
202 | 420 | options.container = options.container[:-1] | 442 | identity = Identity.find() |
203 | 421 | 443 | ||
214 | 422 | with LXCManager(container_root=os.path.dirname(options.container), | 444 | with LXCContainer(name, path) as c: |
215 | 423 | identity=Identity.find(), | 445 | c.set_identity(identity) |
216 | 424 | cleanup=False, | 446 | if not c.running: |
217 | 425 | destroy=False) as m: | 447 | c.start(options.timeout) |
218 | 426 | with m.container(os.path.basename(options.container)) as c: | 448 | c.here(options.script) |
209 | 427 | if not c.running: | ||
210 | 428 | c.start() | ||
211 | 429 | c.here(options.script, | ||
212 | 430 | user=options.user, | ||
213 | 431 | identity=options.ssh_identity) | ||
219 | 432 | 449 | ||
220 | 433 | 450 | ||
221 | 434 | def scp(): | 451 | def scp(): |
225 | 435 | parser = argparse.ArgumentParser() | 452 | parser = _start_parser() |
223 | 436 | parser.add_argument('-l', '--log-level', dest="log_level", | ||
224 | 437 | default=logging.INFO) | ||
226 | 438 | parser.add_argument('-r', '--recursive', action="store_true") | 453 | parser.add_argument('-r', '--recursive', action="store_true") |
227 | 439 | parser.add_argument('container') | 454 | parser.add_argument('container') |
228 | 440 | parser.add_argument('source') | 455 | parser.add_argument('source') |
229 | @@ -442,46 +457,58 @@ | |||
230 | 442 | options = parser.parse_args() | 457 | options = parser.parse_args() |
231 | 443 | 458 | ||
232 | 444 | logging.basicConfig(level=options.log_level) | 459 | logging.basicConfig(level=options.log_level) |
252 | 445 | if options.container.endswith('/'): | 460 | name, path = _normalize_container(options.container) |
253 | 446 | options.container = options.container[:-1] | 461 | identity = Identity.find() |
254 | 447 | 462 | ||
255 | 448 | with LXCManager(container_root=os.path.dirname(options.container), | 463 | with LXCContainer(name, path) as c: |
256 | 449 | identity=Identity.find(), | 464 | c.set_identity(identity) |
257 | 450 | cleanup=False, | 465 | if not c.running: |
258 | 451 | destroy=False) as m: | 466 | c.start(options.timeout) |
259 | 452 | with m.container(os.path.basename(options.container)) as c: | 467 | c.scp(options.source, options.dest, |
260 | 453 | if not c.running: | 468 | recursive=options.recursive) |
261 | 454 | c.start() | 469 | |
262 | 455 | c.scp(options.source, options.dest, | 470 | |
263 | 456 | recursive=options.recursive, | 471 | def ssh(): |
264 | 457 | user=options.user, identity=options.ssh_identity) | 472 | parser = _start_parser() |
246 | 458 | |||
247 | 459 | |||
248 | 460 | def main(): | ||
249 | 461 | parser = argparse.ArgumentParser() | ||
250 | 462 | parser.add_argument('-l', '--log-level', | ||
251 | 463 | dest="log_level", default=logging.INFO) | ||
265 | 464 | parser.add_argument('container') | 473 | parser.add_argument('container') |
266 | 465 | parser.add_argument('cmd', nargs="+") | 474 | parser.add_argument('cmd', nargs="+") |
267 | 466 | options = parser.parse_args() | 475 | options = parser.parse_args() |
268 | 467 | 476 | ||
269 | 468 | logging.basicConfig(level=options.log_level) | 477 | logging.basicConfig(level=options.log_level) |
289 | 469 | if options.container.endswith('/'): | 478 | name, path = _normalize_container(options.container) |
290 | 470 | options.container = options.container[:-1] | 479 | identity = Identity.find() |
291 | 471 | 480 | ||
292 | 472 | with LXCManager(container_root=os.path.dirname(options.container), | 481 | with LXCContainer(name, path) as c: |
293 | 473 | identity=Identity.find(), | 482 | c.set_identity(identity) |
294 | 474 | cleanup=False, | 483 | if not c.running: |
295 | 475 | destroy=False) as m: | 484 | c.start(options.timeout) |
296 | 476 | with m.container(os.path.basename(options.container)) as c: | 485 | result = c.ssh(options.cmd) |
297 | 477 | if not c.running: | 486 | |
298 | 478 | c.start() | 487 | if result.stdout: |
299 | 479 | result = c.ssh(options.cmd, user=options.user, | 488 | print(result.stdout) |
300 | 480 | identity=options.ssh_identity) | 489 | if result.stderr: |
301 | 481 | if result.stdout: | 490 | print(result.stderr) |
302 | 482 | print(result.stdout) | 491 | |
303 | 483 | if result.stderr: | 492 | |
304 | 484 | print(result.stderr) | 493 | def start(): |
305 | 485 | 494 | parser = _start_parser() | |
306 | 486 | if __name__ == '__main__': | 495 | parser.add_argument('container') |
307 | 487 | main() | 496 | options = parser.parse_args() |
308 | 497 | |||
309 | 498 | logging.basicConfig(level=options.log_level) | ||
310 | 499 | name, path = _normalize_container(options.container) | ||
311 | 500 | identity = Identity.find() | ||
312 | 501 | |||
313 | 502 | c = LXCContainer(name, path) | ||
314 | 503 | c.set_identity(identity) | ||
315 | 504 | if not c.running: | ||
316 | 505 | c.start(options.timeout) | ||
317 | 506 | print('Container will be left running, to stop:\n' | ||
318 | 507 | 'sudo lxc-stop -P {} -n {}'.format( | ||
319 | 508 | c.container_root, c.name)) | ||
320 | 509 | sys.stdout.flush() | ||
321 | 510 | |||
322 | 511 | ssh_args = ["ssh", "-o", "StrictHostKeyChecking=no", | ||
323 | 512 | "-o", "UserKnownHostsFile=/dev/null", | ||
324 | 513 | "ubuntu@{}".format(c.info()['ipv4'])] | ||
325 | 514 | os.execvp('ssh', ssh_args) |
*** Submitted:
more verbose output, better ssh interactions