Merge lp:~nuclearbob/utah/rsyslog-client into lp:utah
- rsyslog-client
- Merge into dev
Status: | Merged |
---|---|
Merged at revision: | 870 |
Proposed branch: | lp:~nuclearbob/utah/rsyslog-client |
Merge into: | lp:utah |
Diff against target: |
286 lines (+78/-11) 6 files modified
client.py (+9/-3) debian/changelog (+3/-0) utah/client/common.py (+50/-8) utah/client/runner.py (+7/-0) utah/client/testcase.py (+5/-0) utah/client/testsuite.py (+4/-0) |
To merge this branch: | bzr merge lp:~nuclearbob/utah/rsyslog-client |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andy Doan (community) | Approve | ||
Max Brustkern (community) | Needs Resubmitting | ||
Review via email:
|
Commit message
Description of the change
This branch adds syslog output on the LOCAL0 facility to the client. I'd like to add server-side code to use and process this output, but that's not done yet, and I'd like to get some other thoughts on the client-side design and implementation before I proceed.
- 862. By Max Brustkern
-
Merged iso squashfs validation update
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Max Brustkern (nuclearbob) wrote : | # |
> On 04/15/2013 03:49 PM, Max Brustkern wrote:
>
> ...
>
> is the "\n" going to make the syslog stuff look/act weird?
Not sure, but I can check.
>
> ...
>
> This seems inefficient. Why not use cStringIO? Then you won't have to do
> the '\n'.join(...) at the end of the function.
The only time I tried to use cStringIO it turned into a horrible mess that didn't work, so I haven't really looked at it again since then. If you think it's a good fit for here, I think something better than what I've got would be nice. I can try that.
>
> > try:
> > - stdout, stderr = p.communicate()
> > - stdout = _normalize_
> > - stderr = _normalize_
> > + while p.poll() is None:
>
> Is this a safe way to deal with a process? The concern I have is that
> stdout might be blocked, but there is data on stderr that needs to be
> read and we sit blocked? I think we might want to make this a "select"
> type look on stdout/stderr fd's.
I should look up the "select" thing, since I'm not familiar with it. I'm learning lots of things today! We used to do basically this as part of the vm-tools integration, and it worked fine, but that's no guarantee it couldn't fail here.
- 863. By Javier Collado
-
Merged documentation string fixes
Source branch: lp:~javier.collado/utah/documentation_fixes
- 864. By Javier Collado
-
Merged changes to validate master runlist before provisioning (LP: #1082087)
Source branch: lp:~javier.collado/utah/bug1082087
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Max Brustkern (nuclearbob) wrote : | # |
> On 04/15/2013 03:49 PM, Max Brustkern wrote:
>
> > https:/
>
> > === modified file 'client.py'
> > if not os.getuid() == 0:
> > parser.
> > - sys.stderr.
> > - 'to be executed as root user\n'
> > - .format(
> > + msg = ('{}: UTAH client is expected to be executed as root user\n'
> > + .format(
> > + sys.stderr.
> > + syslog.
>
> is the "\n" going to make the syslog stuff look/act weird?
>
It looks okay to me:
Apr 17 09:09:42 eden utah: client.py: UTAH client is expected to be executed as root user
Apr 17 09:09:48 eden utah: client.py: UTAH client is expected to be executed as root user
Apr 17 09:10:04 eden utah: Fetching testsuite: utah_tests_sample
I'm going to work on the other things.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Max Brustkern (nuclearbob) wrote : | # |
I switched to StringIO and pushed that. As for subprocess stuff, what do you think about something like this:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) wrote : | # |
On 04/17/2013 12:45 PM, Max Brustkern wrote:
> As for subprocess stuff, what do you think about something like this:
> http://
that stinks. in C a pipe is normal FD and you can do select stuff. I
guess threads are the way to go.
- 865. By Javier Collado
-
Fix broken build
The package build is broken because a weird exception:
RuntimeError: sys.path must be a list of directory namesLooking in python source code I see this change:
http://hg.python. org/cpython/ rev/c9bb16b7d14 8 that indeed changes what it was an ImportError to a RuntimeError. However, it's
not clear why PyList_Check(path) is failing because I'm pretty sure that
sys.path is a list (I even tried to filter it with os.path.isdir). Hence, I
guess that path in that code isn't really sys.path under some circumstances.Anyway, catching that exception and handling it the same way as ImportError
works fine.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Max Brustkern (nuclearbob) wrote : | # |
I implemented the thread and queue method and tested it. The script I use always sends stdout before stderr, but in the output if this, sometimes the stderr comes before the stdout. I don't know how big of a problem that is, or whether there's a reasonable thing to do about it that doesn't potentially allow one buffer to fill up while waiting on the other one. The stdout and stderr in the yaml at the end do come out right.
- 875. By Max Brustkern
-
Using StringIO instead of arrays
- 876. By Max Brustkern
-
Using threads and queues to read process output; this seems like something we could use a generalized function, but I don't see anywhere else we need it right now
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) : | # |
Preview Diff
1 | === modified file 'client.py' |
2 | --- client.py 2013-04-04 13:13:34 +0000 |
3 | +++ client.py 2013-04-18 18:26:24 +0000 |
4 | @@ -21,6 +21,7 @@ |
5 | import logging |
6 | import platform |
7 | import sys |
8 | +import syslog |
9 | import os |
10 | |
11 | from utah.url import url_argument |
12 | @@ -88,6 +89,8 @@ |
13 | |
14 | |
15 | def main(): |
16 | + syslog.openlog('utah', 0, syslog.LOG_LOCAL0) |
17 | + |
18 | """Interpret command line arguments and run tests.""" |
19 | # TODO: write <2.7 optparse version and set based on version of python |
20 | # being used. |
21 | @@ -96,9 +99,10 @@ |
22 | |
23 | if not os.getuid() == 0: |
24 | parser.print_usage() |
25 | - sys.stderr.write('{}: UTAH client is expected ' |
26 | - 'to be executed as root user\n' |
27 | - .format(parser.prog)) |
28 | + msg = ('{}: UTAH client is expected to be executed as root user\n' |
29 | + .format(parser.prog)) |
30 | + sys.stderr.write(msg) |
31 | + syslog.syslog(syslog.LOG_ERR, msg) |
32 | sys.exit(ReturnCodes.INVALID_USER) |
33 | |
34 | setup_logging(args.debug) |
35 | @@ -131,6 +135,7 @@ |
36 | sys.stdout = open(output, open_flags) |
37 | except IOError as e: |
38 | sys.stderr.write(str(e)) |
39 | + syslog.syslog(syslog.LOG_ERR, str(e)) |
40 | sys.exit(ReturnCodes.EXCEPTION_ERROR) |
41 | if args.format == 'text': |
42 | result_class = Result |
43 | @@ -152,6 +157,7 @@ |
44 | |
45 | except UTAHClientError as e: |
46 | sys.stderr.write(str(e)) |
47 | + syslog.syslog(syslog.LOG_ERR, str(e)) |
48 | sys.exit(ReturnCodes.EXCEPTION_ERROR) |
49 | |
50 | sys.exit(returncode) |
51 | |
52 | === modified file 'debian/changelog' |
53 | --- debian/changelog 2013-04-17 08:26:37 +0000 |
54 | +++ debian/changelog 2013-04-18 18:26:24 +0000 |
55 | @@ -4,6 +4,9 @@ |
56 | * Check squashfs on the server images as well during ISO static |
57 | validation. |
58 | |
59 | + [ Max Brustkern ] |
60 | + * Added syslog output (including real time test output) to client |
61 | + |
62 | -- Max Brustkern <max@canonical.com> Tue, 16 Apr 2013 09:21:19 -0400 |
63 | |
64 | utah (0.10ubuntu1) UNRELEASED; urgency=low |
65 | |
66 | === modified file 'utah/client/common.py' |
67 | --- utah/client/common.py 2013-04-08 19:16:05 +0000 |
68 | +++ utah/client/common.py 2013-04-18 18:26:24 +0000 |
69 | @@ -24,8 +24,13 @@ |
70 | import re |
71 | import signal |
72 | import subprocess |
73 | +import syslog |
74 | import time |
75 | |
76 | +from Queue import Queue, Empty |
77 | +from StringIO import StringIO |
78 | +from threading import Thread |
79 | + |
80 | import jsonschema |
81 | import yaml |
82 | |
83 | @@ -108,6 +113,14 @@ |
84 | """ |
85 | |
86 | |
87 | +# Process queue stuff inspired by: |
88 | +# http://stackoverflow.com/a/4896288 |
89 | +def enqueue_output(out, queue): |
90 | + for line in iter(out.readline, b''): |
91 | + queue.put(line) |
92 | + out.close() |
93 | + |
94 | + |
95 | # Inspired by: |
96 | # http://stackoverflow.com/a/3326559 |
97 | def run_cmd(command, cwd=None, timeout=0, cmd_type=CMD_TC_TEST, run_as=None, |
98 | @@ -166,19 +179,43 @@ |
99 | if battery_measurements: |
100 | start_battery = battery.get_data() |
101 | |
102 | + cmd = '{} {}'.format(cmd_prefix, command) |
103 | + syslog.syslog('Running command: {}'.format(cmd)) |
104 | + |
105 | + stdout = StringIO() |
106 | + stderr = StringIO() |
107 | + outq = Queue() |
108 | + errq = Queue() |
109 | + |
110 | start_time = datetime.datetime.now() |
111 | - p = subprocess.Popen("{} {}".format(cmd_prefix, command), |
112 | - shell=True, cwd=cwd, |
113 | - stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
114 | + p = subprocess.Popen(cmd, shell=True, cwd=cwd, stdout=subprocess.PIPE, |
115 | + stderr=subprocess.PIPE) |
116 | |
117 | if timeout != 0: |
118 | signal.signal(signal.SIGALRM, alarm_handler) |
119 | signal.alarm(timeout) |
120 | |
121 | + outt = Thread(target=enqueue_output, args=(p.stdout, outq)) |
122 | + outt.daemon = True |
123 | + outt.start() |
124 | + errt = Thread(target=enqueue_output, args=(p.stderr, errq)) |
125 | + errt.daemon = True |
126 | + errt.start() |
127 | + |
128 | try: |
129 | - stdout, stderr = p.communicate() |
130 | - stdout = _normalize_encoding(stdout) |
131 | - stderr = _normalize_encoding(stderr) |
132 | + while p.poll() is None: |
133 | + try: |
134 | + stdout_tmp = _normalize_encoding(outq.get_nowait()) |
135 | + syslog.syslog(stdout_tmp) |
136 | + stdout.write(stdout_tmp) |
137 | + except Empty: |
138 | + pass |
139 | + try: |
140 | + stderr_tmp = _normalize_encoding(errq.get_nowait()) |
141 | + syslog.syslog(stderr_tmp) |
142 | + stderr.write(stderr_tmp) |
143 | + except Empty: |
144 | + pass |
145 | |
146 | if timeout != 0: |
147 | signal.alarm(0) |
148 | @@ -209,10 +246,15 @@ |
149 | |
150 | time_delta = datetime.datetime.now() - start_time |
151 | |
152 | + if p.returncode: |
153 | + syslog.syslog(syslog.LOG_ERR, |
154 | + 'Process exited with non-zero return code: {}' |
155 | + .format(p.returncode)) |
156 | + |
157 | kwargs = {'command': command, |
158 | 'retcode': p.returncode, |
159 | - 'stdout': stdout, |
160 | - 'stderr': stderr, |
161 | + 'stdout': stdout.getvalue(), |
162 | + 'stderr': stderr.getvalue(), |
163 | 'start_time': start_time.strftime(DATE_FORMAT), |
164 | 'time_delta': str(time_delta), |
165 | 'cmd_type': cmd_type, |
166 | |
167 | === modified file 'utah/client/runner.py' |
168 | --- utah/client/runner.py 2013-04-04 15:04:43 +0000 |
169 | +++ utah/client/runner.py 2013-04-18 18:26:24 +0000 |
170 | @@ -22,6 +22,7 @@ |
171 | import shutil |
172 | import stat |
173 | import subprocess |
174 | +import syslog |
175 | import urllib |
176 | |
177 | from utah.client import exceptions |
178 | @@ -308,6 +309,7 @@ |
179 | # Return success despite of the results |
180 | # to let server know that it has to check the state file |
181 | # to know the final result after the reboot |
182 | + syslog.syslog('REBOOT: Test case requested reboot') |
183 | return ReturnCodes.REBOOT |
184 | |
185 | self.errors += suite.errors |
186 | @@ -530,6 +532,7 @@ |
187 | return result |
188 | |
189 | if name not in self.fetched_suites: |
190 | + syslog.syslog('Fetching testsuite: {}'.format(name)) |
191 | # Retry bzr export on http errors for 60 seconds |
192 | res = timeout(60, retry, vcs_get_retriable) |
193 | self.result.add_result(res) |
194 | @@ -623,8 +626,12 @@ |
195 | def returncode(self): |
196 | """Provide return code based on test status.""" |
197 | if self.errors > 0 or self.fetch_errors > 0: |
198 | + syslog.syslog(syslog.LOG_ERR, |
199 | + 'ERROR: Errors encountered during testing') |
200 | return ReturnCodes.ERROR |
201 | elif self.failures > 0: |
202 | + syslog.syslog(syslog.LOG_ERR, 'FAIL: Not all tests passed') |
203 | return ReturnCodes.FAIL |
204 | else: |
205 | + syslog.syslog('PASS: All tests passed') |
206 | return ReturnCodes.PASS |
207 | |
208 | === modified file 'utah/client/testcase.py' |
209 | --- utah/client/testcase.py 2013-04-08 19:12:25 +0000 |
210 | +++ utah/client/testcase.py 2013-04-18 18:26:24 +0000 |
211 | @@ -17,6 +17,7 @@ |
212 | |
213 | |
214 | import os |
215 | +import syslog |
216 | |
217 | import jsonschema |
218 | |
219 | @@ -170,6 +171,7 @@ |
220 | """Run build, but only if we haven't started a run yet.""" |
221 | if self.status == 'NOTRUN': |
222 | self.set_status('BUILD') |
223 | + syslog.syslog('Testcase {} build'.format(self.name)) |
224 | cmd_result = run_cmd( |
225 | self.build_cmd, |
226 | cwd=self.working_dir, |
227 | @@ -181,6 +183,7 @@ |
228 | """Run tc_setup, but only if build() has just passed.""" |
229 | if self.status == 'BUILD' and result.status == 'PASS': |
230 | self.set_status('SETUP') |
231 | + syslog.syslog('Testcase {} setup'.format(self.name)) |
232 | cmd_result = run_cmd( |
233 | self.tc_setup, |
234 | cwd=self.working_dir, |
235 | @@ -192,6 +195,7 @@ |
236 | """Run tc_cleanup after a run.""" |
237 | if self.status == 'RUN': |
238 | self.set_status('CLEANUP') |
239 | + syslog.syslog('Testcase {} cleanup'.format(self.name)) |
240 | cmd_result = run_cmd( |
241 | self.tc_cleanup, |
242 | cwd=self.working_dir, |
243 | @@ -256,6 +260,7 @@ |
244 | 'action': self.action, |
245 | 'expected_results': self.expected_results, |
246 | } |
247 | + syslog.syslog('Testcase {} run'.format(self.name)) |
248 | cmd_result = run_cmd( |
249 | self.command, |
250 | timeout=timeout, |
251 | |
252 | === modified file 'utah/client/testsuite.py' |
253 | --- utah/client/testsuite.py 2013-04-04 15:09:51 +0000 |
254 | +++ utah/client/testsuite.py 2013-04-18 18:26:24 +0000 |
255 | @@ -18,6 +18,7 @@ |
256 | |
257 | import jsonschema |
258 | import os |
259 | +import syslog |
260 | import yaml |
261 | |
262 | from utah.client import exceptions |
263 | @@ -204,6 +205,7 @@ |
264 | """Run build, but only if we haven't started a run yet.""" |
265 | if self.status == 'NOTRUN': |
266 | self.set_status('BUILD') |
267 | + syslog.syslog('Testsuite {} build'.format(self.name)) |
268 | cmd_result = run_cmd( |
269 | self.build_cmd, |
270 | cwd=self.name, |
271 | @@ -215,6 +217,7 @@ |
272 | """Run ts_setup, but only if build() has just passed.""" |
273 | if self.status == 'BUILD' and result.status == 'PASS': |
274 | self.set_status('SETUP') |
275 | + syslog.syslog('Testsuite {} setup'.format(self.name)) |
276 | cmd_result = run_cmd( |
277 | self.ts_setup, |
278 | cwd=self.name, |
279 | @@ -226,6 +229,7 @@ |
280 | """Run ts_cleanup after a run.""" |
281 | if self.status == 'INPROGRESS': |
282 | self.set_status('CLEANUP') |
283 | + syslog.syslog('Testsuite {} cleanup'.format(self.name)) |
284 | cmd_result = run_cmd( |
285 | self.ts_cleanup, |
286 | cwd=self.name, |
On 04/15/2013 03:49 PM, Max Brustkern wrote:
> https:/ /code.launchpad .net/~nuclearbo b/utah/ rsyslog- client/ +merge/ 159015
> === modified file 'client.py' print_usage( ) write(' {}: UTAH client is expected ' parser. prog)) parser. prog)) write(msg) syslog( syslog. LOG_ERR, msg)
> if not os.getuid() == 0:
> parser.
> - sys.stderr.
> - 'to be executed as root user\n'
> - .format(
> + msg = ('{}: UTAH client is expected to be executed as root user\n'
> + .format(
> + sys.stderr.
> + syslog.
is the "\n" going to make the syslog stuff look/act weird?
> === modified file 'utah/client/ common. py' common. py 2013-04-08 19:16:05 +0000
> --- utah/client/
> - p = subprocess. Popen(" {} {}".format( cmd_prefix, command), subprocess. PIPE, stderr= subprocess. PIPE) Popen(cmd, shell=True, cwd=cwd, stdout= subprocess. PIPE, subprocess. PIPE) signal( signal. SIGALRM, alarm_handler) alarm(timeout)
> - shell=True, cwd=cwd,
> - stdout=
> + p = subprocess.
> + stderr=
>
> if timeout != 0:
> signal.
> signal.
>
> + stdout = []
> + stderr = []
This seems inefficient. Why not use cStringIO? Then you won't have to do
the '\n'.join(...) at the end of the function.
> try: encoding( stdout) encoding( stderr)
> - stdout, stderr = p.communicate()
> - stdout = _normalize_
> - stderr = _normalize_
> + while p.poll() is None:
Is this a safe way to deal with a process? The concern I have is that
stdout might be blocked, but there is data on stderr that needs to be
read and we sit blocked? I think we might want to make this a "select"
type look on stdout/stderr fd's.
> + stdout_tmp = _normalize_ encoding( p.stdout. readline( ).strip( )) encoding( p.stderr. readline( ).strip( )) syslog( stdout_ tmp) syslog( stderr_ tmp) append( stdout_ tmp) append( stderr_ tmp)
> + stderr_tmp = _normalize_
> + syslog.
> + syslog.
> + stdout.
> + stderr.
>
> if timeout != 0:
> signal.alarm(0)