Merge lp:~vila/ubuntu-ci-services-itself/debug into lp:ubuntu-ci-services-itself
- debug
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Vincent Ladeuil |
Approved revision: | 405 |
Merged at revision: | 411 |
Proposed branch: | lp:~vila/ubuntu-ci-services-itself/debug |
Merge into: | lp:ubuntu-ci-services-itself |
Diff against target: |
245 lines (+83/-54) 3 files modified
test_runner/run_test.py (+7/-3) test_runner/tstrun/run_worker.py (+64/-42) test_runner/tstrun/testbed.py (+12/-9) |
To merge this branch: | bzr merge lp:~vila/ubuntu-ci-services-itself/debug |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andy Doan (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email: mp+211384@code.launchpad.net |
Commit message
Rename summary and log produced on the test bed and add them to the artifacts
Description of the change
This provides more logs for the test runner to help debug.
Paul encountered a case where the subunit stream content was broken. This
requires the summary.log created by adt-run.
The log of the test run itself wasn't saved as an artifact, it is now,
package by package.
Finally, the cloud-init log was only available on errors, it's now available
in all cases as it may contains useful info for diagnosis (like which
package *versions* were installed).
I've tested locally and will run a ticket through the engine (as soon as I get an updated deployment...)
PS Jenkins bot (ps-jenkins) wrote : | # |
- 404. By Vincent Ladeuil
-
Fix pep8 issues.
Andy Doan (doanac) wrote : | # |
100 + results.
101 + 'name': '{}.{}'
102 + 'reference': url,
103 + 'type': kind,
104 + })
there's a method in the base class that does this now:
7 def run_test(package):
114 + def save_testbed_
I wish there was a better way to keep the artifacts in sync between these two modules. ie just pull everything from a directory and make an assumption on the file-type if its LOGS/RESULTS type. But I don't see anything obvious or easy right now. Just something to think about in the future.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:404
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 405. By Vincent Ladeuil
-
Fix review comments.
Vincent Ladeuil (vila) wrote : | # |
> 100 + results.
> 101 + 'name': '{}.{}'
> 102 + 'reference': url,
> 103 + 'type': kind,
> 104 + })
>
> there's a method in the base class that does this now:
>
> http://
> itself/
Called.
>
>
> 7 def run_test(package):
> 114 + def save_testbed_
> package):
>
> I wish there was a better way to keep the artifacts in sync between these two
> modules. ie just pull everything from a directory and make an assumption on
> the file-type if its LOGS/RESULTS type. But I don't see anything obvious or
> easy right now. Just something to think about in the future.
Agreed, it's not super pretty, especially when you consider that they are downloaded from the testbed to be uploaded to swift. I didn't want to refactor too aggressively either without tests.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:405
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Andy Doan (doanac) wrote : | # |
On 03/17/2014 07:52 PM, Vincent Ladeuil wrote:
> Agreed, it's not super pretty, especially when you consider that they are downloaded from the testbed to be uploaded to swift. I didn't want to refactor too aggressively either without tests.
agreed also.
Andy Doan (doanac) : | # |
Vincent Ladeuil (vila) wrote : | # |
Finally ! After an epic fight against swift uploads on hp (ticket filed with support), it appears that I can get some success as long as my uploads stay below some mysterious limit... with enough retries.
So, I've tested this: http://
All logs are there, properly named, even in the left column under 'Image Testing' for easy access, pfew, what a journey.
Preview Diff
1 | === modified file 'test_runner/run_test.py' | |||
2 | --- test_runner/run_test.py 2014-03-14 10:37:28 +0000 | |||
3 | +++ test_runner/run_test.py 2014-03-18 00:50:25 +0000 | |||
4 | @@ -137,7 +137,8 @@ | |||
5 | 137 | 137 | ||
6 | 138 | 138 | ||
7 | 139 | def run_test(package): | 139 | def run_test(package): |
9 | 140 | output = open('result.subunit', 'wb') | 140 | subunit_path = '{}.subunit'.format(package) |
10 | 141 | output = open(subunit_path, 'wb') | ||
11 | 141 | result = subunit.TestProtocolClient(output) | 142 | result = subunit.TestProtocolClient(output) |
12 | 142 | result.startTestRun() | 143 | result.startTestRun() |
13 | 143 | os.mkdir(package) | 144 | os.mkdir(package) |
14 | @@ -147,15 +148,18 @@ | |||
15 | 147 | # MISSINGTEST: dsc file not found | 148 | # MISSINGTEST: dsc file not found |
16 | 148 | dsc_path = get_dsc_path(package, cwd=package) | 149 | dsc_path = get_dsc_path(package, cwd=package) |
17 | 149 | try: | 150 | try: |
18 | 151 | summary_path = '{}-summary.log'.format(package) | ||
19 | 152 | log_path = '{}.log'.format(package) | ||
20 | 150 | cmd = ['sudo', 'adt-run', dsc_path, | 153 | cmd = ['sudo', 'adt-run', dsc_path, |
22 | 151 | '--summary', 'summary.log', | 154 | '--summary', summary_path, |
23 | 155 | '--log-file', log_path, | ||
24 | 152 | # Required to get files produced in 'results' | 156 | # Required to get files produced in 'results' |
25 | 153 | '--paths-testbed', | 157 | '--paths-testbed', |
26 | 154 | '--output-dir', 'results', | 158 | '--output-dir', 'results', |
27 | 155 | '---', 'adt-virt-null'] | 159 | '---', 'adt-virt-null'] |
28 | 156 | proc, out, err = run(*cmd, out=None, err=None, check_rc=False) | 160 | proc, out, err = run(*cmd, out=None, err=None, check_rc=False) |
29 | 157 | rc = proc.returncode | 161 | rc = proc.returncode |
31 | 158 | for name, status in parse_summary('summary.log'): | 162 | for name, status in parse_summary(summary_path): |
32 | 159 | process_test_result(package, result, name, status) | 163 | process_test_result(package, result, name, status) |
33 | 160 | finally: | 164 | finally: |
34 | 161 | result.stopTestRun() | 165 | result.stopTestRun() |
35 | 162 | 166 | ||
36 | === modified file 'test_runner/tstrun/run_worker.py' | |||
37 | --- test_runner/tstrun/run_worker.py 2014-03-14 21:54:37 +0000 | |||
38 | +++ test_runner/tstrun/run_worker.py 2014-03-18 00:50:25 +0000 | |||
39 | @@ -28,39 +28,60 @@ | |||
40 | 28 | super(TestRunnerWorker, self).__init__('image_test') | 28 | super(TestRunnerWorker, self).__init__('image_test') |
41 | 29 | self.data_store = None | 29 | self.data_store = None |
42 | 30 | 30 | ||
76 | 31 | def save_subunit(self, log, package, stream, results): | 31 | def save_artifact(self, logger, results, name, value, description=None, |
77 | 32 | # make this exception-safe since we can already report pass/fail | 32 | kind='LOGS'): |
78 | 33 | # with or without this file | 33 | """Save an artifact catching and reporting exceptions. |
79 | 34 | try: | 34 | |
80 | 35 | log.info('Saving subunit results for {}'.format(package)) | 35 | This should only be used to upload artifacts after the pass/fail status |
81 | 36 | name = 'test-runner.{}-subunit-stream'.format(package) | 36 | has been established to it's safe to fail the upload. |
82 | 37 | url = self.data_store.put_file(name, stream, 'text/plain') | 37 | |
83 | 38 | results.setdefault('artifacts', []).append({ | 38 | :param logger: To report execution. |
84 | 39 | 'name': name, | 39 | |
85 | 40 | 'reference': url, | 40 | :param results: The dict holding the 'artifacts' attribute. |
86 | 41 | 'type': 'RESULTS', | 41 | |
87 | 42 | }) | 42 | :param name: The artifact name. |
88 | 43 | except: | 43 | |
89 | 44 | log.exception( | 44 | :param value: The artifact value as a string. |
90 | 45 | 'Unable to upload subunit result for {}'.format(package)) | 45 | |
91 | 46 | 46 | :param description: For reporting purposes. | |
92 | 47 | def save_testbed_console(self, log, test_bed, results): | 47 | |
93 | 48 | # make this exception-safe since we can already report pass/fail | 48 | :param kind: The kind of artifact (defaults to 'LOGS'). |
94 | 49 | # with or without this file | 49 | """ |
95 | 50 | try: | 50 | if description is None: |
96 | 51 | log.info('Saving testbed console') | 51 | description = name |
97 | 52 | name = 'test-runner.testbed-cloud-init.log' | 52 | try: |
98 | 53 | console = test_bed.get_cloud_init_console() | 53 | logger.info('Saving {}'.format(description)) |
99 | 54 | url = self.data_store.put_file(name, console, 'text/plain') | 54 | url = self.data_store.put_file(name, value, 'text/plain') |
100 | 55 | results.setdefault('artifacts', []).append({ | 55 | self._create_artifact('{}.{}'.format(self.logger_name, name), |
101 | 56 | 'name': name, | 56 | url, results, kind) |
102 | 57 | 'reference': url, | 57 | except: |
103 | 58 | 'type': 'LOGS', | 58 | logger.exception('Unable to upload {}'.format(description)) |
104 | 59 | }) | 59 | |
105 | 60 | except: | 60 | def save_testbed_console(self, logger, results, test_bed): |
106 | 61 | log.exception('unable to upload testbed console') | 61 | self.save_artifact(logger, results, |
107 | 62 | 62 | 'testbed-cloud-init.log', | |
108 | 63 | def handle_request(self, log, params): | 63 | test_bed.get_cloud_init_console(), |
109 | 64 | 'testbed console') | ||
110 | 65 | |||
111 | 66 | def save_testbed_artifacts(self, logger, results, test_bed, package): | ||
112 | 67 | subunit_path = '{}.subunit'.format(package) | ||
113 | 68 | self.save_artifact( | ||
114 | 69 | logger, results, | ||
115 | 70 | subunit_path, test_bed.get_remote_content(subunit_path), | ||
116 | 71 | 'subunit results for {}'.format(package), | ||
117 | 72 | 'RESULTS') | ||
118 | 73 | package_log_path = '{}.log'.format(package) | ||
119 | 74 | self.save_artifact( | ||
120 | 75 | logger, results, | ||
121 | 76 | package_log_path, test_bed.get_remote_content(package_log_path), | ||
122 | 77 | 'adt-run log for {}'.format(package)) | ||
123 | 78 | summary_log_path = '{}-summary.log'.format(package) | ||
124 | 79 | self.save_artifact( | ||
125 | 80 | logger, results, | ||
126 | 81 | summary_log_path, test_bed.get_remote_content(summary_log_path), | ||
127 | 82 | 'adt-run summary for {}'.format(package)) | ||
128 | 83 | |||
129 | 84 | def handle_request(self, logger, params): | ||
130 | 64 | ticket_id = params['ticket_id'] | 85 | ticket_id = params['ticket_id'] |
131 | 65 | progress_queue = params['progress_trigger'] | 86 | progress_queue = params['progress_trigger'] |
132 | 66 | image_id = params['image_id'] | 87 | image_id = params['image_id'] |
133 | @@ -69,7 +90,7 @@ | |||
134 | 69 | results = {} | 90 | results = {} |
135 | 70 | 91 | ||
136 | 71 | def status_cb(msg): | 92 | def status_cb(msg): |
138 | 72 | log.info(msg) | 93 | logger.info(msg) |
139 | 73 | amqp_utils.progress_update(progress_queue, {'message': msg}) | 94 | amqp_utils.progress_update(progress_queue, {'message': msg}) |
140 | 74 | 95 | ||
141 | 75 | self.data_store = self._create_data_store(ticket_id) | 96 | self.data_store = self._create_data_store(ticket_id) |
142 | @@ -79,41 +100,42 @@ | |||
143 | 79 | test_bed = testbed.TestBed('testbed-{}'.format(progress_queue), | 100 | test_bed = testbed.TestBed('testbed-{}'.format(progress_queue), |
144 | 80 | flavors, image_id, status_cb) | 101 | flavors, image_id, status_cb) |
145 | 81 | except: | 102 | except: |
147 | 82 | log.exception( | 103 | logger.exception( |
148 | 83 | 'The testbed creation for {} failed'.format(ticket_id)) | 104 | 'The testbed creation for {} failed'.format(ticket_id)) |
149 | 84 | return amqp_utils.progress_failed, results | 105 | return amqp_utils.progress_failed, results |
150 | 85 | 106 | ||
151 | 86 | try: | 107 | try: |
152 | 87 | test_bed.setup() | 108 | test_bed.setup() |
153 | 88 | except: | 109 | except: |
155 | 89 | log.exception( | 110 | logger.exception( |
156 | 90 | 'The testbed setup for {} failed'.format(ticket_id)) | 111 | 'The testbed setup for {} failed'.format(ticket_id)) |
157 | 91 | if test_bed.instance is not None: | 112 | if test_bed.instance is not None: |
159 | 92 | self.save_testbed_console(log, test_bed, results) | 113 | self.save_testbed_console(logger, results, test_bed) |
160 | 93 | test_bed.teardown() | 114 | test_bed.teardown() |
161 | 94 | return amqp_utils.progress_failed, results | 115 | return amqp_utils.progress_failed, results |
162 | 95 | 116 | ||
163 | 117 | self.save_testbed_console(logger, results, test_bed) | ||
164 | 96 | status_cb('The test bed is ready') | 118 | status_cb('The test bed is ready') |
165 | 97 | # The tests will succeed unless they fail ;) | 119 | # The tests will succeed unless they fail ;) |
166 | 98 | notify = amqp_utils.progress_completed | 120 | notify = amqp_utils.progress_completed |
167 | 99 | try: | 121 | try: |
168 | 100 | for package in package_list: | 122 | for package in package_list: |
169 | 101 | if params.get('cancelled'): | 123 | if params.get('cancelled'): |
172 | 102 | log.error('The request for {} has been cancelled,' | 124 | logger.error('The request for {} has been cancelled,' |
173 | 103 | ' exiting'.format(ticket_id)) | 125 | ' exiting'.format(ticket_id)) |
174 | 104 | return amqp_utils.progress_failed, results | 126 | return amqp_utils.progress_failed, results |
175 | 105 | status_cb('Testing {}'.format(package)) | 127 | status_cb('Testing {}'.format(package)) |
176 | 106 | # uci-vms shell adt-run ... --- adt-virt-null | 128 | # uci-vms shell adt-run ... --- adt-virt-null |
178 | 107 | return_code, subunit_stream = test_bed.run_test(package) | 129 | return_code = test_bed.run_test(package) |
179 | 108 | # 0 is success, 8 is skipped and considered a success | 130 | # 0 is success, 8 is skipped and considered a success |
180 | 109 | if return_code not in (0, 8): | 131 | if return_code not in (0, 8): |
181 | 110 | # At least one test failed | 132 | # At least one test failed |
182 | 111 | notify = amqp_utils.progress_failed | 133 | notify = amqp_utils.progress_failed |
184 | 112 | self.save_subunit(log, package, subunit_stream, results) | 134 | self.save_testbed_artifacts(logger, results, test_bed, package) |
185 | 113 | status_cb('Test completed for ticket {}'.format(ticket_id)) | 135 | status_cb('Test completed for ticket {}'.format(ticket_id)) |
186 | 114 | return notify, results | 136 | return notify, results |
187 | 115 | except: | 137 | except: |
189 | 116 | log.exception( | 138 | logger.exception( |
190 | 117 | 'Exception while handling ticket {}'.format(ticket_id)) | 139 | 'Exception while handling ticket {}'.format(ticket_id)) |
191 | 118 | raise | 140 | raise |
192 | 119 | finally: | 141 | finally: |
193 | 120 | 142 | ||
194 | === modified file 'test_runner/tstrun/testbed.py' | |||
195 | --- test_runner/tstrun/testbed.py 2014-03-14 17:57:46 +0000 | |||
196 | +++ test_runner/tstrun/testbed.py 2014-03-18 00:50:25 +0000 | |||
197 | @@ -245,7 +245,7 @@ | |||
198 | 245 | def wait_for_cloud_init(self): | 245 | def wait_for_cloud_init(self): |
199 | 246 | # FIXME: cloud_init_timeout should be a config option (related to | 246 | # FIXME: cloud_init_timeout should be a config option (related to |
200 | 247 | # get_ip_timeout and probably the two can be merged) -- vila 2014-01-30 | 247 | # get_ip_timeout and probably the two can be merged) -- vila 2014-01-30 |
202 | 248 | cloud_init_timeout = 300 # in seconds so 5 minutes | 248 | cloud_init_timeout = 600 # in seconds so 10 minutes |
203 | 249 | timeout_limit = time.time() + cloud_init_timeout | 249 | timeout_limit = time.time() + cloud_init_timeout |
204 | 250 | while time.time() < timeout_limit: | 250 | while time.time() < timeout_limit: |
205 | 251 | # A relatively cheap way to catch cloud-init completion is to watch | 251 | # A relatively cheap way to catch cloud-init completion is to watch |
206 | @@ -262,8 +262,6 @@ | |||
207 | 262 | # We're good to go | 262 | # We're good to go |
208 | 263 | log.info( | 263 | log.info( |
209 | 264 | 'cloud-init completed for {}'.format(self.instance.id)) | 264 | 'cloud-init completed for {}'.format(self.instance.id)) |
210 | 265 | # FIXME: Right place to report how long it | ||
211 | 266 | # took for cloud-init to finish. -- vila 2014-01-30 | ||
212 | 267 | return | 265 | return |
213 | 268 | time.sleep(5) | 266 | time.sleep(5) |
214 | 269 | raise NovaClientException('Instance never completed cloud-init') | 267 | raise NovaClientException('Instance never completed cloud-init') |
215 | @@ -295,12 +293,13 @@ | |||
216 | 295 | out, err = proc.communicate() | 293 | out, err = proc.communicate() |
217 | 296 | return proc, out, err | 294 | return proc, out, err |
218 | 297 | 295 | ||
219 | 296 | def get_remote_content(self, path): | ||
220 | 297 | _, content, _ = self.ssh('cat', path, out=subprocess.PIPE) | ||
221 | 298 | return content | ||
222 | 299 | |||
223 | 298 | def run_test(self, package): | 300 | def run_test(self, package): |
224 | 299 | proc, _, _ = self.ssh('/tmp/run_test.py', package) | 301 | proc, _, _ = self.ssh('/tmp/run_test.py', package) |
229 | 300 | # FIXME: Does that look like a pipe ? -- vila 2014-02-06 | 302 | return proc.returncode |
226 | 301 | _, subunit_stream, _ = self.ssh('cat', 'result.subunit', | ||
227 | 302 | out=subprocess.PIPE) | ||
228 | 303 | return proc.returncode, subunit_stream | ||
230 | 304 | 303 | ||
231 | 305 | 304 | ||
232 | 306 | # The following are helpers for local manual tests, they should disappear once | 305 | # The following are helpers for local manual tests, they should disappear once |
233 | @@ -345,6 +344,10 @@ | |||
234 | 345 | from ci_utils import dump_stack | 344 | from ci_utils import dump_stack |
235 | 346 | dump_stack.install_stack_dump_signal() | 345 | dump_stack.install_stack_dump_signal() |
236 | 347 | test_bed = test_print_ip() | 346 | test_bed = test_print_ip() |
239 | 348 | print test_bed.run_test('libpng') # Known to run no tests | 347 | # libpng is known to run no tests |
240 | 349 | print test_bed.run_test('juju-core') | 348 | for package in ('libpng',): |
241 | 349 | print test_bed.run_test(package) | ||
242 | 350 | # print test_bed.get_remote_content('{}.log'.format(package)) | ||
243 | 351 | print test_bed.get_remote_content('{}-summary.log'.format(package)) | ||
244 | 352 | print test_bed.get_remote_content('{}.subunit'.format(package)) | ||
245 | 350 | test_bed.teardown() | 353 | test_bed.teardown() |
FAILED: Continuous integration, rev:403 /code.launchpad .net/~vila/ ubuntu- ci-services- itself/ debug/+ merge/211384/ +edit-commit- message
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http:// s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/449/
Executed test runs:
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/449/ rebuild
http://