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 | license="MIT", |
6 | keywords="testing substates juju", |
7 | description=README, |
8 | - entry_points = { |
9 | + entry_points={ |
10 | "console_scripts": [ |
11 | 'substrate_dispatch = substrateDispatcher.dispatcher:main', |
12 | - 'jssh = substrateDispatcher.jlxc:main', |
13 | + 'jssh = substrateDispatcher.jlxc:ssh', |
14 | 'jhere = substrateDispatcher.jlxc:here', |
15 | - 'jscp = substrateDispatcher.jlxc:scp' |
16 | + 'jscp = substrateDispatcher.jlxc:scp', |
17 | + 'jstart = substrateDispatcher.jlxc:start' |
18 | ] |
19 | } |
20 | ) |
21 | |
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 | import tempfile |
27 | import time |
28 | |
29 | -from multiprocessing import Process |
30 | - |
31 | from .identity import Identity |
32 | |
33 | log = default_log = logging.getLogger("jlxc") |
34 | @@ -21,36 +19,35 @@ |
35 | return self[key] |
36 | |
37 | |
38 | -def concurrent(fn): |
39 | - """Simple concurrency decorator""" |
40 | - def run(*args, **kwargs): |
41 | - c = Process(target=fn, args=args, kwargs=kwargs) |
42 | - c.start() |
43 | - c.join() |
44 | - return c |
45 | - return run |
46 | - |
47 | - |
48 | def run(cmd, **kwargs): |
49 | if isinstance(cmd, str): |
50 | cmd = shlex.split(cmd) |
51 | pipe = subprocess.PIPE |
52 | fatal = kwargs.pop('fatal', False) |
53 | + inline = kwargs.pop('inline', False) |
54 | + on_failure = kwargs.pop('on_failure', None) |
55 | + stdout = kwargs.pop('stdout', inline and sys.stdout or pipe) |
56 | + |
57 | cmdstr = ' '.join(cmd) |
58 | log.debug("Exec Command: {}".format(cmdstr)) |
59 | + |
60 | process = subprocess.Popen( |
61 | - cmd, stdout=kwargs.pop('stdout', pipe), |
62 | + cmd, |
63 | + stdout=stdout, |
64 | stderr=kwargs.pop('stderr', pipe), |
65 | close_fds=kwargs.pop('close_fds', True), **kwargs) |
66 | stdout, stderr = process.communicate() |
67 | if process.returncode != 0: |
68 | log.debug('Error invoking cmd: {} -> {}'.format( |
69 | cmdstr, process.returncode)) |
70 | - if fatal: |
71 | + if fatal or on_failure: |
72 | exception = subprocess.CalledProcessError( |
73 | process.returncode, cmdstr) |
74 | exception.output = ''.join(filter(None, [stdout, stderr])) |
75 | - raise exception |
76 | + if on_failure: |
77 | + on_failure(cmd, process, exception) |
78 | + if fatal: |
79 | + raise exception |
80 | return O(returncode=process.returncode, |
81 | stdout=stdout, stderr=stderr) |
82 | |
83 | @@ -165,7 +162,7 @@ |
84 | # they will skip so it makes sense to do this in the base image. |
85 | with LXCContainer(series_container) as c: |
86 | c.set_identity(self.identity) |
87 | - c.start(True) |
88 | + c.start() |
89 | c.run_template('customize_container', |
90 | fatal=True) |
91 | c.stop() |
92 | @@ -234,11 +231,24 @@ |
93 | def start(self, wait=False): |
94 | if self.running: |
95 | return |
96 | - params = ["sudo", "lxc-start", '-d'] |
97 | + log_path = os.path.join(self.container_root, |
98 | + "%s-container.log" % self.name) |
99 | + |
100 | + def dump_log(cmd, process, exc): |
101 | + with open(log_path, 'r') as fp: |
102 | + output = fp.read() |
103 | + exc_output = getattr(exc, 'output') |
104 | + if exc_output: |
105 | + output += exc_output |
106 | + log.error("Command failure: %s\n%s", |
107 | + cmd, output, exc_info=exc) |
108 | + |
109 | + params = ["sudo", "lxc-start", '-d', |
110 | + '-o', log_path] |
111 | if self.container_root: |
112 | params.extend(["-P", self.container_root]) |
113 | params.extend(["--name", self.name]) |
114 | - run(params, fatal=True) |
115 | + run(params, fatal=True, on_failure=dump_log) |
116 | |
117 | if wait: |
118 | self.wait_for_running(wait) |
119 | @@ -343,7 +353,7 @@ |
120 | return result |
121 | return self.ssh(dest, **kwargs) |
122 | |
123 | - def run_template(self, template, inline=False, **kwargs): |
124 | + def run_template(self, template, **kwargs): |
125 | if not os.path.exists(template): |
126 | template = os.path.join('templates', template) |
127 | if not os.path.exists(template): |
128 | @@ -352,11 +362,9 @@ |
129 | script = open(template, 'r').read() |
130 | script = script.format(**kwargs) |
131 | logging.debug("Executing HERE document::\n{}".format(script)) |
132 | - return self.here(script, |
133 | - stdout=sys.stdout if inline else None, |
134 | - fatal=fatal) |
135 | + return self.here(script, fatal=fatal) |
136 | |
137 | - def wait_for(self, interval, timeout, *callbacks): |
138 | + def wait_for(self, timeout, interval, *callbacks): |
139 | """ |
140 | Repeatedly try callbacks until all return True |
141 | |
142 | @@ -376,12 +384,13 @@ |
143 | if passes is True: |
144 | break |
145 | current = time.time() |
146 | - if current - start >= timeout: |
147 | - raise OSError("Timeout exceeded in waitFor") |
148 | + if current - start >= timeout or \ |
149 | + current + interval > timeout: |
150 | + raise OSError("Timeout exceeded in wait_for") |
151 | time.sleep(interval) |
152 | |
153 | - def wait_for_running(self, interval=5, timeout=30): |
154 | - self.wait_for(interval, timeout, valid_ip, running_ssh) |
155 | + def wait_for_running(self, timeout=30, interval=2): |
156 | + self.wait_for(timeout, interval, valid_ip, running_ssh) |
157 | |
158 | |
159 | # wait_for callbacks |
160 | @@ -398,7 +407,7 @@ |
161 | s = socket.socket() |
162 | # check ssh port |
163 | addr = (ip, 22) |
164 | - logging.info('Waiting on ssh: %s', addr) |
165 | + logging.debug('Waiting on ssh: %s', addr) |
166 | s.connect(addr) |
167 | s.close() |
168 | return True |
169 | @@ -407,34 +416,40 @@ |
170 | return False |
171 | |
172 | |
173 | +def _normalize_container(value): |
174 | + if value.endswith('/'): |
175 | + value = value[:-1] |
176 | + name, path = (os.path.basename(value), |
177 | + os.path.dirname(value)) |
178 | + return name, path |
179 | + |
180 | + |
181 | +def _start_parser(): |
182 | + parser = argparse.ArgumentParser() |
183 | + parser.add_argument('-l', '--log-level', |
184 | + dest="log_level", default=logging.INFO) |
185 | + parser.add_argument('--timeout', type=int, default=35) |
186 | + return parser |
187 | + |
188 | + |
189 | def here(): |
190 | - parser = argparse.ArgumentParser() |
191 | - parser.add_argument('-l', '--log-level', |
192 | - dest="log_level", default=logging.INFO) |
193 | + parser = _start_parser() |
194 | parser.add_argument('container') |
195 | parser.add_argument('script', type=file) |
196 | options = parser.parse_args() |
197 | - |
198 | logging.basicConfig(level=options.log_level) |
199 | - if options.container.endswith('/'): |
200 | - options.container = options.container[:-1] |
201 | + name, path = _normalize_container(options.container) |
202 | + identity = Identity.find() |
203 | |
204 | - with LXCManager(container_root=os.path.dirname(options.container), |
205 | - identity=Identity.find(), |
206 | - cleanup=False, |
207 | - destroy=False) as m: |
208 | - with m.container(os.path.basename(options.container)) as c: |
209 | - if not c.running: |
210 | - c.start() |
211 | - c.here(options.script, |
212 | - user=options.user, |
213 | - identity=options.ssh_identity) |
214 | + with LXCContainer(name, path) as c: |
215 | + c.set_identity(identity) |
216 | + if not c.running: |
217 | + c.start(options.timeout) |
218 | + c.here(options.script) |
219 | |
220 | |
221 | def scp(): |
222 | - parser = argparse.ArgumentParser() |
223 | - parser.add_argument('-l', '--log-level', dest="log_level", |
224 | - default=logging.INFO) |
225 | + parser = _start_parser() |
226 | parser.add_argument('-r', '--recursive', action="store_true") |
227 | parser.add_argument('container') |
228 | parser.add_argument('source') |
229 | @@ -442,46 +457,58 @@ |
230 | options = parser.parse_args() |
231 | |
232 | logging.basicConfig(level=options.log_level) |
233 | - if options.container.endswith('/'): |
234 | - options.container = options.container[:-1] |
235 | - |
236 | - with LXCManager(container_root=os.path.dirname(options.container), |
237 | - identity=Identity.find(), |
238 | - cleanup=False, |
239 | - destroy=False) as m: |
240 | - with m.container(os.path.basename(options.container)) as c: |
241 | - if not c.running: |
242 | - c.start() |
243 | - c.scp(options.source, options.dest, |
244 | - recursive=options.recursive, |
245 | - user=options.user, identity=options.ssh_identity) |
246 | - |
247 | - |
248 | -def main(): |
249 | - parser = argparse.ArgumentParser() |
250 | - parser.add_argument('-l', '--log-level', |
251 | - dest="log_level", default=logging.INFO) |
252 | + name, path = _normalize_container(options.container) |
253 | + identity = Identity.find() |
254 | + |
255 | + with LXCContainer(name, path) as c: |
256 | + c.set_identity(identity) |
257 | + if not c.running: |
258 | + c.start(options.timeout) |
259 | + c.scp(options.source, options.dest, |
260 | + recursive=options.recursive) |
261 | + |
262 | + |
263 | +def ssh(): |
264 | + parser = _start_parser() |
265 | parser.add_argument('container') |
266 | parser.add_argument('cmd', nargs="+") |
267 | options = parser.parse_args() |
268 | |
269 | logging.basicConfig(level=options.log_level) |
270 | - if options.container.endswith('/'): |
271 | - options.container = options.container[:-1] |
272 | - |
273 | - with LXCManager(container_root=os.path.dirname(options.container), |
274 | - identity=Identity.find(), |
275 | - cleanup=False, |
276 | - destroy=False) as m: |
277 | - with m.container(os.path.basename(options.container)) as c: |
278 | - if not c.running: |
279 | - c.start() |
280 | - result = c.ssh(options.cmd, user=options.user, |
281 | - identity=options.ssh_identity) |
282 | - if result.stdout: |
283 | - print(result.stdout) |
284 | - if result.stderr: |
285 | - print(result.stderr) |
286 | - |
287 | -if __name__ == '__main__': |
288 | - main() |
289 | + name, path = _normalize_container(options.container) |
290 | + identity = Identity.find() |
291 | + |
292 | + with LXCContainer(name, path) as c: |
293 | + c.set_identity(identity) |
294 | + if not c.running: |
295 | + c.start(options.timeout) |
296 | + result = c.ssh(options.cmd) |
297 | + |
298 | + if result.stdout: |
299 | + print(result.stdout) |
300 | + if result.stderr: |
301 | + print(result.stderr) |
302 | + |
303 | + |
304 | +def start(): |
305 | + parser = _start_parser() |
306 | + parser.add_argument('container') |
307 | + options = parser.parse_args() |
308 | + |
309 | + logging.basicConfig(level=options.log_level) |
310 | + name, path = _normalize_container(options.container) |
311 | + identity = Identity.find() |
312 | + |
313 | + c = LXCContainer(name, path) |
314 | + c.set_identity(identity) |
315 | + if not c.running: |
316 | + c.start(options.timeout) |
317 | + print('Container will be left running, to stop:\n' |
318 | + 'sudo lxc-stop -P {} -n {}'.format( |
319 | + c.container_root, c.name)) |
320 | + sys.stdout.flush() |
321 | + |
322 | + ssh_args = ["ssh", "-o", "StrictHostKeyChecking=no", |
323 | + "-o", "UserKnownHostsFile=/dev/null", |
324 | + "ubuntu@{}".format(c.info()['ipv4'])] |
325 | + os.execvp('ssh', ssh_args) |
*** Submitted:
more verbose output, better ssh interactions