Merge ~sbaldassin/bileto:ust-bileto into bileto:master

Proposed by Santiago Baldassin
Status: Rejected
Rejected by: Robert Bruce Park
Proposed branch: ~sbaldassin/bileto:ust-bileto
Merge into: bileto:master
Diff against target: 856 lines (+742/-2)
7 files modified
.bzr-builddeb/default.conf (+2/-0)
bileto/models.py (+14/-1)
bileto/static/index.html (+285/-0)
bileto/static/style.css (+384/-0)
britney/iterate.py (+30/-1)
db_migrations.sh (+1/-0)
scripts/import.sh (+26/-0)
Reviewer Review Type Date Requested Status
Robert Bruce Park (community) Needs Information
Review via email: mp+306251@code.launchpad.net

Commit message

Adding ubuntu system tests to bileto

Description of the change

This change adds ubuntu system tests to bileto.

In order to deploy this, you'll need the qakit module available in the canonical-platform-qa ppa

To post a comment you must log in.
Revision history for this message
Robert Bruce Park (robru) wrote :

Thanks for rebasing your works Santiago, apologies again for the git history situation. Some comments inline.

Before you fix any of the issues I raise in my review, though, we need to consider that Steve and Martin both want this triggered from Britney, so that it can work again during proposed-migration.

Also, during the meeting, we agreed that we would transition this in slowly, such that at first we just trigger the tests without reporting them anywhere, so that results can be manually inspected. Today I'll be digging into the britney code to figure out the best way to trigger this.

review: Needs Information
Revision history for this message
Robert Bruce Park (robru) wrote :

Also, where is the code for qakit.ust.worker?

Thanks

Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

Thanks for your feedback Robert. I'll be working on your comments. In the meantime, here's the code for qakit: https://code.launchpad.net/qakit.

About triggering this from Britney during proposed migration, we would just need to re-use the code that I've added in bileto or we could just publish an amqp message (containing the ppa) to /ubuntu-system-tests. What we have to consider is that we want to avoid a message to be publish for every single package in ppa. From what I understood when I took a look at britney, a different amqp message is published for every single source package in the ppa and we need a single message for the whole ppa. The current worker is already listening amqp so I can give you a hand to test this if you want

Last but not least, I'd like to keep the reporting in bileto if you don't mind since it will help us to trace the results easily to our jenkins builds. Polling for results will generate a single http request which response is a 100 bytes message so the performance degradation in bileto should be minimum and I also make sure that if no response is received then an empty response is returned so that angularjs can parse it in any case

Revision history for this message
Robert Bruce Park (robru) wrote :

Ok, so I've had a chance to review the qakit source and I need to raise some concerns here. One thing is that the compute_ust_signoff as you've written it will be triggered every time the ticket is modified, for any reason. So it's true that the status job will update the ticket every 20 minutes, which will result in ust polling every 20 minutes, but it will also do a ust poll, eg, if a user edits a field on the ticket, and this is a really unacceptable slowdown for user experience, to do a syncronous poll and block the thread while the user is waiting for their change to save.

**IF** we go ahead with the idea of bileto polling ust results, we'll need to model it after britney/iterate.py, eg, there should be an independent script that runs from cron and then feeds the results into bileto. That way it would run much more consistently and performantly, without interfering with user edits of tickets.

Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

Good point Robert! There's no need to poll for results when the user is editing the form. Instead of using an independent script to get the results, I'd like to suggest doing it with an ajax call in the app.js which will be triggered every 2 minutes. That way users will have to wait only 2 minutes in the worst case scenario and we'll be using a non blocking asynchronous method. As long as we update the scope in the callback, angular will take care of reflecting that in the ui

Revision history for this message
Robert Bruce Park (robru) wrote :

Well the problem with doing it in app.js is that it then runs client side:
if two people are viewing the same ticket you'll get redundant polling of
results, and if nobody is viewing the ticket then the results never appear!
if app.js then attempts to save the result to the ticket, the audit log
will appear as if users are entering information manually, eg, the log
would show "sbalda: ust_signoff: approved" which would be quite strange. Is
this really the behavior you want?

I think really the only sensible approach is to model britney/iterate.py:
run it from cron periodically, query bileto for what tickets to submit for
testing, get results and save them to the ticket. Except for the first
phase we just want to submit requests and not poll or display results just
yet.

On Sep 21, 2016 6:59 AM, "Santiago Baldassin" <
<email address hidden>> wrote:

> Good point Robert! There's no need to poll for results when the user is
> editing the form. Instead of using an independent script to get the
> results, I'd like to suggest doing it with an ajax call in the app.js which
> will be triggered every 2 minutes. That way users will have to wait only 2
> minutes in the worst case scenario and we'll be using a non blocking
> asynchronous method. As long as we update the scope in the callback,
> angular will take care of reflecting that in the ui
> --
> https://code.launchpad.net/~sbaldassin/bileto/+git/bileto/+merge/306251
> You are reviewing the proposed merge of ~sbaldassin/bileto:ust-bileto into
> bileto:master.
>

Revision history for this message
Robert Bruce Park (robru) wrote :

Sorry, another question: Are you saying it's possible to trigger UST with an ajax request? Or is the ajax request only for querying the status after it's been triggered?

If the former, I'd strongly prefer that over importing this qakit module, which imports jenkins module. Even when we used jenkins extensively, bileto only ever spoke JSON to it, I never had to import jenkins module, so I'd prefer not to start using that now if it can be helped.

Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

Thanks Robert. I've addressed all your comments and replied to a couple of them inline. I've remove the polling results part for now and I'll be submitting a new mp for that

Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

> Sorry, another question: Are you saying it's possible to trigger UST with an
> ajax request? Or is the ajax request only for querying the status after it's
> been triggered?
>
> If the former, I'd strongly prefer that over importing this qakit module,
> which imports jenkins module. Even when we used jenkins extensively, bileto
> only ever spoke JSON to it, I never had to import jenkins module, so I'd
> prefer not to start using that now if it can be helped.

I was thinking about an ajax request to get the results. About importing qakit, I'd like to keep it if you guys don't mind. Please take into account that qakit is not only talking to Jenkins but also to practitest and to launchpad. The beauty of using the jenkins lib is that you forget about dealing with http requests, catching errors, packing/unpacking requests and results, etc. By the way, the jenkins module is a well known and widely use module, so I'm not sure why we should not use it, that said, If there's any technical roadblock related to it, I'm open to change it

Revision history for this message
Robert Bruce Park (robru) wrote :

Hey Santiago, apologies I've been meaning to dedicate more time to this but the bileto.ubuntu.com rollout was not particularly smooth so I've had a few fires to put out.

The single greatest challenge we will face is the fact that britney runs in precise, which limits us to python 3.2, which is really showing its age. It's not clear to me that qakit or jenkins modules are going to work in precise, and backporting them may well be harder than just writing a few http requests.

In terms of the bileto side, I like to keep things as simple as possible, so loading the big jenkins module in my server process is really unappealing to me (even if you resolve the issue of polling jenkins every time a user edits a ticket, I won't accept any code that imports jenkins in bileto/*.py) keep in mind that our flask server has 15 worker processes so anything imported in bileto/*.py is going to be imported in 15 different processes and stick around forever.

The significantly better approach is to just have one script called periodically from cron. It can load jenkins, iterate over tickets, submit some tests, and then exit without being a huge burden on the system for a prolonged period of time.

Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

Hi Robert, no need to apologize. I'll be working on getting rid of Jenkins in qakit and let you know when it's ready.

~sbaldassin/bileto:ust-bileto updated
0c5bc46... by Santiago Baldassin

Moving the trigger for ust to iterate.py

3560ed2... by Santiago Baldassin

Merge branch 'master' of git+ssh://git.launchpad.net/bileto

Unmerged commits

0c5bc46... by Santiago Baldassin

Moving the trigger for ust to iterate.py

cbf2617... by Santiago Baldassin

Adding ubuntu system tests signoff

This includes an additional signoff for the requests in bileto.
Once a silo is assigned to the requests and the lander approved
the changes, ubuntu system tests will be triggered

3560ed2... by Santiago Baldassin

Merge branch 'master' of git+ssh://git.launchpad.net/bileto

5f56daa... by Santiago Baldassin

Merge branch 'master' of https://git.launchpad.net/bileto

3f02943... by Robert Bruce Park

Retry git fetch a few times before giving up.

83f2a02... by Robert Bruce Park

PPAs are no longer Assigned, they are Active.

e1975b4... by Robert Bruce Park

Add multiverse dependency as per cjwatson.

51950d6... by Robert Bruce Park

Create dir before copying into it.

7096cc8... by Robert Bruce Park

Increase authorized_size.

0ba81f4... by Robert Bruce Park

Ephemeral PPAs phase two: Use them for new tickets.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.bzr-builddeb/default.conf b/.bzr-builddeb/default.conf
2new file mode 100644
3index 0000000..6c96a98
4--- /dev/null
5+++ b/.bzr-builddeb/default.conf
6@@ -0,0 +1,2 @@
7+[BUILDDEB]
8+split = True
9diff --git a/bileto/models.py b/bileto/models.py
10index 5ea297f..a3a5bd2 100644
11--- a/bileto/models.py
12+++ b/bileto/models.py
13@@ -3,7 +3,6 @@
14 This file defines the two DB schemas, Request and Comment, and various
15 metadata related to those.
16 """
17-
18 import re
19
20 from os import environ
21@@ -12,6 +11,7 @@ from datetime import datetime
22 from functools import lru_cache
23 from fcntl import LOCK_EX, LOCK_NB, flock
24
25+from qakit.ust.worker import Worker, UstResult
26 from sqlalchemy import BigInteger, desc, text
27 from sqlalchemy.inspection import inspect
28 from sqlalchemy.orm import relationship
29@@ -53,6 +53,11 @@ BRITNEY_SIGNOFF = """Automated Signoff
30 Indicates if britney has approved the ticket based on autopkgtests & other \
31 factors. Check the excuses page for details."""
32
33+UST_SIGNOFF = """Ubuntu System Tests Signoff
34+
35+Indicates if ubuntu system tests has approved the silo"""
36+
37+
38 CREATOR = """Request Creator
39
40 Launchpad nickname of the person who created this request."""
41@@ -103,6 +108,10 @@ QA = """QA Signoff
42
43 Indicates whether or not the ticket has been approved by QA team."""
44
45+UST = """UST Signoff
46+
47+Indicates whether or not the silo has been approved by Ubuntu system tests."""
48+
49 SERIES = """Target Series
50
51 What series to build for, eg, wily, vivid, or yakkety+xenial+vivid."""
52@@ -239,6 +248,8 @@ class Request(db.Model, BiletoModel):
53 dest = db.Column(db.Text, default=EMPTY, doc=PPA)
54 lander_signoff = db.Column(db.Text, default=EMPTY, doc=LANDER_SIGNOFF)
55 britney_signoff = db.Column(db.Text, default=EMPTY, doc=BRITNEY_SIGNOFF)
56+ ust_signoff = db.Column(db.Text, default=EMPTY, doc=UST_SIGNOFF)
57+ ust_build_url = db.Column(db.Text, default=EMPTY, doc=UST_SIGNOFF)
58 qa_signoff = db.Column(db.Text, default=EMPTY, doc=QA)
59
60 search = db.Column(db.Text, default=EMPTY, doc='Searchable Field')
61@@ -329,6 +340,8 @@ class Request(db.Model, BiletoModel):
62 if lander_fail or any(clear in status for clear in CLEAR):
63 self.lander_signoff = EMPTY
64 self.britney_signoff = EMPTY
65+ self.ust_signoff = EMPTY
66+ self.ust_build_url = EMPTY
67 self.qa_signoff = 'Required'
68 self.autopkgtest = EMPTY
69
70diff --git a/bileto/static/index.html b/bileto/static/index.html
71new file mode 100644
72index 0000000..9422e18
73--- /dev/null
74+++ b/bileto/static/index.html
75@@ -0,0 +1,285 @@
76+<!DOCTYPE html>
77+<html ng-app="Bileto" ng-controller="appController">
78+<head>
79+<meta charset="utf-8" />
80+<title>Bileto Landing Tickets</title>
81+<link rel="stylesheet" type="text/css" href="/static/style.css">
82+<link rel="icon" type="image/gif" href="/static/goldenticket.png">
83+<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
84+<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-sanitize.min.js"></script>
85+<script src="/static/app.js" charset="utf-8"></script>
86+</head>
87+<body>
88+
89+<datalist ng-repeat="(list, items) in meta.datalists" id="{{list}}">
90+<option ng-repeat="item in items" value="{{item}}" />
91+</datalist>
92+
93+<div id="nav">
94+<div id="login">
95+<a href="/logout" ng-if="data.nickname">Log out {{data.nickname}}</a>
96+<form name="sso_login" action="/login" method=post ng-if="!data.nickname">
97+<input type=submit value="Log in with Ubuntu SSO">
98+<input type=hidden name=next value="/#{{here}}">
99+</form>
100+</div>
101+<a href="#/create" ng-if="data.nickname">Create New Request</a>
102+<a href="#" ng-click="editing = null">All Requests</a>
103+<a href="#/silo/landing" ng-click="editing = null">{{data.assigned}} Active</a>
104+<a href="#/user/{{data.ircnick}}" ng-click="editing = null" ng-if="data.ircnick">Mine</a>
105+<a href="#/failures" ng-click="editing = null">Failures</a>
106+<a href="#/tickets?active_only&amp;lander_signoff=Approved" ng-click="editing = null">Lander Approved</a>
107+<a href="#/tickets?active_only&amp;qa_signoff=Ready" ng-click="editing = null">Ready for QA</a>
108+<a href="#/publishable" ng-click="editing = null">Publishable</a>
109+<a href="#/tickets?status=queue" ng-click="editing = null">Queued</a>
110+<a href="#/tickets?status=pocket" ng-click="editing = null">Migrating</a>
111+<a href="#/tickets?status=Landed" ng-click="editing = null">Landed</a>
112+<a href="#/tickets?status=Abandoned" ng-click="editing = null">Abandoned</a>
113+<form name="live_search">
114+<input type="search" class="search" placeholder="Search..." ng-model="searchstring" />
115+<input type="submit" ng-click="search(searchstring)" value="Search All Requests" />
116+<input type="submit" ng-click="search(searchstring, true)" value="Search Active Requests" />
117+<input type="submit" ng-click="siloname(searchstring)" value="Show PPA #" />
118+<input type="submit" ng-click="request(searchstring)" value="Show Ticket #" />
119+</form>
120+</div>
121+
122+<div id="body">
123+
124+<div id="error" ng-if="error">
125+<h2>{{error.header}}</h2>
126+<img src="/static/error.gif">
127+<p>{{error.message}}</p>
128+</div>
129+
130+<div ng-if="data.prev" class="center">
131+<a ng-click="prev()">... newer ...</a>
132+</div>
133+
134+<div id="requests">
135+<div class="req" ng-repeat="req in data.requests" ng-class="{card: !expand}">
136+
137+<div class="summary" ng-if="!expand" ng-click="request(req.request_id)">
138+<p><span class="{{req.status_color}}" title="{{interpret_color(req.status_color)}}">&nbsp;</span>
139+<span class="signoff_{{req.lander_signoff}}" title="Lander {{req.lander_signoff}}">&nbsp;</span>
140+<span class="signoff_{{req.britney_signoff}}" title="Britney {{req.britney_signoff}}">&nbsp;</span>
141+<span class="signoff_{{req.ust_signoff}}" title="UST {{req.britney_signoff}}">&nbsp;</span>
142+<span class="signoff_{{req.qa_signoff}}" title="QA {{req.qa_signoff}}">&nbsp;</span></p>
143+<p><a href="#/ticket/{{req.request_id}}">{{req.request_id}}.&nbsp;{{req.date | date:'yyyy-MM-dd'}}&nbsp;{{req.siloname}}</a></p>
144+<p>{{req.sources}}</p>
145+<p ng-bind-html="linkify(req.description)"></p>
146+<p>{{req.landers}}</p>
147+</div>
148+
149+<div class="expanded" ng-if="expand">
150+<div class="display" ng-if="!editing">
151+<form name="live_edit">
152+<table class="details">
153+
154+<tr>
155+<th class="signoff">
156+<a target="_blank" href="{{req.job_log}}" ng-if="req.job_log" class="status_details" title="Click for Details">&#187; {{title(meta.docs.status)}}</a>
157+<span ng-if="!req.job_log">{{title(meta.docs.status)}}</span>
158+</th>
159+<td ng-bind-html="display_status(req.status)" class="{{req.status_color}}"></td>
160+</tr>
161+
162+<tr>
163+<th class="signoff">{{title(meta.docs.lander_signoff)}}</th>
164+<td class="signoff signoff_{{req.lander_signoff}}">
165+<select class="signoff signoff_{{req.lander_signoff}}" name="lander_signoff" title="{{meta.docs.lander_signoff}}" ng-change="submit(live_edit, req)" ng-model="req.lander_signoff" ng-options="value for value in meta.datalists.lander_signoff" ng-disabled="!data.teams.length"></select>
166+</td>
167+</tr>
168+
169+<tr>
170+<th class="signoff">{{title(meta.docs.britney_signoff)}}</th>
171+<td class="signoff_{{req.britney_signoff}}">{{req.britney_signoff}}</td>
172+</tr>
173+
174+<tr>
175+<th class="signoff">{{title(meta.docs.ust_signoff)}}</th>
176+<td>
177+<a href="{{req.ust_build_url}}">
178+<div class="signoff_{{req.ust_signoff}}">
179+{{req.ust_signoff}}
180+</div>
181+</a>
182+</td>
183+</tr>
184+
185+<tr>
186+<th class="signoff">{{title(meta.docs.qa_signoff)}}</th>
187+<td class="signoff signoff_{{req.qa_signoff}}">
188+<select class="signoff signoff_{{req.qa_signoff}}" name="qa_signoff" title="{{meta.docs.qa_signoff}}" ng-change="submit(live_edit, req)" ng-model="req.qa_signoff" ng-options="value for value in meta.datalists.qa_signoff" ng-disabled="!data.admin.length && !req.download_links"></select>
189+</td>
190+</tr>
191+
192+<tr>
193+<th>{{title(meta.docs.artifacts)}}</th>
194+<td ng-bind-html="linkify(req.artifacts)"></td>
195+</tr>
196+
197+<tr ng-if="req.autopkgtest">
198+<th>{{title(meta.docs.autopkgtest)}}</th>
199+<td ng-bind-html="linkify(req.autopkgtest)"></td>
200+</tr>
201+
202+<tr ng-repeat="field in long_fields">
203+<th>{{title(meta.docs[field])}}</th>
204+<td ng-bind-html="linkify(req[field])"></td>
205+</tr>
206+
207+<tr>
208+<th>{{title(meta.docs.siloname)}}</th>
209+<td ng-bind-html="display_siloname(meta.ppa_team, req.siloname)"></td>
210+</tr>
211+
212+<!-- FIXME: sync_request is bit-rotty and doesn't work. disabled for now.
213+<tr>
214+<th>{{title(meta.docs.sync_request)}}</th>
215+<td>{{req.sync_request}}</td>
216+</tr>
217+-->
218+
219+<tr>
220+<th>{{title(meta.docs.sources)}}</th>
221+<td class="sources" ng-bind-html="display_sources(req.sources)"></td>
222+</tr>
223+
224+<tr ng-repeat="field in dropdowns">
225+<th>{{title(meta.docs[field])}}</th>
226+<td>{{req[field]}}</td>
227+</tr>
228+
229+<tr ng-if="req.series.indexOf('+') === -1">
230+<th>{{title(meta.docs.dest)}}</th>
231+<td ng-bind-html="display_dest(req.dest)"></td>
232+</tr>
233+
234+<tr>
235+<th>{{title(meta.docs.landers)}}</th>
236+<td ng-bind-html="display_landers(req.landers)"></td>
237+</tr>
238+
239+<tr>
240+<th><a href="#/ticket/{{req.request_id}}">{{title(meta.docs.request_id)}}</a></th>
241+<td><a href="#/ticket/{{req.request_id}}">
242+<span ng-bind-html="easter_egg(req.request_id)"></span>
243+{{req.request_id}}.&nbsp;{{req.date | date:'yyyy-MM-dd'}}
244+</a></td>
245+</tr>
246+
247+</table>
248+</form>
249+</div>
250+</div>
251+
252+<div class="edit" ng-if="editing">
253+<form name="edit_request">
254+<p>
255+<textarea rows="6" name="description"
256+ placeholder="{{title(meta.docs.description)}}" title="{{meta.docs.description}}"
257+ ng-model="req.description" required>{{req.description}}</textarea>
258+</p>
259+<p>
260+<textarea rows="6" name="test_plan"
261+ placeholder="{{title(meta.docs.test_plan)}}" title="{{meta.docs.test_plan}}"
262+ ng-model="req.test_plan" required>{{req.test_plan}}</textarea>
263+</p>
264+<p>
265+<textarea rows="6" wrap="off" name="merge_proposals"
266+ placeholder="{{title(meta.docs.merge_proposals)}}" title="{{meta.docs.merge_proposals}}"
267+ ng-model="req.merge_proposals" ng-pattern="/^((https?:\/\/code.launchpad.net\/~\S+\/\+merge\/\d+|#.*)[\s\n]*)*$/">{{req.merge_proposals}}</textarea>
268+</p>
269+<p>
270+<textarea rows="6" wrap="off" name="download_links"
271+ placeholder="{{title(meta.docs.download_links)}}" title="{{meta.docs.download_links}}"
272+ ng-model="req.download_links" ng-pattern="/^(https?:\/\/\S+[\s\n]*)*$/">{{req.download_links}}</textarea>
273+</p>
274+<p>
275+<input type="text" name="landers" list="landers" size="10"
276+ placeholder="{{title(meta.docs.landers)}}" title="{{meta.docs.landers}}"
277+ value="{{req.landers}}" ng-model="req.landers" />
278+</p>
279+<!-- FIXME: sync_request is bit-rotty and doesn't work. disabled for now.
280+<p>
281+<input type="text" name="sync_request" list="sync_request" size="10"
282+ placeholder="{{title(meta.docs.sync_request)}}" title="{{meta.docs.sync_request}}"
283+ value="{{req.sync_request}}" ng-model="req.sync_request"
284+ ng-pattern="/^(\d+|\S+,[a-z0-9.]+)$/"/>
285+</p>
286+-->
287+<p>
288+<input type="text" name="sources" list="sources" size="10"
289+ placeholder="{{title(meta.docs.sources)}}" title="{{meta.docs.sources}}"
290+ value="{{req.sources}}" ng-model="req.sources" />
291+</p>
292+<p ng-repeat="field in dropdowns">
293+<select name="{{field}}" title="{{meta.docs[field]}}" ng-model="req[field]" ng-options="value for value in meta.datalists[field]"></select>
294+</p>
295+<p ng-if="req.series.indexOf('+') === -1">
296+<select name="dest" title="{{meta.docs.dest}}" ng-model="req.dest">
297+<option value="">Ubuntu Archive</option>
298+<option value="ci-train-ppa-service/ubuntu/stable-phone-overlay">Stable Overlay PPA</option>
299+</select>
300+</p>
301+<p>
302+<input type="hidden" name="distribution" value="ubuntu" ng-model="req.distribution" />
303+<button ng-click="submit(edit_request, req)" ng-disabled="busy || !edit_request.$valid">
304+<span ng-if="req.request_id">Save #{{req.request_id}}</span>
305+<span ng-if="!req.request_id">Create New Request</span>
306+<span ng-if="!edit_request.$valid">(mouse over pink fields for help)</span>
307+</button>
308+<div class="red" ng-if="display_sru_warning(req)">Warning: Are you sure you want to
309+publish an SRU?</div>
310+</p>
311+</form>
312+</div>
313+
314+<div class="actionbox" ng-if="req.request_id && expand">
315+<table class="actions"><tr>
316+<td ng-if="!editing"><a href="/log/{{req.request_id}}/build" target="_blank">Build</a></td>
317+<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/diff" target="_blank">Diff</a></td>
318+<td ng-if="!editing"><a href="/log/{{req.request_id}}/revert" target="_blank">Revert</a></td>
319+<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/publish" target="_blank">Publish</a></td>
320+<td ng-if="!editing"><a href="/log/{{req.request_id}}/finalize" target="_blank">Finalize</a></td>
321+<td ng-if="!editing"><a href="/log/{{req.request_id}}/abandon" target="_blank">Abandon</a></td>
322+<td ng-if="editing"><a ng-click="cancel()">Cancel Edit</a></td>
323+<td ng-if="data.nickname && !editing"><a ng-click="edit(req)">Edit</a></td>
324+</tr></table>
325+</div>
326+
327+<div class="comments" ng-if="expand">
328+<a ng-click="toggle_log()" ng-if="expand && data.requests[0].comments.length"><span ng-if="!full_log">&#9656;&nbsp;Show</span><span ng-if="full_log">&#9662;&nbsp;Hide</span> Audit Logs</a>
329+<div class="comment" ng-repeat="comment in req.comments" ng-if="full_log || !comment.log_url">
330+<strong>
331+<a target="_blank" href="{{comment.log_url}}" ng-if="comment.log_url">{{comment.date | date:'yyyy-MM-dd HH:mm:ss Z'}} ({{comment.author}})</a>
332+<span ng-if="!comment.log_url">{{comment.date | date:'yyyy-MM-dd HH:mm:ss Z'}} ({{comment.author}})</span>
333+</strong>
334+<span ng-bind-html="display_status(comment.text)"></span>
335+</div>
336+</div>
337+
338+<div class="new_comment" ng-if="req.request_id && data.nickname && expand && !editing">
339+<form name="new_comment_form">
340+<textarea class="commentbox" rows="2" name="comment_text" placeholder="Add a comment..." ng-model="new_comment.text"></textarea>
341+<button ng-click="post_comment(req.request_id, new_comment.text)" ng-disabled="busy">Save Comment</button>
342+</form>
343+</div>
344+
345+</div>
346+
347+<div ng-if="data.next" class="center">
348+<a ng-click="next()">... older ...</a>
349+</div>
350+
351+<div id="admin">
352+<a href="/static/logs.html">All Logs</a>
353+<a href="/static/britney/index.html">Britney Files</a>
354+<a href="/static/report.txt">Diagnostic Report</a>
355+</div>
356+
357+<div id="busy" ng-if="busy"></div>
358+
359+</body>
360+</html>
361diff --git a/bileto/static/style.css b/bileto/static/style.css
362new file mode 100644
363index 0000000..985e85f
364--- /dev/null
365+++ b/bileto/static/style.css
366@@ -0,0 +1,384 @@
367+html, body {
368+ background-color: white;
369+ color: black;
370+ font-family: Ubuntu, Arial, sans-serif;
371+ margin: 0;
372+ padding: 0;
373+}
374+
375+body {
376+ padding-bottom: 1em;
377+}
378+
379+input, select, button, textarea {
380+ width: 100%;
381+ font-size: medium;
382+}
383+
384+a {
385+ color: #dd4814;
386+ background-color: white;
387+}
388+
389+a, input[type=submit], select, button, .summary {
390+ cursor: pointer;
391+ text-decoration: none;
392+ outline: 0;
393+ margin: 0;
394+ border: 0;
395+ border-radius: 0;
396+ font-size: medium;
397+}
398+
399+#nav {
400+ text-align: center;
401+ width: auto;
402+ float: right;
403+}
404+
405+#body {
406+ width: auto;
407+ overflow: hidden;
408+}
409+
410+form {
411+ display: block;
412+ margin: 0;
413+ padding: 0;
414+ width: 100%;
415+}
416+
417+h1, #nav a, #nav input, .new_comment button, .actions a {
418+ background-color: #dd4814;
419+ border: 0;
420+ color: white;
421+ display: block;
422+ margin: 0;
423+ padding: 1em;
424+ text-decoration: none;
425+ text-align: center;
426+}
427+
428+.status_details {
429+ background-color: #dd4814;
430+ color: white;
431+ padding: 0.3em;
432+}
433+
434+h1 {
435+ font-size: large;
436+ text-align: center;
437+ width: auto;
438+}
439+
440+th {
441+ text-align: right;
442+ vertical-align: top;
443+ white-space: nowrap;
444+ padding-right: 0.5em;
445+}
446+
447+td {
448+ vertical-align: top;
449+ text-align: left;
450+}
451+
452+#nav input, .new_comment input {
453+ width: 100%;
454+}
455+
456+#nav input[type=search] {
457+ border-top: 1px solid white;
458+}
459+
460+#nav a:hover, #nav input:hover, h1:hover,
461+.edit input[type=submit]:hover, .edit button:hover,
462+.new_comment input:hover, .new_comment button:hover,
463+.actions a:hover {
464+ text-decoration: none;
465+ background-color: white;
466+ color: #dd4814;
467+}
468+
469+#requests, .display, .edit {
470+ display: block;
471+}
472+
473+#requests {
474+ padding-top: 2em;
475+}
476+
477+#requests > * {
478+ vertical-align: top;
479+}
480+
481+.req {
482+ text-align: center;
483+}
484+
485+.edit {
486+ margin-top: -2em;
487+}
488+
489+.summary {
490+ padding: 0.5em 1em 1em 1em;
491+ max-height: 20em;
492+}
493+
494+.summary p {
495+ margin: 0.5em;
496+}
497+
498+.display {
499+ padding: 1em;
500+}
501+
502+.card {
503+ width: 450px;
504+ display: inline-block;
505+ text-align: left;
506+ border-top: 1px solid #ccc;
507+ margin: 1em;
508+ overflow: auto;
509+}
510+
511+.card, .display {
512+ margin-top: 0;
513+ padding-top: 0;
514+}
515+
516+.details {
517+ width: auto;
518+ margin: 0 auto;
519+}
520+
521+.details td, .details th {
522+ padding: 0.2em;
523+}
524+
525+td.sources a {
526+ display: block;
527+}
528+
529+.center {
530+ text-align: center;
531+}
532+
533+input, textarea, button, select {
534+ width: 100%;
535+ display: block;
536+ color: black;
537+ background-color: #eee;
538+ padding: 0.5em;
539+ border: 0;
540+ box-sizing: border-box;
541+ -webkid-box-sizing: border-box;
542+ -moz-box-sizing: border-box;
543+}
544+
545+.edit input[type=submit], .edit button {
546+ background-color: #dd4814;
547+ color: white;
548+ padding: 1em;
549+ border: 0;
550+}
551+
552+p {
553+ margin: 1em 1em 0 1em;
554+}
555+
556+.comments {
557+ display: inline-block;
558+ width: auto;
559+ padding: 0 2em;
560+ margin: 0 auto;
561+ text-align: left;
562+}
563+
564+.new_comment {
565+ padding: 0;
566+ margin: 1em;
567+}
568+
569+table, tr, td {
570+ border-collapse: collapse;
571+ margin: 0;
572+ padding: 0;
573+}
574+
575+table {
576+ width: 100%;
577+}
578+
579+.commentbox {
580+ width: 100%;
581+}
582+
583+.actionbox {
584+ margin: 1em;
585+}
586+
587+table.actions {
588+ table-layout: fixed;
589+}
590+
591+table.actions tr, table.actions td {
592+ height: 100%;
593+ vertical-align: middle;
594+}
595+
596+table.actions a {
597+ display: block;
598+ width: 100%;
599+ height: 100%;
600+ padding: 1em 0;
601+ vertical-align: middle;
602+ box-sizing: border-box;
603+ -webkid-box-sizing: border-box;
604+ -moz-box-sizing: border-box;
605+}
606+
607+select.signoff {
608+ width: auto;
609+ display: inline-block;
610+}
611+
612+/* These colors must be kept in sync with $scope.interpret_colors in app.js */
613+.black::before, .signoff_::before, .signoff_Required::before,
614+.red::before, .signoff_Failed::before,
615+.orange::before, .signoff_Queued::before, .signoff_Running::before, .signoff_Ready::before,
616+.purple::before,
617+.green::before, .signoff_N\/A::before, .signoff_Approved::before,
618+.olive::before,
619+.blue::before,
620+.grey::before {
621+ font-size: xx-large;
622+ line-height: 0;
623+ display: inline-block;
624+ text-align: center;
625+ width: 1em;
626+}
627+
628+.black, .signoff_, .signoff_Required {
629+ color: black;
630+}
631+
632+.black::before, .signoff_::before, .signoff_Required::before {
633+ color: black;
634+ content: "— ";
635+}
636+
637+.red, .signoff_Failed {
638+ color: red;
639+ font-weight: bold;
640+}
641+
642+.red::before, .signoff_Failed::before {
643+ color: red;
644+ content: "✘ ";
645+}
646+
647+.orange, .signoff_Queued, .signoff_Running, .signoff_Ready {
648+ color: orange;
649+ font-weight: bold;
650+}
651+
652+.orange::before, .signoff_Queued::before, .signoff_Running::before, .signoff_Ready::before {
653+ color: orange;
654+ content: "⚡ ";
655+}
656+
657+.purple {
658+ color: purple;
659+}
660+
661+.purple::before {
662+ color: purple;
663+ content: "♨ ";
664+}
665+
666+.green, .signoff_N\/A, .signoff_Approved {
667+ color: green;
668+}
669+
670+.green::before, .signoff_N\/A::before, .signoff_Approved::before {
671+ color: green;
672+ content: "✔ ";
673+}
674+
675+.olive {
676+ color: olive;
677+ font-weight: bold;
678+}
679+
680+.olive::before {
681+ color: olive;
682+ content: "✱ ";
683+}
684+
685+.blue {
686+ color: blue;
687+}
688+
689+.blue::before {
690+ color: blue;
691+ content: "✔ ";
692+}
693+
694+.grey {
695+ color: grey;
696+}
697+
698+.grey::before {
699+ color: #888;
700+ content: "◌ ";
701+}
702+
703+#busy {
704+ background-color: black;
705+ color: black;
706+ height: 100%;
707+ left: 0;
708+ margin: 0;
709+ opacity: 0.3;
710+ padding: 0;
711+ position: absolute;
712+ top: 0;
713+ width: 100%;
714+}
715+
716+.fifty {
717+ width: 50%;
718+ display: inline-block;
719+}
720+
721+#error {
722+ width: 100%;
723+ text-align: center;
724+}
725+
726+textarea.ng-invalid, input.ng-invalid {
727+ background-color: #ffe5e5;
728+}
729+
730+button[disabled], button[disabled]:hover, button[disabled]:focus, button[disabled]:active,
731+select[disabled], select[disabled]:hover, select[disabled]:focus, select[disabled]:active {
732+ color: #666;
733+ background-color: #ccc;
734+ cursor: default;
735+}
736+
737+.signoff {
738+ vertical-align: middle;
739+}
740+
741+#admin {
742+ margin: 3em 0 0 0;
743+ text-align: center;
744+ font-size: x-small;
745+}
746+
747+#admin a {
748+ margin: 1em;
749+ font-size: x-small;
750+}
751diff --git a/britney/iterate.py b/britney/iterate.py
752index edd9b35..08d3bae 100755
753--- a/britney/iterate.py
754+++ b/britney/iterate.py
755@@ -20,6 +20,7 @@ from fcntl import LOCK_EX, LOCK_NB, flock
756 from os import unlink, symlink
757 from os.path import abspath, dirname, exists
758
759+from qakit.ust.worker import UstResult, Worker
760 from requests import get, post
761
762 HOME = dirname(abspath(__file__))
763@@ -179,6 +180,34 @@ def parallelize_fetching(all_series, runs):
764 update_request(ticket)
765
766
767+def compute_ust_signoff(requests):
768+ """Notify the ticket status in ubuntu system tests.
769+ Once a silo is assigned to the ticket, ust is triggered only if
770+ the lander has approved the ticket. After triggering the tests,
771+ the worker is asked for results
772+ :return UstResult which has two attributes: status and build url
773+ which are both use in the UI"""
774+ ust_signoff = None
775+ for request in requests:
776+ if not request['siloname']:
777+ ust_worker = Worker()
778+ if request['ust_signoff'] == "":
779+ if request['lander_signoff'] == 'Approved':
780+ try:
781+ ust_worker.run_ust(request['siloname'])
782+ ust_signoff = UstResult("Queued", "")
783+ except Exception:
784+ pass
785+
786+ if ust_signoff:
787+ req = dict(
788+ author='ust-bot',
789+ request_id=request['request_id'],
790+ ust_signoff=ust_signoff.status)
791+ post(WRITE_API + Config.api_token, dumps(req, sort_keys=True),
792+ headers={'content-type': 'application/json'})
793+
794+
795 def print_proc(proc):
796 """Print the output from a subprocess."""
797 sys.stdout.buffer.write(BNEWLINE.join(
798@@ -194,6 +223,7 @@ def main():
799 sys.stderr = sys.stdout = open(LOG_NAME, 'x')
800 reqs = get_requests()
801 parallelize_fetching(*build_siloname_dict(reqs))
802+ compute_ust_signoff(reqs)
803 except BlockingIOError:
804 return 0
805 except (IOError, ConnectionRefusedError):
806@@ -205,6 +235,5 @@ def main():
807 cleanup(lock)
808 return 0
809
810-
811 if __name__ == '__main__': # pragma: no cover
812 sys.exit(main())
813diff --git a/db_migrations.sh b/db_migrations.sh
814index e346c14..f6a8b3f 100755
815--- a/db_migrations.sh
816+++ b/db_migrations.sh
817@@ -32,6 +32,7 @@ exit 0
818 # These ones have succeeded in production and only kept for reference
819 $PG "ALTER TABLE request ADD COLUMN lander_signoff text DEFAULT '';" || true
820 $PG "ALTER TABLE request ADD COLUMN britney_signoff text DEFAULT '';" || true
821+$PG "ALTER TABLE request ADD COLUMN ust_signoff text DEFAULT '';" || true
822 $PG "ALTER TABLE request ADD COLUMN status_color text DEFAULT '';" || true
823 $PG "ALTER TABLE request ADD COLUMN artifacts text DEFAULT '';" || true
824
825diff --git a/scripts/import.sh b/scripts/import.sh
826new file mode 100755
827index 0000000..60b8173
828--- /dev/null
829+++ b/scripts/import.sh
830@@ -0,0 +1,26 @@
831+#!/bin/sh
832+
833+# This is a history-destroying git-to-bzr converter,
834+# for when you just want the code.
835+
836+repos="https://github.com/emacs-mirror/emacs.git"
837+
838+sleep "$(shuf --input-range 0-60 --head-count 1)"
839+
840+set -eux
841+
842+
843+for repo in $repos; do
844+ base="$(basename "$repo" .git)"
845+ tmpdir="/tmp/$base"
846+
847+ [ -d "$tmpdir" ] || git clone --depth 1 "$repo" "$tmpdir"
848+ cd "$tmpdir"
849+ git pull
850+
851+ [ -d ".bzr" ] || bzr init .
852+ echo '.git*' > .bzrignore
853+ bzr add .
854+ bzr commit --message "Auto commit $(date)"
855+ bzr push "lp:~/$base/imported" --overwrite
856+done

Subscribers

People subscribed via source and target branches