Add ability to stop drivers

This adds an optional method to the driver interface that will stop a
driver when the scheduler's connections object is stopped. This is
useful to clean up drivers that have state that are not connections. In
particular it prevents timers from leaking apscheduler threads all over
the test suite.

For defensive purposes we add code to start a new apsched within the
driver if the driver has been previously stopped. This is because
without having connection objects which can be replaced the driver may
stay in use though it currently only stops when the scheduler stops.

Change-Id: Ibfc638d88c77f18bdc9f9509776a665a9edfc2ae
diff --git a/zuul/driver/__init__.py b/zuul/driver/__init__.py
index 1cc5235..57b5cf9 100644
--- a/zuul/driver/__init__.py
+++ b/zuul/driver/__init__.py
@@ -68,6 +68,17 @@
         """
         pass
 
+    def stop(self):
+        """Stop the driver from running.
+
+        This method is optional; the base implementation does nothing.
+
+        This method is called when the connection registry is stopped
+        allowing you additionally stop any running Driver computation
+        not specific to a connection.
+        """
+        pass
+
 
 @six.add_metaclass(abc.ABCMeta)
 class ConnectionInterface(object):
diff --git a/zuul/driver/timer/__init__.py b/zuul/driver/timer/__init__.py
index 00ddb26..115e6af 100644
--- a/zuul/driver/timer/__init__.py
+++ b/zuul/driver/timer/__init__.py
@@ -38,6 +38,10 @@
 
     def reconfigure(self, tenant):
         self._removeJobs(tenant)
+        if not self.apsched:
+            # Handle possible reuse of the driver without connection objects.
+            self.apsched = BackgroundScheduler()
+            self.apsched.start()
         self._addJobs(tenant)
 
     def _removeJobs(self, tenant):
@@ -87,7 +91,9 @@
             self.sched.addEvent(event)
 
     def stop(self):
-        self.apsched.shutdown()
+        if self.apsched:
+            self.apsched.shutdown()
+            self.apsched = None
 
     def getTrigger(self, connection_name, config=None):
         return timertrigger.TimerTrigger(self, config)
diff --git a/zuul/lib/connections.py b/zuul/lib/connections.py
index 262c7cb..03cba2a 100644
--- a/zuul/lib/connections.py
+++ b/zuul/lib/connections.py
@@ -67,6 +67,8 @@
     def stop(self):
         for connection_name, connection in self.connections.items():
             connection.onStop()
+        for driver in self.drivers.values():
+            driver.stop()
 
     def configure(self, config):
         # Register connections from the config