Merge "Support nodes setting 'auto' python-path"

This commit is contained in:
Zuul 2019-09-30 15:18:10 +00:00 committed by Gerrit Code Review
commit 3e63b9fe3c
9 changed files with 136 additions and 9 deletions

View File

@ -64,9 +64,8 @@ at the tops of individual source files.
Python Version Support
----------------------
Zuul v3 requires Python 3. It does not support Python 2.
Zuul requires Python 3. It does not support Python 2.
Since Zuul uses Ansible to drive CI jobs, Zuul can run tests anywhere
Ansible can, including Python 2 environments.
As Ansible is used for the execution of jobs, it's important to note that
while Ansible does support Python 3, not all of Ansible's modules do. Zuul
currently sets ``ansible_python_interpreter`` to python2 so that remote
content will be executed with Python 2.

View File

@ -797,6 +797,42 @@ To enable or disable running Ansible in verbose mode (with the
``-vvv`` argument to ansible-playbook) run ``zuul-executor verbose``
and ``zuul-executor unverbose``.
Ansible and Python 3
~~~~~~~~~~~~~~~~~~~~
As noted above, the executor runs Ansible playbooks against the remote
node(s) allocated for the job. Since part of executing playbooks on
remote hosts is running Python scripts on them, Ansible needs to know
what Python interpreter to use on the remote host. With older
distributions, ``/usr/bin/python2`` was a generally sensible choice.
However, over time a heterogeneous Python ecosystem has evolved where
older distributions may only provide Python 2, most provide a mixed
2/3 environment and newer distributions may only provide Python 3 (and
then others like RHEL8 may even have separate "system" Python versions
to add to confusion!).
Ansible's ``ansible_python_interpreter`` variable configures the path
to the remote Python interpreter to use during playbook execution.
This value is set by Zuul from the ``python-path`` specified for the
node by Nodepool; see the `nodepool configuration documentation
<https://zuul-ci.org/docs/nodepool/configuration.html>`__.
This defaults to ``auto``, where Ansible will automatically discover
the interpreter available on the remote host. However, this setting
only became available in Ansible >=2.8, so Zuul will translate
``auto`` into the old default of ``/usr/bin/python2`` when configured
to use older Ansible versions.
Thus for modern Python 3-only hosts no further configuration is needed
when using Ansible >=2.8 (e.g. Fedora, Bionic onwards). If using
earlier Ansible versions you may need to explicitly set the
``python-path`` if ``/usr/bin/python2`` is not available on the node.
Ansible roles/modules which include Python code are generally Python 3
safe now, but there is still a small possibility of incompatibility.
See also the Ansible `Python 3 support page
<https://docs.ansible.com/ansible/latest/reference_appendices/python_3_support.html>`__.
.. _web-server:
Web Server

View File

@ -0,0 +1,7 @@
---
features:
- |
Nodes can set their ``python-path`` in nodepool configuration to
``auto`` to allow for `automatic ansible_python_interpreter
discovery <https://docs.ansible.com/ansible/2.8/reference_appendices/interpreter_discovery.html>`__
when using Ansible >=2.8.

View File

@ -2507,7 +2507,7 @@ class FakeNodepool(object):
self.remote_ansible = False
self.attributes = None
self.resources = None
self.python_path = '/usr/bin/python2'
self.python_path = 'auto'
def stop(self):
self._running = False

View File

@ -0,0 +1,2 @@
- hosts: all
tasks: []

View File

@ -89,4 +89,20 @@
label: ubuntu-xenial
run: playbooks/jinja2-message.yaml
- job:
name: ansible-version27-inventory
nodeset:
nodes:
- name: ubuntu-xenial
label: ubuntu-xenial
ansible-version: '2.7'
run: playbooks/ansible-version.yaml
- job:
name: ansible-version28-inventory
nodeset:
nodes:
- name: ubuntu-xenial
label: ubuntu-xenial
ansible-version: '2.8'
run: playbooks/ansible-version.yaml

View File

@ -7,3 +7,5 @@
- executor-only-inventory
- group-inventory
- hostvars-inventory
- ansible-version27-inventory
- ansible-version28-inventory

View File

@ -76,6 +76,57 @@ class TestInventoryPythonPath(TestInventoryBase):
self.waitUntilSettled()
class TestInventoryAutoPython(TestInventoryBase):
def test_auto_python_ansible28_inventory(self):
inventory = self._get_build_inventory('ansible-version28-inventory')
all_nodes = ('ubuntu-xenial',)
self.assertIn('all', inventory)
self.assertIn('hosts', inventory['all'])
self.assertIn('vars', inventory['all'])
for node_name in all_nodes:
self.assertIn(node_name, inventory['all']['hosts'])
node_vars = inventory['all']['hosts'][node_name]
self.assertEqual(
'auto', node_vars['ansible_python_interpreter'])
self.assertIn('zuul', inventory['all']['vars'])
z_vars = inventory['all']['vars']['zuul']
self.assertIn('executor', z_vars)
self.assertIn('src_root', z_vars['executor'])
self.assertIn('job', z_vars)
self.assertEqual(z_vars['job'], 'ansible-version28-inventory')
self.assertEqual(z_vars['message'], 'QQ==')
self.executor_server.release()
self.waitUntilSettled()
def test_auto_python_ansible27_inventory(self):
inventory = self._get_build_inventory('ansible-version27-inventory')
all_nodes = ('ubuntu-xenial',)
self.assertIn('all', inventory)
self.assertIn('hosts', inventory['all'])
self.assertIn('vars', inventory['all'])
for node_name in all_nodes:
self.assertIn(node_name, inventory['all']['hosts'])
node_vars = inventory['all']['hosts'][node_name]
self.assertEqual(
'/usr/bin/python2', node_vars['ansible_python_interpreter'])
self.assertIn('zuul', inventory['all']['vars'])
z_vars = inventory['all']['vars']['zuul']
self.assertIn('executor', z_vars)
self.assertIn('src_root', z_vars['executor'])
self.assertIn('job', z_vars)
self.assertEqual(z_vars['job'], 'ansible-version27-inventory')
self.assertEqual(z_vars['message'], 'QQ==')
self.executor_server.release()
self.waitUntilSettled()
class TestInventory(TestInventoryBase):
def test_single_inventory(self):

View File

@ -1370,9 +1370,23 @@ class AnsibleJob(object):
private_ipv4=node.get('private_ipv4'),
public_ipv6=node.get('public_ipv6'))))
host_vars.setdefault(
'ansible_python_interpreter',
node.get('python_path', '/usr/bin/python2'))
# Ansible >=2.8 introduced "auto" as an
# ansible_python_interpreter argument that looks up
# which python to use on the remote host in an inbuilt
# table and essentially "does the right thing"
# (i.e. chooses python3 on 3-only hosts like later
# Fedoras). For "auto" with prior versions, fall back
# to the old default of /usr/bin/python2 for backwards
# compatability.
python = node.get('python_path', 'auto')
compat = self.arguments.get('ansible_version') in \
('2.5', '2.6', '2.7')
if python == "auto" and compat:
self.log.debug(
"ansible_version set to auto but "
"overriding to python2 for Ansible <2.8")
python = '/usr/bin/python2'
host_vars.setdefault('ansible_python_interpreter', python)
username = node.get('username')
if username: