[autotest] Threaded asynchronous task execution on drones.

This cl does the following:
1. Creates a ThreadedTaskQueue capable of executing calls across
    drones in parallel.
2. Breaks drone_manager.refresh into 2 stages, a trigger and sync,
    thereby making it asynchronous.
3. Creates a localhost host object for the localhost drone so we run
    drone_utility through another process instead of directly importing it
    as a module. This fits better with the overall drone manager design, and
    allows us to multithread the monitoring of drones while still using
    signals within drone utility.
4. Adds stats, unittests and documentation.

TEST=Ran jobs, added unittests.
BUG=chromium:374322, chromium:380459
DEPLOY=scheduler

Change-Id: I950cf260fdc3e5d1a2d4f6fdb4f5954c6371c871
Reviewed-on: https://chromium-review.googlesource.com/207094
Reviewed-by: Prashanth B <beeps@chromium.org>
Tested-by: Prashanth B <beeps@chromium.org>
Commit-Queue: Prashanth B <beeps@chromium.org>
diff --git a/scheduler/drones_unittest.py b/scheduler/drones_unittest.py
index d395288..a3c3953 100755
--- a/scheduler/drones_unittest.py
+++ b/scheduler/drones_unittest.py
@@ -1,4 +1,5 @@
 #!/usr/bin/python
+#pylint: disable-msg=C0111
 
 """Tests for autotest_lib.scheduler.drones."""
 
@@ -12,11 +13,13 @@
 
 
 class RemoteDroneTest(unittest.TestCase):
+
     def setUp(self):
         self.god = mock.mock_god()
         self._mock_host = self.god.create_mock_class(ssh_host.SSHHost,
                                                      'mock SSHHost')
         self.god.stub_function(drones.drone_utility, 'create_host')
+        self.drone_utility_path = 'mock-drone-utility-path'
 
 
     def tearDown(self):
@@ -33,22 +36,43 @@
 
     def test_execute_calls_impl(self):
         self.god.stub_with(drones._RemoteDrone, '_drone_utility_path',
-                           'mock-drone-utility-path')
+                           self.drone_utility_path)
         drones.drone_utility.create_host.expect_call('fakehost').and_return(
                 self._mock_host)
         self._mock_host.is_up.expect_call().and_return(True)
         mock_calls = ('foo',)
         mock_result = utils.CmdResult(stdout=cPickle.dumps('mock return'))
         self._mock_host.run.expect_call(
-                'python mock-drone-utility-path',
+                'python %s' % self.drone_utility_path,
                 stdin=cPickle.dumps(mock_calls), stdout_tee=None,
                 connect_timeout=mock.is_instance_comparator(int)).and_return(
                         mock_result)
-
         drone = drones._RemoteDrone('fakehost')
         self.assertEqual('mock return', drone._execute_calls_impl(mock_calls))
         self.god.check_playback()
 
 
+    def test_execute_queued_calls(self):
+        self.god.stub_with(drones._RemoteDrone, '_drone_utility_path',
+                           self.drone_utility_path)
+        drones.drone_utility.create_host.expect_call('fakehost').and_return(
+                self._mock_host)
+        self._mock_host.is_up.expect_call().and_return(True)
+        drone = drones._RemoteDrone('fakehost')
+        mock_return={}
+        mock_return['results'] = ['mock return']
+        mock_return['warnings'] = []
+        drone.queue_call('foo')
+        mock_result = utils.CmdResult(stdout=cPickle.dumps(mock_return))
+        self._mock_host.run.expect_call(
+                'python %s' % self.drone_utility_path,
+                stdin=cPickle.dumps(drone.get_calls()), stdout_tee=None,
+                connect_timeout=mock.is_instance_comparator(int)).and_return(
+                        mock_result)
+        self.assertEqual(mock_return['results'], drone.execute_queued_calls())
+        self.god.check_playback()
+
+
+
 if __name__ == '__main__':
     unittest.main()