Add Planner triage actions

Signed-off-by: James Ren <[email protected]>

--- /dev/null   2009-12-17 12:29:38.000000000 -0800
+++ 
autotest/frontend/client/src/autotest/planner/resources/PlannerImageBundle.java 
    2010-04-06 15:56:40.000000000 -0700
@@ -0,0 +1,8 @@
+package autotest.planner.resources;
+
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.ImageBundle;
+
+public interface PlannerImageBundle extends ImageBundle {
+    public AbstractImagePrototype close();
+}
--- autotest/frontend/client/src/autotest/planner/triage/FailureTable.java      
2010-04-05 13:07:24.000000000 -0700
+++ autotest/frontend/client/src/autotest/planner/triage/FailureTable.java      
2010-04-06 15:56:40.000000000 -0700
@@ -5,19 +5,22 @@
 import autotest.common.spreadsheet.Spreadsheet.Header;
 import autotest.common.spreadsheet.Spreadsheet.HeaderImpl;
 import autotest.common.spreadsheet.Spreadsheet.SpreadsheetListener;
+import autotest.common.ui.NotifyManager;
 
 import com.google.gwt.json.client.JSONObject;
 import com.google.gwt.user.client.IncrementalCommand;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 
-class FailureTable implements SpreadsheetListener {
+class FailureTable implements SpreadsheetListener, TriagePopup.Listener {
     
     public interface Display {
         public Spreadsheet getSpreadsheet();
+        public TriagePopup.Display generateTriagePopupDisplay();
     }
     
     private static class Failure {
@@ -59,10 +62,11 @@
     
     public void bindDisplay(Display display) {
         this.display = display;
+        display.getSpreadsheet().setListener(this);
     }
     
     public void addFailure(JSONObject failureObj) {
-        rendered = false;
+        setRendered(false);
         
         Failure failure = Failure.fromJsonObject(failureObj);
         
@@ -81,8 +85,14 @@
         return display;
     }
     
+    private void setRendered(boolean rendered) {
+        this.rendered = rendered;
+        display.getSpreadsheet().setVisible(rendered);
+    }
+    
     public void renderDisplay() {
         Spreadsheet spreadsheet = display.getSpreadsheet();
+        spreadsheet.clear();
         
         Header rowFields = 
HeaderImpl.fromBaseType(Collections.singletonList("machine"));
         Header columnFields = new HeaderImpl();
@@ -110,6 +120,8 @@
             
             test.contents = failure.testName;
             reason.contents = failure.reason;
+            test.testIndex = failure.id;
+            reason.testIndex = failure.id;
             
             if (!failure.seen) {
                 test.contents = "<b>" + test.contents + "</b>";
@@ -122,8 +134,8 @@
         spreadsheet.render(new IncrementalCommand() {
             @Override
             public boolean execute() {
-              rendered = true;
-              return false;
+                setRendered(true);
+                return false;
             }
         });
     }
@@ -137,6 +149,39 @@
 
     @Override
     public void onCellClicked(CellInfo cellInfo, boolean isRightClick) {
-        //TODO: handle row clicks (pop up the triage panel)
+        if (cellInfo.testIndex == 0) {
+            return;
+        }
+        
+        TriagePopup popup = new TriagePopup(this, cellInfo.testIndex);
+        popup.bindDisplay(display.generateTriagePopupDisplay());
+        popup.render();
+    }
+
+    @Override
+    public void onTriage(TriagePopup source) {
+        if (removeFailure(source.getId())) {
+            renderDisplay();
+        }
+    }
+    
+    private boolean removeFailure(int id) {
+        setRendered(false);
+        
+        Iterator<Failure> iter = failures.iterator();
+        while (iter.hasNext()) {
+            if (iter.next().id == id) {
+                iter.remove();
+                return true;
+            }
+        }
+        
+        /*
+         * TODO: throw an Exception instead, and register a handler with
+         * GWT.setUncaughtExceptionHandler()
+         */
+        NotifyManager.getInstance().showError("Did not find failure id " + id);
+        setRendered(true);
+        return false;
     }
 }
--- 
autotest/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java   
    2010-04-05 13:07:24.000000000 -0700
+++ 
autotest/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java   
    2010-04-06 15:56:40.000000000 -0700
@@ -12,7 +12,13 @@
         initWidget(spreadsheet);
     }
     
+    @Override
     public Spreadsheet getSpreadsheet() {
         return spreadsheet;
     }
+    
+    @Override
+    public TriagePopup.Display generateTriagePopupDisplay() {
+        return new TriagePopupDisplay();
+    }
 }
--- /dev/null   2009-12-17 12:29:38.000000000 -0800
+++ autotest/frontend/client/src/autotest/planner/triage/TriagePopup.java       
2010-04-06 15:56:40.000000000 -0700
@@ -0,0 +1,152 @@
+package autotest.planner.triage;
+
+import autotest.common.JsonRpcCallback;
+import autotest.common.JsonRpcProxy;
+import autotest.common.StaticDataRepository;
+import autotest.common.ui.SimplifiedList;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONBoolean;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+public class TriagePopup implements ClickHandler, CloseHandler<PopupPanel> {
+    public static interface Display extends HasCloseHandlers<PopupPanel> {
+        public HasClickHandlers getCloseButton();
+        public HasText getLabelsField();
+        public HasText getKeyvalsField();
+        public HasText getBugsField();
+        public HasText getReasonField();
+        public SimplifiedList getHostActionField();
+        public SimplifiedList getTestActionField();
+        public HasValue<Boolean> getInvalidateField();
+        public HasClickHandlers getApplyButton();
+        public void center();
+        public void hide();
+    }
+    
+    public static interface Listener {
+        public void onTriage(TriagePopup source);
+    }
+    
+    private Display display;
+    private Listener listener;
+    private int id;
+    private boolean triaged = false;
+    
+    public TriagePopup(Listener listener, int id) {
+        this.listener = listener;
+        this.id = id;
+    }
+    
+    public void bindDisplay(Display display) {
+        this.display = display;
+        display.addCloseHandler(this);
+        populateActionsFields();
+        setHandlers();
+    }
+    
+    public void render() {
+        display.center();
+    }
+    
+    public int getId() {
+        return id;
+    }
+    
+    private void populateActionsFields() {
+        populateList("host_actions", display.getHostActionField());
+        populateList("test_actions", display.getTestActionField());
+    }
+    
+    private void populateList(String staticDataKey, SimplifiedList list) {
+        JSONArray choices = 
StaticDataRepository.getRepository().getData(staticDataKey).isArray();
+        for (int i = 0; i < choices.size(); i++) {
+            String item = choices.get(i).isString().stringValue();
+            list.addItem(item, item);
+        }
+    }
+    
+    private void setHandlers() {
+        display.getCloseButton().addClickHandler(this);
+        display.getApplyButton().addClickHandler(this);
+    }
+    
+    @Override
+    public void onClick(ClickEvent event) {
+        if (event.getSource() == display.getCloseButton()) {
+            display.hide();
+        } else {
+            assert event.getSource() == display.getApplyButton();
+            JsonRpcProxy proxy = JsonRpcProxy.getProxy();
+            
+            JSONObject params = getParams();
+            
+            proxy.rpcCall("process_failure", params, new JsonRpcCallback() {
+                @Override
+                public void onSuccess(JSONValue result) {
+                    triaged = true;
+                    display.hide();
+                }
+            });
+        }
+    }
+    
+    private JSONObject getParams() {
+        JSONObject params = new JSONObject();
+        params.put("failure_id", new JSONNumber(id));
+        params.put("host_action", new 
JSONString(display.getHostActionField().getSelectedName()));
+        params.put("test_action", new 
JSONString(display.getTestActionField().getSelectedName()));
+        
+        if (!display.getLabelsField().getText().trim().equals("")) {
+            params.put("labels", 
parseCommaDelimited(display.getLabelsField().getText()));
+        }
+        
+        if (!display.getKeyvalsField().getText().trim().equals("")) {
+            JSONObject keyvals = new JSONObject();
+            for (String keyval : 
display.getKeyvalsField().getText().split("\n")) {
+                String split[] = keyval.split("=", 2);
+                keyvals.put(split[0].trim(), new JSONString(split[1].trim()));
+            }
+            params.put("keyvals", keyvals);
+        }
+        
+        if (!display.getBugsField().getText().trim().equals("")) {
+            params.put("bugs", 
parseCommaDelimited(display.getBugsField().getText()));
+        }
+        
+        if (!display.getReasonField().getText().trim().equals("")) {
+            params.put("reason", new 
JSONString(display.getReasonField().getText()));
+        }
+        
+        params.put("invalidate", 
JSONBoolean.getInstance(display.getInvalidateField().getValue()));
+        
+        return params;
+    }
+    
+    private JSONArray parseCommaDelimited(String line) {
+        JSONArray values = new JSONArray();
+        for (String value : line.split(",")) {
+            values.set(values.size(), new JSONString(value.trim()));
+        }
+        return values;
+    }
+
+    @Override
+    public void onClose(CloseEvent<PopupPanel> event) {
+        if (triaged) {
+            listener.onTriage(this);
+        }
+    }
+}
--- /dev/null   2009-12-17 12:29:38.000000000 -0800
+++ 
autotest/frontend/client/src/autotest/planner/triage/TriagePopupDisplay.java    
    2010-04-06 15:56:40.000000000 -0700
@@ -0,0 +1,113 @@
+package autotest.planner.triage;
+
+import autotest.common.ui.ExtendedListBox;
+import autotest.common.ui.SimplifiedList;
+import autotest.planner.resources.PlannerImageBundle;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class TriagePopupDisplay extends PopupPanel implements 
TriagePopup.Display {
+    private Panel container = new VerticalPanel();
+    private Image closeX =
+            ((PlannerImageBundle) 
GWT.create(PlannerImageBundle.class)).close().createImage();
+    private TextBox labels = new TextBox();
+    private TextArea keyvals = new TextArea();
+    private TextBox bugs = new TextBox();
+    private TextBox reason = new TextBox();
+    private ExtendedListBox hostAction = new ExtendedListBox();
+    private ExtendedListBox testAction = new ExtendedListBox();
+    private CheckBox invalidate = new CheckBox("Invalidate Test");
+    private Button apply = new Button("Apply");
+    
+    public TriagePopupDisplay() {
+        super(false, true);
+        
+        HorizontalPanel topPanel = new HorizontalPanel();
+        topPanel.setWidth("100%");
+        topPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
+        topPanel.add(closeX);
+        container.add(topPanel);
+        
+        FlexTable bottomTable = new FlexTable();
+        addRow(bottomTable, "Labels", labels);
+        addRow(bottomTable, "Keyvals", keyvals);
+        addRow(bottomTable, "Bugs", bugs);
+        addRow(bottomTable, "Reason", reason);
+        addRow(bottomTable, "Host", hostAction);
+        addRow(bottomTable, "Test", testAction);
+        addRow(bottomTable, null, invalidate);
+        container.add(bottomTable);
+        
+        container.add(apply);
+        
+        setWidget(container);
+    }
+    
+    private void addRow(FlexTable table, String label, Widget field) {
+        int row = table.getRowCount();
+        if (label != null) {
+            table.setText(row, 0, label + ":");
+        }
+        table.setWidget(row, 1, field);
+    }
+
+    @Override
+    public HasClickHandlers getApplyButton() {
+      return apply;
+    }
+
+    @Override
+    public HasText getBugsField() {
+      return bugs;
+    }
+
+    @Override
+    public HasClickHandlers getCloseButton() {
+      return closeX;
+    }
+
+    @Override
+    public SimplifiedList getHostActionField() {
+      return hostAction;
+    }
+
+    @Override
+    public HasValue<Boolean> getInvalidateField() {
+      return invalidate;
+    }
+
+    @Override
+    public HasText getKeyvalsField() {
+      return keyvals;
+    }
+
+    @Override
+    public HasText getLabelsField() {
+      return labels;
+    }
+
+    @Override
+    public HasText getReasonField() {
+      return reason;
+    }
+
+    @Override
+    public SimplifiedList getTestActionField() {
+      return testAction;
+    }
+}
--- /dev/null   2009-12-17 12:29:38.000000000 -0800
+++ autotest/frontend/migrations/057_add_planner_triage_actions.py      
2010-04-06 15:56:40.000000000 -0700
@@ -0,0 +1,15 @@
+UP_SQL = """
+ALTER TABLE planner_test_runs
+ADD COLUMN invalidated TINYINT(1) DEFAULT FALSE;
+
+ALTER TABLE planner_test_jobs
+ADD COLUMN requires_rerun TINYINT(1) DEFAULT FALSE;
+"""
+
+DOWN_SQL = """
+ALTER TABLE planner_test_jobs
+DROP COLUMN requires_rerun;
+
+ALTER TABLE planner_test_runs
+DROP COLUMN invalidated;
+"""
--- /dev/null   2009-12-17 12:29:38.000000000 -0800
+++ autotest/frontend/planner/failure_actions.py        2010-04-06 
15:56:40.000000000 -0700
@@ -0,0 +1,17 @@
+import common
+from autotest_lib.client.common_lib import enum, utils
+
+
+def _site_host_actions_dummy():
+    return []
+
+_site_host_actions = utils.import_site_function(
+        __file__, 'autotest_lib.frontend.planner.site_failure_actions',
+        'site_host_actions', _site_host_actions_dummy)
+
+HostAction = enum.Enum(
+        string_values=True,
+        *(_site_host_actions() + ['Block', 'Unblock', 'Reinstall']))
+
+
+TestAction = enum.Enum('Skip', 'Rerun', string_values=True)
--- autotest/frontend/planner/models.py 2010-04-06 14:05:56.000000000 -0700
+++ autotest/frontend/planner/models.py 2010-04-06 15:56:40.000000000 -0700
@@ -167,6 +167,7 @@
     """
     test_config = dbmodels.ForeignKey(TestConfig)
     afe_job = dbmodels.ForeignKey(afe_models.Job)
+    requires_rerun = dbmodels.BooleanField(default=False)
 
     class Meta:
         db_table = 'planner_test_jobs'
@@ -222,6 +223,7 @@
     finalized = dbmodels.BooleanField(default=False)
     seen = dbmodels.BooleanField(default=False)
     triaged = dbmodels.BooleanField(default=False)
+    invalidated = dbmodels.BooleanField(default=False)
 
     bugs = dbmodels.ManyToManyField(Bug, null=True,
                                     db_table='planner_test_run_bugs')
--- autotest/frontend/planner/rpc_interface.py  2010-04-06 14:05:56.000000000 
-0700
+++ autotest/frontend/planner/rpc_interface.py  2010-04-06 15:56:40.000000000 
-0700
@@ -13,6 +13,7 @@
 from autotest_lib.frontend.afe import rpc_utils as afe_rpc_utils
 from autotest_lib.frontend.tko import models as tko_models
 from autotest_lib.frontend.planner import models, rpc_utils, model_attributes
+from autotest_lib.frontend.planner import failure_actions
 from autotest_lib.client.common_lib import utils
 
 # basic getter/setter calls
@@ -207,7 +208,7 @@
                 tko_test_idx: the ID of the TKO test added
                 hostname: the host added
     """
-    plan = models.Plan.objects.get(id=plan_id)
+    plan = models.Plan.smart_get(plan_id)
     updated = []
 
     for planner_job in plan.job_set.all():
@@ -298,6 +299,90 @@
     config.skipped_hosts.add(afe_models.Host.objects.get(hostname=hostname))
 
 
+def mark_failures_as_seen(failure_ids):
+    """
+    Marks a set of failures as 'seen'
+
+    @param failure_ids: A list of failure IDs, as returned by get_failures(), 
to
+                        mark as seen
+    """
+    models.TestRun.objects.filter(id__in=failure_ids).update(seen=True)
+
+
+def process_failure(failure_id, host_action, test_action, labels=(),
+                    keyvals=None, bugs=(), reason=None, invalidate=False):
+    """
+    Triage a failure
+
+    @param failure_id: The failure ID, as returned by get_failures()
+    @param host_action: One of 'Block', 'Unblock', 'Reinstall'
+    @param test_action: One of 'Skip', 'Rerun'
+
+    @param labels: Test labels to apply, by name
+    @param keyvals: Dictionary of job keyvals to add (or replace)
+    @param bugs: List of bug IDs to associate with this failure
+    @param reason: An override for the test failure reason
+    @param invalidate: True if failure should be invalidated for the purposes 
of
+                       reporting. Defaults to False.
+    """
+    if keyvals is None:
+        keyvals = {}
+
+    host_choices = failure_actions.HostAction.values
+    test_choices = failure_actions.TestAction.values
+    if host_action not in host_choices:
+        raise model_logic.ValidationError(
+                {'host_action': ('host action %s not valid; must be one of %s'
+                                 % (host_action, ', '.join(host_choices)))})
+    if test_action not in test_choices:
+        raise model_logic.ValidationError(
+                {'test_action': ('test action %s not valid; must be one of %s'
+                                 % (test_action, ', '.join(test_choices)))})
+
+    failure = models.TestRun.objects.get(id=failure_id)
+
+    rpc_utils.process_host_action(failure.host, host_action)
+    rpc_utils.process_test_action(failure.test_job, test_action)
+
+    # Add the test labels
+    for label in labels:
+        tko_test_label, _ = (
+                tko_models.TestLabel.objects.get_or_create(name=label))
+        failure.tko_test.testlabel_set.add(tko_test_label)
+
+    # Set the job keyvals
+    for key, value in keyvals.iteritems():
+        keyval, created = tko_models.JobKeyval.objects.get_or_create(
+                job=failure.tko_test.job, key=key)
+        if not created:
+            tko_models.JobKeyval.objects.create(job=failure.tko_test.job,
+                                                key='original_' + key,
+                                                value=keyval.value)
+        keyval.value = value
+        keyval.save()
+
+    # Add the bugs
+    for bug_id in bugs:
+        bug, _ = models.Bug.objects.get_or_create(external_uid=bug_id)
+        failure.bugs.add(bug)
+
+    # Set the failure reason
+    if reason is not None:
+        tko_models.TestAttribute.objects.create(test=failure.tko_test,
+                                                attribute='original_reason',
+                                                value=failure.tko_test.reason)
+        failure.tko_test.reason = reason
+        failure.tko_test.save()
+
+    # Set 'invalidated', 'seen', and 'triaged'
+    failure.invalidated = invalidate
+    failure.seen = True
+    failure.triaged = True
+    failure.save()
+
+
 def get_static_data():
-    result = {'motd': afe_rpc_utils.get_motd()}
+    result = {'motd': afe_rpc_utils.get_motd(),
+              'host_actions': sorted(failure_actions.HostAction.values),
+              'test_actions': sorted(failure_actions.TestAction.values)}
     return result
--- autotest/frontend/planner/rpc_interface_unittest.py 2010-04-06 
14:05:56.000000000 -0700
+++ autotest/frontend/planner/rpc_interface_unittest.py 2010-04-06 
15:56:40.000000000 -0700
@@ -5,6 +5,7 @@
 from autotest_lib.frontend import setup_django_environment
 from autotest_lib.frontend.planner import planner_test_utils, model_attributes
 from autotest_lib.frontend.planner import rpc_interface, models, rpc_utils
+from autotest_lib.frontend.planner import failure_actions
 from autotest_lib.frontend.afe import model_logic
 from autotest_lib.frontend.afe import models as afe_models
 from autotest_lib.frontend.tko import models as tko_models
@@ -162,5 +163,55 @@
         self.god.check_playback()
 
 
+    def test_process_failure(self):
+        self._setup_active_plan()
+        tko_test = tko_models.Test.objects.create(job=self._tko_job,
+                                                  machine=self._tko_machine,
+                                                  kernel=self._tko_kernel,
+                                                  status=self._running_status)
+        failure = models.TestRun.objects.create(
+                plan=self._plan,
+                test_job=self._planner_job,
+                tko_test=tko_test,
+                host=self._planner_host,
+                status=model_attributes.TestRunStatus.FAILED,
+                finalized=True, seen=False, triaged=False)
+        host_action = failure_actions.HostAction.UNBLOCK
+        test_action = failure_actions.TestAction.SKIP
+        labels = ['label1', 'label2']
+        keyvals = {'key1': 'value1',
+                   'key2': 'value2'}
+        bugs = ['bug1', 'bug2']
+        reason = 'overriden reason'
+        invalidate = True
+
+        self.god.stub_function(rpc_utils, 'process_host_action')
+        self.god.stub_function(rpc_utils, 'process_test_action')
+
+        rpc_utils.process_host_action.expect_call(self._planner_host,
+                                                  host_action)
+        rpc_utils.process_test_action.expect_call(self._planner_job,
+                                                  test_action)
+
+        rpc_interface.process_failure(failure.id, host_action, test_action,
+                                      labels, keyvals, bugs, reason, 
invalidate)
+        failure = models.TestRun.objects.get(id=failure.id)
+
+        self.assertEqual(
+                set(failure.tko_test.testlabel_set.all()),
+                set(tko_models.TestLabel.objects.filter(name__in=labels)))
+        self.assertEqual(
+                set(failure.tko_test.job.jobkeyval_set.all()),
+                set(tko_models.JobKeyval.objects.filter(
+                        key__in=keyvals.iterkeys())))
+        self.assertEqual(set(failure.bugs.all()),
+                         set(models.Bug.objects.filter(external_uid__in=bugs)))
+        self.assertEqual(failure.tko_test.reason, reason)
+        self.assertEqual(failure.invalidated, invalidate)
+        self.assertTrue(failure.seen)
+        self.assertTrue(failure.triaged)
+        self.god.check_playback()
+
+
 if __name__ == '__main__':
     unittest.main()
--- autotest/frontend/planner/rpc_utils.py      2010-04-06 14:05:56.000000000 
-0700
+++ autotest/frontend/planner/rpc_utils.py      2010-04-06 15:56:40.000000000 
-0700
@@ -2,6 +2,7 @@
 import os
 from autotest_lib.frontend.afe import models as afe_models, model_logic
 from autotest_lib.frontend.planner import models, model_attributes
+from autotest_lib.frontend.planner import failure_actions
 from autotest_lib.client.common_lib import global_config, utils
 
 
@@ -159,3 +160,50 @@
                                                        host=planner_host)
     test_run.status = status
     test_run.save()
+
+
+def _site_process_host_action_dummy(host, action):
+    return False
+
+
+def process_host_action(host, action):
+    """
+    Takes the specified action on the host
+    """
+    HostAction = failure_actions.HostAction
+    if action not in HostAction.values:
+        raise ValueError('Unexpected host action %s' % action)
+
+    site_process = utils.import_site_function(
+            __file__, 'autotest_lib.frontend.planner.site_rpc_utils',
+            'site_process_host_action', _site_process_host_action_dummy)
+
+    if not site_process(host, action):
+        # site_process_host_action returns True and and only if it matched a
+        # site-specific processing option
+        if action == HostAction.BLOCK:
+            host.blocked = True
+        elif action == HostAction.UNBLOCK:
+            host.blocked = False
+        else:
+            assert action == HostAction.REINSTALL
+            raise NotImplemented('TODO: implement reinstall')
+
+        host.save()
+
+
+def process_test_action(planner_job, action):
+    """
+    Takes the specified action for this planner job
+    """
+    TestAction = failure_actions.TestAction
+    if action not in TestAction.values:
+        raise ValueError('Unexpected test action %s' % action)
+
+    if action == TestAction.SKIP:
+        # Do nothing
+        pass
+    else:
+        assert action == TestAction.RERUN
+        planner_job.requires_rerun = True
+        planner_job.save()
--- autotest/frontend/planner/rpc_utils_unittest.py     2010-04-06 
14:05:56.000000000 -0700
+++ autotest/frontend/planner/rpc_utils_unittest.py     2010-04-06 
15:56:40.000000000 -0700
@@ -5,8 +5,7 @@
 from autotest_lib.frontend import setup_django_environment
 from autotest_lib.frontend.planner import planner_test_utils
 from autotest_lib.frontend.afe import model_logic, models as afe_models
-from autotest_lib.frontend.afe import rpc_interface as afe_rpc_interface
-from autotest_lib.frontend.planner import models, rpc_utils
+from autotest_lib.frontend.planner import models, rpc_utils, failure_actions
 from autotest_lib.client.common_lib import utils, host_queue_entry_states
 
 
@@ -133,5 +132,88 @@
         self.assertTrue(self._planner_host.complete)
 
 
+    def _replace_site_process_host_action(self, replacement):
+        self.god.stub_function(utils, 'import_site_function')
+        utils.import_site_function.expect_any_call().and_return(replacement)
+
+
+    def _remove_site_process_host_action(self):
+        def _site_process_host_action_dummy(host, action):
+            return False
+        self._replace_site_process_host_action(_site_process_host_action_dummy)
+
+
+    def test_process_host_action_block(self):
+        self._remove_site_process_host_action()
+        host = models.Host.objects.create(plan=self._plan, host=self.hosts[0],
+                                          blocked=False)
+        assert not host.blocked
+
+        rpc_utils.process_host_action(host, failure_actions.HostAction.BLOCK)
+        host = models.Host.objects.get(id=host.id)
+
+        self.assertTrue(host.blocked)
+        self.god.check_playback()
+
+
+    def test_process_host_action_unblock(self):
+        self._remove_site_process_host_action()
+        host = models.Host.objects.create(plan=self._plan, host=self.hosts[0],
+                                          blocked=True)
+        assert host.blocked
+
+        rpc_utils.process_host_action(host, failure_actions.HostAction.UNBLOCK)
+        host = models.Host.objects.get(id=host.id)
+
+        self.assertFalse(host.blocked)
+        self.god.check_playback()
+
+
+    def test_process_host_action_site(self):
+        self._remove_site_process_host_action
+        action = object()
+        failure_actions.HostAction.values.append(action)
+        host = models.Host.objects.create(plan=self._plan, host=self.hosts[0])
+
+        self.assertRaises(AssertionError, rpc_utils.process_host_action,
+                          host, action)
+        self.god.check_playback()
+
+        self._called = False
+        def _site_process_host_action(host, action):
+            self._called = True
+            return True
+        self._replace_site_process_host_action(_site_process_host_action)
+
+        rpc_utils.process_host_action(host, action)
+
+        self.assertTrue(self._called)
+        self.god.check_playback()
+
+
+    def test_process_test_action_skip(self):
+        self._setup_active_plan()
+        planner_job = self._planner_job
+        assert not planner_job.requires_rerun
+
+        rpc_utils.process_test_action(planner_job,
+                                      failure_actions.TestAction.SKIP)
+        planner_job = models.Job.objects.get(id=planner_job.id)
+
+        self.assertFalse(planner_job.requires_rerun)
+
+
+    def test_process_test_action_rerun(self):
+        self._setup_active_plan()
+        planner_job = self._planner_job
+        assert not planner_job.requires_rerun
+
+        rpc_utils.process_test_action(planner_job,
+                                      failure_actions.TestAction.RERUN)
+        planner_job = models.Job.objects.get(id=planner_job.id)
+
+        self.assertTrue(planner_job.requires_rerun)
+
+
 if __name__ == '__main__':
     unittest.main()
_______________________________________________
Autotest mailing list
[email protected]
http://test.kernel.org/cgi-bin/mailman/listinfo/autotest

Reply via email to