Skip to content

VIP20: Varnish ABI and packaging

Dridi Boukelmoune edited this page Mar 13, 2018 · 4 revisions

Synopsis

Make Varnish upgrades safe when VMODs are installed from packages.

Why?

Why would anyone want unsafe upgrades? :)

Packaging background

This VIP focuses on RPM because its authors has little knowledge of DPKG, but he is convinced that similar mechanisms exist. There are no packages officially supported outside of the linux world, which is nice for the VIP author because he wears linux blinders that prevents him from seeing the other platforms.

With RPM, dependency management is not limited to package names (eg. Requires: varnish >= x.y) and instead RPM operates on capabilities or virtual dependencies. For example, Requires: java >= 1.8 could bring any package that Provides: java = 1.8 (or later versions).

You can list everything that a package provides:

$ rpm -q --provides java-1.8.0-openjdk | grep 'java ='             
java = 1.8.0

A more familiar example:

$ rpm -q --provides varnish-devel | grep pkgconfig
pkgconfig(varnishapi) = 5.1.3

We, as the Varnish project, don't specify that we provide a "pkgconfig" capability in the varnish-devel package. This is done by RPM, the installation is scanned during the package build and the presence of the varnishapi.pc file in the right directory is enough for RPM to add the capability.

RPM ships with more "auto provide" scripts than just pkg-config files detection. One of them analyzes ELF binaries and can see the installed libraries and their soname. There is also an "auto require" side of it, for what's dynamically linked at build time.

For example, on Fedora 26 there is a varnish-libs package:

$ rpm -q --provides varnish-libs | grep ^lib
libvarnishapi.so.1()(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.0)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.1)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.2)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.3)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.4)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.5)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.6)(64bit)
libvmod_directors.so()(64bit)
libvmod_std.so()(64bit)

It can obviously also take symbol versioning into account. It's not very useful to expose VMODs, but if we added the possibility to link VMODs to others (wink wink) it would come in handy too. Except that we build VMODs without any version at all, but that's a different discussion.

And since the varnish package ships with utilities, a natural dependency exists:

$ rpm -q --requires varnish | grep varnish # look again, this time it's --requires ;)
config(varnish) = 5.1.3-2.fc26
libvarnishapi.so.1()(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.0)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.1)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.2)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.3)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.4)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.5)(64bit)
libvarnishapi.so.1(LIBVARNISHAPI_1.6)(64bit)
varnish-libs(x86-64) = 5.1.3-2.fc26

There is an explicit dependency to the varnish-libs package, but only because it was added manually, it is not needed. Installing varnish would always bring varnish-libs, but it's better to ensure that we bring the same exact version:

https://src.fedoraproject.org/rpms/varnish/blob/f26/f/varnish.spec#_50

The link shows:

Requires: %{name}-libs%{?_isa} = %{version}-%{release}

Basically, a strict dependency for the varnish-libs package for the same exact version on the same exact architecture (isa: instruction set architecture, mostly for x86 repositories shipping both 32bit and 64bit packages).

While this works great for a VUT-like package, or basically anything that relies on libvarnishapi, the story is different for VMODs.

Varnish ABI background

Before the "How?" section, let's recap how VMODs have related to the Varnish ABI over time.

Varnish 3.0

VMODs were built against the Varnish source tree and varnishd would only load them if the commit shortened hash matched.

Varnish 4.0

The required headers are installed alongside Varnish, and shipped by RPM varnish-devel and DPKG varnish-dev packages. VMODs can now be built against a regular Varnish installation (provided that development files are present) and pkg-config helps us find where the useful bits are.

VMOD authors have full access to libvarnish because varnishd already links to it. As such, they have access to all symbols, even those not exposed when the symbol version script (libvarnish.map) is in effect. Utility authors have access to some libvarnish symbols via libvarnishapi when the version script is in effect.

The result is satisfying for libvarnishapi consumers.

VMOD authors also have full access to varnishd symbols, but an unclear subset of those symbols are blessed: the Varnish RunTime. It consists in symbols accessible to VCL once turned into C by (excluding inline C).

Varnish 4.0.1

A new VRT version is introduced, VMODs will now include the VRT major.minor in their metadata so that varnishd can reject a VMOD when its target is not compatible. However this was implemented with two flaws:

  • building Varnish from master falls back to a commit shortened hash match
  • VMODs couldn't express VRT vs strict ABI compliance

For an example of the master branch problem: https://github.com/varnish/varnish-modules/issues/70

We maintain an incomplete list of the reasons behind minor or major bumps in vrt.h.

Varnish 5.1

libvarnish is statistically linked into its consumers.

Varnish 5.2

An $ABI stanza is added for VCC descriptors. It lets VMOD authors express their level of compliance, but the VRT scope is still unclear. The rule of thumb being whatever symbols pure VCL has access to and associated data structures.

See https://github.com/varnishcache/varnish-cache/pull/2330

The result is a safer upgrade path: Varnish could still upgrade and become incompatible with installed VMODs, but VMODs requiring a strict ABI compliance would no longer load based on the fact that VRT major.minor is still compatible with what they expect. VMODs built before 5.2 would get strict or VRT compliance depending on how Varnish was built as explained in the Varnish 4.0.1 section.

Trunk

There is an ongoing effort to clearly define the scope of VRT, and possibly a new "cache" scope in-between VRT and strict (full ABI) compliance.

How?

As of Varnish 6, supported Varnish packages will expose virtual provides.

For RPM packages:

Provides: varnishd(abi)(x86-64) = sha1
Provides: varnishd(vrt)(x86-64) = x.y

For Deb packages:

Provides:
 varnishd-abi-sha1
 varnishd-vrt (= x.y)

This means that someone packaging VMODs as Debs or RPMs can manually adjust requirements to ensure that a package upgrade doesn't occur if compatibility is no longer guaranteed. For example, a VMOD relying strictly on symbols and data structures covered by the VRT version could have the following requirements:

Requires: varnishd(vrt)%{?_isa} >= 6.2
Requires: varnishd(vrt)%{?_isa} < 7

Assuming that the package was originally built against a varnish package exposing Provides: varnishd(vrt) = 6.2.

The next step to complete this VIP would be to ship an RPM find-provides script (and the Deb equivalent) to automatically require either strict ABI or loose VRT compatibility based on metadata found in installed VMODs. Even better, as this is currently done outside of Varnish itself and lives in pkg-varnish-cache, move the ABI detection in Varnish so that other downstream distributors could implement a similar mechanism for package managers that support it.

Clone this wiki locally