Dmitry Tantsur (Principal Software Engineer, Red Hat)
owlet.today/talks/berlin-python-openstack
Free and open source software to create public and private clouds.
Implements IaaS (infrastructure as a service) - on-demand:
Provides complete and consistent API, as well as web UI.
An example of routine tasks achievable via API/UI:
Create two small virtual servers with Ubuntu 16.04 and one large bare metal server with CentOS 7. Connect the Ubuntu machines with a private network with network address 192.168.42.0/24, route it to the internet, expose port 80 of the Ubuntu machines through a load balancer. Connect all machines with another private network with network address 10.0.10.0/24. Put my SSH public key on all machines.
* in the last release codenamed "Pike"
OpenStack is nearly entirely written in Python!
OpenStack is all about glueing together various great bits of free software and providing a solid API on top of it.
Python is very good at glueing things together with it's rich collection of network libraries and good ability to interface with C code.
Performance is rarely a concern, as most of the actual logic lives in other projects.
Why?
setup.py is bad:
* please, PLEASE, don't execute pip install
from
within your setup.py!
pbr means Python Build Reasonableness.
Addon on top of setuptools
import setuptools
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)
This gets committed in your repository and only ever changes to bump the required version of pbr.
The real magic happens in setup.cfg:
[metadata]
name = coolcats
summary = I wrote this project because I'm good at writing
description-file =
README.md
author = dtantsur
author-email = noreply@google.com
home-page = https://owlet.today
classifier =
Environment :: OpenStack
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Handles packages (detects modules recursively!), scripts and data files:
[files]
packages =
coolcats
scripts =
scripts/find-a-cat
data_files =
share/pyfoo/ = images/funny-cats/*
Generates versions based on git tags:
$ python setup.py --version
9.2.1.dev124
Generates nice AUTHORS and CHANGES from git.
Generated MANIFEST based on files added to git.
Downsides:
Why?
Managing virtual environments in a repeatable way is annoying:
$ virtualenv venv
$ venv/bin/pip install -r requirements.txt
$ venv/bin/pip install -e .
$ venv/bin/python -m unittest discover coolcats.tests # for example
Now repeat for every supported Python version...
tox simplifies routine task on managing virtual environments and running stuff in them.
Run unit tests on Python 2.7:
$ tox -epy27
Run unit tests on the default Python 3:
$ tox -epy3
Build a generic environment and run some commands there:
$ tox -evenv -- python -m some.package
Configuration in tox.ini:
[tox]
envlist = py3,py27
[testenv]
usedevelop = True
deps =
-r{toxinidir}/requirements.txt
commands =
python -m unittest discover coolcats.tests
setenv =
PYTHONDONTWRITEBYTECODE=1
Custom environments doing anything:
[testenv:pep8]
basepython = python2.7
deps =
flake8
doc8
commands =
flake8 coolcats
doc8 README.rst doc/source
[testenv:venv]
commands = {posargs}
Why?
pbr can generate a ChangeLog.
But user/operator facing release notes is a different thing: you need to highlight important things and hide techinical details.
Writing release notes by project maintainers does not scale.
Appending them to a single file is messy on merges and backports.
reno allows a commit author to create a simple yaml file with associated release notes for their change:
---
features:
- |
Introduces support for downloading funny cats pictures.
upgrade:
- |
Make sure to enable downloading funny cats pictures or
we'll set your hard drive on fire.
deprecations:
- |
Not watching funny cats is lame and deprecated.
When building release notes, the reno tool:
Entry points are great, let's have MORE of them!
This is a great, but often overlooked, feature of setuptools.
Essentially, a collection of dictionaries, mapping short names to Python objects.
Entry points of the same group from different Python projects are merged by setuptools, which make this feature perfect for plugins!
pbr supports entry points in setup.cfg:
console_scripts =
make-cat-photo = coolcats.cli:my_cat_photo
post-cat-photo = coolcats.cli:my_cat_photo
coolcats.cats =
small-and-cute = coolcats.cats:SmallAndCute
fat-and-awesome = coolcats.cats:FatAndAwesome
The standard console_scripts
group simplify creating
scripts a lot.
stevedore is a library simplifying interaction with entry points. Provides convenient classes for common patterns:
Each type can be enabled automatically (when a package with it is installed) or explicitly (e.g. via configuration).
Why bother when we have standard configparser?
Options schema defined in Python:
opts = [
cfg.StrOpt('host_ip',
default='0.0.0.0',
help=_('The IP address on which ironic-api listens.')),
cfg.PortOpt('port',
default=6385,
help=_('The TCP port on which ironic-api listens.')),
cfg.IntOpt('max_limit',
default=1000,
help=_('The maximum number of items returned in a single '
'response from a collection resource.')),
]
Real example from one of our projects.
Options accessed as CONF.group.name
:
from oslo_config.cfg import CONF
host_port = "{}.{}".format(CONF.api.host_ip,
CONF.api.port)
Using a global CONF
object is ugly, but simplifies
things.
Bonus: generating example configurations:
[api]
# The IP address on which ironic-api listens. (string value)
#host_ip = 0.0.0.0
# The TCP port on which ironic-api listens. (port value)
# Minimum value: 0
# Maximum value: 65535
#port = 6385
# The maximum number of items returned in a single response
# from a collection resource. (integer value)
#max_limit = 1000
Bonus: generating documentation
https://docs.openstack.org/ironic/latest/configuration/config.html#api.
Just populate a requirements.txt, pbr will catch it automatically:
stevedore>=1.20.0
requests>=2.14.2
six>=1.10.0
That's all, right?
Major* versions tend to break things.
* if you're breaking things in non-major versions, please STOP.
Still easy, insert an upper cap:
stevedore>=1.20.0,<2.0.0
requests>=2.14.2,<3.0.0
six>=1.10.0,<2.0.0
That's all now, right? RIGHT?
Your fellow project may want a newer or older major version:
stevedore>=1.20.0,<2.0.0
requests>=1.0.0,<2.0.0
six>=1.10.0,<2.0.0
What if somebody tries to install both projects at the same time?
Centralized requirements handling:
This gets us:
Requirements on stable branches* should not change much.
* git branches and release series produced from them that only receive important bug fixes
Upper caps to the rescue?
stevedore>=1.20.0,<1.21.0
requests>=2.14.2,<2.15.0
six>=1.10.0,<1.11.0
Upper caps to the rescue?
stevedore>=1.20.0,<1.21.0
requests>=2.14.2,<2.15.0
six>=1.10.0,<1.11.0
What if e.g. requests 2.15.1
is a critical bug fix?
What if a non-OpenStack project on the same machine requires
requests>=2.16
?
After years of struggling we decided to mostly stop using upper caps, except for known major breaking changes.
We let downstream consumers to decide on appropriate versions.
But what to do with the CI?
We still don't want new versions of random projects to break our CI.
Especially on stable branches.
We also want to have a recommendation for downstream consumers on which versions we known to work.
Solution: upper constraints.
Poll: who knows what -c
flag for pip install
does?
Upper contraints complement requirements with stricter limits:
stevedore===1.28.0
requests===2.18.4
six===1.11.0
Upper contraints are not synced to projects and are not enforced outside of the CI.
On the master (current development) branch a bot periodically proposes updates to upper constraints, which then go through the CI.
On stable branches upper constraints are only updated automatically for other stable OpenStack component releases.
For other releases upper constraints on stable branches can only be updated manually.
Typical release of a Python project:
$ git tag -s 1.0.0
$ git push origin 1.0.0
$ python setup.py sdist upload
Problem solved?
Problems with manual releases:
Releases through CI:
What about peer review and validation before tagging?
Repository for review requests:
---
team: nova
type: service
release-notes: https://docs.openstack.org/releasenotes/nova/unreleased.html
releases:
- version: 17.0.0.0b1
projects:
- repo: openstack/nova
hash: af4703cb38580a8cb9c9b293dd4b1637f2734cad
Allows running CI jobs on release requests!
On merging a new release request:
Bonus: stable branches are created the same way:
branches:
- location: 16.0.0.0rc1
name: stable/pike