Merge "Remove ZUUL_UUID" into feature/zuulv3
diff --git a/doc/source/admin/components.rst b/doc/source/admin/components.rst
index cc9d181..e37c05c 100644
--- a/doc/source/admin/components.rst
+++ b/doc/source/admin/components.rst
@@ -296,10 +296,29 @@
      finger_port=79
 
 **git_dir**
-  Directory that Zuul should clone local git repositories to::
+  Directory that Zuul should clone local git repositories to.  The
+  executor keeps a local copy of every git repository it works with to
+  speed operations and perform speculative merging.
+
+  This should be on the same filesystem as **job_dir** so that when
+  git repos are cloned into the job workspaces, they can be
+  hard-linked to the local git cache.  Example::
 
      git_dir=/var/lib/zuul/git
 
+**job_dir**
+  Directory that Zuul should use to hold temporary job directories.
+  When each job is run, a new entry will be created under this
+  directory to hold the configuration and scratch workspace for that
+  job.  It will be deleted at the end of the job (unless the
+  `--keep-jobdir` command line option is specified).
+
+  This should be on the same filesystem as **git_dir** so that when
+  git repos are cloned into the job workspaces, they can be
+  hard-linked to the local git cache.  Example::
+
+     job_dir=/var/lib/zuul/jobs
+
 **log_config**
   Path to log config file for the executor process::
 
diff --git a/doc/source/admin/tenants.rst b/doc/source/admin/tenants.rst
index 60873a9..18ec381 100644
--- a/doc/source/admin/tenants.rst
+++ b/doc/source/admin/tenants.rst
@@ -33,7 +33,7 @@
           config-projects:
             - common-config
             - shared-jobs:
-                include: jobs
+                include: job
           untrusted-projects:
             - zuul-jobs:
                 shadow: common-config
diff --git a/tests/base.py b/tests/base.py
index 1080570..ea7559c 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -2222,19 +2222,12 @@
         # Make sure no jobs are running
         self.assertEqual({}, self.executor_server.job_workers)
         # Make sure that git.Repo objects have been garbage collected.
-        repos = []
         gc.disable()
         gc.collect()
         for obj in gc.get_objects():
             if isinstance(obj, git.Repo):
                 self.log.debug("Leaked git repo object: 0x%x %s" %
                                (id(obj), repr(obj)))
-                for ref in gc.get_referrers(obj):
-                    self.log.debug("  Referrer %s" % (repr(ref)))
-                repos.append(obj)
-        if repos:
-            for obj in gc.garbage:
-                self.log.debug("  Garbage %s" % (repr(obj)))
         gc.enable()
         self.assertEmptyQueues()
         self.assertNodepoolState()
diff --git a/zuul/ansible/callback/zuul_stream.py b/zuul/ansible/callback/zuul_stream.py
index 9622f24..078e1c9 100644
--- a/zuul/ansible/callback/zuul_stream.py
+++ b/zuul/ansible/callback/zuul_stream.py
@@ -284,6 +284,19 @@
         # Log an extra blank line to get space after each task
         self._log("")
 
+    def v2_runner_on_skipped(self, result):
+        reason = result._result.get('skip_reason')
+        if reason:
+            # No reason means it's an item, which we'll log differently
+            self._log_message(result, status='skipping', msg=reason)
+
+    def v2_runner_item_on_skipped(self, result):
+        reason = result._result.get('skip_reason')
+        if reason:
+            self._log_message(result, status='skipping', msg=reason)
+        else:
+            self._log_message(result, status='skipping')
+
     def v2_runner_on_ok(self, result):
         if (self._play.strategy == 'free'
                 and self._last_task_banner != result._task._uuid):
diff --git a/zuul/cmd/executor.py b/zuul/cmd/executor.py
index 6a1a214..06ef0ba 100755
--- a/zuul/cmd/executor.py
+++ b/zuul/cmd/executor.py
@@ -82,7 +82,7 @@
 
             self.log.info("Starting log streamer")
             streamer = zuul.lib.log_streamer.LogStreamer(
-                self.user, '0.0.0.0', self.finger_port, self.jobroot_dir)
+                self.user, '0.0.0.0', self.finger_port, self.job_dir)
 
             # Keep running until the parent dies:
             pipe_read = os.fdopen(pipe_read)
@@ -111,15 +111,15 @@
 
         self.user = get_default(self.config, 'executor', 'user', 'zuul')
 
-        if self.config.has_option('executor', 'jobroot_dir'):
-            self.jobroot_dir = os.path.expanduser(
-                self.config.get('executor', 'jobroot_dir'))
-            if not os.path.isdir(self.jobroot_dir):
-                print("Invalid jobroot_dir: {jobroot_dir}".format(
-                    jobroot_dir=self.jobroot_dir))
+        if self.config.has_option('executor', 'job_dir'):
+            self.job_dir = os.path.expanduser(
+                self.config.get('executor', 'job_dir'))
+            if not os.path.isdir(self.job_dir):
+                print("Invalid job_dir: {job_dir}".format(
+                    job_dir=self.job_dir))
                 sys.exit(1)
         else:
-            self.jobroot_dir = tempfile.gettempdir()
+            self.job_dir = tempfile.gettempdir()
 
         self.setup_logging('executor', 'log_config')
         self.log = logging.getLogger("zuul.Executor")
@@ -134,7 +134,7 @@
 
         ExecutorServer = zuul.executor.server.ExecutorServer
         self.executor = ExecutorServer(self.config, self.connections,
-                                       jobdir_root=self.jobroot_dir,
+                                       jobdir_root=self.job_dir,
                                        keep_jobdir=self.args.keep_jobdir,
                                        log_streaming_port=self.finger_port)
         self.executor.start()
diff --git a/zuul/driver/bubblewrap/__init__.py b/zuul/driver/bubblewrap/__init__.py
index 3609a71..ee81bb1 100644
--- a/zuul/driver/bubblewrap/__init__.py
+++ b/zuul/driver/bubblewrap/__init__.py
@@ -81,8 +81,8 @@
     def stop(self):
         pass
 
-    def setMountsMap(self, state_dir, ro_dirs=[], rw_dirs=[]):
-        self.mounts_map = {'ro': ro_dirs, 'rw': [state_dir] + rw_dirs}
+    def setMountsMap(self, ro_dirs=[], rw_dirs=[]):
+        self.mounts_map = {'ro': ro_dirs, 'rw': [] + rw_dirs}
 
     def getPopen(self, **kwargs):
         # Set zuul_dir if it was not passed in
@@ -180,12 +180,16 @@
     driver = BubblewrapDriver()
 
     parser = argparse.ArgumentParser()
+    parser.add_argument('--ro-bind', nargs='+')
+    parser.add_argument('--rw-bind', nargs='+')
     parser.add_argument('work_dir')
     parser.add_argument('run_args', nargs='+')
     cli_args = parser.parse_args()
 
     ssh_auth_sock = os.environ.get('SSH_AUTH_SOCK')
 
+    driver.setMountsMap(cli_args.ro_bind, cli_args.rw_bind)
+
     popen = driver.getPopen(work_dir=cli_args.work_dir,
                             ssh_auth_sock=ssh_auth_sock)
     x = popen(cli_args.run_args)
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index 4448db8..720f503 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -1370,11 +1370,12 @@
                               '%s_ro_dirs' % opt_prefix)
         rw_dirs = get_default(self.executor_server.config, 'executor',
                               '%s_rw_dirs' % opt_prefix)
-        state_dir = get_default(self.executor_server.config, 'executor',
-                                'state_dir', '/var/lib/zuul', expand_user=True)
         ro_dirs = ro_dirs.split(":") if ro_dirs else []
         rw_dirs = rw_dirs.split(":") if rw_dirs else []
-        self.executor_server.execution_wrapper.setMountsMap(state_dir, ro_dirs,
+
+        ro_dirs.append(self.executor_server.ansible_dir)
+
+        self.executor_server.execution_wrapper.setMountsMap(ro_dirs,
                                                             rw_dirs)
 
         popen = self.executor_server.execution_wrapper.getPopen(