Merge lp:~vila/uci-engine/britney-process-reports into lp:uci-engine

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: 918
Merged at revision: 873
Proposed branch: lp:~vila/uci-engine/britney-process-reports
Merge into: lp:uci-engine
Prerequisite: lp:~vila/uci-engine/fix-britney-results
Diff against target: 701 lines (+501/-108)
8 files modified
britney_proxy/bin/uci-britney-report (+63/-0)
britney_proxy/bin/uci-britney-request (+5/-3)
britney_proxy/britney/config.py (+83/-0)
britney_proxy/britney/post_request.py (+3/-67)
britney_proxy/britney/process_reports.py (+179/-0)
britney_proxy/britney/tests/test_config.py (+55/-0)
britney_proxy/britney/tests/test_post_request.py (+1/-38)
britney_proxy/britney/tests/test_process_reports.py (+112/-0)
To merge this branch: bzr merge lp:~vila/uci-engine/britney-process-reports
Reviewer Review Type Date Requested Status
Francis Ginther Approve
PS Jenkins bot (community) continuous-integration Approve
Martin Pitt Pending
Review via email: mp+240180@code.launchpad.net

Commit message

Process britney test result reports.

Description of the change

This is the last bit of the britney-proxy which can be used on jenkins or to validate the migration (among other tools).

It includes some slight refactoring to share more code but really build on
top of the previous parts, the "real" work is done in
britney_proxy/britney/process_reports.py BritneyReportHandler.handle().

Followups MPs will factorize the relevant pieces so a new service can be implemented with less paperwork ;)

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:908
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1652/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1652/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:909
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1653/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1653/rebuild

review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:912
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1654/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1654/rebuild

review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:914
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1655/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1655/rebuild

review: Approve (continuous-integration)
Revision history for this message
Vincent Ladeuil (vila) wrote :

What this MP provides may be clearer when looking at its output (sample from a live run):

2014-10-31 00:53:23,951 INFO trusty amd64 zope.interface
2014-10-31 00:53:24,741 INFO utopic amd64 abi-compliance-checker
2014-10-31 00:53:48,177 INFO trusty amd64 zope.testing
2014-10-31 00:54:18,008 INFO trusty amd64 zope.session
2014-10-31 00:54:31,651 INFO trusty amd64 zope.testrunner
2014-10-31 00:54:32,796 INFO trusty amd64 zope.formlib
2014-10-31 00:55:37,291 INFO utopic amd64 adduser
2014-10-31 00:55:59,448 INFO utopic amd64 alglib
2014-10-31 00:56:00,395 INFO utopic amd64 apache2
2014-10-31 00:56:25,780 INFO utopic amd64 apparmor-easyprof-ubuntu
2014-10-31 00:56:42,962 INFO utopic amd64 appstream
2014-10-31 00:56:55,261 INFO utopic amd64 apq-postgresql
2014-10-31 00:57:32,190 INFO utopic amd64 adequate

Measures included ;)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:915
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1656/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1656/rebuild

review: Approve (continuous-integration)
Revision history for this message
Martin Pitt (pitti) wrote :

I have some nitpicks and a question below. In general I'm still unsure why we need a "britney report/result queue". We will *not* listen to a queue in britney, that just doesn't conceptually work. We want to poll a results tree (like trusty/amd64/libp/libpng/yymmdd_hhmmss/) from swift, rsync, or similar (i. e. a network file system); preferably swift as that's where we write the results to, and we should not unnecessarily put extra stuff in between which can again fail :-)

Revision history for this message
Vincent Ladeuil (vila) wrote :

> I have some nitpicks and a question below. In general I'm still unsure why we
> need a "britney report/result queue". We will *not* listen to a queue in
> britney, that just doesn't conceptually work. We want to poll a results tree
> (like trusty/amd64/libp/libpng/yymmdd_hhmmss/) from swift, rsync, or similar
> (i. e. a network file system); preferably swift as that's where we write the
> results to, and we should not unnecessarily put extra stuff in between which
> can again fail :-)

Yup, that's what I want to reach asap without blocking the validation which can progress with this MP.

Revision history for this message
Vincent Ladeuil (vila) wrote :

Grr, lp didn't save the comments in the previous reply

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:916
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1658/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1658/rebuild

review: Approve (continuous-integration)
Revision history for this message
Francis Ginther (fginther) wrote :

Just a few questions and a typo. Otherwise looks good.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

All fixed, replies inline.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:918
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1660/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1660/rebuild

review: Approve (continuous-integration)
Revision history for this message
Francis Ginther (fginther) wrote :

Approve.

review: Approve
Revision history for this message
Ubuntu CI Bot (uci-bot) wrote :
Download full text (65.9 KiB)

The attempt to merge lp:~vila/uci-engine/britney-process-reports into lp:uci-engine failed. Below is the output from the failed tests.

Running cm...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
uploading webui-content.tgz to swift
Updating source dependencies...
2014-11-01 10:48:20 INFO juju.cmd supercommand.go:37 running jujud [1.20.11.1-precise-amd64 gc]
2014-11-01 10:48:20 DEBUG juju.agent agent.go:377 read agent config, format "1.18"
2014-11-01 10:48:20 INFO juju.jujud unit.go:78 unit agent unit-ci-airline-ts-django-0 start (1.20.11.1-precise-amd64 [gc])
2014-11-01 10:48:20 INFO juju.worker runner.go:260 start "api"
2014-11-01 10:48:20 INFO juju.state.api apiclient.go:242 dialing "wss://10.0.3.1:17070/"
2014-11-01 10:48:20 INFO juju.state.api apiclient.go:176 connection established to "wss://10.0.3.1:17070/"
2014-11-01 10:48:20 INFO juju.state.api apiclient.go:242 dialing "wss://10.0.3.1:17070/"
2014-11-01 10:48:20 INFO juju.state.api apiclient.go:176 connection established to "wss://10.0.3.1:17070/"
2014-11-01 10:48:21 INFO juju.state.api apiclient.go:242 dialing "wss://10.0.3.1:17070/"
2014-11-01 10:48:21 INFO juju.state.api apiclient.go:176 connection established to "wss://10.0.3.1:17070/"
2014-11-01 10:48:22 INFO juju.worker runner.go:260 start "upgrader"
2014-11-01 10:48:22 INFO juju.worker runner.go:260 start "logger"
2014-11-01 10:48:22 DEBUG juju.worker.logger logger.go:35 initial log config: "<root>=DEBUG"
2014-11-01 10:48:22 INFO juju.worker runner.go:260 start "uniter"
2014-11-01 10:48:22 DEBUG juju.worker.logger logger.go:60 logger setup
2014-11-01 10:48:22 INFO juju.worker runner.go:260 start "apiaddressupdater"
2014-11-01 10:48:22 INFO juju.worker runner.go:260 start "rsyslog"
2014-11-01 10:48:22 DEBUG juju.worker.rsyslog worker.go:75 starting rsyslog worker mode 1 for "unit-ci-airline-ts-django-0" "tarmac-local"
2014-11-01 10:48:22 DEBUG juju.worker.logger logger.go:45 reconfiguring logging from "<root>=DEBUG" to "<root>=WARNING;unit=DEBUG"
2014-11-01 10:48:39 INFO juju-log Making dir /srv/ci-airline-ts-django/code/ root:root 555
2014-11-01 10:48:39 INFO juju-log Adding dependencies.
2014-11-01 10:48:39 INFO juju-log installing apt packages...
2014-11-01 10:48:40 INFO config-changed gpg: keyring `/tmp/tmpGfAGA_/secring.gpg' created
2014-11-01 10:48:40 INFO config-changed gpg: keyring `/tmp/tmpGfAGA_/pubring.gpg' created
2014-11-01 10:48:40 INFO config-changed gpg: requesting key 6A8DFC40 from hkp server keyserver.ubuntu.com
2014-11-01 10:48:40 INFO config-changed gpg: /tmp/tmpGfAGA_/trustdb.gpg: trustdb created
2014-11-01 10:48:40 INFO config-changed gpg: key 6A8DFC40: public key "Launchpad PPA for Canonical CI Engineering" imported
2014-11-01 10:48:40 INFO config-changed gpg: Total number processed: 1
2014-11-01 10:48:40 INFO config-changed gpg: imported: 1 (RSA: 1)
2014-11-01 10:48:41 INFO config-changed OK
2014-11-01 10:48:43 INFO config-changed Hit http://archive.ubuntu.com precise Release.gpg
2014-11-01 10:48:43 INFO config-chan...

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (3.4 KiB)

2014-11-01 11:05:02 INFO pgsql-relation-joined OperationalError: FATAL: the database system is shutting down
2014-11-01 11:05:02 INFO pgsql-relation-joined FATAL: the database system is shutting down
2014-11-01 11:05:02 INFO pgsql-relation-joined Syncing...
2014-11-01 11:05:02 INFO juju-log pgsql:28: migration command failed, retrying...
2014-11-01 11:05:08 INFO pgsql-relation-joined OperationalError: could not connect to server: Connection refused
2014-11-01 11:05:08 INFO pgsql-relation-joined Is the server running on host "10.0.3.85" and accepting
2014-11-01 11:05:08 INFO pgsql-relation-joined TCP/IP connections on port 5432?
2014-11-01 11:05:08 INFO pgsql-relation-joined Syncing...
2014-11-01 11:05:08 INFO juju-log pgsql:28: migration command failed, retrying...
2014-11-01 11:05:13 INFO pgsql-relation-joined OperationalError: could not connect to server: Connection refused
2014-11-01 11:05:13 INFO pgsql-relation-joined Is the server running on host "10.0.3.85" and accepting
2014-11-01 11:05:13 INFO pgsql-relation-joined TCP/IP connections on port 5432?
2014-11-01 11:05:13 INFO pgsql-relation-joined Syncing...
2014-11-01 11:05:13 INFO juju-log pgsql:28: migration command failed, retrying...
2014-11-01 11:05:19 INFO pgsql-relation-joined OperationalError: could not connect to server: Connection refused
2014-11-01 11:05:19 INFO pgsql-relation-joined Is the server running on host "10.0.3.85" and accepting
2014-11-01 11:05:19 INFO pgsql-relation-joined TCP/IP connections on port 5432?
2014-11-01 11:05:19 INFO pgsql-relation-joined Syncing...
2014-11-01 11:05:19 INFO juju-log pgsql:28: migration command failed, retrying...
2014-11-01 11:05:25 INFO pgsql-relation-joined OperationalError: could not connect to server: Connection refused
2014-11-01 11:05:25 INFO pgsql-relation-joined Is the server running on host "10.0.3.85" and accepting
2014-11-01 11:05:25 INFO pgsql-relation-joined TCP/IP connections on port 5432?
2014-11-01 11:05:25 INFO pgsql-relation-joined Syncing...
2014-11-01 11:05:25 INFO juju-log pgsql:28: migration command failed, retrying...
2014-11-01 11:05:34 INFO juju-log pgsql:28: migration command failed
2014-11-01 11:05:34 INFO pgsql-relation-joined Traceback (most recent call last):
2014-11-01 11:05:34 INFO pgsql-relation-joined File "/var/lib/juju/agents/unit-ci-airline-ts-django-0/charm/hooks/pgsql-relation-joined", line 613, in <module>
2014-11-01 11:05:34 INFO pgsql-relation-joined hooks.execute(sys.argv)
2014-11-01 11:05:34 INFO pgsql-relation-joined File "/var/lib/juju/agents/unit-ci-airline-ts-django-0/charm/hooks/charmhelpers/core/hookenv.py", line 480, in execute
2014-11-01 11:05:34 INFO pgsql-relation-joined self._hooks[hook_name]()
2014-11-01 11:05:34 INFO pgsql-relation-joined File "/var/lib/juju/agents/unit-ci-airline-ts-django-0/charm/hooks/pgsql-relation-joined", line 550, in pgsql_relation_joined_changed
2014-11-01 11:05:34 INFO pgsql-relation-joined _db_migrate()
2014-11-01 11:05:34 INFO pgsql-relation-joined File "/var/lib/juju/agents/unit-ci-airline-ts-django-0/charm/hooks/pgsql-relation-joined", line 444, in _db_migrate
2014-11-01 11:05:34 INFO pgsql-relation-joined raise Exce...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'britney_proxy/bin/uci-britney-report'
2--- britney_proxy/bin/uci-britney-report 1970-01-01 00:00:00 +0000
3+++ britney_proxy/bin/uci-britney-report 2014-10-31 16:39:46 +0000
4@@ -0,0 +1,63 @@
5+#!/usr/bin/env python
6+# Ubuntu CI Engine
7+# Copyright 2014 Canonical Ltd.
8+
9+# This program is free software: you can redistribute it and/or modify it
10+# under the terms of the GNU Affero General Public License version 3, as
11+# published by the Free Software Foundation.
12+
13+# This program is distributed in the hope that it will be useful, but
14+# WITHOUT ANY WARRANTY; without even the implied warranties of
15+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16+# PURPOSE. See the GNU Affero General Public License for more details.
17+
18+# You should have received a copy of the GNU Affero General Public License
19+# along with this program. If not, see <http://www.gnu.org/licenses/>.
20+"""The script to collect britney results from outside the uci-engine."""
21+
22+import logging
23+import os
24+import sys
25+
26+# FIXME: Add -n to handle only 'n' requests and quit (default to 1, 0 to loop
27+# forver) -- vila 2014-10-30
28+
29+# FIXME: 'sys.path.{insert,append}' is evil and should be fixed when
30+# packaging uci-engine or part of it. -- vila 2014-10-15
31+
32+HERE = os.path.abspath(os.path.dirname(__file__))
33+# We want to be able to import from the branch root
34+BRANCH_ROOT = os.path.abspath(os.path.join(HERE, '..', '..'))
35+sys.path.insert(0, os.path.join(BRANCH_ROOT, 'britney_proxy'))
36+sys.path.insert(0, os.path.join(BRANCH_ROOT, 'ci-utils'))
37+
38+
39+from uciconfig import errors
40+
41+
42+from britney import (
43+ config,
44+ process_reports,
45+)
46+from ci_utils import (
47+ amqp_utils,
48+ dump_stack,
49+)
50+
51+
52+if __name__ == '__main__':
53+ dump_stack.install_stack_dump_signal()
54+ logging.basicConfig(level=logging.INFO,
55+ format="%(asctime)s %(levelname)s %(message)s")
56+ # Force amqp_utils to use our config instead of the non existant
57+ # amqp_config.py one (only available for rabbit workers in uci-engine).
58+ amqp_utils.get_config = config.amqp_config(config.BritneyStack())
59+
60+ try:
61+ ret = process_reports.cli_run(sys.argv[1:], sys.stdout, sys.stderr)
62+ except errors.ConfigError as e:
63+ # FIXME: If one option is missing, the other mandatory ones are
64+ # propably missing too, report them all. -- vila 2014-10-16
65+ stderr.write('{}\n'.format(e))
66+ ret = 1
67+ sys.exit(ret)
68
69=== modified file 'britney_proxy/bin/uci-britney-request'
70--- britney_proxy/bin/uci-britney-request 2014-10-30 19:31:08 +0000
71+++ britney_proxy/bin/uci-britney-request 2014-10-31 16:39:46 +0000
72@@ -32,15 +32,17 @@
73 from uciconfig import errors
74
75
76-from britney import post_request
77+from britney import (
78+ config,
79+ post_request,
80+)
81 from ci_utils import amqp_utils
82
83
84 if __name__ == '__main__':
85 # Force amqp_utils to use our config instead of the non existant
86 # amqp_config.py one (only available for rabbit workers in uci-engine).
87- amqp_utils.get_config = post_request.amqp_config(
88- post_request.BritneyStack())
89+ amqp_utils.get_config = config.amqp_config(config.BritneyStack())
90 try:
91 ret = post_request.cli_run(sys.argv[1:], sys.stdout, sys.stderr)
92 except errors.ConfigError as e:
93
94=== added file 'britney_proxy/britney/config.py'
95--- britney_proxy/britney/config.py 1970-01-01 00:00:00 +0000
96+++ britney_proxy/britney/config.py 2014-10-31 16:39:46 +0000
97@@ -0,0 +1,83 @@
98+# Ubuntu CI Engine
99+# Copyright 2014 Canonical Ltd.
100+
101+# This program is free software: you can redistribute it and/or modify it
102+# under the terms of the GNU Affero General Public License version 3, as
103+# published by the Free Software Foundation.
104+
105+# This program is distributed in the hope that it will be useful, but
106+# WITHOUT ANY WARRANTY; without even the implied warranties of
107+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
108+# PURPOSE. See the GNU Affero General Public License for more details.
109+
110+# You should have received a copy of the GNU Affero General Public License
111+# along with this program. If not, see <http://www.gnu.org/licenses/>.
112+"""Configuration options related to the britney proxy."""
113+
114+import os
115+
116+
117+from uciconfig import (
118+ options,
119+ stacks,
120+ stores,
121+)
122+
123+
124+BritneyStore = stores.FileStore
125+
126+
127+def user_config_dir():
128+ conf_dir = os.environ.get('XDG_CONFIG_HOME',
129+ os.path.expanduser('~/.config'))
130+ return conf_dir
131+
132+
133+class BritneyStack(stacks.Stack):
134+ """Per-user options."""
135+
136+ def __init__(self):
137+ """Make a new stack for britney interactions with the uci engine.
138+
139+ The options are searched in ~/.config/uci-britney.conf.
140+ """
141+ upath = os.path.join(user_config_dir(), 'uci-britney.conf')
142+ user_store = self.get_shared_store(BritneyStore(upath))
143+ super(BritneyStack, self).__init__(
144+ # We only care about the no name section
145+ [stacks.NameMatcher(user_store, None).get_sections],
146+ user_store, mutable_section_id=None)
147+
148+
149+def register(option):
150+ options.option_registry.register(option)
151+
152+
153+register(options.Option('uci.rabbit.host',
154+ default=options.MANDATORY,
155+ help_string='''\
156+The host:port address for the uci-britney rabbit server.
157+'''))
158+register(options.Option('uci.rabbit.user',
159+ default=options.MANDATORY,
160+ help_string='''\
161+The user name for the uci-britney rabbit server.
162+'''))
163+register(options.Option('uci.rabbit.password',
164+ default=options.MANDATORY,
165+ help_string='''\
166+The user password for the uci-britney rabbit server.
167+'''))
168+
169+
170+def amqp_config(conf):
171+ """Create the rabbit config from the britney one."""
172+ class AmqpConfig(object):
173+
174+ def __init__(self):
175+ self.AMQP_HOST = conf.get('uci.rabbit.host')
176+ self.AMQP_VHOST = '/'
177+ self.AMQP_USER = conf.get('uci.rabbit.user')
178+ self.AMQP_PASSWORD = conf.get('uci.rabbit.password')
179+
180+ return AmqpConfig
181
182=== modified file 'britney_proxy/britney/post_request.py'
183--- britney_proxy/britney/post_request.py 2014-10-15 14:55:04 +0000
184+++ britney_proxy/britney/post_request.py 2014-10-31 16:39:46 +0000
185@@ -15,17 +15,9 @@
186
187
188 import argparse
189-import os
190 import sys
191
192
193-from uciconfig import (
194- options,
195- stacks,
196- stores,
197-)
198-
199-
200 from britney import queues
201
202
203@@ -87,74 +79,18 @@
204 sys.stderr = err_orig
205
206
207-BritneyStore = stores.FileStore
208-
209-
210-def user_config_dir():
211- conf_dir = os.environ.get('XDG_CONFIG_HOME',
212- os.path.expanduser('~/.config'))
213- return conf_dir
214-
215-
216-class BritneyStack(stacks.Stack):
217- """Per-user options."""
218-
219- def __init__(self):
220- """Make a new stack for britney interactions with the uci engine.
221-
222- The options are searched in ~/.config/uci-britney.conf.
223- """
224- upath = os.path.join(user_config_dir(), 'uci-britney.conf')
225- user_store = self.get_shared_store(BritneyStore(upath))
226- super(BritneyStack, self).__init__(
227- # We only care about the no name section
228- [stacks.NameMatcher(user_store, None).get_sections],
229- user_store, mutable_section_id=None)
230-
231-
232-def register(option):
233- options.option_registry.register(option)
234-
235-
236-register(options.Option('uci.rabbit.host',
237- default=options.MANDATORY,
238- help_string='''\
239-The host:port address for the uci-britney rabbit server.
240-'''))
241-register(options.Option('uci.rabbit.user',
242- default=options.MANDATORY,
243- help_string='''\
244-The user name for the uci-britney rabbit server.
245-'''))
246-register(options.Option('uci.rabbit.password',
247- default=options.MANDATORY,
248- help_string='''\
249-The user password for the uci-britney rabbit server.
250-'''))
251-
252-
253 def post(queue, series, architecture, package, output=None, report=None):
254 msg = dict(series=series, architecture=architecture, package=package)
255 if output is not None:
256 msg['output_queue'] = output
257 if report is not None:
258 msg['report_queue'] = report
259+ # FIXME: Error checking to guarantee that we at least give a proper error
260+ # message. Known failures: bad host (can't connect), bad credentials (can't
261+ # connect). Possible failures: rabbit down. -- vila 2014-10-24
262 queue.publish(msg)
263
264
265-def amqp_config(conf):
266- """Create the rabbit config from the britney one."""
267- class AmqpConfig(object):
268-
269- def __init__(self):
270- self.AMQP_HOST = conf.get('uci.rabbit.host')
271- self.AMQP_VHOST = '/'
272- self.AMQP_USER = conf.get('uci.rabbit.user')
273- self.AMQP_PASSWORD = conf.get('uci.rabbit.password')
274-
275- return AmqpConfig
276-
277-
278 def cli_run(args, stdout, stderr):
279 """Post a britney test request from the command line."""
280 parser = PostRequestArgParser()
281
282=== added file 'britney_proxy/britney/process_reports.py'
283--- britney_proxy/britney/process_reports.py 1970-01-01 00:00:00 +0000
284+++ britney_proxy/britney/process_reports.py 2014-10-31 16:39:46 +0000
285@@ -0,0 +1,179 @@
286+#!/usr/bin/env python
287+# Ubuntu CI Engine
288+# Copyright 2014 Canonical Ltd.
289+
290+# This program is free software: you can redistribute it and/or modify it
291+# under the terms of the GNU Affero General Public License version 3, as
292+# published by the Free Software Foundation.
293+
294+# This program is distributed in the hope that it will be useful, but
295+# WITHOUT ANY WARRANTY; without even the implied warranties of
296+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
297+# PURPOSE. See the GNU Affero General Public License for more details.
298+
299+# You should have received a copy of the GNU Affero General Public License
300+# along with this program. If not, see <http://www.gnu.org/licenses/>.
301+from __future__ import unicode_literals
302+
303+
304+import argparse
305+import errno
306+import os
307+import json
308+import logging
309+import sys
310+
311+
312+from ci_utils import amqp_utils
313+
314+
315+class BritneyReportHandler(object):
316+ """Process a britney test report.
317+
318+ This is fed by messages received on a queue or directly for tests by
319+ calling the ``handle()`` method.
320+ """
321+
322+ def __init__(self, output_dir):
323+ self.output_dir = output_dir
324+ try:
325+ # Try to create the needed dirs
326+ os.makedirs(self.output_dir)
327+ except OSError as e:
328+ # They are already there, no worries
329+ if e.errno != errno.EEXIST:
330+ raise
331+
332+ def handle(self, request):
333+ """Handle a report request.
334+
335+ :param request: The request describing a britney result to process.
336+
337+ This creates a file in the 'output_dir' directory under a name and with
338+ the content expected by britney.
339+
340+ The name is something like:
341+ {series}_{arch}_{package}-{DATE}_{HOUR}[_{whatever}].result
342+
343+ The content is (all fields on a single line separated by spaces):
344+ {series} {arch} {package} {version} {PASS|FAILED} [{dep} {dep_version]*
345+
346+ If an error occured while setting up the testbed (by adt-run) we get
347+ only:
348+ {series} {arch} {package}
349+ """
350+
351+ # From 'result_at' we get info for the path
352+ store_prefix = request['result_at']
353+ # The prefix has the timestamp
354+ # Sample: trusty/amd64/libp/libpng/20141001_000000_localhost
355+ stamp = store_prefix.split('/')[4]
356+ day, hour = stamp.split('_')[:2]
357+
358+ # From 'result' we get the series, arch and package parts
359+ result = request['britney_result']
360+ # The first 3 fields are guaranteed to exist.
361+ series, arch, package = result.split()[:3]
362+ logging.info('{} {} {}'.format(series, arch, package))
363+
364+ path = os.path.join(self.output_dir, '{}_{}_{}_{}-{}.result'.format(
365+ series, arch, package, day, hour))
366+ with open(path, 'wb') as f:
367+ f.write(result)
368+
369+
370+class BritneyReportsQueue(object):
371+ """The queue receiving the reports for the britney results."""
372+
373+ def __init__(self, queue_name, request_handler, max_reports=0):
374+ """Britney reports queue.
375+
376+ :param queue_name: The queue name where the reports are received.
377+
378+ :param request_handler: The handler that processes the reports.
379+
380+ :param max_reports: Max number of reports to handle before
381+ quitting. The default value (0) is used to never quit.
382+ """
383+ self.queue_name = queue_name
384+ self.request_handler = request_handler
385+ self.max_reports = max_reports
386+ self.nb_reports = 0
387+
388+ def consume(self):
389+ conf = amqp_utils.get_config()
390+ amqp_utils.process_queue(conf, self.queue_name, self.handle_request)
391+
392+ def handle_request(self, msg):
393+ logging.debug('Will handle {}'.format(msg.body))
394+ body = json.loads(msg.body)
395+ ret = self.request_handler.handle(body)
396+ msg.channel.basic_ack(msg.delivery_tag)
397+ logging.debug('Handling returned: {} for {}'.format(ret, msg.body))
398+ self.nb_reports += 1
399+ if self.max_reports and self.nb_reports >= self.max_reports:
400+ # Leave the loop
401+ msg.channel.basic_cancel(msg.consumer_tag)
402+ return ret
403+
404+
405+class ProcessReportArgParser(argparse.ArgumentParser):
406+
407+ def __init__(self, prog=None, description=None):
408+ if prog is None:
409+ prog = 'uci-britney-report'
410+ if description is None:
411+ description = 'Process a test report from britney.'
412+ super(ProcessReportArgParser, self).__init__(prog=prog,
413+ description=description)
414+ self.add_argument(
415+ 'report', metavar='REPORT_QUEUE',
416+ help='The REPORT_QUEUE publishing the test results.')
417+ self.add_argument(
418+ 'output', metavar='OUTPUT',
419+ help='The OUTPUT directory where the results are stored.')
420+ # Optional arguments
421+ self.add_argument(
422+ '--nb-reports', '-n', metavar='REPORTS', default=1, type=int,
423+ help='The number of reports to process from the queue'
424+ ' named REPORT_QUEUE.')
425+
426+ def parse_args(self, args=None, out=None, err=None):
427+ """Parse arguments, overridding stdout/stderr if provided.
428+
429+ Overridding stdout/stderr is provided for tests to address arpgarse
430+ hardcoding sys.{stdout,stderr} usage.
431+
432+ :params args: The arguments to the script.
433+
434+ :param out: Default to sys.stdout.
435+
436+ :param err: Default to sys.stderr.
437+
438+ :return: The populated namespace.
439+
440+ """
441+ out_orig = sys.stdout
442+ err_orig = sys.stderr
443+ try:
444+ if out is not None:
445+ sys.stdout = out
446+ if err is not None:
447+ sys.stderr = err
448+ return super(ProcessReportArgParser, self).parse_args(args)
449+ finally:
450+ sys.stdout = out_orig
451+ sys.stderr = err_orig
452+
453+
454+def cli_run(args, stdout, stderr):
455+ """Process britney test reports from the command line."""
456+ parser = ProcessReportArgParser()
457+ opts = parser.parse_args(args, out=stdout, err=stderr)
458+ request_handler = BritneyReportHandler(opts.output)
459+ queue = BritneyReportsQueue(opts.report, request_handler,
460+ max_reports=opts.nb_reports)
461+ if opts.nb_reports == 0:
462+ sys.stderr.write('Waiting for reports. ^C to exit.')
463+ queue.consume()
464+ return 0
465
466=== added file 'britney_proxy/britney/tests/test_config.py'
467--- britney_proxy/britney/tests/test_config.py 1970-01-01 00:00:00 +0000
468+++ britney_proxy/britney/tests/test_config.py 2014-10-31 16:39:46 +0000
469@@ -0,0 +1,55 @@
470+# Copyright 2014 Canonical Ltd.
471+
472+# This program is free software: you can redistribute it and/or modify it
473+# under the terms of the GNU Affero General Public License version 3, as
474+# published by the Free Software Foundation.
475+
476+# This program is distributed in the hope that it will be useful, but
477+# WITHOUT ANY WARRANTY; without even the implied warranties of
478+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
479+# PURPOSE. See the GNU Affero General Public License for more details.
480+
481+# You should have received a copy of the GNU Affero General Public License
482+# along with this program. If not, see <http://www.gnu.org/licenses/>.
483+
484+import os
485+import unittest
486+
487+
488+from ucitests import fixtures
489+
490+
491+from britney import config
492+
493+
494+class TestConfigDir(unittest.TestCase):
495+
496+ def setUp(self):
497+ super(TestConfigDir, self).setUp()
498+ fixtures.set_uniq_cwd(self)
499+ fixtures.isolate_from_env(self, dict(HOME=self.uniq_dir,
500+ XDG_CONFIG_HOME=None))
501+
502+ def test_default(self):
503+ conf_dir = os.path.join(self.uniq_dir, '.config')
504+ self.assertEqual(conf_dir, config.user_config_dir())
505+
506+ def test_xdg_config_home_override(self):
507+ xdg_dir = os.path.join(self.uniq_dir, 'xdg')
508+ os.environ['XDG_CONFIG_HOME'] = xdg_dir
509+ self.assertEqual(xdg_dir, config.user_config_dir())
510+
511+
512+class TestConfig(unittest.TestCase):
513+
514+ def setUp(self):
515+ super(TestConfig, self).setUp()
516+ fixtures.set_uniq_cwd(self)
517+ fixtures.isolate_from_env(self, dict(HOME=self.uniq_dir,
518+ XDG_CONFIG_HOME=None))
519+ self.config = config.BritneyStack()
520+
521+ def test_empty_config_errors(self):
522+ with self.assertRaises(Exception) as cm:
523+ self.config.get('uci.rabbit.host')
524+ self.assertEqual('uci.rabbit.host must be set.', unicode(cm.exception))
525
526=== modified file 'britney_proxy/britney/tests/test_post_request.py'
527--- britney_proxy/britney/tests/test_post_request.py 2014-10-15 14:29:40 +0000
528+++ britney_proxy/britney/tests/test_post_request.py 2014-10-31 16:39:46 +0000
529@@ -13,14 +13,10 @@
530 # along with this program. If not, see <http://www.gnu.org/licenses/>.
531
532 from cStringIO import StringIO
533-import os
534 import unittest
535
536
537-from ucitests import (
538- assertions,
539- fixtures as uci_fixtures,
540-)
541+from ucitests import assertions
542
543
544 from britney import post_request
545@@ -60,39 +56,6 @@
546 self.assertEqual('', self.err.getvalue())
547
548
549-class TestConfigDir(unittest.TestCase):
550-
551- def setUp(self):
552- super(TestConfigDir, self).setUp()
553- uci_fixtures.set_uniq_cwd(self)
554- uci_fixtures.isolate_from_env(self, dict(HOME=self.uniq_dir,
555- XDG_CONFIG_HOME=None))
556-
557- def test_default(self):
558- conf_dir = os.path.join(self.uniq_dir, '.config')
559- self.assertEqual(conf_dir, post_request.user_config_dir())
560-
561- def test_xdg_config_home_override(self):
562- xdg_dir = os.path.join(self.uniq_dir, 'xdg')
563- os.environ['XDG_CONFIG_HOME'] = xdg_dir
564- self.assertEqual(xdg_dir, post_request.user_config_dir())
565-
566-
567-class TestConfig(unittest.TestCase):
568-
569- def setUp(self):
570- super(TestConfig, self).setUp()
571- uci_fixtures.set_uniq_cwd(self)
572- uci_fixtures.isolate_from_env(self, dict(HOME=self.uniq_dir,
573- XDG_CONFIG_HOME=None))
574- self.config = post_request.BritneyStack()
575-
576- def test_empty_config_errors(self):
577- with self.assertRaises(Exception) as cm:
578- self.config.get('uci.rabbit.host')
579- self.assertEqual('uci.rabbit.host must be set.', unicode(cm.exception))
580-
581-
582 class TestPost(unittest.TestCase):
583
584 def test_post_simple(self):
585
586=== added file 'britney_proxy/britney/tests/test_process_reports.py'
587--- britney_proxy/britney/tests/test_process_reports.py 1970-01-01 00:00:00 +0000
588+++ britney_proxy/britney/tests/test_process_reports.py 2014-10-31 16:39:46 +0000
589@@ -0,0 +1,112 @@
590+# Ubuntu CI Engine
591+# Copyright 2014 Canonical Ltd.
592+
593+# This program is free software: you can redistribute it and/or modify it
594+# under the terms of the GNU Affero General Public License version 3, as
595+# published by the Free Software Foundation.
596+
597+# This program is distributed in the hope that it will be useful, but
598+# WITHOUT ANY WARRANTY; without even the implied warranties of
599+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
600+# PURPOSE. See the GNU Affero General Public License for more details.
601+
602+# You should have received a copy of the GNU Affero General Public License
603+# along with this program. If not, see <http://www.gnu.org/licenses/>.
604+
605+from cStringIO import StringIO
606+import os
607+import unittest
608+
609+
610+from ucitests import fixtures as fixtures
611+
612+
613+from britney import process_reports
614+
615+
616+class TestBritneyReportHandlerErrors(unittest.TestCase):
617+
618+ def setUp(self):
619+ super(TestBritneyReportHandlerErrors, self).setUp()
620+ self.handler = process_reports.BritneyReportHandler('unused')
621+
622+ def assertError(self, exc_class, exc_msg, request):
623+ with self.assertRaises(exc_class) as cm:
624+ self.handler.handle(request)
625+ self.assertEqual(exc_msg, cm.exception.message)
626+
627+ def test_empty_message(self):
628+ self.assertError(KeyError, 'result_at', dict())
629+
630+ def test_wrong_prefix(self):
631+ self.assertError(IndexError, 'list index out of range',
632+ dict(result_at='xxx'))
633+
634+ def test_wrong_date(self):
635+ self.assertError(ValueError, 'need more than 1 value to unpack',
636+ dict(result_at='trusty/amd64/libp/libpng/xxx',
637+ britney_result='trusty amd64 libpng'))
638+
639+ def test_empty_result(self):
640+ self.assertError(
641+ ValueError, 'need more than 0 values to unpack',
642+ dict(result_at='trusty/amd64/libp/libpng/yymmdd_hhmmss',
643+ britney_result=''))
644+
645+
646+class TestBritneyReportHandler(unittest.TestCase):
647+
648+ def setUp(self):
649+ super(TestBritneyReportHandler, self).setUp()
650+ fixtures.set_uniq_cwd(self)
651+ prefix = 'trusty/amd64/'
652+ self.handler = process_reports.BritneyReportHandler(prefix)
653+
654+ def test_result_file_created(self):
655+ result = 'trusty amd64 libpng'
656+ self.handler.handle(
657+ dict(result_at='trusty/amd64/libp/libpng/yymmdd_hhmmss',
658+ britney_result=result))
659+ result_path = 'trusty/amd64/trusty_amd64_libpng_yymmdd-hhmmss.result'
660+ self.assertTrue(os.path.exists(result_path),
661+ '{} is not there'.format(result_path))
662+ with open(result_path) as f:
663+ self.assertEqual(result, f.read())
664+
665+
666+class TestCliRun(unittest.TestCase):
667+ "Smoke blackbox tests."""
668+
669+ def setUp(self):
670+ super(TestCliRun, self).setUp()
671+ self.out = StringIO()
672+ self.err = StringIO()
673+
674+ def test_run_no_args_errors(self):
675+ with self.assertRaises(SystemExit):
676+ process_reports.cli_run([], self.out, self.err)
677+
678+
679+class TestOptionParsing(unittest.TestCase):
680+
681+ def setUp(self):
682+ super(TestOptionParsing, self).setUp()
683+ self.out = StringIO()
684+ self.err = StringIO()
685+
686+ def parse_args(self, args):
687+ ns = process_reports.ProcessReportArgParser().parse_args(
688+ args, self.out, self.err)
689+ return ns
690+
691+ def test_default_values(self):
692+ ns = self.parse_args(['reports', 'output'])
693+ self.assertEqual('output', ns.output)
694+ self.assertEqual('reports', ns.report)
695+ self.assertEqual(1, ns.nb_reports)
696+ self.assertEqual('', self.out.getvalue())
697+ self.assertEqual('', self.err.getvalue())
698+
699+ def test_nb_reports(self):
700+ ns = self.parse_args(['reports', 'output', '-n0'])
701+ self.assertEqual(0, ns.nb_reports)

Subscribers

People subscribed via source and target branches