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
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)

Subscribers

People subscribed via source and target branches

to all changes: