Add nodepool integration test

This uses the python unittest framework to run a test similar to
the existing zuul-nodepool unit test, but expecting an actual
running nodepool.

Change-Id: I769e1421c146cc3545dce606487c8c72e3d3a4c5
diff --git a/tests/nodepool/__init__.py b/tests/nodepool/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/nodepool/__init__.py
diff --git a/tests/nodepool/test_nodepool_integration.py b/tests/nodepool/test_nodepool_integration.py
new file mode 100644
index 0000000..95027fe
--- /dev/null
+++ b/tests/nodepool/test_nodepool_integration.py
@@ -0,0 +1,116 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import time
+from unittest import skip
+
+import zuul.zk
+import zuul.nodepool
+from zuul import model
+
+from tests.base import BaseTestCase
+
+
+class TestNodepoolIntegration(BaseTestCase):
+    # Tests the Nodepool interface class using a *real* nodepool and
+    # fake scheduler.
+
+    def setUp(self):
+        super(BaseTestCase, self).setUp()
+
+        self.zk_config = zuul.zk.ZooKeeperConnectionConfig('localhost')
+        self.zk = zuul.zk.ZooKeeper()
+        self.zk.connect([self.zk_config])
+
+        self.provisioned_requests = []
+        # This class implements the scheduler methods zuul.nodepool
+        # needs, so we pass 'self' as the scheduler.
+        self.nodepool = zuul.nodepool.Nodepool(self)
+
+    def waitForRequests(self):
+        # Wait until all requests are complete.
+        while self.nodepool.requests:
+            time.sleep(0.1)
+
+    def onNodesProvisioned(self, request):
+        # This is a scheduler method that the nodepool class calls
+        # back when a request is provisioned.
+        self.provisioned_requests.append(request)
+
+    def test_node_request(self):
+        # Test a simple node request
+
+        nodeset = model.NodeSet()
+        nodeset.addNode(model.Node('controller', 'fake-label'))
+        nodeset.addNode(model.Node('compute', 'fake-label'))
+        job = model.Job('testjob')
+        job.nodeset = nodeset
+        request = self.nodepool.requestNodes(None, job)
+        self.waitForRequests()
+        self.assertEqual(len(self.provisioned_requests), 1)
+        self.assertEqual(request.state, 'fulfilled')
+
+        # Accept the nodes
+        self.nodepool.acceptNodes(request)
+        nodeset = request.nodeset
+
+        for node in nodeset.getNodes():
+            self.assertIsNotNone(node.lock)
+            self.assertEqual(node.state, 'ready')
+
+        # Mark the nodes in use
+        self.nodepool.useNodeSet(nodeset)
+        for node in nodeset.getNodes():
+            self.assertEqual(node.state, 'in-use')
+
+        # Return the nodes
+        self.nodepool.returnNodeSet(nodeset)
+        for node in nodeset.getNodes():
+            self.assertIsNone(node.lock)
+            self.assertEqual(node.state, 'used')
+
+    @skip("Disabled until nodepool is ready")
+    def test_node_request_disconnect(self):
+        # Test that node requests are re-submitted after disconnect
+
+        nodeset = model.NodeSet()
+        nodeset.addNode(model.Node('controller', 'ubuntu-xenial'))
+        nodeset.addNode(model.Node('compute', 'ubuntu-xenial'))
+        job = model.Job('testjob')
+        job.nodeset = nodeset
+        self.fake_nodepool.paused = True
+        request = self.nodepool.requestNodes(None, job)
+        self.zk.client.stop()
+        self.zk.client.start()
+        self.fake_nodepool.paused = False
+        self.waitForRequests()
+        self.assertEqual(len(self.provisioned_requests), 1)
+        self.assertEqual(request.state, 'fulfilled')
+
+    @skip("Disabled until nodepool is ready")
+    def test_node_request_canceled(self):
+        # Test that node requests can be canceled
+
+        nodeset = model.NodeSet()
+        nodeset.addNode(model.Node('controller', 'ubuntu-xenial'))
+        nodeset.addNode(model.Node('compute', 'ubuntu-xenial'))
+        job = model.Job('testjob')
+        job.nodeset = nodeset
+        self.fake_nodepool.paused = True
+        request = self.nodepool.requestNodes(None, job)
+        self.nodepool.cancelRequest(request)
+
+        self.waitForRequests()
+        self.assertEqual(len(self.provisioned_requests), 0)
diff --git a/tox.ini b/tox.ini
index 9c2daee..b7cbf27 100644
--- a/tox.ini
+++ b/tox.ini
@@ -44,6 +44,11 @@
 [testenv:validate-layout]
 commands = zuul-server -c etc/zuul.conf-sample -t -l {posargs}
 
+[testenv:nodepool]
+setenv =
+   OS_TEST_PATH = ./tests/nodepool
+commands = python setup.py testr --slowest --testr-args='--concurrency=1 {posargs}'
+
 [flake8]
 # These are ignored intentionally in openstack-infra projects;
 # please don't submit patches that solely correct them or enable them.