Merge "Enable py3 tests" into feature/zuulv3
diff --git a/tests/base.py b/tests/base.py
index 21a5892..9bacf21 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -1364,7 +1364,10 @@
 
     def run(self):
         while self._running:
-            self._run()
+            try:
+                self._run()
+            except Exception:
+                self.log.exception("Error in fake nodepool:")
             time.sleep(0.1)
 
     def _run(self):
@@ -1462,7 +1465,10 @@
         path = self.REQUEST_ROOT + '/' + oid
         data = json.dumps(request).encode('utf8')
         self.log.debug("Fulfilling node request: %s %s" % (oid, data))
-        self.client.set(path, data)
+        try:
+            self.client.set(path, data)
+        except kazoo.exceptions.NoNodeError:
+            self.log.debug("Node request %s %s disappeared" % (oid, data))
 
 
 class ChrootedKazooFixture(fixtures.Fixture):
diff --git a/zuul/ansible/library/command.py b/zuul/ansible/library/command.py
index 328ae7b..52de5a4 100644
--- a/zuul/ansible/library/command.py
+++ b/zuul/ansible/library/command.py
@@ -123,6 +123,8 @@
 
 LOG_STREAM_FILE = '/tmp/console.log'
 PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?')
+# List to save stdout log lines in as we collect them
+_log_lines = []
 
 
 class Console(object):
@@ -150,6 +152,7 @@
             line = fd.readline()
             if not line:
                 break
+            _log_lines.append(line)
             if not line.endswith('\n'):
                 line += '\n'
                 newline_warning = True
@@ -330,7 +333,8 @@
         # cmd.stdout.close()
 
         # ZUUL: stdout and stderr are in the console log file
-        stdout = ''
+        # ZUUL: return the saved log lines so we can ship them back
+        stdout = ''.join(_log_lines)
         stderr = ''
 
         rc = cmd.returncode
diff --git a/zuul/model.py b/zuul/model.py
index fe66397..7f6223b 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -490,9 +490,10 @@
         self.stat = None
         self.uid = uuid4().hex
         self.id = None
-        # Zuul internal failure flag (not stored in ZK so it's not
+        # Zuul internal flags (not stored in ZK so they are not
         # overwritten).
         self.failed = False
+        self.canceled = False
 
     @property
     def fulfilled(self):
diff --git a/zuul/nodepool.py b/zuul/nodepool.py
index e94b950..8f6489c 100644
--- a/zuul/nodepool.py
+++ b/zuul/nodepool.py
@@ -38,11 +38,11 @@
     def cancelRequest(self, request):
         self.log.info("Canceling node request %s" % (request,))
         if request.uid in self.requests:
+            request.canceled = True
             try:
                 self.sched.zk.deleteNodeRequest(request)
             except Exception:
                 self.log.exception("Error deleting node request:")
-            del self.requests[request.uid]
 
     def useNodeSet(self, nodeset):
         self.log.info("Setting nodeset %s in use" % (nodeset,))
@@ -98,6 +98,10 @@
         if request.uid not in self.requests:
             return False
 
+        if request.canceled:
+            del self.requests[request.uid]
+            return False
+
         if request.state in (model.STATE_FULFILLED, model.STATE_FAILED):
             self.log.info("Node request %s %s" % (request, request.state))
 
@@ -119,6 +123,11 @@
 
         self.log.info("Accepting node request %s" % (request,))
 
+        if request.canceled:
+            self.log.info("Ignoring canceled node request %s" % (request,))
+            # The request was already deleted when it was canceled
+            return
+
         locked = False
         if request.fulfilled:
             # If the request suceeded, try to lock the nodes.
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 2e9bef2..a67973e 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -871,6 +871,8 @@
         build_set = request.build_set
 
         self.nodepool.acceptNodes(request)
+        if request.canceled:
+            return
 
         if build_set is not build_set.item.current_build_set:
             self.log.warning("Build set %s is not current" % (build_set,))