hydra/src/root/edit-jobset.tt
Graham Christensen 5fae9d96a2
hydra-evaluator: add a 'ONE_AT_A_TIME' evaluator style
In the past, jobsets which are automatically evaluated are evaluated
regularly, on a schedule. This schedule means a new evaluation is
created every checkInterval seconds (assuming something changed.)

This model works well for architectures where our build farm can
easily keep up with demand.

This commit adds a new type of evaluation, called ONE_AT_A_TIME, which
only schedules a new evaluation if the previous evaluation of the
jobset has no unfinished builds.

This model of evaluation lets us have 'low-tier' architectures.

For example, we could now have a jobset for ARMv7l builds, where
the buildfarm only has a single, underpowered ARMv7l builder.
Configuring that jobset as ONE_AT_A_TIME will create an evaluation
and then won't schedule another evaluation until every job of
the existing evaluation is complete.

This way, the cache will have a complete collection of pre-built
software for some commits, but the underpowered architecture will
never become backlogged in ancient revisions.
2020-03-03 19:28:44 -05:00

222 lines
8.1 KiB
Plaintext

[% WRAPPER layout.tt title=
(create ? "Creating jobset in project $project.name" :
createFromEval ? "Creating jobset from evaluation $eval.id of $project.name:$jobset.name" :
cloneJobset ? "Cloning jobset $project.name:$jobset.name" :
"Editing jobset $project.name:$jobset.name") %]
[% PROCESS common.tt %]
[% USE format %]
[% BLOCK renderJobsetInput %]
<tr class="input [% extraClass %]" [% IF id %]id="[% id %]"[% END %]>
<td>
<button type="button" class="btn btn-warning" onclick='$(this).parents(".input").remove()'><i class="icon-trash icon-white"></i></button>
</td>
<td>
<input type="text" id="[% baseName %]-name" name="[% baseName %]-name" [% HTML.attributes(value => input.name) %]/>
</td>
<td>
[% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes edit=1 %]
</td>
<td id="[% baseName %]">
[% IF createFromEval %]
[% value = (input.uri or input.value); IF input.revision; value = value _ " " _ input.revision; END;
warn = input.altnr != 0;
%]
[% ELSE %]
[% alt = input.search_related('jobsetinputalts', {altnr => 0});
value = alt.value
warn = input.jobsetinputalts_rs.count > 1;
%]
[% END %]
[% IF warn %]
<div class="alert alert-warning">Warning: This input had more
than one value. This is no longer supported. The additional
values have been removed.</div>
[% END %]
<input style="width: 95%" type="text" [% HTML.attributes(value => value, id => "$baseName-value", name => "$baseName-value") %]/>
</td>
<td>
<input type="checkbox" id="[% baseName %]-emailresponsible" name="[% baseName %]-emailresponsible" [% IF input.emailresponsible; 'checked="checked"'; END %]/>
</td>
</tr>
[% END %]
[% BLOCK renderJobsetInputs %]
<table class="table table-striped table-condensed">
<thead>
<tr><th></th><th>Input name</th><th>Type</th><th style="width: 50%">Value</th><th>Notify committers</th></tr>
</thead>
<tbody class="inputs">
[% inputs = createFromEval ? eval.jobsetevalinputs : jobset.jobsetinputs; FOREACH input IN inputs %]
[% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %]
[% END %]
<tr>
<td colspan="4" style="text-align: center;"><button type="button" class="add-input btn btn-success"><i class="icon-plus icon-white"></i> Add a new input</button></td>
</tr>
</tbody>
</table>
[% END %]
<form class="form-horizontal">
<fieldset>
<div class="control-group">
<label class="control-label">State</label>
<div class="controls">
<div class="btn-group" data-toggle="buttons-radio">
<input type="hidden" name="enabled" value="[% jobset.enabled %]" />
<button type="button" class="btn" value="1">Enabled</button>
<button type="button" class="btn" value="2">One-shot</button>
<button type="button" class="btn" value="3">One-at-a-time</button>
<button type="button" class="btn" value="0">Disabled</button>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="visible" [% IF !jobset.hidden; 'checked="checked"'; END %]/>Visible
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Identifier</label>
<div class="controls">
<input type="text" class="span3" name="name" [% HTML.attributes(value => edit ? jobset.name : "") %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Description</label>
<div class="controls">
<input type="text" class="span3" name="description" [% HTML.attributes(value => jobset.description) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Nix expression</label>
<div class="controls">
<input type="text" class="span3" name="nixexprpath" [% HTML.attributes(value => jobset.nixexprpath) %]/>
in
<input type="text" class="span3" name="nixexprinput" [% HTML.attributes(value => jobset.nixexprinput) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Check interval</label>
<div class="controls">
<div class="input-append">
<input type="number" class="span3" name="checkinterval" [% HTML.attributes(value => jobset.checkinterval) %]/>
<span class="add-on">sec</span>
</div>
<span class="help-inline">(0 to disable polling)</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Scheduling shares</label>
<div class="controls">
<div class="input-append">
<input type="number" class="span3" name="schedulingshares" [% HTML.attributes(value => jobset.schedulingshares) %]/>
</div>
[% IF totalShares %]
<span class="help-inline">([% f = format("%.2f"); f(jobset.schedulingshares / totalShares * 100) %]% out of [% totalShares %] shares)</span>
[% END %]
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="enableemail" [% IF jobset.enableemail; 'checked="checked"'; END %] [%IF !emailNotification%]disabled=1[%END%] />Email notification
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Email override</label>
<div class="controls">
<input type="text" class="span3" name="emailoverride" [% HTML.attributes(value => jobset.emailoverride) %] [%IF !emailNotification%]disabled=1[%END%] />
</div>
</div>
<div class="control-group">
<label class="control-label">Number of evaluations to keep</label>
<div class="controls">
<input type="number" class="span3" name="keepnr" [% HTML.attributes(value => jobset.keepnr) %]/>
</div>
</div>
[% INCLUDE renderJobsetInputs %]
<div class="form-actions">
<button id="submit-jobset" type="submit" class="btn btn-primary"><i class="icon-ok icon-white"></i> [%IF !edit %]Create jobset[% ELSE %]Apply changes[% END %]</button>
</div>
</fieldset>
<table style="display: none">
[% INCLUDE renderJobsetInput input="" extraClass="template" id="input-template" baseName="input-template" %]
</table>
</form>
<script type="text/javascript">
$(document).ready(function() {
var id = 0;
$(".add-input").click(function() {
var newid = "input-" + id++;
var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show();
$("#input-template-name", x).attr("name", newid + "-name");
$("#input-template-type", x).attr("name", newid + "-type");
$("#input-template-value", x).attr("name", newid + "-value");
$("#input-template-emailresponsible", x).attr("name", newid + "-emailresponsible");
$("#input-template", x).attr("id", newid);
return false;
});
});
$("#submit-jobset").click(function() {
var formElements = $(this).parents("form").serializeArray();
var data = { 'inputs': {} };
var inputs = {};
for (var i = 0; formElements.length > i; i++) {
var elem = formElements[i];
var match = elem.name.match(/^input-([\w-]+)-(\w+)$/);
if (match === null) {
data[elem.name] = elem.value;
} else {
var baseName = match[1];
var param = match[2];
if (baseName === "template") continue;
if (!(baseName in inputs))
inputs[baseName] = {};
if (param === "name")
data.inputs[elem.value] = inputs[baseName];
else
inputs[baseName][param] = elem.value;
}
}
redirectJSON({
[% IF !edit %]
url: "[% c.uri_for('/jobset' project.name '.new') %]",
[% ELSE %]
url: "[% c.uri_for('/jobset' project.name jobset.name) %]",
[% END %]
data: JSON.stringify(data),
contentType: 'application/json',
type: 'PUT'
});
return false;
});
</script>
[% END %]