infra-specs/specs/zuulv3.rst
James E. Blair 0aa4fb552d Zuulv3: clarify job dependencies example
This implements a suggestion from clarkb in
Iae04c401378880061acf962d1f35fbd06cebe2a8.

Change-Id: I9b138e557cff0e4118e8a62206e0f15ede2b2240
2017-03-13 10:32:00 -07:00

874 lines
33 KiB
ReStructuredText

::
Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
This work is licensed under a Creative Commons Attribution 3.0
Unported License.
http://creativecommons.org/licenses/by/3.0/legalcode
=======
Zuul v3
=======
Storyboard: https://storyboard.openstack.org/#!/story/2000305
As part of an effort to streamline Zuul and Nodepool into an
easier-to-use system that scales better and is more flexible, some
significant changes are proposed to both. The overall goals are:
* Make zuul scale to thousands of projects.
* Make Zuul more multi-tenant friendly.
* Make it easier to express complex scenarios in layout.
* Make nodepool more useful for non virtual nodes.
* Make nodepool more efficient for multi-node tests.
* Remove need for long-running slaves.
* Make it easier to use Zuul for continuous deployment.
* Support private installations using external test resources.
* Keep zuul simple.
Problem Description
===================
Nodepool
--------
Currently Nodepool is designed to supply single-use nodes to jobs. We
have extended it to support supplying multiple nodes to a single job
for multi-node tests, however the implementation of that is very
inefficient and will not scale with heavy use. The current system
uses a special multi-node label to indicate that a job requires the
number of nodes provided by that label. It means that pairs (or
triplets, or larger sets) of servers need to be created together,
which may cause delays while specific servers are created, and servers
may sit idle because they are destined only for use in multi-node jobs
and can not be used for jobs which require fewer (or more) nodes.
Nodepool also currently has no ability to supply inventory of nodes
which are not created and destroyed. It would be nice to allow
nodepool to mediate access to real hardware, for instance.
Zuul
----
Zuul is currently fundamentally a single-tenant application. Some
folks want to use it in a multi-tenant environment. Even within
OpenStack, we have use for multitenancy. OpenStack might be one
tenant, and each stackforge project might be another. Even without
the OpenStack/stackforge divide, we may still want the kind of
separation multi-tenancy can provide. Multi-tenancy should allow for
multiple tenants to have the same job and project names, but also to
share configuration if desired. Tenants should be able to define
their own pipelines, and optionally control some or all of their own
configuration.
OpenStack's Zuul configuration currently uses Jenkins and Jenkins Job
Builder (JJB) to define jobs. We use very few features of Jenkins and
Zuul was designed to facilitate our move away from Jenkins. The JJB
job definitions are complex, and some of the complexity comes from
specific Jenkins behaviors that we currently need to support.
Additionally, there is no support for orchestrating actions across
multiple test nodes.
Proposed Change
===============
Nodepool
--------
Nodepool should be made to support explicit node requests and
releases. That is to say, it should act more like its name -- a node
pool. It should support the existing model of single-use nodes as
well as long-term nodes that need mediated access.
Nodepool should use ZooKeeper to fulfill node requests from Zuul. A
request should be made using the Zookeeper `priority queue`_ construct
at the path::
/nodepool/requests/500-123
node_types: [list of node types]
requestor: descriptive string of requestor (eg zuul)
created_time: <unix timestamp>
state: requested | pending | fulfilled | failed
state_time: <unix timestamp>
nodes: [list of node ids]
declined_by: [list of launchers declining this request]
The name of the request node, "500-123", is composed of the priority
("500") followed by the sequence number ("123"). After creating the
request node, Zuul should read the request node back and set a watch
on it. If the read associated with the watch set indicates that the
request has already been fulfilled, it should proceed to use the
nodes, otherwise, it should wait to be notified by the watch. Note
special care will need to be taken to re-set watches if the connection
to ZooKeeper is reset. The pattern of read to test whether request is
fulfilled and set watch if not can be repeated as many times as
necessary until the request is fulfilled.
This model is much more efficient for multi-node tests, where we will
no longer have to have special multinode labels. Instead the
multinode configuration can be much more ad-hoc and vary per job.
Requests for nodes are in a FIFO queue and will be satisfied in the
order received according to node availability. This should make
demand and allocation calculations much simpler.
A node type is simply a string such as 'trusty', that corresponds to
an entry in the nodepool config file.
The component of Nodepool which will process these requests is known
as a "launcher". A Nodepool system may consiste of multiple launchers
(for instance, one launcher for each cloud provider). Each launcher
will continuously scan the request queue (sorted by request id) and
attempt to process each request in sorted order. A single launcher
may be engaged in satisfying multiple requests simultaneously.
When satisfying a request, Nodepool will first obtain a lock on the
request using the Zookeeper `lock construct`_ at the path::
/nodepool/requests-lock/005-123
It will then attempt to satisfy the request from available nodes, and
failing that, cause new nodes to be created. When multiple nodes are
requested together, nodepool will return nodes within the same AZ of
the same provider.
A simple algorithm which does not require that any launcher know about
any other launchers is:
#. Obtain next request
#. If image not available, decline
#. If request > quota, decline
#. If request < quota and request > available nodes (due to current
usage), begin satisfying the request and do not process further
requests until satisfied
#. If request < quota and request < available nodes, satisfy the
request and continue processing further requests
Since Nodepool consists of multiple launchers, each of which is only
aware of its own configuration, there is no single component of the
system that can determine if a request is permanently unsatisfiable.
In order to avoid requests remaining in the queue indefinitely, each
launcher will register itself at the path::
/nodepool/launchers/<hostname>-<pid>-<tid>
When a launcher is unable to satisfy a request, it will modify the
request node (while still holding the lock) and add its identifier to
the field `declined_by`. It should then check the contents of this
field and compare it to the current contents of `/nodepool/launchers`.
If all of the currently on-line launchers are represented in
`declined_by` the request should be marked `failed` in the `state`
field. The update of the request node will notify Zuul via the
previously set watch, however, it will check the state, and if the
request is not failed or fulfilled, will simply re-set the watch. The
launcher will then release the lock and, if the request is not yet
failed, other launchers will be able to attempt to process the
request. When processing the request queue, the launcher should avoid
obtaining the lock on any request it has already declined (though it
should always perform a check for whether the request should be marked
as failed in case the last launcher went off-line shortly after it
declined the request).
Requests should not be marked as failed for transient errors (if a
node destined for a request fails to boot, another node should take
its place). Only in the case where it is impossible for Nodepool to
satisfy a request should it be marked as failed. In that case, Zuul
may report job failure as a result.
If at any point Nodepool detects that the ephemeral request node has
been deleted, it should return any allocated nodes to the pool.
Each node should have a record in Zookeeper at the path::
/nodepool/nodes/456
type: ubuntu-trusty
provider: rax
region: ord
az: None
public_ipv4: <IPv4 address>
private_ipv4: <IPv4 address>
public_ipv6: <IPv6 address>
allocated_to: <request id>
state: building | testing | ready | in-use | used | hold | deleting
created_time: <unix timestamp>
updated_time: <unix timestamp>
image_id: /nodepool/image/ubuntu-trusty/builds/123/provider/rax/images/456
launcher: <hostname>-<pid>-<tid>
The node should start in the `building` state and if being created in
response to demand, set `allocated_to` to the id of the node request.
While building, Nodepool should hold a lock on the node at::
/nodepool/nodes/456/lock
Once complete, the metadata should be updated, the state set to
`ready`, and the lock released. Once all of the nodes in a request
are ready, Nodepool should update the state of the request to
`fulfilled` and release the lock. Zuul, which will have been notified
of the change by the watch it set, should then obtain the lock on each
node in the request and update its state to 'in-use'. It should then
delete the request node.
When Zuul is finished with the nodes, it should set their states to
`used` and release their locks.
Nodepool will then decide whether the nodes should be returned to the
pool, rebuilt, or deleted according to the type of node and current
demand.
If any Nodepool or Zuul component fails at any point in this process,
it should be possible to determine this and either recover or at least
avoid leaking nodes. Nodepool should periodically examine all of the
nodes and look for the following conditions:
* A node allocated to a request that does not exist where the node is
in the `ready` state for more than a short period of time (e.g., 300
seconds). This is a node that was either part of a fulfilled
request and given to a requestor but the requestor has done nothing
with it yet, or the request was canceled immediately after being
fulfilled.
* A node in the `building` or `testing` states without a lock. This
means the Nodepool launcher handling that node died; it should be
deleted.
* A node in the `in-use` state without a lock. This means the Zuul
launcher using the node died.
This should allow the main work of nodepool to be performed by
multiple independent launchers, each of which is capable of processing
the request queue and modifying the pool state as represented in
Zookeeper.
The initial implementation will assume only one launcher is running
for each provider in order to avoid complexities involving quota
spanning across launchers, rate limits, and how to prevent request
starvation in the case of multiple launchers for the same provider
where one is handling a very large request. However, future work may
enable this with more coordination between launchers in zk.
Nodepool should also allow the specification of static inventory of
non-dynamic nodes. These may be nodes that are running on real
hardware, for instance.
.. _lock construct:
http://zookeeper.apache.org/doc/trunk/recipes.html#sc_recipes_Locks
.. _priority queue:
https://zookeeper.apache.org/doc/trunk/recipes.html#sc_recipes_priorityQueues
Zuul
----
Tenants
~~~~~~~
Zuul's main configuration should define tenants, and tenants should
specify config files to include. These include files should define
pipelines, jobs, and projects, all of which are namespaced to the
tenant (so different tenants may have different jobs with the same
names)::
### main.yaml
- tenant:
name: openstack
include:
- global_config.yaml
- openstack.yaml
Files may be included by more than one tenant, so common items can be
placed in a common file and referenced globally. This means that for,
eg, OpenStack, we can define pipelines and our base job definitions
(with logging info, etc) once, and include them in all of our tenants::
### main.yaml (continued)
- tenant:
name: openstack-infra
include:
- global_config.yaml
- infra.yaml
A tenant may optionally specify repos from which it may derive its
configuration. In this manner, a repo may keep its Zuul configuration
within its own repo. This would only happen if the main configuration
file specified that it is permitted::
### main.yaml (continued)
- tenant:
name: random-stackforge-project
include:
- global_config.yaml
source:
my-gerrit:
repos:
- stackforge/random # Specific project config is in-repo
Nodesets
~~~~~~~~
A significant focus of Zuul v3 is a close interaction with Nodepool to
both make running multi-node jobs simpler, as well as facilitate
running jobs on static resources. To that end, the node configuration
for a job is introduced as a first-class resource. This allows both
simple and complex node configurations to be independently defined and
then referenced by name in jobs::
### global_config.yaml
- nodeset:
name: precise
nodes:
- name: controller
image: ubuntu-precise
- nodeset:
name: trusty
nodes:
- name: controller
image: ubuntu-trusty
- nodeset:
name: multinode
nodes:
- name: controller
image: ubuntu-xenial
- name: compute
image: ubuntu-xenial
Jobs may either specify their own node configuration in-line, or refer
to a previously defined nodeset by name.
Jobs
~~~~
Jobs defined in-repo may not have access to the full feature set
(including some authorization features). They also may not override
existing jobs.
Job definitions continue to have the features in the current Zuul
layout, but they also take on some of the responsibilities currently
handled by the Jenkins (or other worker) definition::
### global_config.yaml
# Every tenant in the system has access to these jobs (because their
# tenant definition includes it).
- job:
name: base
timeout: 30m
nodes: precise
auth:
inherit: true # Child jobs may inherit these credentials
secrets:
- logserver-credentials
workspace: /opt/workspace # Where to place git repositories
post-run:
- archive-logs
Jobs have inheritance, and the above definition provides a base level
of functionality for all jobs. It sets a default timeout, requests a
single node (of type precise), and requests credentials to upload
logs. For security, job credentials are not available to be inherited
unless the 'inherit' flag is set to true. For example, a job to
publish a release may need credentials to upload to a distribution
site -- users should not be able to subclass that job and use its
credentials for another purpose.
Further jobs may extend and override the remaining parameters::
### global_config.yaml (continued)
# The python 2.7 unit test job
- job:
name: python27
parent: base
nodes: trusty
Our use of job names specific to projects is a holdover from when we
wanted long-lived slaves on Jenkins to efficiently re-use workspaces.
This hasn't been necessary for a while, though we have used this to
our advantage when collecting stats and reports. However, job
configuration can be simplified greatly if we simply have a job that
runs the python 2.7 unit tests which can be used for any project. To
the degree that we want to know how often this job failed on nova, we
can add that information back in when reporting statistics. Jobs may
have multiple aspects to accomodate differences among branches, etc.::
### global_config.yaml (continued)
# Version that is run for changes on stable/diablo
- job:
name: python27
parent: base
branches: stable/diablo
nodes:
- name: controller
image: ubuntu-lucid
# Version that is run for changes on stable/juno
- job:
name: python27
parent: base
branches: stable/juno # Could be combined into previous with regex
nodes: precise # if concept of "best match" is defined
Jobs may specify that they use other repos in the same tenant, and the
launcher will ensure all of the named repos are in place at the start
of the job::
### global_config.yaml (continued)
- job:
name: devstack
parent: base
repos:
- openstack/nova
- openstack/keystone
- openstack/glance
Jobs may specify that they require more than one node::
### global_config.yaml (continued)
- job:
name: devstack-multinode
parent: devstack
nodes: multinode
Jobs may specify auth info::
### global_config.yaml (continued)
- job:
name: pypi-upload
parent: base
auth:
secrets:
- pypi-credentials
# This looks up the secrets bundle named 'pypi-credentials'
# and adds it into variables for the job
Jobs may indicate that they may only be used by certain projects::
### shade.yaml (continued)
- job:
name: shade-api-test
parent: base
allowed-projects:
- openstack-infra/shade
auth:
secrets:
- shade-cloud-credentials
Note that this job may not be inherited from because of the auth
information.
Secrets
~~~~~~~
The `auth` attribute of a job provides way to add authentication or
authorization requirements to a job. The example above includes only
`secrets`, though other systems may be added in the future.
A `secret` is a collection of key/value pairs and is defined as a
top-level configuration object::
### global_config.yaml (continued)
- secret:
name: pypi-credentials
data:
username: !encrypted/pkcs1 o+7OscBFYWJh26rlLWpBIg==
password: !encrypted/pkcs1 o+7OscBF8GHW26rlLWpBIg==
PKCS1 with RSAES-OAEP (implemented by the Python `cryptography`
library) will be used so that the data are effectively padded. Since
the encryption scheme is specified by a YAML tag (`encrypted/pkcs1` in
this case), this can be extended later.
Zuul will maintain a private/public keypair for each repository
(config or project) specified in its configuration. It will look for
the keypair in `/var/lib/zuul/keys/<source name>/<repo name>.pem`. If
a keypair is needed but not available, Zuul will generate one. Zuul
will serve the public keys using its web server so that users can
download them for use in creating the encrypted secrets. It should be
easy for an end user to encrypt a secret, whether that is with an
existing tool such as OpenSSL or a new Zuul CLI.
There is a keypair for each repository so that users can not copy a
ciphertext from a given repo into a different repo that they control
in order to coerce Zuul into decrypting it for them (since the private
keys are different, decryption will fail).
It would still be possible for a user to copy a previously (or even
currently) used secret in that same repo. Depending on how expansive
and diverse the content of that repo is, that may be undesirable.
However, this system allows for management of secrets to be pushed
into repos where they are used and can be reviewed by people most
knowledgable about their use. By facilitating management of secrets
by repo specialists rather than forcing secrets for unrelated projects
to be centrally managed, this risk should be minimized.
Further, a secret may only be used by a job that is defined in the
same repo as that secret. This prevents users from defining a job
which requests unrelated secrets and exposes them.
In many cases, jobs which use secrets will be safe to use by any
repository in the system (for example, a Pypi upload job can be
applied to any repo because it does not execute untrusted code from
that repo). However, in some cases, jobs that use secrets will be too
dangerous to allow other repositories to use them (especially when
those repositories may be able to influence the job and cause it to
expose secrets). We should add a flag to jobs which indicate that
they may only be used by certain projects (typically only the repo in
which they are defined).
Pipelines may be configured to either allow or disallow the use of
secrets with a new boolean attribute, 'allow-secrets'. This is
intended to avoid the exposure of secrets by a job which was subject
to dynamic reconfiguration in a check pipeline. We would disable the
use of secrets in our check pipelines so that no jobs with secrets
could be configured to run in it. However, jobs which use secrets for
pre-merge testing (for example, to perform live API testing on a
public cloud) could still be run in the gate pipeline (which would
only happen after human review verified they were safe), or an access
restricted on-demand pipeline.
Projects
~~~~~~~~
Pipeline definitions are similar to the current syntax, except that it
supports specifying additional information for jobs in the context of
a given project and pipeline. For instance, rather than specifying
that a job is globally non-voting, you may specify that it is
non-voting for a given project in a given pipeline::
### openstack.yaml
- project:
name: openstack/nova
gate:
queue: integrated # Shared queues are manually built
jobs:
- python27 # Runs version of job appropriate to branch
- pep8:
nodes: trusty # override the node type for this project
- devstack
- devstack-deprecated-feature:
branches: stable/juno # Only run on stable/juno changes
voting: false # Non-voting
post:
jobs:
- tarball
- wheel
- pypi-upload:
dependencies:
- tarball
- wheel
Project templates are still supported, and can modify job parameters
in the same way described above.
Before Zuul executes a job, it finalizes the job content and
parameters by incorporating input from the multiple job definitions
which may apply. The job that will ultimately be run is a job which
inherits from all of the matching job definitions in the order in
which they were encountered in the configuration. This allows for
increasingly specific job definitions. For example, a python unit
test job may be defined globally. A variant of that job (with the
same name) may be specified with an alternate node definition for
"stable" branches. Further, a project-local job specification may
indicate that job should only run when files in the "tests/" directory
are modified. The result is that the job will only run when files in
"tests/" are modified, and, if the change is on a stable branch, the
alternate node definition will be used.
Currently unique job names are used to build shared change queues.
Since job names will no longer be unique, shared queues must be
manually constructed by assigning them a name. Projects with the same
queue name for the same pipeline will have a shared queue.
Job dependencies were previously expressed in a YAML tree form, where
if, in the list of jobs for a project's pipeline, a job appeared as a
dictionary entry within another job, that indicated that the second
job would only run if the first completed successfully. In Zuul v3,
job dependencies may be expressed as a directed acyclic graph. That
is to say that a job may depend on more than one job completing
successfully, as long as those dependencies do not create a cycle.
Because a job may appear more than once within a project's pipeline,
it is impractical to express these dependencies in YAML tree from, so
the collection of jobs to run for a given project's pipeline is now a
simple list, however, each job in that list may express its
dependencies on other jobs using the `dependencies` keyword.
A subset of functionality is available to projects that are permitted
to use in-repo configuration::
### stackforge/random/.zuul.yaml
- job:
name: random-job
parent: base # From global config; gets us logs
nodes: precise
- project:
name: stackforge/random
gate:
jobs:
- python27 # From global config
- random-job # Flom local config
Ansible
~~~~~~~
The actual execution of jobs will continue to be distributed to
workers over Gearman. Therefore the actual implementation of how jobs
are executed will remain pluggable, however, the zuul-gearman protocol
will need to change. Because the system needs to perform coordinated
tasks on one or more remote systems, the initial implementation of the
workers will use Ansible, which is particularly suited to that job.
The executable content of jobs should be defined as ansible playbooks.
Playbooks can be fairly simple and might consist of little more than
"run this shell script" for those who are not otherwise interested in
ansible::
### stackforge/random/playbooks/random-job.yaml
---
hosts: controller
tasks:
- shell: run_some_tests.sh
Global jobs may define ansible roles for common functions::
### openstack-infra/zuul-playbooks/python27.yaml
---
hosts: controller
roles:
- tox:
env: py27
Because ansible has well-articulated multi-node orchestration
features, this permits very expressive job definitions for multi-node
tests. A playbook can specify different roles to apply to the
different nodes that the job requested::
### openstack-infra/zuul-playbooks/devstack-multinode.yaml
---
hosts: controller
roles:
- devstack
---
hosts: compute
roles:
- devstack-compute
Additionally, if a project is already defining ansible roles for its
deployment, then those roles may be easily applied in testing, making
CI even closer to CD.
The pre- and post-run entries in the job definition might also apply
to ansible playbooks and can be used to simplify job setup and
cleanup::
### openstack-infra/zuul-playbooks/archive-logs.yaml
---
hosts: all
roles:
- archive-logs: "/opt/workspace/logs"
All of the content of Ansible playbooks is held in the git
repositories that Zuul operates on, and this is true for some of the
Ansible roles as well, though some playbooks will require roles that
are defined outside of this system. Because the content of roles must
be already present on the host executing a playbook, Zuul will need to
be able to prepare these roles prior to executing a job. To
facilitate this, job definitions may also specify role dependencies::
### global_config.yaml (continued)
- job:
name: ansible-nova
parent: base
roles:
- zuul: openstack-infra/infra-roles
- galaxy: openstack.nova
name: nova
This would instruct zuul to prepare the execution context with roles
collected from the zuul-managed "infra-roles" repository, as well as
the "openstack.nova" role from Ansible Galaxy. An optional "name"
attribute will cause the role will to be placed in a directory with
that name so that the role may be referenced by it. When constructing
a job using inheritance, roles for the child job will extend the list
of roles from the parent job (this is intended to make it simple to
ensure that all jobs have a basic set of roles available).
If a job references a role in a Zuul-managed repo, the usual
dependency processing will apply (so that jobs can run with un-merged
changes in other repositories).
A Zuul repository might be a bare single-role repository (e.g.,
ansible-role-puppet), or it might be a repository which contains
multiple roles (e.g., infra-roles, or even project-config). Zuul
should detect these cases and handle them accordingly.
* If a repository appears to be a bare role (has tasks/, vars/,
etc. directories at the root of the repo), the directory containing
the repo checkout (which should otherwise be empty) should be added
to the roles_path Ansible configuration value.
* If a repository has a roles/ directory at the root, the roles/
directory within the repo should be added to roles_path.
* Otherwise, the root of the repository should be added to the roles
path (under the assumption that individual directories in the
repository are roles).
In the future, Zuul may support reading Ansible requirements.yaml
files to determine roles needed for jobs.
Execution
~~~~~~~~~
A new Zuul component would be created to execute jobs. Rather than
running a worker process on each node (which requires installing
software on the test node, and establishing and maintaining network
connectivity back to Zuul, and the ability to coordinate actions
across nodes for multi-node tests), this new component will pick up
accept jobs from Zuul, and for each one, write an ansible inventory
file with the node and variable information, and then execute the
ansible playbook for that job. This means that the new Zuul component
will maintain ssh connections to all hosts currently running a job.
This could become a bottleneck, but ansible and ssh have been known to
scale to a large number of simultaneous hosts, and this component may
be scaled horizontally. It should be simple enough that it could even
be automatically scaled if needed. In turn, however, this does make
node configuration simpler (test nodes need only have an ssh public
key installed) and makes tests behave more like deployment.
To support the use case where the Zuul control plane should not be
accessible by the workers (for instance, because the control plane is
on a private network while the workers are in a public cloud), the
direction of transfer of changes under test to the workers will be
reversed.
Instead of workers fetching from zuul-mergers, the new zuul-launcher
will take on the task of calculating merges as well as running
ansible.
Continuous Deployment
~~~~~~~~~~~~~~~~~~~~~
Special consideration is needed in order to use Zuul to drive
continuous deployment of development or production systems. Rather
than specifying that Zuul should obtain a node from nodepool in order
to run a job, it may be configured to simply execute an ansible task
on a specified host::
- job:
name: run-puppet-apply
parent: base
host: review.openstack.org
fingerprint: 4a:28:cb:03:6a:d6:79:0b:cc:dc:60:ae:6a:62:cf:5b
Because any configuration of the host and credential information is
potentially accessible to anyone able to read the Zuul configuration
(which is everyone for OpenStack's configuration) and therefore could
be copied to their own section of Zuul's configuration, users must add
one of two public keys to the server in order for the job to function.
Zuul will generate an SSH keypair for every tenant as well as every
project. If a user trusts anyone able to make configuration changes
to their tenant, then they may use Zuul's public key for their tenant.
If they are only able to trust their own project configuration in
Zuul, they may add Zuul's public key for that specific project. Zuul
will make all public keys available at known HTTP addresses so that
users may retrieve them. When executing such a job, Zuul will try the
project and tenant SSH keys in order.
Tenant Isolation
~~~~~~~~~~~~~~~~
In order to prevent users of one Zuul tenant from accessing the git
repositories of other tenants, Zuul will no longer consider the git
repositories it manages to be public. This could be solved by passing
credentials to the workers for them to use when fetching changes,
however, an additional consideration is the desire to have workers
fully network isolated from the Zuul control plane.
Instead of workers fetching from zuul-mergers, the new zuul-launcher
will take on the task of calculating merges as well as running
ansible. The launcher will then be responsible for placing prepared
versions of requested repositories onto the worker.
Status reporting will also be tenant isolated, however without
HTTP-level access controls, additional measures may be needed to
prevent tenants from accessing the status of other tenants.
Eventually, Zuul may support an authenticated REST API that will solve
this problem natively.
Alternatives
------------
Continuing with the status quo is an alternative, as well as
continuing the process of switching to Turbo Hipster to replace
Jenkins. However, this addresses only some of the goals stated at the
top.
Implementation
==============
Assignee(s)
-----------
Primary assignee:
* corvus
Also:
* jhesketh
* mordred
Gerrit Branch
-------------
Nodepool and Zuul will both be branched for development related to
this spec. The "master" branches will continue to receive patches
related to maintaining the current versions, and the "feature/zuulv3"
branches will receive patches related to this spec. The .gitreview
files will be updated to submit to the correct branches by default.
Work Items
----------
* Modify nodepool to support new allocation and distribution (mordred)
* Modify zuul to support new syntax and isolation (corvus)
* Create zuul launcher (jhesketh)
* Prepare basic infra ansible roles
* Translate OpenStack JJB config to ansible
Repositories
------------
We may create new repositories for ansible roles, or they may live in
project-config.
Servers
-------
We may create more combined zuul-launcher/mergers.
DNS Entries
-----------
No changes other than needed for additional servers.
Documentation
-------------
This will require changes to Nodepool and Zuul's documentation, as
well as infra-manual.
Security
--------
No substantial changes to security around the Zuul server; use of Zuul
private keys for access to remote hosts by Zuul has security
implications but will not be immediately used by OpenStack
Infrastructure.
Testing
-------
Existing nodepool and Zuul tests will need to be adapted.
Configuration will be different, however, much functionality should be
the same, so many functional tests should have direct equivalencies.
Dependencies
============
None.