Added a new input that allows used to specify a one-time host when
creating a job. The job will be run against that host once, and the
host will not appear in the "Available hosts" selector.
Risk: medium (deleting records from database)
Visibility: medium (adding an input field)
git-svn-id: http://test.kernel.org/svn/autotest/trunk@1768 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/afe/models.py b/frontend/afe/models.py
index b8ec10d..c0ce596 100644
--- a/frontend/afe/models.py
+++ b/frontend/afe/models.py
@@ -86,6 +86,21 @@
objects = model_logic.ExtendedManager()
valid_objects = model_logic.ValidObjectsManager()
+ @staticmethod
+ def create_one_time_host(hostname):
+ query = Host.objects.filter(hostname=hostname)
+ if query.count() == 0:
+ host = Host(hostname=hostname, invalid=True)
+ else:
+ host = query[0]
+ if not host.invalid:
+ raise model_logic.ValidationError({
+ 'hostname' : '%s already exists!' % hostname
+ })
+ host.clean_object()
+ host.status = Host.Status.READY
+ host.save()
+ return host
def clean_object(self):
self.aclgroup_set.clear()
@@ -102,6 +117,12 @@
everyone = AclGroup.objects.get(name='Everyone')
everyone.hosts.add(self)
+ def delete(self):
+ for queue_entry in self.hostqueueentry_set.all():
+ queue_entry.deleted = True
+ queue_entry.abort()
+ super(Host, self).delete()
+
def enqueue_job(self, job):
' Enqueue a job on this host.'
@@ -466,7 +487,8 @@
def requeue(self, new_owner):
'Creates a new job identical to this one'
hosts = [queue_entry.meta_host or queue_entry.host
- for queue_entry in self.hostqueueentry_set.all()]
+ for queue_entry
+ in self.hostqueueentry_set.filter(deleted=False)]
new_job = Job.create(
owner=new_owner, name=self.name, priority=self.priority,
control_file=self.control_file,
@@ -478,13 +500,7 @@
def abort(self):
for queue_entry in self.hostqueueentry_set.all():
- if queue_entry.active:
- queue_entry.status = Job.Status.ABORT
- elif not queue_entry.complete:
- queue_entry.status = Job.Status.ABORTED
- queue_entry.active = False
- queue_entry.complete = True
- queue_entry.save()
+ queue_entry.abort()
def user(self):
@@ -528,6 +544,7 @@
db_column='meta_host')
active = dbmodels.BooleanField(default=False)
complete = dbmodels.BooleanField(default=False)
+ deleted = dbmodels.BooleanField(default=False)
objects = model_logic.ExtendedManager()
@@ -536,6 +553,14 @@
'True if this is a entry has a meta_host instead of a host.'
return self.host is None and self.meta_host is not None
+ def abort(self):
+ if self.active:
+ self.status = Job.Status.ABORT
+ elif not self.complete:
+ self.status = Job.Status.ABORTED
+ self.active = False
+ self.complete = True
+ self.save()
class Meta:
db_table = 'host_queue_entries'
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index 2e03eff..f482530 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -250,7 +250,8 @@
def create_job(name, priority, control_file, control_type, timeout=None,
- is_synchronous=None, hosts=None, meta_hosts=None):
+ is_synchronous=None, hosts=None, meta_hosts=None,
+ one_time_hosts=None):
"""\
Create and enqueue a job.
@@ -271,10 +272,10 @@
owner = rpc_utils.get_user().login
# input validation
- if not hosts and not meta_hosts:
+ if not hosts and not meta_hosts and not one_time_hosts:
raise model_logic.ValidationError({
- 'arguments' : "You must pass at least one of 'hosts' or "
- "'meta_hosts'"
+ 'arguments' : "You must pass at least one of 'hosts', "
+ "'meta_hosts', or 'one_time_hosts'"
})
requested_host_counts = {}
@@ -289,6 +290,9 @@
host_objects.append(this_label)
requested_host_counts.setdefault(this_label.name, 0)
requested_host_counts[this_label.name] += 1
+ for host in one_time_hosts or []:
+ this_host = models.Host.create_one_time_host(host)
+ host_objects.append(this_host)
# check that each metahost request has enough hosts under the label
if meta_hosts:
diff --git a/frontend/client/src/autotest/afe/CreateJobView.java b/frontend/client/src/autotest/afe/CreateJobView.java
index 4647b9c..f5d5b06 100644
--- a/frontend/client/src/autotest/afe/CreateJobView.java
+++ b/frontend/client/src/autotest/afe/CreateJobView.java
@@ -619,6 +619,8 @@
HostSelector.HostSelection hosts = hostSelector.getSelectedHosts();
args.put("hosts", Utils.stringsToJSON(hosts.hosts));
args.put("meta_hosts", Utils.stringsToJSON(hosts.metaHosts));
+ args.put("one_time_hosts",
+ Utils.stringsToJSON(hosts.oneTimeHosts));
rpcProxy.rpcCall("create_job", args, new JsonRpcCallback() {
@Override
diff --git a/frontend/client/src/autotest/afe/HostSelector.java b/frontend/client/src/autotest/afe/HostSelector.java
index 1cab559..8f74e1c 100644
--- a/frontend/client/src/autotest/afe/HostSelector.java
+++ b/frontend/client/src/autotest/afe/HostSelector.java
@@ -39,10 +39,12 @@
public class HostSelector {
public static final int TABLE_SIZE = 10;
public static final String META_PREFIX = "Any ";
+ public static final String ONE_TIME = "(one-time host)";
static class HostSelection {
public List<String> hosts = new ArrayList<String>();
public List<String> metaHosts = new ArrayList<String>();
+ public List<String> oneTimeHosts = new ArrayList<String>();
}
protected ArrayDataSource<JSONObject> selectedHostData =
@@ -150,6 +152,21 @@
RootPanel.get("create_meta_select").add(metaLabelSelect);
RootPanel.get("create_meta_number").add(metaNumber);
RootPanel.get("create_meta_button").add(metaButton);
+
+ final TextBox oneTimeHostField = new TextBox();
+ final Button oneTimeHostButton = new Button("Add");
+ oneTimeHostButton.addClickListener(new ClickListener() {
+ public void onClick(Widget sender) {
+ String hostname = oneTimeHostField.getText();
+ JSONObject oneTimeObject = new JSONObject();
+ oneTimeObject.put("hostname", new JSONString(hostname));
+ oneTimeObject.put("platform", new JSONString(ONE_TIME));
+ selectRow(oneTimeObject);
+ selectionRefresh();
+ }
+ });
+ RootPanel.get("create_one_time_field").add(oneTimeHostField);
+ RootPanel.get("create_one_time_button").add(oneTimeHostButton);
}
protected void selectMetaHost(String label, String number) {
@@ -216,6 +233,14 @@
return Integer.parseInt(getHostname(row).substring(META_PREFIX.length()));
}
+ protected boolean isOneTimeHost(JSONObject row) {
+ JSONString platform = row.get("platform").isString();
+ if (platform == null) {
+ return false;
+ }
+ return platform.stringValue().equals(ONE_TIME);
+ }
+
/**
* Retrieve the set of selected hosts.
*/
@@ -232,7 +257,11 @@
}
else {
String hostname = getHostname(row);
- selection.hosts.add(hostname);
+ if (isOneTimeHost(row)) {
+ selection.oneTimeHosts.add(hostname);
+ } else {
+ selection.hosts.add(hostname);
+ }
}
}
diff --git a/frontend/client/src/autotest/public/AfeClient.html b/frontend/client/src/autotest/public/AfeClient.html
index d9a8442..540cb50 100644
--- a/frontend/client/src/autotest/public/AfeClient.html
+++ b/frontend/client/src/autotest/public/AfeClient.html
@@ -89,6 +89,11 @@
<td colspan="3" class="box">
<table><tr>
<td>
+ <div>
+ <span class="field-name">One-time host:</span>
+ <span id="create_one_time_field"></span>
+ <span id="create_one_time_button"></span>
+ </div>
<div class="box-full">
Run on any <span id="create_meta_select"></span><br>
Number: <span id="create_meta_number"></span>
diff --git a/frontend/migrations/011_support_one_time_hosts.py b/frontend/migrations/011_support_one_time_hosts.py
new file mode 100644
index 0000000..96c666d
--- /dev/null
+++ b/frontend/migrations/011_support_one_time_hosts.py
@@ -0,0 +1,41 @@
+def migrate_up(manager):
+ manager.execute_script(CLEAN_DATABASE)
+ manager.execute(ADD_HOST_QUEUE_DELETED_COLUMN)
+ manager.execute(DROP_DEFAULT)
+
+def migrate_down(manager):
+ manager.execute(DROP_HOST_QUEUE_DELETED_COLUMN)
+
+CLEAN_DATABASE = """DELETE FROM acl_groups_hosts
+ WHERE host_id IN
+ (SELECT id FROM hosts WHERE invalid = TRUE);
+
+ DELETE FROM ineligible_host_queues
+ WHERE host_id IN
+ (SELECT id FROM hosts WHERE invalid = TRUE);
+
+ UPDATE host_queue_entries
+ SET status = 'Abort'
+ WHERE host_id IN
+ (SELECT id FROM hosts WHERE invalid = TRUE)
+ AND active = TRUE;
+
+ UPDATE host_queue_entries
+ SET status = 'Aborted', complete = TRUE
+ WHERE host_id IN
+ (SELECT id FROM hosts WHERE invalid = TRUE)
+ AND active = FALSE AND complete = FALSE;
+
+ DELETE FROM hosts_labels
+ WHERE host_id IN
+ (SELECT id FROM hosts WHERE invalid = TRUE);"""
+
+DROP_HOST_QUEUE_DELETED_COLUMN = """ALTER TABLE host_queue_entries
+ DROP COLUMN deleted"""
+
+ADD_HOST_QUEUE_DELETED_COLUMN = """ALTER TABLE host_queue_entries
+ ADD COLUMN deleted BOOLEAN
+ NOT NULL DEFAULT FALSE"""
+
+DROP_DEFAULT = """ALTER TABLE host_queue_entries
+ ALTER COLUMN deleted DROP DEFAULT"""
diff --git a/scheduler/monitor_db.py b/scheduler/monitor_db.py
index 410904b..667e4ee 100644
--- a/scheduler/monitor_db.py
+++ b/scheduler/monitor_db.py
@@ -552,9 +552,9 @@
extra_join +
# exclude hosts with a running entry
'WHERE active_hqe.host_id IS NULL '
- # exclude locked, invalid, and non-Ready hosts
+ # exclude locked and non-Ready hosts
"""
- AND h.locked=false AND h.invalid=false
+ AND h.locked=false
AND (h.status IS null OR h.status='Ready')
""")
if extra_where:
@@ -600,7 +600,7 @@
LEFT JOIN ineligible_host_queues AS ihq
ON (ihq.job_id=queued_hqe.job_id AND ihq.host_id=h.id)
"""
- block_where = 'ihq.id IS NULL'
+ block_where = 'ihq.id IS NULL AND h.invalid = FALSE'
extra_join = '\n'.join([labels_join, queued_hqe_join,
acl_join, block_join])
return self._get_runnable_entries(extra_join,
@@ -1482,7 +1482,7 @@
@classmethod
def _fields(cls):
return ['id', 'job_id', 'host_id', 'priority', 'status',
- 'meta_host', 'active', 'complete']
+ 'meta_host', 'active', 'complete', 'deleted']
def set_host(self, host):