Merge "Simplify github status descriptions" into feature/zuulv3
diff --git a/doc/source/admin/drivers/github.rst b/doc/source/admin/drivers/github.rst
index ed577a5..cbbc5cc 100644
--- a/doc/source/admin/drivers/github.rst
+++ b/doc/source/admin/drivers/github.rst
@@ -75,6 +75,12 @@
   job's working directory, they appear under this directory name.
   ``canonical_hostname=git.example.com``
 
+**verify_ssl**
+  Optional: Enable or disable ssl verification for GitHub Enterprise.  This is
+  useful for a connection to a test installation. If not specified, defaults
+  to ``true``.
+  ``verify_ssl=true``
+
 Trigger Configuration
 ---------------------
 GitHub webhook events can be configured as triggers.
diff --git a/doc/source/admin/tenants.rst b/doc/source/admin/tenants.rst
index 18ec381..1f8f7db 100644
--- a/doc/source/admin/tenants.rst
+++ b/doc/source/admin/tenants.rst
@@ -28,6 +28,7 @@
 
   - tenant:
       name: my-tenant
+      max-nodes-per-job: 5
       source:
         gerrit:
           config-projects:
@@ -48,6 +49,10 @@
   characters (ASCII letters, numbers, hyphen and underscore) and you
   should avoid changing it unless necessary.
 
+**max-nodes-per-job** (optional)
+  The maximum number of nodes a job can request, default to 5.
+  A '-1' value removes the limit.
+
 **source** (required)
   A dictionary of sources to consult for projects.  A tenant may
   contain projects from multiple sources; each of those sources must
diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst
new file mode 100644
index 0000000..98bee10
--- /dev/null
+++ b/doc/source/glossary.rst
@@ -0,0 +1,57 @@
+.. _glossary:
+
+Glossary
+========
+
+.. glossary::
+   :sorted:
+
+   check
+
+      By convention, the name of a pipeline which performs pre-merge
+      tests.  Such a pipeline might be triggered by creating a new
+      change or pull request.  It may run with changes which have not
+      yet seen any human review, so care must be taken in selecting
+      the kinds of jobs to run, and what resources will be available
+      to them in order to avoid misuse of the system or credential
+      compromise.
+
+   config-project
+
+      One of two types of projects which may be specified by the
+      administrator in the tenant config file.  A config-project is
+      primarily tasked with holding configuration information and job
+      content for Zuul.  Jobs which are defined in a config-project
+      are run with elevated privileges, and all Zuul configuration
+      items are available for use.  It is expected that changes to
+      config-projects will undergo careful scrutiny before being
+      merged.
+
+   gate
+
+      By convention, the name of a pipeline which performs project
+      gating.  Such a pipeline might be triggered by a core team
+      member approving a change or pull request.  It should have a
+      :value:`dependent <pipeline.manager.dependent>` pipeline manager
+      so that it can combine and sequence changes as they are
+      approved.
+
+   reporter
+
+      A reporter is a :ref:`pipeline attribute <reporters>` which
+      describes the action performed when an item is dequeued after
+      its jobs complete.  Reporters are implemented by :ref:`drivers`
+      so their actions may be quite varied.  For example, a reporter
+      might leave feedback in a remote system on a proposed change,
+      send email, or store information in a database.
+
+   untrusted-project
+
+      One of two types of projects which may be specified by the
+      administrator in the tenant config file.  An untrusted-project
+      is one whose primary focus is not to operate Zuul, but rather it
+      is one of the projects being tested or deployed.  The Zuul
+      configuration language available to these projects is somewhat
+      restricted, and jobs defined in these projects run in a
+      restricted execution environment since they may be operating on
+      changes which have not yet undergone review.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 0aefa84..677e958 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -21,9 +21,15 @@
    admin/index
    developer/index
 
+.. toctree::
+   :hidden:
+
+   glossary
+
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`search`
+* :ref:`glossary`
 
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index 3eccbb5..25d192c 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -16,15 +16,15 @@
 they specify one of two security contexts for that project.  A
 *config-project* is one which is primarily tasked with holding
 configuration information and job content for Zuul.  Jobs which are
-defined in a *config-project* are run with elevated privileges, and
-all Zuul configuration items are available for use.  It is expected
-that changes to *config-projects* will undergo careful scrutiny before
-being merged.
+defined in a config-project are run with elevated privileges, and all
+Zuul configuration items are available for use.  It is expected that
+changes to config-projects will undergo careful scrutiny before being
+merged.
 
 An *untrusted-project* is a project whose primary focus is not to
 operate Zuul, but rather it is one of the projects being tested or
 deployed.  The Zuul configuration language available to these projects
-is somewhat restricted (as detailed in individual section below), and
+is somewhat restricted (as detailed in individual sections below), and
 jobs defined in these projects run in a restricted execution
 environment since they may be operating on changes which have not yet
 undergone review.
@@ -33,23 +33,23 @@
 ---------------------
 
 When Zuul starts, it examines all of the git repositories which are
-specified by the system administrator in :ref:`tenant-config` and searches
-for files in the root of each repository. Zuul looks first for a file named
-`zuul.yaml` or a directory named `zuul.d`, and if they are not found,
-`.zuul.yaml` or `.zuul.d` (with a leading dot). In the case of an
-*untrusted-project*, the configuration from every branch is included,
-however, in the case of a *config-project*, only the `master` branch is
-examined.
+specified by the system administrator in :ref:`tenant-config` and
+searches for files in the root of each repository. Zuul looks first
+for a file named ``zuul.yaml`` or a directory named ``zuul.d``, and if
+they are not found, ``.zuul.yaml`` or ``.zuul.d`` (with a leading
+dot). In the case of an :term:`untrusted-project`, the configuration
+from every branch is included, however, in the case of a
+:term:`config-project`, only the ``master`` branch is examined.
 
 When a change is proposed to one of these files in an
-*untrusted-project*, the configuration proposed in the change is
-merged into the running configuration so that any changes to Zuul's
+untrusted-project, the configuration proposed in the change is merged
+into the running configuration so that any changes to Zuul's
 configuration are self-testing as part of that change.  If there is a
 configuration error, no jobs will be run and the error will be
 reported by any applicable pipelines.  In the case of a change to a
-*config-project*, the new configuration is parsed and examined for
+config-project, the new configuration is parsed and examined for
 errors, but the new configuration is not used in testing the change.
-This is because configuration in *config-projects* is able to access
+This is because configuration in config-projects is able to access
 elevated privileges and should always be reviewed before being merged.
 
 As soon as a change containing a Zuul configuration change merges to
@@ -59,14 +59,15 @@
 Configuration Items
 -------------------
 
-The `zuul.yaml` and `.zuul.yaml` configuration files are
+The ``zuul.yaml`` and ``.zuul.yaml`` configuration files are
 YAML-formatted and are structured as a series of items, each of which
 is described below.
 
-In the case of a `zuul.d` directory, Zuul recurses the directory and extends
-the configuration using all the .yaml files in the sorted path order.
-For example, to keep job's variants in a separate file, it needs to be loaded
-after the main entries, for example using number prefixes in file's names::
+In the case of a ``zuul.d`` directory, Zuul recurses the directory and
+extends the configuration using all the .yaml files in the sorted path
+order.  For example, to keep job's variants in a separate file, it
+needs to be loaded after the main entries, for example using number
+prefixes in file's names::
 
 * zuul.d/pipelines.yaml
 * zuul.d/projects.yaml
@@ -87,24 +88,24 @@
 projects.
 
 By way of example, one of the primary uses of Zuul is to perform
-project gating.  To do so, one can create a *gate* pipeline which
-tells Zuul that when a certain event (such as approval by a code
+project gating.  To do so, one can create a :term:`gate` pipeline
+which tells Zuul that when a certain event (such as approval by a code
 reviewer) occurs, the corresponding change or pull request should be
 enqueued into the pipeline.  When that happens, the jobs which have
-been configured to run for that project in the *gate* pipeline are
-run, and when they complete, the pipeline reports the results to the
-user.
+been configured to run for that project in the gate pipeline are run,
+and when they complete, the pipeline reports the results to the user.
 
-Pipeline configuration items may only appear in *config-projects*.
+Pipeline configuration items may only appear in :term:`config-projects
+<config-project>`.
 
 Generally, a Zuul administrator would define a small number of
 pipelines which represent the workflow processes used in their
 environment.  Each project can then be added to the available
 pipelines as appropriate.
 
-Here is an example *check* pipeline, which runs whenever a new
+Here is an example :term:`check` pipeline, which runs whenever a new
 patchset is created in Gerrit.  If the associated jobs all report
-success, the pipeline reports back to Gerrit with a *Verified* vote of
+success, the pipeline reports back to Gerrit with ``Verified`` vote of
 +1, or if at least one of them fails, a -1:
 
 .. code-block:: yaml
@@ -183,14 +184,16 @@
          dependent pipeline manager, see: :doc:`gating`.
 
    .. attr:: allow-secrets
+      :default: false
 
       This is a boolean which can be used to prevent jobs which
       require secrets from running in this pipeline.  Some pipelines
       run on proposed changes and therefore execute code which has not
       yet been reviewed.  In such a case, allowing a job to use a
       secret could result in that secret being exposed.  The default
-      is False, meaning that in order to run jobs with secrets, this
-      must be explicitly enabled on each Pipeline where that is safe.
+      is ``false``, meaning that in order to run jobs with secrets,
+      this must be explicitly enabled on each Pipeline where that is
+      safe.
 
       For more information, see :ref:`secret`.
 
@@ -200,16 +203,19 @@
       pipeline.  It may appear in the status page or in documentation.
 
    .. attr:: success-message
+      :default: Build successful.
 
       The introductory text in reports when all the voting jobs are
-      successful.  Defaults to "Build successful."
+      successful.
 
    .. attr:: failure-message
+      :default: Build failed.
 
       The introductory text in reports when at least one voting job
-      fails.  Defaults to "Build failed."
+      fails.
 
    .. attr:: merge-failure-message
+      :default: Merge failed.
 
       The introductory text in the message reported when a change
       fails to merge with the current state of the repository.
@@ -234,7 +240,7 @@
 
    .. attr:: require
 
-      If this section is present, it establishes pre-requisites for
+      If this section is present, it establishes prerequisites for
       any kind of item entering the Pipeline.  Regardless of how the
       item is to be enqueued (via any trigger or automatic dependency
       resolution), the conditions specified here must be met or the
@@ -247,23 +253,25 @@
 
    .. attr:: reject
 
-      If this section is present, it establishes pre-requisites that
+      If this section is present, it establishes prerequisites that
       can block an item from being enqueued. It can be considered a
-      negative version of **require**.
+      negative version of :attr:`pipeline.require`.
 
       Requirements are loaded from their connection name. The driver
       type of the connection will dictate which options are available.
       See :ref:`drivers`.
 
    .. attr:: dequeue-on-new-patchset
+      :default: true
 
       Normally, if a new patchset is uploaded to a change that is in a
       pipeline, the existing entry in the pipeline will be removed
       (with jobs canceled and any dependent changes that can no longer
       merge as well.  To suppress this behavior (and allow jobs to
-      continue running), set this to ``false``.  Default: ``true``.
+      continue running), set this to ``false``.
 
    .. attr:: ignore-dependencies
+      :default: false
 
       In any kind of pipeline (dependent or independent), Zuul will
       attempt to enqueue all dependencies ahead of the current change
@@ -271,9 +279,10 @@
       the results of each change regardless of the results of changes
       ahead).  To ignore dependencies completely in an independent
       pipeline, set this to ``true``.  This option is ignored by
-      dependent pipelines.  The default is: ``false``.
+      dependent pipelines.
 
    .. attr:: precedence
+      :default: normal
 
       Indicates how the build scheduler should prioritize jobs for
       different pipelines.  Each pipeline may have one precedence,
@@ -281,23 +290,25 @@
       ones with lower.  The value should be one of ``high``,
       ``normal``, or ``low``.  Default: ``normal``.
 
-   The following options configure *reporters*.  Reporters are
-   complementary to triggers; where a trigger is an event on a
-   connection which causes Zuul to enqueue an item, a reporter is the
-   action performed on a connection when an item is dequeued after its
-   jobs complete.  The actual syntax for a reporter is defined by the
-   driver which implements it.  See :ref:`drivers` for more
-   information.
+   .. _reporters:
+
+   The following options configure :term:`reporters <reporter>`.
+   Reporters are complementary to triggers; where a trigger is an
+   event on a connection which causes Zuul to enqueue an item, a
+   reporter is the action performed on a connection when an item is
+   dequeued after its jobs complete.  The actual syntax for a reporter
+   is defined by the driver which implements it.  See :ref:`drivers`
+   for more information.
 
    .. attr:: success
 
       Describes where Zuul should report to if all the jobs complete
       successfully.  This section is optional; if it is omitted, Zuul
       will run jobs and do nothing on success -- it will not report at
-      all.  If the section is present, the listed reporters will be
-      asked to report on the jobs.  The reporters are listed by their
-      connection name. The options available depend on the driver for
-      the supplied connection.
+      all.  If the section is present, the listed :term:`reporters
+      <reporter>` will be asked to report on the jobs.  The reporters
+      are listed by their connection name. The options available
+      depend on the driver for the supplied connection.
 
    .. attr:: failure
 
@@ -329,14 +340,15 @@
 
    .. attr:: disable-after-consecutive-failures
 
-      If set, a pipeline can enter a ''disabled'' state if too many
+      If set, a pipeline can enter a *disabled* state if too many
       changes in a row fail. When this value is exceeded the pipeline
-      will stop reporting to any of the ``success``, ``failure`` or
-      ``merge-failure`` reporters and instead only report to the
-      ``disabled`` reporters.  (No ``start`` reports are made when a
+      will stop reporting to any of the **success**, **failure** or
+      **merge-failure** reporters and instead only report to the
+      **disabled** reporters.  (No **start** reports are made when a
       pipeline is disabled).
 
    .. attr:: window
+      :default: 20
 
       Dependent pipeline managers only. Zuul can rate limit dependent
       pipelines in a manner similar to TCP flow control.  Jobs are
@@ -344,47 +356,65 @@
       actionable window for the pipeline. The initial length of this
       window is configurable with this value. The value given should
       be a positive integer value. A value of ``0`` disables rate
-      limiting on the DependentPipelineManager.  Default: ``20``.
+      limiting on the :value:`dependent pipeline manager
+      <pipeline.manager.dependent>`.
 
    .. attr:: window-floor
+      :default: 3
 
       Dependent pipeline managers only. This is the minimum value for
       the window described above. Should be a positive non zero
-      integer value.  Default: ``3``.
+      integer value.
 
    .. attr:: window-increase-type
+      :default: linear
 
       Dependent pipeline managers only. This value describes how the
-      window should grow when changes are successfully merged by
-      zuul. A value of ``linear`` indicates that
-      ``window-increase-factor`` should be added to the previous
-      window value. A value of ``exponential`` indicates that
-      ``window-increase-factor`` should be multiplied against the
-      previous window value and the result will become the window
-      size.  Default: ``linear``.
+      window should grow when changes are successfully merged by zuul.
+
+      .. value:: linear
+
+         Indicates that **window-increase-factor** should be added to
+         the previous window value.
+
+      .. value:: exponential
+
+         Indicates that **window-increase-factor** should be
+         multiplied against the previous window value and the result
+         will become the window size.
 
    .. attr:: window-increase-factor
+      :default: 1
 
       Dependent pipeline managers only. The value to be added or
       multiplied against the previous window value to determine the
-      new window after successful change merges.  Default: ``1``.
+      new window after successful change merges.
 
    .. attr:: window-decrease-type
+      :default: exponential
 
       Dependent pipeline managers only. This value describes how the
       window should shrink when changes are not able to be merged by
-      Zuul. A value of ``linear`` indicates that
-      ``window-decrease-factor`` should be subtracted from the
-      previous window value. A value of ``exponential`` indicates that
-      ``window-decrease-factor`` should be divided against the
-      previous window value and the result will become the window
-      size.  Default: ``exponential``.
+      Zuul.
+
+      .. value:: linear
+
+         Indicates that **window-decrease-factor** should be
+         subtracted from the previous window value.
+
+      .. value:: exponential
+
+         Indicates that **window-decrease-factor** should be divided
+         against the previous window value and the result will become
+         the window size.
 
    .. attr:: window-decrease-factor
+      :default: 2
 
-      Dependent pipline managers only. The value to be subtracted or
-      divided against the previous window value to determine the new
-      window after unsuccessful change merges.  Default: ``2``.
+      :value:`Dependent pipeline managers
+      <pipeline.manager.dependent>` only. The value to be subtracted
+      or divided against the previous window value to determine the
+      new window after unsuccessful change merges.
 
 
 .. _job:
@@ -407,7 +437,7 @@
 jobs on the system should have, progressing through stages of
 specialization before arriving at a particular job.  A job may inherit
 from any other job in any project (however, if the other job is marked
-as `final`, some attributes may not be overidden).
+as ``final``, some attributes may not be overidden).
 
 Jobs also support a concept called variance.  The first time a job
 definition appears is called the reference definition of the job.
@@ -475,8 +505,8 @@
 
    .. attr:: parent
 
-      Specifie s a job to inherit from.  The parent job can be defined
-      in this or a ny other project.  Any attributes not specified on
+      Specifies a job to inherit from.  The parent job can be defined
+      in this or any other project.  Any attributes not specified on
       a job will be collected from its parent.
 
    .. attr:: description
@@ -487,16 +517,18 @@
       ReStructuredText.
 
    .. attr:: success-message
+      :default: SUCCESS
 
-      Normally when a job succeeds, the string "SUCCESS" is reported
+      Normally when a job succeeds, the string ``SUCCESS`` is reported
       as the result for the job.  If set, this option may be used to
-      supply a different string.  Default: "SUCCESS".
+      supply a different string.
 
    .. attr:: failure-message
+      :default: FAILURE
 
-      Normally when a job fails, the string "FAILURE" is reported as
+      Normally when a job fails, the string ``FAILURE`` is reported as
       the result for the job.  If set, this option may be used to
-      supply a different string.  Default: "FAILURE".
+      supply a different string.
 
    .. attr:: success-url
 
@@ -510,7 +542,7 @@
       **zuul.log_url** is set, then the two will be combined to
       produce the URL used for the report.  This can be used to
       specify that certain jobs should "deep link" into the stored job
-      artifacts.  Default: none.
+      artifacts.
 
    .. attr:: failure-url
 
@@ -518,26 +550,27 @@
       Otherwise behaves the same as **success-url**.
 
    .. attr:: hold-following-changes
+      :default: false
 
       In a dependent pipeline, this option may be used to indicate
       that no jobs should start on any items which depend on the
       current item until this job has completed successfully.  This
       may be used to conserve build resources, at the expense of
       inhibiting the parallelization which speeds the processing of
-      items in a dependent pipeline.  A boolean value, default: false.
+      items in a dependent pipeline.
 
    .. attr:: voting
+      :default: true
 
       Indicates whether the result of this job should be used in
-      determining the overall result of the item.  A boolean value,
-      default: true.
+      determining the overall result of the item.
 
    .. attr:: semaphore
 
       The name of a :ref:`semaphore` which should be acquired and
       released when the job begins and ends.  If the semaphore is at
       maximum capacity, then Zuul will wait until it can be acquired
-      before starting the job.  Default: none.
+      before starting the job.
 
    .. attr:: tags
 
@@ -551,7 +584,7 @@
       strings, and when inheriting jobs or applying variants, tags
       accumulate in a set, so the result is always a set of all the
       tags from all the jobs and variants used in constructing the
-      frozen job, with no duplication.  Default: none.
+      frozen job, with no duplication.
 
    .. attr:: branches
 
@@ -580,45 +613,46 @@
              branch: stable/2.0
              nodes: old-release
 
-      In some cases, Zuul uses an implied value for the branch specifier
-      if none is supplied:
+      In some cases, Zuul uses an implied value for the branch
+      specifier if none is supplied:
 
-      * For a job definition in a *config-project*, no implied branch
-        specifier is used.  If no branch specifier appears, the job
-        applies to all branches.
+      * For a job definition in a :term:`config-project`, no implied
+        branch specifier is used.  If no branch specifier appears, the
+        job applies to all branches.
 
-      * In the case of an *untrusted-project*, no implied branch specifier
-        is applied to the reference definition of a job.  That is to say,
-        that if the first appearance of the job definition appears without
-        a branch specifier, then it will apply to all branches.  Note that
-        when collecting its configuration, Zuul reads the `master` branch
-        of a given project first, then other branches in alphabetical
-        order.
+      * In the case of an :term:`untrusted-project`, no implied branch
+        specifier is applied to the reference definition of a job.
+        That is to say, that if the first appearance of the job
+        definition appears without a branch specifier, then it will
+        apply to all branches.  Note that when collecting its
+        configuration, Zuul reads the ``master`` branch of a given
+        project first, then other branches in alphabetical order.
 
-      * Any further job variants other than the reference definition in an
-        *untrusted-project* will, if they do not have a branch specifier,
-        will have an implied branch specifier for the current branch
-        applied.
+      * Any further job variants other than the reference definition
+        in an untrusted-project will, if they do not have a branch
+        specifier, will have an implied branch specifier for the
+        current branch applied.
 
       This allows for the very simple and expected workflow where if a
-      project defines a job on the master branch with no branch specifier,
-      and then creates a new branch based on master, any changes to that
-      job definition within the new branch only affect that branch.
+      project defines a job on the ``master`` branch with no branch
+      specifier, and then creates a new branch based on ``master``,
+      any changes to that job definition within the new branch only
+      affect that branch.
 
    .. attr:: files
 
       This attribute indicates that the job should only run on changes
       where the specified files are modified.  This is a regular
-      expression or list of regular expressions.  Default: none.
+      expression or list of regular expressions.
 
    .. attr:: irrelevant-files
 
-      This is a negative complement of `files`.  It indicates that the
-      job should run unless *all* of the files changed match this
-      list.  In other words, if the regular expression `docs/.*` is
+      This is a negative complement of **files**.  It indicates that
+      the job should run unless *all* of the files changed match this
+      list.  In other words, if the regular expression ``docs/.*`` is
       supplied, then this job will not run if the only files changed
       are in the docs directory.  A regular expression or list of
-      regular expressions.  Default: none.
+      regular expressions.
 
    .. attr:: auth
 
@@ -626,6 +660,7 @@
       This is a dictionary with two potential keys:
 
       .. attr:: inherit
+         :default: false
 
          A boolean indicating that the authentication information
          referenced by this job should be able to be inherited by
@@ -635,7 +670,7 @@
          that secret information is unable to be exposed by a child
          job which may alter the job's behavior.  If it is safe for
          the contents of the authentication section to be used by
-         child jobs, set this to ``true``.  Default: ``false``.
+         child jobs, set this to ``true``.
 
       .. attr:: secrets
 
@@ -675,9 +710,9 @@
       tested applies to a different branch (this is only likely to be
       useful if there is some cross-branch interaction with some
       component of the system being tested).  See also the
-      project-specific **override-branch** attribute under
-      **required-projects** to apply this behavior to a subset of a
-      job's projects.
+      project-specific :attr:`job.required-projects.override-branch`
+      attribute to apply this behavior to a subset of a job's
+      projects.
 
    .. attr:: timeout
 
@@ -687,13 +722,14 @@
       timeout is highly recommended.
 
    .. attr:: attempts
+      :default: 3
 
       When Zuul encounters an error running a job's pre-run playbook,
       Zuul will stop and restart the job.  Errors during the main or
       post-run -playbook phase of a job are not affected by this
       parameter (they are reported immediately).  This parameter
       controls the number of attempts to make before an error is
-      reported.  Default: 3.
+      reported.
 
    .. attr:: pre-run
 
@@ -720,15 +756,15 @@
 
       The name of the main playbook for this job.  This parameter is
       not normally necessary, as it defaults to a playbook with the
-      same name as the job inside of the `playbooks/` directory (e.g.,
-      the `foo` job would default to `playbooks/foo`.  However, if a
-      playbook with a different name is needed, it can be specified
-      here.  The file extension is not required, but the full path
-      within the repo is.  When a child inherits from a parent, a
-      playbook with the name of the child job is implicitly searched
-      first, before falling back on the playbook used by the parent
-      job (unless the child job specifies a ``run`` attribute, in
-      which case that value is used).  Example:
+      same name as the job inside of the ``playbooks/`` directory
+      (e.g., the ``foo`` job would default to ``playbooks/foo``.
+      However, if a playbook with a different name is needed, it can
+      be specified here.  The file extension is not required, but the
+      full path within the repo is.  When a child inherits from a
+      parent, a playbook with the name of the child job is implicitly
+      searched first, before falling back on the playbook used by the
+      parent job (unless the child job specifies a ``run`` attribute,
+      in which case that value is used).  Example:
 
       .. code-block:: yaml
 
@@ -758,25 +794,25 @@
       child adds its own pre and post playbooks, then any roles added
       by the child will be available to the child's playbooks.  This
       is so that a job which inherits from a parent does not
-      inadvertantly alter the behavior of the parent's playbooks by
+      inadvertently alter the behavior of the parent's playbooks by
       the addition of conflicting roles.  Roles added by a child will
       appear before those it inherits from its parent.
 
       A project which supplies a role may be structured in one of two
       configurations: a bare role (in which the role exists at the
       root of the project), or a contained role (in which the role
-      exists within the `roles/` directory of the project, perhaps
+      exists within the ``roles/`` directory of the project, perhaps
       along with other roles).  In the case of a contained role, the
-      `roles/` directory of the project is added to the role search
+      ``roles/`` directory of the project is added to the role search
       path.  In the case of a bare role, the project itself is added
       to the role search path.  In case the name of the project is not
       the name under which the role should be installed (and therefore
-      referenced from Ansible), the `name` attribute may be used to
+      referenced from Ansible), the ``name`` attribute may be used to
       specify an alternate.
 
       A job automatically has the project in which it is defined added
       to the roles path if that project appears to contain a role or
-      `roles/` directory.  By default, the project is added to the
+      ``roles/`` directory.  By default, the project is added to the
       path under its own name, however, that may be changed by
       explicitly listing the project in the roles list in the usual
       way.
@@ -827,7 +863,7 @@
          This attribute is used to override that behavior and indicate
          that this job should, regardless of the branch for the queue
          item, use the indicated branch instead, for only this
-         project.  See also the **override-branch** attribute of jobs
+         project.  See also the :attr:`job.override-branch` attribute
          to apply the same behavior to all projects in a job.
 
    .. attr:: vars
@@ -856,7 +892,7 @@
       all projects permitted to use the job.  The current project
       (where the job is defined) is not automatically included, so if
       it should be able to run this job, then it must be explicitly
-      listed.  Default: the empty list (all projects may use the job).
+      listed.  By default, all projects may use the job.
 
 
 .. _project:
@@ -865,14 +901,14 @@
 ~~~~~~~
 
 A project corresponds to a source code repository with which Zuul is
-configured to interact.  The main responsibility of the `Project`
+configured to interact.  The main responsibility of the project
 configuration item is to specify which jobs should run in which
-pipelines for a given project.  Within each `Project` definition, a
-section for each `Pipeline` may appear.  This project-pipeline
-definition is what determines how a project participates in a
-pipeline.
+pipelines for a given project.  Within each project definition, a
+section for each :ref:`pipeline <pipeline>` may appear.  This
+project-pipeline definition is what determines how a project
+participates in a pipeline.
 
-Consider the following `Project` definition::
+Consider the following project definition::
 
   - project:
       name: yoyodyne
@@ -886,13 +922,13 @@
           - unit-tests
           - integration-tests
 
-The project has two project-pipeline stanzas, one for the `check`
-pipeline, and one for `gate`.  Each specifies which jobs shuld run
-when a change for that project enteres the respective pipeline -- when
-a change enters `check`, the `check-syntax` and `unit-test` jobs are
-run.
+The project has two project-pipeline stanzas, one for the ``check``
+pipeline, and one for ``gate``.  Each specifies which jobs should run
+when a change for that project enters the respective pipeline -- when
+a change enters ``check``, the ``check-syntax`` and ``unit-test`` jobs
+are run.
 
-Pipelines which use the dependent pipeline manager (e.g., the `gate`
+Pipelines which use the dependent pipeline manager (e.g., the ``gate``
 example shown earlier) maintain separate queues for groups of
 projects.  When Zuul serializes a set of changes which represent
 future potential project states, it must know about all of the
@@ -912,12 +948,12 @@
 for a dependent pipeline, set the ``queue`` parameter to the same
 value for those projects.
 
-The `gate` project-pipeline definition above specifies that this
-project participates in the `integrated` shared queue for that
+The ``gate`` project-pipeline definition above specifies that this
+project participates in the ``integrated`` shared queue for that
 pipeline.
 
 In addition to a project-pipeline definition for one or more
-`Pipelines`, the following attributes may appear in a Project:
+pipelines, the following attributes may appear in a project:
 
 **name** (required)
   The name of the project.  If Zuul is configured with two or more
@@ -994,21 +1030,68 @@
 groups of node types once and referring to them by name, job
 configuration may be simplified.
 
-A Nodeset requires two attributes:
+.. code-block:: yaml
 
-**name** (required)
-  The name of the Nodeset, to be referenced by a :ref:`job`.
+   - nodeset:
+       name: nodeset1
+       nodes:
+         - name: controller
+           label: controller-label
+         - name: compute1
+           label: compute-label
+         - name: compute2
+           label: compute-label
+       groups:
+         - name: ceph-osd
+           nodes:
+             - controller
+         - name: ceph-monitor
+           nodes:
+             - controller
+             - compute1
+             - compute2
 
-**nodes** (required)
-  A list of node definitions, each of which has the following format:
+.. attr:: nodeset
 
-  **name** (required)
-    The name of the node.  This will appear in the Ansible inventory
-    for the job.
+   A Nodeset requires two attributes:
 
-  **label** (required)
-    The Nodepool label for the node.  Zuul will request a node with
-    this label.
+   .. attr:: name
+      :required:
+
+      The name of the Nodeset, to be referenced by a :ref:`job`.
+
+   .. attr:: nodes
+      :required:
+
+      A list of node definitions, each of which has the following format:
+
+      .. attr:: name
+         :required:
+
+         The name of the node.  This will appear in the Ansible inventory
+         for the job.
+
+      .. attr:: label
+         :required:
+
+         The Nodepool label for the node.  Zuul will request a node with
+         this label.
+
+   .. attr:: groups
+
+      Additional groups can be defined which are accessible from the ansible
+      playbooks.
+
+      .. attr:: name
+         :required:
+
+         The name of the group to be referenced by an ansible playbook.
+
+      .. attr:: nodes
+         :required:
+
+         The nodes that shall be part of the group. This is specified as a list
+         of strings.
 
 .. _semaphore:
 
@@ -1023,20 +1106,27 @@
 
 Semaphores are never subject to dynamic reconfiguration.  If the value
 of a semaphore is changed, it will take effect only when the change
-where it is updated is merged.  An example follows::
+where it is updated is merged.  An example follows:
 
-  - semaphore:
-      name: semaphore-foo
-      max: 5
-  - semaphore:
-      name: semaphore-bar
-      max: 3
+.. code-block:: yaml
 
-The following attributes are available:
+   - semaphore:
+       name: semaphore-foo
+       max: 5
+   - semaphore:
+       name: semaphore-bar
+       max: 3
 
-**name** (required)
-  The name of the semaphore, referenced by jobs.
+.. attr:: semaphore
 
-**max**
-  The maximum number of running jobs which can use this semaphore.
-  Defaults to 1.
+   The following attributes are available:
+
+   .. attr:: name
+      :required:
+
+      The name of the semaphore, referenced by jobs.
+
+   .. attr:: max
+      :default: 1
+
+      The maximum number of running jobs which can use this semaphore.
diff --git a/doc/source/user/jobs.rst b/doc/source/user/jobs.rst
index 80ce3f9..b6a8564 100644
--- a/doc/source/user/jobs.rst
+++ b/doc/source/user/jobs.rst
@@ -175,6 +175,10 @@
 **zuul.project.name**
   The name of the project, excluding hostname.  E.g., `org/project`.
 
+**zuul.project.short_name**
+  The name of the project, excluding directories or organizations.
+  E.g., `project`.
+
 **zuul.project.canonical_hostname**
   The canonical hostname where the project lives.  E.g.,
   `git.example.com`.
@@ -198,21 +202,25 @@
 
   **project.name**
     The name of the project, excluding hostname.  E.g., `org/project`.
-  
+
+  **project.short_name**
+    The name of the project, excluding directories or organizations.
+    E.g., `project`.
+
   **project.canonical_hostname**
     The canonical hostname where the project lives.  E.g.,
     `git.example.com`.
-  
+
   **project.canonical_name**
     The full canonical name of the project including hostname.  E.g.,
     `git.example.com/org/project`.
-  
+
   **branch**
     The target branch of the change (without the `refs/heads/` prefix).
-  
+
   **change**
     The identifier for the change.
-  
+
   **patchset**
     The patchset identifier for the change.  If a change is revised,
     this will have a different value.
diff --git a/etc/zuul.conf-sample b/etc/zuul.conf-sample
index 0ae42a2..6e79f9b 100644
--- a/etc/zuul.conf-sample
+++ b/etc/zuul.conf-sample
@@ -23,7 +23,6 @@
 git_dir=/var/lib/zuul/git
 ;git_user_email=zuul@example.com
 ;git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 default_username=zuul
diff --git a/tests/fixtures/config/multi-tenant/main.yaml b/tests/fixtures/config/multi-tenant/main.yaml
index 3ae7756..4916905 100644
--- a/tests/fixtures/config/multi-tenant/main.yaml
+++ b/tests/fixtures/config/multi-tenant/main.yaml
@@ -10,6 +10,7 @@
 
 - tenant:
     name: tenant-two
+    max-nodes-per-job: 10
     source:
       gerrit:
         config-projects:
diff --git a/tests/fixtures/zuul-connections-gerrit-and-github.conf b/tests/fixtures/zuul-connections-gerrit-and-github.conf
index 04f2cc2..49e53c7 100644
--- a/tests/fixtures/zuul-connections-gerrit-and-github.conf
+++ b/tests/fixtures/zuul-connections-gerrit-and-github.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-connections-merger.conf b/tests/fixtures/zuul-connections-merger.conf
index df465d5..771fc50 100644
--- a/tests/fixtures/zuul-connections-merger.conf
+++ b/tests/fixtures/zuul-connections-merger.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-connections-multiple-gerrits.conf b/tests/fixtures/zuul-connections-multiple-gerrits.conf
index 66a6926..c6eb39e 100644
--- a/tests/fixtures/zuul-connections-multiple-gerrits.conf
+++ b/tests/fixtures/zuul-connections-multiple-gerrits.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/merger-git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-connections-same-gerrit.conf b/tests/fixtures/zuul-connections-same-gerrit.conf
index 3262294..a4f558d 100644
--- a/tests/fixtures/zuul-connections-same-gerrit.conf
+++ b/tests/fixtures/zuul-connections-same-gerrit.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/merger-git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-disk-accounting.conf b/tests/fixtures/zuul-disk-accounting.conf
index b0ae48e..6f02fa4 100644
--- a/tests/fixtures/zuul-disk-accounting.conf
+++ b/tests/fixtures/zuul-disk-accounting.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/merger-git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-git-driver.conf b/tests/fixtures/zuul-git-driver.conf
index 4321871..b24b0a1 100644
--- a/tests/fixtures/zuul-git-driver.conf
+++ b/tests/fixtures/zuul-git-driver.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-github-driver.conf b/tests/fixtures/zuul-github-driver.conf
index 732c30a..a96bde2 100644
--- a/tests/fixtures/zuul-github-driver.conf
+++ b/tests/fixtures/zuul-github-driver.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-push-reqs.conf b/tests/fixtures/zuul-push-reqs.conf
index cb699e0..2217f94 100644
--- a/tests/fixtures/zuul-push-reqs.conf
+++ b/tests/fixtures/zuul-push-reqs.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-sql-driver-bad.conf b/tests/fixtures/zuul-sql-driver-bad.conf
index 1f1b75f..e2a9438 100644
--- a/tests/fixtures/zuul-sql-driver-bad.conf
+++ b/tests/fixtures/zuul-sql-driver-bad.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/merger-git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul-sql-driver.conf b/tests/fixtures/zuul-sql-driver.conf
index 688d65b..e0ff3d5 100644
--- a/tests/fixtures/zuul-sql-driver.conf
+++ b/tests/fixtures/zuul-sql-driver.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/merger-git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/fixtures/zuul.conf b/tests/fixtures/zuul.conf
index d6de76c..7bc8c59 100644
--- a/tests/fixtures/zuul.conf
+++ b/tests/fixtures/zuul.conf
@@ -8,7 +8,6 @@
 git_dir=/tmp/zuul-test/merger-git
 git_user_email=zuul@example.com
 git_user_name=zuul
-zuul_url=http://zuul.example.com/p
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
diff --git a/tests/unit/test_disk_accountant.py b/tests/unit/test_disk_accountant.py
index db38d45..7081b53 100644
--- a/tests/unit/test_disk_accountant.py
+++ b/tests/unit/test_disk_accountant.py
@@ -41,18 +41,18 @@
                             cache_dir)
         da.start()
 
-        jobdir = os.path.join(jobs_dir, '012345')
-        os.mkdir(jobdir)
-        testfile = os.path.join(jobdir, 'tfile')
-        with open(testfile, 'w') as tf:
-            tf.write(2 * 1024 * 1024 * '.')
-
-        # da should catch over-limit dir within 5 seconds
-        for i in range(0, 50):
-            if jobdir in executor_server.stopped_jobs:
-                break
-            time.sleep(0.1)
         try:
+            jobdir = os.path.join(jobs_dir, '012345')
+            os.mkdir(jobdir)
+            testfile = os.path.join(jobdir, 'tfile')
+            with open(testfile, 'w') as tf:
+                tf.write(2 * 1024 * 1024 * '.')
+
+            # da should catch over-limit dir within 5 seconds
+            for i in range(0, 50):
+                if jobdir in executor_server.stopped_jobs:
+                    break
+                time.sleep(0.1)
             self.assertEqual(set([jobdir]), executor_server.stopped_jobs)
         finally:
             da.stop()
@@ -70,6 +70,7 @@
         da = DiskAccountant(jobs_dir, 1, executor_server.stopJobByJobDir,
                             cache_dir, executor_server.usage)
         da.start()
+        self.addCleanup(da.stop)
 
         jobdir = os.path.join(jobs_dir, '012345')
         os.mkdir(jobdir)
@@ -87,9 +88,6 @@
             if jobdir in executor_server.used:
                 break
             time.sleep(0.1)
-        try:
-            self.assertEqual(set(), executor_server.stopped_jobs)
-            self.assertIn(jobdir, executor_server.used)
-            self.assertTrue(executor_server.used[jobdir] <= 1)
-        finally:
-            da.stop()
+        self.assertEqual(set(), executor_server.stopped_jobs)
+        self.assertIn(jobdir, executor_server.used)
+        self.assertTrue(executor_server.used[jobdir] <= 1)
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
old mode 100644
new mode 100755
index 2d68089..44cd5f6
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -948,3 +948,41 @@
         self.waitUntilSettled()
         self.assertHistory([
             dict(name='dd-big-empty-file', result='ABORTED', changes='1,1')])
+
+
+class TestMaxNodesPerJob(AnsibleZuulTestCase):
+    tenant_config_file = 'config/multi-tenant/main.yaml'
+
+    def test_max_nodes_reached(self):
+        in_repo_conf = textwrap.dedent(
+            """
+            - job:
+                name: test-job
+                nodes:
+                  - name: node01
+                    label: fake
+                  - name: node02
+                    label: fake
+                  - name: node03
+                    label: fake
+                  - name: node04
+                    label: fake
+                  - name: node05
+                    label: fake
+                  - name: node06
+                    label: fake
+            """)
+        file_dict = {'.zuul.yaml': in_repo_conf}
+        A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A',
+                                           files=file_dict)
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertIn('The job "test-job" exceeds tenant max-nodes-per-job 5.',
+                      A.messages[0], "A should fail because of nodes limit")
+
+        B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A',
+                                           files=file_dict)
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertNotIn("exceeds tenant max-nodes", B.messages[0],
+                         "B should not fail because of nodes limit")
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 3fc10b8..4b9b8a0 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -67,6 +67,15 @@
         super(DuplicateNodeError, self).__init__(message)
 
 
+class MaxNodeError(Exception):
+    def __init__(self, job, tenant):
+        message = textwrap.dedent("""\
+        The job "{job}" exceeds tenant max-nodes-per-job {maxnodes}.""")
+        message = textwrap.fill(message.format(
+            job=job.name, maxnodes=tenant.max_nodes_per_job))
+        super(MaxNodeError, self).__init__(message)
+
+
 class DuplicateGroupError(Exception):
     def __init__(self, nodeset, group):
         message = textwrap.dedent("""\
@@ -475,6 +484,9 @@
                 for conf_node in conf_nodes:
                     node = model.Node(conf_node['name'], conf_node['label'])
                     ns.addNode(node)
+            if tenant.max_nodes_per_job != -1 and \
+               len(ns) > tenant.max_nodes_per_job:
+                raise MaxNodeError(job, tenant)
             job.nodeset = ns
 
         if 'required-projects' in conf:
@@ -952,6 +964,7 @@
     @staticmethod
     def getSchema(connections=None):
         tenant = {vs.Required('name'): str,
+                  'max-nodes-per-job': int,
                   'source': TenantParser.validateTenantSources(connections)}
         return vs.Schema(tenant)
 
@@ -960,6 +973,8 @@
                  cached):
         TenantParser.getSchema(connections)(conf)
         tenant = model.Tenant(conf['name'])
+        if conf.get('max-nodes-per-job') is not None:
+            tenant.max_nodes_per_job = conf['max-nodes-per-job']
         tenant.unparsed_config = conf
         unparsed_config = model.UnparsedTenantConfig()
         # tpcs is TenantProjectConfigs
diff --git a/zuul/driver/gerrit/gerritconnection.py b/zuul/driver/gerrit/gerritconnection.py
index 63f3093..d23857f 100644
--- a/zuul/driver/gerrit/gerritconnection.py
+++ b/zuul/driver/gerrit/gerritconnection.py
@@ -25,8 +25,10 @@
 import queue
 import voluptuous as v
 
+from typing import Dict, List
+
 from zuul.connection import BaseConnection
-from zuul.model import Ref, Tag, Branch
+from zuul.model import Ref, Tag, Branch, Project
 from zuul import exceptions
 from zuul.driver.gerrit.gerritmodel import GerritChange, GerritTriggerEvent
 
@@ -614,7 +616,7 @@
                                    (record.get('number'),))
         return changes
 
-    def getProjectBranches(self, project):
+    def getProjectBranches(self, project: Project) -> List[str]:
         refs = self.getInfoRefs(project)
         heads = [str(k[len('refs/heads/'):]) for k in refs.keys()
                  if k.startswith('refs/heads/')]
@@ -711,8 +713,8 @@
             chunk, more_changes = _query_chunk("%s %s" % (query, resume))
         return alldata
 
-    def _uploadPack(self, project_name):
-        cmd = "git-upload-pack %s" % project_name
+    def _uploadPack(self, project: Project) -> str:
+        cmd = "git-upload-pack %s" % project.name
         out, err = self._ssh(cmd, "0000")
         return out
 
@@ -757,7 +759,7 @@
             raise Exception("Gerrit error executing %s" % command)
         return (out, err)
 
-    def getInfoRefs(self, project):
+    def getInfoRefs(self, project: Project) -> Dict[str, str]:
         try:
             data = self._uploadPack(project)
         except:
diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py
index b095215..48603a0 100644
--- a/zuul/driver/github/githubconnection.py
+++ b/zuul/driver/github/githubconnection.py
@@ -363,6 +363,12 @@
             'canonical_hostname', self.server)
         self.source = driver.getSource(self)
 
+        # ssl verification must default to true
+        verify_ssl = self.connection_config.get('verify_ssl', 'true')
+        self.verify_ssl = True
+        if verify_ssl.lower() == 'false':
+            self.verify_ssl = False
+
         self._github = None
         self.app_id = None
         self.app_key = None
@@ -395,7 +401,11 @@
     def _createGithubClient(self):
         if self.server != 'github.com':
             url = 'https://%s/' % self.server
-            github = github3.GitHubEnterprise(url)
+            if not self.verify_ssl:
+                # disabling ssl verification is evil so emit a warning
+                self.log.warning("SSL verification disabled for "
+                                 "GitHub Enterprise")
+            github = github3.GitHubEnterprise(url, verify=self.verify_ssl)
         else:
             github = github3.GitHub()
 
diff --git a/zuul/executor/client.py b/zuul/executor/client.py
index dfd4225..cf70520 100644
--- a/zuul/executor/client.py
+++ b/zuul/executor/client.py
@@ -152,6 +152,7 @@
         # replace the environment variables below.
         project = dict(
             name=item.change.project.name,
+            short_name=item.change.project.name.split('/')[-1],
             canonical_hostname=item.change.project.canonical_hostname,
             canonical_name=item.change.project.canonical_name)
 
@@ -181,6 +182,7 @@
             d = dict()
             d['project'] = dict(
                 name=i.change.project.name,
+                short_name=i.change.project.name.split('/')[-1],
                 canonical_hostname=i.change.project.canonical_hostname,
                 canonical_name=i.change.project.canonical_name)
             if hasattr(i.change, 'number'):
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index 21c4cf1..22ca59f 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -507,7 +507,6 @@
         # perhaps hostname+pid.
         self.hostname = socket.gethostname()
         self.log_streaming_port = log_streaming_port
-        self.zuul_url = config.get('merger', 'zuul_url')
         self.merger_lock = threading.Lock()
         self.verbose = False
         self.command_map = dict(
@@ -798,8 +797,7 @@
                                          args['branch'], args['files'],
                                          args.get('dirs', []))
         result = dict(updated=True,
-                      files=files,
-                      zuul_url=self.zuul_url)
+                      files=files)
         job.sendWorkComplete(json.dumps(result))
 
     def merge(self, job):
@@ -808,8 +806,7 @@
             ret = self.merger.mergeChanges(args['items'], args.get('files'),
                                            args.get('dirs', []),
                                            args.get('repo_state'))
-        result = dict(merged=(ret is not None),
-                      zuul_url=self.zuul_url)
+        result = dict(merged=(ret is not None))
         if ret is None:
             result['commit'] = result['files'] = result['repo_state'] = None
         else:
diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py
index 09b09d7..dfb3238 100644
--- a/zuul/manager/__init__.py
+++ b/zuul/manager/__init__.py
@@ -675,7 +675,6 @@
         build_set = event.build_set
         item = build_set.item
         build_set.merge_state = build_set.COMPLETE
-        build_set.zuul_url = event.zuul_url
         if event.merged:
             build_set.commit = event.commit
             build_set.files.setFiles(event.files)
diff --git a/zuul/merger/client.py b/zuul/merger/client.py
index e354d5d..dd9c8d5 100644
--- a/zuul/merger/client.py
+++ b/zuul/merger/client.py
@@ -128,7 +128,6 @@
 
     def onBuildCompleted(self, job):
         data = getJobData(job)
-        zuul_url = data.get('zuul_url')
         merged = data.get('merged', False)
         updated = data.get('updated', False)
         commit = data.get('commit')
@@ -140,7 +139,7 @@
                       (job, merged, updated, commit))
         job.setComplete()
         if job.build_set:
-            self.sched.onMergeCompleted(job.build_set, zuul_url,
+            self.sched.onMergeCompleted(job.build_set,
                                         merged, updated, commit, files,
                                         repo_state)
         # The test suite expects the job to be removed from the
diff --git a/zuul/merger/server.py b/zuul/merger/server.py
index 555a4bc..c342e1a 100644
--- a/zuul/merger/server.py
+++ b/zuul/merger/server.py
@@ -28,7 +28,6 @@
 
     def __init__(self, config, connections={}):
         self.config = config
-        self.zuul_url = config.get('merger', 'zuul_url')
 
         merge_root = get_default(self.config, 'merger', 'git_dir',
                                  '/var/lib/zuul/merger-git')
@@ -97,8 +96,7 @@
         ret = self.merger.mergeChanges(
             args['items'], args.get('files'),
             args.get('dirs'), args.get('repo_state'))
-        result = dict(merged=(ret is not None),
-                      zuul_url=self.zuul_url)
+        result = dict(merged=(ret is not None))
         if ret is None:
             result['commit'] = result['files'] = result['repo_state'] = None
         else:
@@ -113,6 +111,5 @@
                                      args['branch'], args['files'],
                                      args.get('dirs'))
         result = dict(updated=True,
-                      files=files,
-                      zuul_url=self.zuul_url)
+                      files=files)
         job.sendWorkComplete(json.dumps(result))
diff --git a/zuul/model.py b/zuul/model.py
index 90cc81d..27ed243 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -501,6 +501,9 @@
             name = ''
         return '<NodeSet %s%s%s>' % (name, self.nodes, self.groups)
 
+    def __len__(self):
+        return len(self.nodes)
+
 
 class NodeRequest(object):
     """A request for a set of nodes."""
@@ -1237,7 +1240,6 @@
         self.previous_build_set = None
         self.uuid = None
         self.commit = None
-        self.zuul_url = None
         self.dependent_items = None
         self.merger_items = None
         self.unable_to_merge = False
@@ -2448,6 +2450,7 @@
 class Tenant(object):
     def __init__(self, name):
         self.name = name
+        self.max_nodes_per_job = 5
         self.layout = None
         # The unparsed configuration from the main zuul config for
         # this tenant.
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 0a33b00..a64d9e0 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -136,17 +136,15 @@
     """A remote merge operation has completed
 
     :arg BuildSet build_set: The build_set which is ready.
-    :arg str zuul_url: The URL of the Zuul Merger.
     :arg bool merged: Whether the merge succeeded (changes with refs).
     :arg bool updated: Whether the repo was updated (changes without refs).
     :arg str commit: The SHA of the merged commit (changes with refs).
     :arg dict repo_state: The starting repo state before the merge.
     """
 
-    def __init__(self, build_set, zuul_url, merged, updated, commit,
+    def __init__(self, build_set, merged, updated, commit,
                  files, repo_state):
         self.build_set = build_set
-        self.zuul_url = zuul_url
         self.merged = merged
         self.updated = updated
         self.commit = commit
@@ -317,11 +315,11 @@
         self.wake_event.set()
         self.log.debug("Done adding complete event for build: %s" % build)
 
-    def onMergeCompleted(self, build_set, zuul_url, merged, updated,
+    def onMergeCompleted(self, build_set, merged, updated,
                          commit, files, repo_state):
         self.log.debug("Adding merge complete event for build set: %s" %
                        build_set)
-        event = MergeCompletedEvent(build_set, zuul_url, merged,
+        event = MergeCompletedEvent(build_set, merged,
                                     updated, commit, files, repo_state)
         self.result_event_queue.put(event)
         self.wake_event.set()
diff --git a/zuul/sphinx/zuul.py b/zuul/sphinx/zuul.py
index 7798720..b4133d7 100644
--- a/zuul/sphinx/zuul.py
+++ b/zuul/sphinx/zuul.py
@@ -75,6 +75,7 @@
 
     option_spec = {
         'required': lambda x: x,
+        'default': lambda x: x,
     }
 
     def before_content(self):
@@ -88,11 +89,21 @@
 
     def handle_signature(self, sig, signode):
         path = self.get_path()
+        signode['is_multiline'] = True
+        line = addnodes.desc_signature_line()
+        line['add_permalink'] = True
         for x in path:
-            signode += addnodes.desc_addname(x + '.', x + '.')
-        signode += addnodes.desc_name(sig, sig)
+            line += addnodes.desc_addname(x + '.', x + '.')
+        line += addnodes.desc_name(sig, sig)
         if 'required' in self.options:
-            signode += addnodes.desc_annotation(' (required)', ' (required)')
+            line += addnodes.desc_annotation(' (required)', ' (required)')
+        signode += line
+        if 'default' in self.options:
+            line = addnodes.desc_signature_line()
+            line += addnodes.desc_type('Default: ', 'Default: ')
+            line += nodes.literal(self.options['default'],
+                                  self.options['default'])
+            signode += line
         return sig
 
 
diff --git a/zuul/webapp.py b/zuul/webapp.py
index e4feaa0..b9129b8 100644
--- a/zuul/webapp.py
+++ b/zuul/webapp.py
@@ -133,11 +133,15 @@
                 return handler(path, '', request)
 
         # Now try with a tenant_name stripped
-        tenant_name = request.path.split('/')[1]
-        path = request.path.replace('/' + tenant_name, '')
+        x, tenant_name, path = request.path.split('/', 2)
+        path = '/' + path
         # Handle keys
         if path.startswith('/keys'):
-            return self._handle_keys(request, path)
+            try:
+                return self._handle_keys(request, path)
+            except Exception as e:
+                self.log.exception("Issue with _handle_keys")
+                raise
         for path_re, handler in self.routes.values():
             if path_re.match(path):
                 return handler(path, tenant_name, request)