Commit graph

122 commits

Author SHA1 Message Date
Tom Gundersen
ee86b57392 pipeline: back var by the store
This makes sure all disk access is backed by the same disk. We may
want this for performance reasons (avoiding moving across disks), but
also to experiment with different backing stores for all disk access.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2020-01-27 15:51:47 +01:00
Tom Gundersen
2837604bf8 buildroot: allow customizing the backing store for /var
Currently /var was always backed by /var/tmp, but we may want to
control exactly what it is backed by. The default is the same, so
this is not a behavioral change.
2020-01-27 15:51:47 +01:00
Christian Kellner
cf9c9946e0 pipeline: bind mount the osbuild module for the stages
The dnf stage wants to import `osbuild.sources` but currently the
osbuild module is not available in the stages. Apply the same hack
done in the Assembler also in for the stages, i.e. bind mount the
osbuild module to the stages/osbuild.
2020-01-23 00:49:11 +01:00
Lars Karlitski
7bb06d2334 loop: handle set_status returning EBUSY
This happens rarely when the same loop device is used in rapid
succession. The kernel flushes the page cache asynchronously, which
means that it might not be cleared yet when a new file is bound.
`set_status` checks if the cache is clear (`set_fd` doesn't).

Handle this by trying a different device when `set_status` returns
`EBUSY`.

Fixes #177
2020-01-19 22:19:25 +01:00
Lars Karlitski
b487126bb8 loop: explicitly close fds to loop devices
Don't wait until python's garbage collector closes the file descriptors
to loop devices. Close them when the `LoopServer` context manager exits,
after an assembler has finished running.
2020-01-19 22:19:25 +01:00
Lars Karlitski
47dc1b5b92 loop: don't leak open fd to /dev
Close the file descriptor to `/dev` when we opened it.
2020-01-19 22:19:25 +01:00
Lars Karlitski
977f0a465b loop: fix typo in LoopInfo member 2020-01-19 22:19:25 +01:00
Christian Kellner
64addbe2d2 buildroot: allow creating device nodes on s390x
The z Initial Program Loader (zipl) when creating the bootmap in
bootmap_creat (src/zipl/bootmap.c) wants to create a device node
via misc_temp_dev (bootmap_create:1141) for the device that it
is installing the bootloader to[1]. Currently access to loopback
devices is allowed from within the container (it is used to mount
the image), but only read/write access. On s390x also allow the
creation of device nodes, so zipl can do its work and install
the bootloader stages on the "disk".

[1] zipl source at commit dcce14923c3e9615df53773d1d8a3a22cbb23b96
2020-01-13 20:05:10 +01:00
Christian Kellner
bf41326ac6 remoteloop: don't use O_DIRECT on s390x
Using O_DIRECT to open the image partition and then using that fd
for the backing of the loopback device will break the mounting of
the formatted partition, i.e mount will fail with:

  mount: /tmp/looptest-6qrtkp5e/mountpoint-root: wrong fs type,
  bad option, bad superblock on /dev/loop0, missing codepage or
  helper program, or other error.

Reproducible with the follow small-ish python script, executed via
'env PYTHONPATH=$(pwd) python3 looptest.py':

---- 8< ---- 8< ---- [ looptest.py ] ---- 8< ---- 8< ----

import contextlib
import json
import os
import subprocess
import stat
import tempfile

from osbuild import loop

@contextlib.contextmanager
def mount(source, dest):
    subprocess.run(["mount", source, dest], check=True)
    try:
        yield dest
    finally:
        subprocess.run(["umount", "-R", dest], check=True)

@contextlib.contextmanager
def os_open(path, flags):
    fd = os.open(path, flags)
    try:
        yield fd
    finally:
        os.close(fd)

def main():
    size = 512 * 1024 * 1024
    ptuuid = "0x14fc63d2"

    with contextlib.ExitStack() as cm:
        tmpdir = cm.enter_context(tempfile.TemporaryDirectory(prefix="looptest-"))
        print(f"Temporary directory at {tmpdir}")

        devdir = os.path.join(tmpdir, "dev")
        os.makedirs(devdir, exist_ok=True)
        dir_fd = cm.enter_context(os_open(devdir, os.O_DIRECTORY))

        image = os.path.join(tmpdir, "image")
        subprocess.run(["truncate", "--size", str(size), image], check=True)
        table = f"label: mbr\nlabel-id: {ptuuid}\nbootable, type=83"
        subprocess.run(["sfdisk", image], input=table, encoding='utf-8',
                       check=True)
        # read it back
        r = subprocess.run(["sfdisk", "--json", image],
                           stdout=subprocess.PIPE,
                           encoding='utf-8', check=True)
        table = json.loads(r.stdout)["partitiontable"]
        partitions = table["partitions"]
        start = partitions[0]["start"] * 512
        size = partitions[0]["size"] * 512

        # fails here with os.O_DIRECT
        image_fd = cm.enter_context(os_open(image, os.O_RDWR | os.O_DIRECT))

        control = loop.LoopControl()
        minor = control.get_unbound()
        lo = loop.Loop(minor)
        lo.set_fd(image_fd)
        lo.set_status(offset=start, sizelimit=size, autoclear=True)
        lo.mknod(dir_fd)
        loopdev = f"/dev/loop{minor}"

        # loopdev = os.path.join(devdir, lo.devname)
        # os.chmod(loopdev, os.stat(loopdev).st_mode | stat.S_IRGRP)

        subprocess.run(["ls", "-la", f"{devdir}"], check=True)
        subprocess.run(["mkfs.ext4", loopdev],
                       input="y", encoding='utf-8', check=True)

        subprocess.run(["blkid", loopdev], check=True)

        mountpoint = os.path.join(tmpdir, "mountpoint-root")
        os.makedirs(mountpoint, exist_ok=True)
        cm.enter_context(mount(loopdev, mountpoint))
        subprocess.run(["ls", "-la", tmpdir], check=True)
        subprocess.run(["ls", "-la", mountpoint], check=True)
        subprocess.run(["mount"], check=True)

if __name__ == '__main__':
    main()
2020-01-13 20:05:10 +01:00
Lars Karlitski
12b5c6aaa4 sources: bump maximum message size to 64k
These messages contain certificate data, which is quite large.

We should probably use streaming sockets in the future.
2020-01-09 23:55:43 +01:00
Lars Karlitski
e123715bc6 osbuild: introduce secrets
Add a new command line option `--secrets`, which accepts a JSON file
that is structured similarly to a source file. It is should contain data
that is necessary to fetch content, but shouldn't appear in any logs.
2020-01-09 23:55:43 +01:00
Lars Karlitski
02ad4e3810 sources: fail gracefully when a source returns invalid JSON
Include the actual output of the source to help debugging.
2020-01-09 23:55:43 +01:00
Lars Karlitski
b9b2f99123 osbuild: create API sockets in the thread they're used in
This might (hopefully) fix a race in destructing the asyncio.EventLoop
that's used in all API classes, which leads to warnings about unhandled
exceptions on CI.

This also puts their creation closer to where the client-side sockets
are created.
2019-12-25 17:48:26 +01:00
Lars Karlitski
510e2b1e94 osbuild: introduce sources
Pipelines encode which source content they need in the form of
repository metadata checksums (or rpm checksums). In addition, they
encode where they fetch that source content from in the form of URLs.
This is overly specific and doesn't have to be in the pipeline's hash:
the checksum is enough to specify an image.

In practice, this precluded using alternative ways of getting at source
packages, such as local mirrors, which could speed up development.

Introduce a new osbuild API: sources. With it, a stage can query for a
way to fetch source content based on checksums.

The first such source is `org.osbuild.dnf`, which returns repository
configuration for a metadata checksum. Note that the dnf stage continues
to verify that the content it received matches the checksum it expects.

Sources are implemented as programs, living in a `sources` directory.
They are run on the host (i.e., uncontained) right now. Each source gets
passed options, which are taken from a new command line argument to
osbuild, and an array of checksums for which to return content.

This API is only available to stages right now.
2019-12-23 01:12:38 +01:00
Christian Kellner
ede3f6baeb pipeline: proper object cleanup on errors
The recent changes removed the {Assembler,Stage}Failed exceptions,
which includes them being thrown from Stage.run and Assembler.run.
Instead result dictionaries are returned even on errors. But the
object store, used as a context manager, relies on exceptions to
detect the error case and thus needs them to cleanup the temporary
objects. Without those exceptions the temporary objects end up in
the store even when the sage or assembler failed.
Restore the old behavior by throwing a generic BuildError exception
from the Stage and Assembler, which will be caught directly in the
pipeline and converted to a result dict.
2019-12-18 12:45:59 +01:00
Lars Karlitski
e0bb65dd71 api and remoteloop: don't close the passed-in socket
The socket that the osbuild and loop apis should talk on are passed into
their `__init__` function. The caller should be responsible for closing
those sockets.

This already happens in all current callers.

This fixes a non-fatal error on RHEL's python 3.6, because it was
calling `socket.close` on an already-closed socket:

    Traceback (most recent call last):
      File "/usr/lib64/python3.6/asyncio/base_events.py", line 529, in __del__
        self.close()
      File "/usr/lib64/python3.6/asyncio/unix_events.py", line 63, in close
        super().close()
      File "/usr/lib64/python3.6/asyncio/selector_events.py", line 99, in close
        self._close_self_pipe()
      File "/usr/lib64/python3.6/asyncio/selector_events.py", line 109, in _close_self_pipe
        self._remove_reader(self._ssock.fileno())
      File "/usr/lib64/python3.6/asyncio/selector_events.py", line 268, in _remove_reader
        key = self._selector.get_key(fd)
      File "/usr/lib64/python3.6/selectors.py", line 189, in get_key
        return mapping[fileobj]
      File "/usr/lib64/python3.6/selectors.py", line 70, in __getitem__
        fd = self._selector._fileobj_lookup(fileobj)
      File "/usr/lib64/python3.6/selectors.py", line 224, in _fileobj_lookup
        return _fileobj_to_fd(fileobj)
      File "/usr/lib64/python3.6/selectors.py", line 41, in _fileobj_to_fd
        raise ValueError("Invalid file descriptor: {}".format(fd))
    ValueError: Invalid file descriptor: -1
2019-12-15 12:05:14 +01:00
Lars Karlitski
61e32ff3ef pipeline: return new-style result from build pipeline
Commit 82a2be53d introduced a new return type from `Pipeline.run()`. It
changed the caller in `__main__.py`, but missed that the build pipeline
uses the same function.
2019-12-15 12:03:43 +01:00
Lars Karlitski
82a2be53d4 pipeline: return logs in --json mode
A pipeline run only returned logs in the `StageFailed` and
`AssemblerFailed` exceptions. Remove those and always return structured
data instead.

It only returns data for stages that actually ran (i.e., didn't come
from the cache). This is similar to the output in interactive mode.

Also change osbuildtest to be able to deal with output that is larger
than the pipe buffer by using subprocess.communicate().
2019-12-14 13:49:24 +01:00
Christian Kellner
24f41495d9 loop: fix a few typos
Some minor spelling corrections and a correction to the API doc in
one place.
2019-12-13 18:15:08 +01:00
Christian Kellner
d1d27567e8 buildroot: dyld workaround also on ppc64le
The workaround of manually linking /lib64 -> /usr/lib64 inside the
container that is needed on s390 is also required on ppc64 because
here the dynamic linker is set to /lib64/ld64.so.2 and the /lib64
link is not created.
2019-12-12 13:16:05 +01:00
Christian Kellner
575039db29 buildroot: work around s390x linker + nspawn issue
Work around a combination of systemd not creating the link from
/lib64 -> /usr/lib64 (see systemd issue #14311) and the dynamic
linker is being set to (/lib/ld64.so.1 -> /lib64/ld64.so.1)
Therefore we manually create the link before calling nspawn
2019-12-12 13:16:05 +01:00
Lars Karlitski
f0a7b2261e pipeline: introduce host runner
osbuild currently throws an error when not passing a build environment
on the command line, because the runner is unset. This is annoying on
hosts which only need a runner set, but no build pipeline.

To simplify running osbuild in this common case, introduce
`org.osbuild.host`, which is a runner that is defined to work on the
host that osbuild is installed on. Use this runner by default and
include a symlink to the right runner in the Fedora and RHEL packages.

Also add `runners/org.osbuild.host` to `.gitignore`, so that developers
can set the symlink when running osbuild from the source directory.

Fixes #171
2019-12-02 13:45:48 +01:00
Lars Karlitski
7754fd8e78 treesum: don't use dir_fd parameter in os.scandir()
This parameter was added in python 3.7, but we're only depending on 3.6
for RHEL.

Pass a path to `/proc/self/fd/...` instead.
2019-11-29 00:45:14 +01:00
Lars Karlitski
64713449ce Introduce runners
We've been using a generic `osbuild-run`, which sets up the build
environment (and works around bugs) for all build roots. It is already
getting unwieldy, because it tries to detect the OS for some things it
configures. It's also about to cause problems for RHEL, which doesn't
currently support a python3 shebang without having /etc around.

This patch changes the `build` key in a pipeline to not be a pipeline
itself, but an object with `runner` and `pipeline` keys. `pipeline` is
the build pipeline, as before. `runner` is the name of the runner to
use. Runners are programs in the `runners` subdirectory.

Three runners are included in this patch. They're copies of osbuild-run
for now (except some additions for rhel82). The idea is that each of
them only contains the minimal setup code necessary for an OS, and that
we can review what's needed when updating a build root.

Also modify the `--build-pipeline` command line switch to accept such a
build object (instead of a pipeline) and rename it accordingly, to
`--build-env`.

Correspondingly, `OSBUILD_TEST_BUILD_PIPELINE` → `OSBUILD_TEST_BUILD_ENV`.
2019-11-25 13:05:22 +01:00
Lars Karlitski
616e1ecbba buildroot: run everything with osbuild-run
`osbuild-run` sets up the build root so that programs can be run
correctly in it. It should be run for all programs, not just stages and
assemblers (even though they're the only consumers right now).

Also, conceptually, `osbuild-run` belongs to the build root. We'll
change its implementation based on the build root in a future commit.

The buildroot already sets up `/run/osbuild/api`. It makes sense to have
it manage libdir as well.

A nice side benefit of this is a simplification of the Stage and
Assembler classes, which grew quite complex and contained duplicate
code.
2019-11-25 13:05:22 +01:00
Christian Kellner
6e5b838892 pipeline: use API to setup stdio inside the container
Use the new the osbuild API to setup the standard input/output
inside the container, i.e. replace stdin, stdout, and stderr with
sockets provided by the host.
2019-10-30 18:44:55 +01:00
Christian Kellner
93e1c60460 api: new host side API to be used by the container
Introduce an osbuild API that can be used by the container to talk
to the osbuild host. It currently supports one method 'setup-stdio'
which should be used by the container to setup its standard input/
output so the stages can transparently do i/o with the osbuild host
via stdio.
The input data (args) is written to a temp-file backed buffer. The
output is either the host's stdout directly or another temp-file
backed buffer; the latter is re-opened (via /proc/self/fd) to get
another file-descriptor for the container, so in theory the host
and the container could do i/o to the same buffer independently.
2019-10-30 18:44:55 +01:00
Christian Kellner
76518db26b dump_fds: add flags and address parameter
Expose the flags, address parameter of the underlying sock.sendmsg
method, in order to be able to explicitly specify the recipient of
the message; as needed in connection-less mode.
2019-10-30 18:44:55 +01:00
Christian Kellner
1c5b97afbc load_fds: use frombytes instead of fromstring
Python 3.2 renamed array.fromstring to array.frombytes, but kept
the former as an, now deprecated, alias. Use the canonical form
which indeed better describes what is going on.
2019-10-30 18:44:55 +01:00
Martin Sehnoutka
27cf84edd5 bind osbuild module from dynamically discovered path 2019-10-21 15:20:31 +02:00
Martin Sehnoutka
831459e9e9 fix execv /usr/lib/osbuild/osbuild-run does not exist
In case osbuild is invoked without libdir parameter, the osbuild files
are not propagated into the buildroot container and therefore all
pipelines containing buildroot fail.

Example:
```
$ sudo osbuild --store /var/osbuild/ qcow2-pipeline.json
...
execv(/usr/lib/osbuild/osbuild-run) failed: No such file or directory
```

Unfortunately this is only the first error. Once you fix it, you realize
that also the symlink from "assemblers" directory is missing and
therefore you cannot import osbuild because it is not available anywhere
in the path. This is why I had to bind the osbuild module from host to
the build container.
2019-10-21 15:20:31 +02:00
Lars Karlitski
cb2f383601 remoteloop: make LoopClient.device a context manager 2019-10-07 10:10:51 +02:00
Lars Karlitski
0dd60b3abf remoteloop: pass filename to create_device
This makes LoopClient simpler to use in the common case.
2019-10-07 10:10:51 +02:00
Lars Karlitski
356f62058f remoteloop: remove dir_fd argument in create_device
If dir_fd wasn't passed, create_device() openend it to `/dev` and forgot
about closing it. To fix this, it would have to gain logic to only close
the fd if it wasn't passed in.

Side-step the problem by removing dir_fd, since nothing is using it
right now. We can add it back if something needs it.
2019-10-07 10:10:51 +02:00
Lars Karlitski
3d3ffda5d8 remoteloop: don't close a socket it didn't open
Closing the socket is the responsibility of whoever opened it.

Fix this in the only user (qemu assembler) by using socket() in a `with`
block, which closes the socket on exit.
2019-10-07 10:10:51 +02:00
Ondřej Budai
f9b2da9ad3 osbuild: print tree id and output id also in non-json mode 2019-10-03 14:50:29 +02:00
Martin Sehnoutka
fa8de2f6d8 move files from /usr/libexec to /usr/lib
There is no real difference in these two directories. Composer already
uses /usr/lib, so OSBuild should use the same as well.
2019-10-02 15:01:01 +02:00
Ondřej Budai
adf5989de2 osbuild/pipeline: Fix crashes when running multiple builds at once
Storytime! I tried to run multiple osbuilds at once. It failed when
unmounting the buildtree. Weird. It turned out the buildtree was not
there anymore when osbuild tried to unmount it. But who unmounted it?

We need to deep dive into mount-types.
Nowadays, the / directory is shared-mounted by systemd. See:
https://serverfault.com/questions/868682/implications-of-mount-make-private
This has interesting implications, see the following example:

we start osbuild1 with /var/tmp/os1 as its store
osbuild1 creates /var/tmp/os1/tmp
osbuild1 bind-mounts / onto /var/tmp/os1/tmp

we start osbuild2 with /var/tmp/os2 as its store
osbuild2 creates /var/tmp/os2/tmp
osbuild2 bind-mounts / onto /var/tmp/os2/tmp

Now, the shared-mounting goes into effect:
The second mount-event gets propagated into the first mount, where it
creates another mount, so we get something like this:
/var/tmp/os1/tmp/var/tmp/os2/tmp

But this is just a start! Imagine running three osbuilds at once.
The event would get propagated to those 3 mounts created by two
osbuilds, creating 3 extra mounts, 7 in total.

It turns out this mounting strategy creates an *exponential number* of
mounts. Crazy, right?

This commit mounts the root inside build root using private bind, which
doesn't propagate bind-events. This solves the problem with the
exponential growth.

But the original problem was different, mount points were disappearing.
So how does this fix solve the problem?

Honestly, I don't know. Something with mount-event propagation is
probably responsible, but I cannot imagine how it is actually affecting
the unbinding.
2019-10-02 06:20:05 +02:00
Lars Karlitski
d42b8113fa osbuild: allow reading pipeline from stdin 2019-09-27 18:22:46 +02:00
Lars Karlitski
83475cc9f4 osbuild: store outputs in objectstore
Treat outputs like we treat trees: store them in the object store. This
simplifies using osbuild and allows returning a cached version if one is
available.

This makes the `--output` parameter redundant. Remove it.
2019-09-25 23:50:50 +02:00
Lars Karlitski
cb173f7d3c objectstore: refer to objects, not trees
Also simplify method names with redundant words:

  has_tree → contains
  get_tree → get
  new_tree → new
2019-09-25 23:50:50 +02:00
Lars Karlitski
9edeb19ebb osbuild: add --json argument
`osbuild --json [ARGS]` will suppress the normal output and print its
result as JSON. For now, it only does this when it returns 0. Otherwise,
it prints the error from the latest stage.

This is useful for other tools to call it and get machine-readable
output.
2019-09-25 23:50:50 +02:00
Lars Karlitski
635b041d84 pipeline: simplify return value of Pipeline.run()
The current implementation was broken, because it didn't return results
from the cached stages. Simpley return a boolean now, True for success.
2019-09-25 23:50:50 +02:00
Lars Karlitski
fd37a5d646 pipeline: introduce output id
Introduce and output id, which is the checksum over a full pipeline,
including all stages and the assembler. The id of a pipeline did not
include assemblers before. To be less confusing, rename the existing id
to "tree id".
2019-09-25 23:50:50 +02:00
Lars Karlitski
f1151a1719 objectstore: clarify ENOTEMPTY handling 2019-09-25 23:50:50 +02:00
Lars Karlitski
cd59b94ded tree-wide: always explicitly pass check to subprocess.run
pylint recently started recommending this.
2019-09-24 20:17:04 +02:00
Ondřej Budai
6bd568192a osbuild: Move /var in BuildRoot outside of tmpfs
In BuildRoot a new mount /var pointing to temporary directory in host's
/var/tmp is created. This enables us to have temporary storage inside
the container which is not hosted on tmpfs. Thanks to that we can move
larger files out of the part of filesystem which is hosted on tmpfs to
save up memory on machines with low memory capacity.
2019-09-07 08:23:03 +02:00
Ondřej Budai
883b9dc3c5 osbuild: Change default value of --store to just .osbuild
This commit changes the default value of --store argument from
.osbuild/store to just .osbuild. The reason is just to make the path
shorter.
2019-09-06 00:22:37 +02:00
Ondřej Budai
cf046fcaeb osbuild: fix stages caching
We have never tried to reuse the first stage due to fact that range in for loop didn't include zero index. This commit fixes it.
2019-09-03 22:11:54 +02:00
Tom Gundersen
ba6918f945 osbuild: allow additional an additional build-pipeline to be prepended
The best practice for creating a pipeline should be to include at least
one level of build-pipelines. This makes sure that the tools used to
generate the target image are well-defined.

In principle one could add several layers, though in pracite, one would
hope that the envinment used to build the buildroot does not affect the
final image (and as we anyway cannot recurr indefinitely, we fall back
to simply using the host system in this case).

This only makes sense, if the contents of the host system truly does not
affect the generated image, and as such we do not include any information
about the host when computing the hash that identifies a pipeline.

In fact, any image could be used in its place, as long as the required
tools are present. This commit takes advantage of that fact. Rather than
run a pipeline with the host as the build root, take a second pipeline
to generate the buildroot, but do not include this when computing the
pipeline id (so it is different from simply editing the original JSON).

This is necessary so we can use the same pipelines on significantly
different host systems (run with different --bulid-pipeline arguments).
In particular, it allows our test pipelines that generate f30 images
to be run unmodified on Travis (which runs Ubuntu).

Signed-off-by: Tom Gundersen <teg@jklm.no>
2019-08-30 12:00:47 +02:00