Merge "Document zuul generated log paths"
diff --git a/etc/status/public_html/app.js b/etc/status/public_html/app.js
index 2649090..aee693b 100644
--- a/etc/status/public_html/app.js
+++ b/etc/status/public_html/app.js
@@ -16,6 +16,7 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
+'use strict';
(function ($) {
var $container, $msg, $indicator, $queueInfo, $queueEventsNum,
@@ -27,19 +28,21 @@
source = demo ?
'./status-' + (demo[1] || 'basic') + '.json-sample' :
'status.json';
- source = source_url ? source_url[1] : source;
+ source = source_url ? source_url[1] : source;
function set_cookie(name, value) {
- document.cookie = name + "=" + value + "; path=/";
+ document.cookie = name + '=' + value + '; path=/';
}
function read_cookie(name, default_value) {
- var nameEQ = name + "=";
+ var nameEQ = name + '=';
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
- while (c.charAt(0) == ' ') c = c.substring(1, c.length);
- if (c.indexOf(nameEQ) == 0) {
+ while (c.charAt(0) === ' ') {
+ c = c.substring(1, c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length);
}
}
@@ -82,11 +85,11 @@
}
if ('zuul_version' in data) {
- $('#zuul-version-span').text(data['zuul_version']);
+ $('#zuul-version-span').text(data.zuul_version);
}
if ('last_reconfigured' in data) {
var last_reconfigured =
- new Date(data['last_reconfigured']);
+ new Date(data.last_reconfigured);
$('#last-reconfigured-span').text(
last_reconfigured.toString());
}
@@ -107,7 +110,7 @@
})
.fail(function (err, jqXHR, errMsg) {
$msg.text(source + ': ' + errMsg).show();
- $msgWrap.removeClass('zuul-msg-wrap-off');
+ $msg.removeClass('zuul-msg-wrap-off');
})
.complete(function () {
xhr = undefined;
@@ -119,6 +122,7 @@
format: {
job: function(job) {
+ var $job_line;
if (job.url !== null) {
$job_line = $('<a href="' + job.url + '" />');
}
@@ -142,7 +146,7 @@
result = job.url ? 'in progress' : 'queued';
}
- if (result == 'in progress') {
+ if (result === 'in progress') {
return zuul.format.job_progress_bar(job.elapsed_time,
job.remaining_time);
}
@@ -152,7 +156,7 @@
},
status_label: function(result) {
- $status = $('<span />');
+ var $status = $('<span />');
$status.addClass('zuul-job-result label');
switch (result) {
@@ -199,24 +203,26 @@
var hours = 60 * 60 * 1000;
var now = Date.now();
var delta = now - ms;
- var status = "text-success";
+ var status = 'text-success';
var text = zuul.format.time(delta, true);
if (delta > (4 * hours)) {
- status = "text-danger";
+ status = 'text-danger';
} else if (delta > (2 * hours)) {
- status = "text-warning";
+ status = 'text-warning';
}
return '<span class="' + status + '">' + text + '</span>';
},
time: function(ms, words) {
- if (typeof(words) === 'undefined') words = false;
+ if (typeof(words) === 'undefined') {
+ words = false;
+ }
var seconds = (+ms)/1000;
var minutes = Math.floor(seconds/60);
var hours = Math.floor(minutes/60);
seconds = Math.floor(seconds % 60);
minutes = Math.floor(minutes % 60);
- r = '';
+ var r = '';
if (words) {
if (hours) {
r += hours;
@@ -224,18 +230,24 @@
}
r += minutes + ' min';
} else {
- if (hours < 10) r += '0';
+ if (hours < 10) {
+ r += '0';
+ }
r += hours + ':';
- if (minutes < 10) r += '0';
+ if (minutes < 10) {
+ r += '0';
+ }
r += minutes + ':';
- if (seconds < 10) r += '0';
+ if (seconds < 10) {
+ r += '0';
+ }
r += seconds;
}
return r;
},
change_total_progress_bar: function(change) {
- job_percent = Math.floor(100 / change.jobs.length);
+ var job_percent = Math.floor(100 / change.jobs.length);
var $bar_outter = $('<div />')
.addClass('progress zuul-change-total-result');
@@ -245,7 +257,7 @@
result = job.url ? 'in progress' : 'queued';
}
- if (result != 'queued') {
+ if (result !== 'queued') {
var $bar_inner = $('<div />')
.addClass('progress-bar');
@@ -273,53 +285,55 @@
},
change_header: function(change) {
- change_id = change.id || 'NA';
+ var change_id = change.id || 'NA';
if (change_id.length === 40) {
change_id = change_id.substr(0, 7);
}
- $change_link = $('<small />');
+ var $change_link = $('<small />');
if (change.url !== null) {
$change_link.append(
- $("<a />").attr("href", change.url).text(change.id)
+ $('<a />').attr('href', change.url).text(change.id)
);
}
else {
$change_link.text(change_id);
}
- $change_progress_row_left = $('<div />')
+ var $change_progress_row_left = $('<div />')
.addClass('col-xs-3')
.append($change_link);
- $change_progress_row_right = $('<div />')
+ var $change_progress_row_right = $('<div />')
.addClass('col-xs-9')
- .append(zuul.format.change_total_progress_bar(change))
+ .append(zuul.format.change_total_progress_bar(change));
- $change_progress_row = $('<div />')
+ var $change_progress_row = $('<div />')
.addClass('row')
.append($change_progress_row_left)
- .append($change_progress_row_right)
+ .append($change_progress_row_right);
- $project_span = $('<span />')
+ var $project_span = $('<span />')
.addClass('change_project')
.text(change.project);
- $left = $('<div />')
+ var $left = $('<div />')
.addClass('col-xs-8')
.append($project_span, $('<br />'), $change_progress_row);
- remaining_time = zuul.format.time(change.remaining_time, true);
- enqueue_time = zuul.format.enqueue_time(change.enqueue_time);
- $remaining_time = $('<small />').addClass('time')
+ var remaining_time = zuul.format.time(
+ change.remaining_time, true);
+ var enqueue_time = zuul.format.enqueue_time(
+ change.enqueue_time);
+ var $remaining_time = $('<small />').addClass('time')
.attr('title', 'Remaining Time').html(remaining_time);
- $enqueue_time = $('<small />').addClass('time')
+ var $enqueue_time = $('<small />').addClass('time')
.attr('title', 'Elapsed Time').html(enqueue_time);
- $right = $('<div />')
+ var $right = $('<div />')
.addClass('col-xs-4 text-right')
.append($remaining_time, $('<br />'), $enqueue_time);
- $header = $('<div />')
+ var $header = $('<div />')
.addClass('row')
.append($left, $right);
return $header;
@@ -347,9 +361,9 @@
var panel_id = change.id ? change.id.replace(',', '_')
: change.project.replace('/', '_') +
- '-' + change.enqueue_time
+ '-' + change.enqueue_time;
var $panel = $('<div />')
- .attr("id", panel_id)
+ .attr('id', panel_id)
.addClass('panel panel-default zuul-change')
.append($header)
.append(zuul.format.change_list(change.jobs));
@@ -393,7 +407,7 @@
}
$.each(changes, function (changeNum, change) {
var $panel = zuul.format.change_panel(change);
- $html.append($panel)
+ $html.append($panel);
zuul.display_patchset($panel);
});
});
@@ -401,7 +415,7 @@
return $html;
},
- filter_form_group: function(default_text) {
+ filter_form_group: function() {
// Update the filter form with a clear button if required
var $label = $('<label />')
@@ -417,7 +431,7 @@
.attr('title',
'project(s), pipeline(s) or review(s) comma ' +
'separated')
- .attr('value', default_text);
+ .attr('value', current_filter);
$input.change(zuul.handle_filter_change);
@@ -432,7 +446,7 @@
$('#filter_string').val('').change();
});
- if (default_text == '') {
+ if (current_filter === '') {
$clear_icon.hide();
}
@@ -443,16 +457,16 @@
},
expand_form_group: function() {
- expand_by_default = (
+ var expand_by_default = (
read_cookie('zuul_expand_by_default', false) === 'true');
- $checkbox = $('<input />')
+ var $checkbox = $('<input />')
.attr('type', 'checkbox')
.attr('id', 'expand_by_default')
.prop('checked', expand_by_default)
.change(zuul.handle_expand_by_default);
- $label = $('<label />')
+ var $label = $('<label />')
.css('padding-left', '1em')
.html('Expand by default: ')
.append($checkbox);
@@ -466,13 +480,13 @@
control_form: function() {
// Build the filter form filling anything from cookies
- $control_form = $('<form />')
+ var $control_form = $('<form />')
.attr('role', 'form')
.addClass('form-inline')
.submit(zuul.handle_filter_change);
$control_form
- .append(zuul.format.filter_form_group(current_filter))
+ .append(zuul.format.filter_form_group())
.append(zuul.format.expand_form_group());
return $control_form;
@@ -500,7 +514,7 @@
$body.toggle(200);
var collapsed_index = zuul.collapsed_exceptions.indexOf(
$panel.attr('id'));
- if (collapsed_index == -1 ) {
+ if (collapsed_index === -1 ) {
// Currently not an exception, add it to list
zuul.collapsed_exceptions.push($panel.attr('id'));
}
@@ -519,8 +533,8 @@
var expand_by_default = $('#expand_by_default').prop('checked');
var collapsed_index = zuul.collapsed_exceptions.indexOf(
$panel.attr('id'));
- if (expand_by_default && collapsed_index == -1 ||
- !expand_by_default && collapsed_index != -1) {
+ if (expand_by_default && collapsed_index === -1 ||
+ !expand_by_default && collapsed_index !== -1) {
// Expand by default, or is an exception
$body.show(animate);
}
@@ -534,20 +548,20 @@
var panel_pipeline = $panel.parents('.zuul-pipeline')
.children('h3').text().toLowerCase();
var panel_change = $panel.attr('id');
- if (current_filter != '') {
- show_panel = false;
- filter = current_filter.trim().split(/[\s,]+/);
+ if (current_filter !== '') {
+ var show_panel = false;
+ var filter = current_filter.trim().split(/[\s,]+/);
$.each(filter, function(index, f_val) {
- if (f_val != '') {
+ if (f_val !== '') {
f_val = f_val.toLowerCase();
- if (panel_project.indexOf(f_val) != '-1' ||
- panel_pipeline.indexOf(f_val) != '-1' ||
- panel_change.indexOf(f_val) != '-1') {
+ if (panel_project.indexOf(f_val) !== -1 ||
+ panel_pipeline.indexOf(f_val) !== -1 ||
+ panel_change.indexOf(f_val) !== -1) {
show_panel = true;
}
}
});
- if (show_panel == true) {
+ if (show_panel === true) {
$panel.show(animate);
}
else {
@@ -559,11 +573,11 @@
}
},
- handle_filter_change: function(e) {
+ handle_filter_change: function() {
// Update the filter and save it to a cookie
current_filter = $('#filter_string').val();
set_cookie('zuul_filter_string', current_filter);
- if (current_filter == '') {
+ if (current_filter === '') {
$('#filter_form_clear_box').hide();
}
else {
@@ -571,9 +585,9 @@
}
$('.zuul-change').each(function(index, obj) {
- $panel = $(obj);
+ var $panel = $(obj);
zuul.display_patchset($panel, 200);
- })
+ });
return false;
},
@@ -582,9 +596,9 @@
set_cookie('zuul_expand_by_default', e.target.checked);
zuul.collapsed_exceptions = [];
$('.zuul-change').each(function(index, obj) {
- $panel = $(obj);
+ var $panel = $(obj);
zuul.display_patchset($panel, 200);
- })
+ });
},
};
@@ -616,20 +630,21 @@
$(function ($) {
$msg = $('<div />').addClass('alert').hide();
- $indicator = $('<button class="btn pull-right zuul-spinner">updating '
- + '<span class="glyphicon glyphicon-refresh"></span>'
- + '</button>');
+ $indicator = $('<button class="btn pull-right zuul-spinner">' +
+ 'updating ' +
+ '<span class="glyphicon glyphicon-refresh"></span>' +
+ '</button>');
$queueInfo = $('<p>Queue lengths: <span>0</span> events, ' +
'<span>0</span> results.</p>');
$queueEventsNum = $queueInfo.find('span').eq(0);
$queueResultsNum = $queueEventsNum.next();
- $control_form = zuul.format.control_form();
+ var $control_form = zuul.format.control_form();
$pipelines = $('<div class="row"></div>');
- $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span">' +
+ var $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span">' +
'</span></p>');
- $lastReconf = $('<p>Last reconfigured: ' +
+ var $lastReconf = $('<p>Last reconfigured: ' +
'<span id="last-reconfigured-span"></span></p>');
$container = $('#zuul-container').append($msg, $indicator,
diff --git a/requirements.txt b/requirements.txt
index bb48290..1c5587b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,3 +16,5 @@
apscheduler>=2.1.1,<3.0
python-swiftclient>=1.6
python-keystoneclient>=0.4.2
+PrettyTable>=0.6,<0.8
+babel
diff --git a/tests/fixtures/layout-idle.yaml b/tests/fixtures/layout-idle.yaml
new file mode 100644
index 0000000..e4574fa
--- /dev/null
+++ b/tests/fixtures/layout-idle.yaml
@@ -0,0 +1,12 @@
+pipelines:
+ - name: periodic
+ manager: IndependentPipelineManager
+ trigger:
+ timer:
+ - time: '* * * * * */1'
+
+projects:
+ - name: org/project
+ periodic:
+ - project-test1
+ - project-test2
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index 7e1416f..7cfea1c 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -3201,6 +3201,33 @@
self.assertIn('project-bitrot-stable-old', status_jobs)
self.assertIn('project-bitrot-stable-older', status_jobs)
+ def test_idle(self):
+ "Test that frequent periodic jobs work"
+ self.worker.hold_jobs_in_build = True
+ self.config.set('zuul', 'layout_config',
+ 'tests/fixtures/layout-idle.yaml')
+ self.sched.reconfigure(self.config)
+ self.registerJobs()
+
+ # The pipeline triggers every second, so we should have seen
+ # several by now.
+ time.sleep(5)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.builds), 2)
+ self.worker.release('.*')
+ self.waitUntilSettled()
+ self.assertEqual(len(self.builds), 0)
+ self.assertEqual(len(self.history), 2)
+
+ time.sleep(5)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.builds), 2)
+ self.assertEqual(len(self.history), 2)
+ self.worker.release('.*')
+ self.waitUntilSettled()
+ self.assertEqual(len(self.builds), 0)
+ self.assertEqual(len(self.history), 4)
+
def test_check_smtp_pool(self):
self.config.set('zuul', 'layout_config',
'tests/fixtures/layout-smtp.yaml')
@@ -3870,3 +3897,62 @@
self.worker.hold_jobs_in_build = False
self.worker.release()
self.waitUntilSettled()
+
+ def test_client_get_running_jobs(self):
+ "Test that the RPC client can get a list of running jobs"
+ self.worker.hold_jobs_in_build = True
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ A.addApproval('CRVW', 2)
+ self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
+ self.waitUntilSettled()
+
+ client = zuul.rpcclient.RPCClient('127.0.0.1',
+ self.gearman_server.port)
+
+ # Wait for gearman server to send the initial workData back to zuul
+ start = time.time()
+ while True:
+ if time.time() - start > 10:
+ raise Exception("Timeout waiting for gearman server to report "
+ + "back to the client")
+ build = self.launcher.builds.values()[0]
+ if build.worker.name == "My Worker":
+ break
+ else:
+ time.sleep(0)
+
+ running_items = client.get_running_jobs()
+
+ self.assertEqual(1, len(running_items))
+ running_item = running_items[0]
+ self.assertEqual([], running_item['failing_reasons'])
+ self.assertEqual([], running_item['items_behind'])
+ self.assertEqual('https://hostname/1', running_item['url'])
+ self.assertEqual(None, running_item['item_ahead'])
+ self.assertEqual('org/project', running_item['project'])
+ self.assertEqual(None, running_item['remaining_time'])
+ self.assertEqual(True, running_item['active'])
+ self.assertEqual('1,1', running_item['id'])
+
+ self.assertEqual(3, len(running_item['jobs']))
+ for job in running_item['jobs']:
+ if job['name'] == 'project-merge':
+ self.assertEqual('project-merge', job['name'])
+ self.assertEqual('gate', job['pipeline'])
+ self.assertEqual(False, job['retry'])
+ self.assertEqual(13, len(job['parameters']))
+ self.assertEqual('https://server/job/project-merge/0/',
+ job['url'])
+ self.assertEqual(7, len(job['worker']))
+ self.assertEqual(False, job['canceled'])
+ self.assertEqual(True, job['voting'])
+ self.assertEqual(None, job['result'])
+ self.assertEqual('gate', job['pipeline'])
+ break
+
+ self.worker.hold_jobs_in_build = False
+ self.worker.release()
+ self.waitUntilSettled()
+
+ running_items = client.get_running_jobs()
+ self.assertEqual(0, len(running_items))
diff --git a/zuul/cmd/client.py b/zuul/cmd/client.py
index a334bff..147fade 100644
--- a/zuul/cmd/client.py
+++ b/zuul/cmd/client.py
@@ -15,11 +15,15 @@
# under the License.
import argparse
+import babel.dates
import ConfigParser
+import datetime
import logging
import logging.config
import os
+import prettytable
import sys
+import time
import zuul.rpcclient
@@ -66,6 +70,23 @@
required=True, nargs='+')
cmd_promote.set_defaults(func=self.promote)
+ cmd_show = subparsers.add_parser('show',
+ help='valid show subcommands')
+ show_subparsers = cmd_show.add_subparsers(title='show')
+ show_running_jobs = show_subparsers.add_parser(
+ 'running-jobs',
+ help='show the running jobs'
+ )
+ show_running_jobs.add_argument(
+ '--columns',
+ help="comma separated list of columns to display (or 'ALL')",
+ choices=self._show_running_jobs_columns().keys().append('ALL'),
+ default='name, worker.name, start_time, result'
+ )
+
+ # TODO: add filters such as queue, project, changeid etc
+ show_running_jobs.set_defaults(func=self.show_running_jobs)
+
self.args = parser.parse_args()
def _get_version(self):
@@ -119,6 +140,147 @@
change_ids=self.args.changes)
return r
+ def show_running_jobs(self):
+ client = zuul.rpcclient.RPCClient(self.server, self.port)
+ running_items = client.get_running_jobs()
+
+ if len(running_items) == 0:
+ print "No jobs currently running"
+ return True
+
+ all_fields = self._show_running_jobs_columns()
+ if self.args.columns.upper() == 'ALL':
+ fields = all_fields.keys()
+ else:
+ fields = [f.strip().lower() for f in self.args.columns.split(',')
+ if f.strip().lower() in all_fields.keys()]
+
+ table = prettytable.PrettyTable(
+ field_names=[all_fields[f]['title'] for f in fields])
+ for item in running_items:
+ for job in item['jobs']:
+ values = []
+ for f in fields:
+ v = job
+ for part in f.split('.'):
+ if hasattr(v, 'get'):
+ v = v.get(part, '')
+ if ('transform' in all_fields[f]
+ and callable(all_fields[f]['transform'])):
+ v = all_fields[f]['transform'](v)
+ if 'append' in all_fields[f]:
+ v += all_fields[f]['append']
+ values.append(v)
+ table.add_row(values)
+ print table
+ return True
+
+ def _epoch_to_relative_time(self, epoch):
+ if epoch:
+ delta = datetime.timedelta(seconds=(time.time() - int(epoch)))
+ return babel.dates.format_timedelta(delta, locale='en_US')
+ else:
+ return "Unknown"
+
+ def _boolean_to_yes_no(self, value):
+ return 'Yes' if value else 'No'
+
+ def _boolean_to_pass_fail(self, value):
+ return 'Pass' if value else 'Fail'
+
+ def _format_list(self, l):
+ return ', '.join(l) if isinstance(l, list) else ''
+
+ def _show_running_jobs_columns(self):
+ """A helper function to get the list of available columns for
+ `zuul show running-jobs`. Also describes how to convert particular
+ values (for example epoch to time string)"""
+
+ return {
+ 'name': {
+ 'title': 'Job Name',
+ },
+ 'elapsed_time': {
+ 'title': 'Elapsed Time',
+ 'transform': self._epoch_to_relative_time
+ },
+ 'remaining_time': {
+ 'title': 'Remaining Time',
+ 'transform': self._epoch_to_relative_time
+ },
+ 'url': {
+ 'title': 'URL'
+ },
+ 'result': {
+ 'title': 'Result'
+ },
+ 'voting': {
+ 'title': 'Voting',
+ 'transform': self._boolean_to_yes_no
+ },
+ 'uuid': {
+ 'title': 'UUID'
+ },
+ 'launch_time': {
+ 'title': 'Launch Time',
+ 'transform': self._epoch_to_relative_time,
+ 'append': ' ago'
+ },
+ 'start_time': {
+ 'title': 'Start Time',
+ 'transform': self._epoch_to_relative_time,
+ 'append': ' ago'
+ },
+ 'end_time': {
+ 'title': 'End Time',
+ 'transform': self._epoch_to_relative_time,
+ 'append': ' ago'
+ },
+ 'estimated_time': {
+ 'title': 'Estimated Time',
+ 'transform': self._epoch_to_relative_time,
+ 'append': ' to go'
+ },
+ 'pipeline': {
+ 'title': 'Pipeline'
+ },
+ 'canceled': {
+ 'title': 'Canceled',
+ 'transform': self._boolean_to_yes_no
+ },
+ 'retry': {
+ 'title': 'Retry'
+ },
+ 'number': {
+ 'title': 'Number'
+ },
+ 'parameters': {
+ 'title': 'Parameters'
+ },
+ 'worker.name': {
+ 'title': 'Worker'
+ },
+ 'worker.hostname': {
+ 'title': 'Worker Hostname'
+ },
+ 'worker.ips': {
+ 'title': 'Worker IPs',
+ 'transform': self._format_list
+ },
+ 'worker.fqdn': {
+ 'title': 'Worker Domain'
+ },
+ 'worker.progam': {
+ 'title': 'Worker Program'
+ },
+ 'worker.version': {
+ 'title': 'Worker Version'
+ },
+ 'worker.extra': {
+ 'title': 'Worker Extra'
+ },
+ }
+
def main():
client = Client()
diff --git a/zuul/model.py b/zuul/model.py
index 9028577..2f4110f 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -269,7 +269,7 @@
if j_changes:
j_queue['heads'].append(j_changes)
j_changes = []
- j_changes.append(self.formatItemJSON(e))
+ j_changes.append(e.formatJSON())
if (len(j_changes) > 1 and
(j_changes[-2]['remaining_time'] is not None) and
(j_changes[-1]['remaining_time'] is not None)):
@@ -280,101 +280,6 @@
j_queue['heads'].append(j_changes)
return j_pipeline
- def formatStatus(self, item, indent=0, html=False):
- changeish = item.change
- indent_str = ' ' * indent
- ret = ''
- if html and hasattr(changeish, 'url') and changeish.url is not None:
- ret += '%sProject %s change <a href="%s">%s</a>\n' % (
- indent_str,
- changeish.project.name,
- changeish.url,
- changeish._id())
- else:
- ret += '%sProject %s change %s based on %s\n' % (
- indent_str,
- changeish.project.name,
- changeish._id(),
- item.item_ahead)
- for job in self.getJobs(changeish):
- build = item.current_build_set.getBuild(job.name)
- if build:
- result = build.result
- else:
- result = None
- job_name = job.name
- if not job.voting:
- voting = ' (non-voting)'
- else:
- voting = ''
- if html:
- if build:
- url = build.url
- else:
- url = None
- if url is not None:
- job_name = '<a href="%s">%s</a>' % (url, job_name)
- ret += '%s %s: %s%s' % (indent_str, job_name, result, voting)
- ret += '\n'
- return ret
-
- def formatItemJSON(self, item):
- changeish = item.change
- ret = {}
- ret['active'] = item.active
- if hasattr(changeish, 'url') and changeish.url is not None:
- ret['url'] = changeish.url
- else:
- ret['url'] = None
- ret['id'] = changeish._id()
- if item.item_ahead:
- ret['item_ahead'] = item.item_ahead.change._id()
- else:
- ret['item_ahead'] = None
- ret['items_behind'] = [i.change._id() for i in item.items_behind]
- ret['failing_reasons'] = item.current_build_set.failing_reasons
- ret['zuul_ref'] = item.current_build_set.ref
- ret['project'] = changeish.project.name
- ret['enqueue_time'] = int(item.enqueue_time * 1000)
- ret['jobs'] = []
- max_remaining = 0
- for job in self.getJobs(changeish):
- now = time.time()
- build = item.current_build_set.getBuild(job.name)
- elapsed = None
- remaining = None
- result = None
- url = None
- if build:
- result = build.result
- url = build.url
- if build.start_time:
- if build.end_time:
- elapsed = int((build.end_time -
- build.start_time) * 1000)
- remaining = 0
- else:
- elapsed = int((now - build.start_time) * 1000)
- if build.estimated_time:
- remaining = max(
- int(build.estimated_time * 1000) - elapsed,
- 0)
- if remaining and remaining > max_remaining:
- max_remaining = remaining
- ret['jobs'].append(
- dict(
- name=job.name,
- elapsed_time=elapsed,
- remaining_time=remaining,
- url=url,
- result=result,
- voting=job.voting))
- if self.haveAllJobsStarted(item):
- ret['remaining_time'] = max_remaining
- else:
- ret['remaining_time'] = None
- return ret
-
class ActionReporter(object):
"""An ActionReporter has a reporter and its configured paramaters"""
@@ -760,6 +665,124 @@
def setReportedResult(self, result):
self.current_build_set.result = result
+ def formatJSON(self):
+ changeish = self.change
+ ret = {}
+ ret['active'] = self.active
+ if hasattr(changeish, 'url') and changeish.url is not None:
+ ret['url'] = changeish.url
+ else:
+ ret['url'] = None
+ ret['id'] = changeish._id()
+ if self.item_ahead:
+ ret['item_ahead'] = self.item_ahead.change._id()
+ else:
+ ret['item_ahead'] = None
+ ret['items_behind'] = [i.change._id() for i in self.items_behind]
+ ret['failing_reasons'] = self.current_build_set.failing_reasons
+ ret['zuul_ref'] = self.current_build_set.ref
+ ret['project'] = changeish.project.name
+ ret['enqueue_time'] = int(self.enqueue_time * 1000)
+ ret['jobs'] = []
+ max_remaining = 0
+ for job in self.pipeline.getJobs(changeish):
+ now = time.time()
+ build = self.current_build_set.getBuild(job.name)
+ elapsed = None
+ remaining = None
+ result = None
+ url = None
+ worker = None
+ if build:
+ result = build.result
+ url = build.url
+ if build.start_time:
+ if build.end_time:
+ elapsed = int((build.end_time -
+ build.start_time) * 1000)
+ remaining = 0
+ else:
+ elapsed = int((now - build.start_time) * 1000)
+ if build.estimated_time:
+ remaining = max(
+ int(build.estimated_time * 1000) - elapsed,
+ 0)
+ worker = {
+ 'name': build.worker.name,
+ 'hostname': build.worker.hostname,
+ 'ips': build.worker.ips,
+ 'fqdn': build.worker.fqdn,
+ 'program': build.worker.program,
+ 'version': build.worker.version,
+ 'extra': build.worker.extra
+ }
+ if remaining and remaining > max_remaining:
+ max_remaining = remaining
+
+ ret['jobs'].append({
+ 'name': job.name,
+ 'elapsed_time': elapsed,
+ 'remaining_time': remaining,
+ 'url': url,
+ 'result': result,
+ 'voting': job.voting,
+ 'uuid': build.uuid if build else None,
+ 'launch_time': build.launch_time if build else None,
+ 'start_time': build.start_time if build else None,
+ 'end_time': build.end_time if build else None,
+ 'estimated_time': build.estimated_time if build else None,
+ 'pipeline': build.pipeline.name if build else None,
+ 'canceled': build.canceled if build else None,
+ 'retry': build.retry if build else None,
+ 'number': build.number if build else None,
+ 'parameters': build.parameters if build else None,
+ 'worker': worker
+ })
+
+ if self.pipeline.haveAllJobsStarted(self):
+ ret['remaining_time'] = max_remaining
+ else:
+ ret['remaining_time'] = None
+ return ret
+
+ def formatStatus(self, indent=0, html=False):
+ changeish = self.change
+ indent_str = ' ' * indent
+ ret = ''
+ if html and hasattr(changeish, 'url') and changeish.url is not None:
+ ret += '%sProject %s change <a href="%s">%s</a>\n' % (
+ indent_str,
+ changeish.project.name,
+ changeish.url,
+ changeish._id())
+ else:
+ ret += '%sProject %s change %s based on %s\n' % (
+ indent_str,
+ changeish.project.name,
+ changeish._id(),
+ self.item_ahead)
+ for job in self.pipeline.getJobs(changeish):
+ build = self.current_build_set.getBuild(job.name)
+ if build:
+ result = build.result
+ else:
+ result = None
+ job_name = job.name
+ if not job.voting:
+ voting = ' (non-voting)'
+ else:
+ voting = ''
+ if html:
+ if build:
+ url = build.url
+ else:
+ url = None
+ if url is not None:
+ job_name = '<a href="%s">%s</a>' % (url, job_name)
+ ret += '%s %s: %s%s' % (indent_str, job_name, result, voting)
+ ret += '\n'
+ return ret
+
class Changeish(object):
"""Something like a change; either a change or a ref"""
@@ -882,6 +905,8 @@
return None
def equals(self, other):
+ if (self.project == other.project):
+ return True
return False
def isUpdateOf(self, other):
diff --git a/zuul/rpcclient.py b/zuul/rpcclient.py
index 69390c0..7f572be 100644
--- a/zuul/rpcclient.py
+++ b/zuul/rpcclient.py
@@ -46,7 +46,7 @@
if job.exception:
raise RPCFailure(job.exception)
self.log.debug("Job complete, success: %s" % (not job.failure))
- return (not job.failure)
+ return job
def enqueue(self, pipeline, project, trigger, change):
data = {'pipeline': pipeline,
@@ -54,13 +54,21 @@
'trigger': trigger,
'change': change,
}
- return self.submitJob('zuul:enqueue', data)
+ return not self.submitJob('zuul:enqueue', data).failure
def promote(self, pipeline, change_ids):
data = {'pipeline': pipeline,
'change_ids': change_ids,
}
- return self.submitJob('zuul:promote', data)
+ return not self.submitJob('zuul:promote', data).failure
+
+ def get_running_jobs(self):
+ data = {}
+ job = self.submitJob('zuul:get_running_jobs', data)
+ if job.failure:
+ return False
+ else:
+ return json.loads(job.data[0])
def shutdown(self):
self.gearman.shutdown()
diff --git a/zuul/rpclistener.py b/zuul/rpclistener.py
index c1b9216..a5a5b5d 100644
--- a/zuul/rpclistener.py
+++ b/zuul/rpclistener.py
@@ -48,6 +48,7 @@
def register(self):
self.worker.registerFunction("zuul:enqueue")
self.worker.registerFunction("zuul:promote")
+ self.worker.registerFunction("zuul:get_running_jobs")
def stop(self):
self.log.debug("Stopping")
@@ -123,3 +124,14 @@
change_ids = args['change_ids']
self.sched.promote(pipeline_name, change_ids)
job.sendWorkComplete()
+
+ def handle_get_running_jobs(self, job):
+ # args = json.loads(job.arguments)
+ # TODO: use args to filter by pipeline etc
+ running_items = []
+ for pipeline_name, pipeline in self.sched.layout.pipelines.iteritems():
+ for queue in pipeline.queues:
+ for item in queue.queue:
+ running_items.append(item.formatJSON())
+
+ job.sendWorkComplete(json.dumps(running_items))
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 18f44db..f5d6629 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -1305,7 +1305,7 @@
changed = True
status = ''
for item in queue.queue:
- status += self.pipeline.formatStatus(item)
+ status += item.formatStatus()
if status:
self.log.debug("Queue %s status is now:\n %s" %
(queue.name, status))
@@ -1334,7 +1334,7 @@
self.pipeline.setResult(item, build)
self.log.debug("Item %s status is now:\n %s" %
- (item, self.pipeline.formatStatus(item)))
+ (item, item.formatStatus()))
self.updateBuildDescriptions(build.build_set)
return True