Commit graph

32 commits

Author SHA1 Message Date
Christian Kellner
5fbf5bb431 objectstore: use 'tmp' subdir for temp directories
Instead of creating temporary directories at the root of the store
create them in a sub-directory called 'tmp'. This should make it
easy to cleanup left-over (temporary) dirs in case of crashes.
Additionally, it has the nice side effect that it is possible to
check that there are no objects that are still in-flight, i.e. not
cleaned-up.
2020-03-07 17:13:21 +01:00
Christian Kellner
01d989e718 objectstore: remove context manager for new method
Turn `ObjectStore.new` into a plain method, since `Object` itself
can be used as a context manager, which is now directly returned,
instead of internally wrapped in a `with` statement and then
yielded. Thus for callers of the method nothing changes and the
behavior of `with objectstore.new() as x` is exactly the same.
2020-03-07 17:13:21 +01:00
Christian Kellner
4b790ac284 objectstore: use a context also for Object.write
Reading from an `Object` via `read` already uses a context manager
to manage the read-only bind mount and also maintain a count of
currently active readers. With this an attempt to start a new
`write` operation while readers were active can be detected and
an exception is throw. Since `write` was not introducing a context
the inverted situation, i.e. reads while a write is ongoing, was
not possible to detect.
This commit therefore introduces a context also for `.write` so
that we can enforce the policy to have either many readers but no
writers, or just one writer and no readers.
A bind mount is also used for write (in read-write mode) to hide
the internal path of the tree.
2020-02-29 01:14:24 +01:00
Christian Kellner
42a365d12f osbuild: no auto commit of the last stage
Do not automatically commit the last stage of the pipeline to the
store. The last stage is most likely not what should be cached,
because it will contain all the individual customization and thus
be very likely different for different users. Instead, the dnf or
rpm stages have a higher chance of being the same and thus are
better candidates for caching.
Technically this change is done via two big changes that build
upon new features introduces in the previous commits, most notably
the copy on write semantics of Object and that input/output is
being done via `objectstore.Object` instead of plain paths. The
first of the two big changes is  to create one new `Object` at
the beginning of `pipeline.run` and use that, in write mode via
`Object.write` across invocations of `stage.run` calls, with
checkpoints being created after each stage on demand.
The very same `Object` is then used in read mode via `Object.read`
as the input tree for the Assembler. After the assembler is done
the resulting image/tree is manually committed to the store.
The other big change is to remove the `ObjectStore.commit` call
from the `ObjectStore.new` method and thus the automatic commit
after the last stage is gone.
NB: since the build tree is being retrieved in `get_buildtree`
from the store, a checkpoint for the last stage of the build
pipeline is forced for now. Future commits will refactor will
do away with that forced commit as well.
Change osbuildtest.TestCase to always create a checkpoint at
the final tree (the last stage of the pipeline), since tests
need it to check the tree contents.
2020-02-28 16:11:49 +01:00
Christian Kellner
7720b5508d objectstore: refactor .get() to use Object
Instead of using custom bind-mount based logic in ObjectStore.get,
use a combination of Object + `Object.read` with the supplied base
(that can be None), which will lead to exactly the same outcome.
2020-02-28 16:11:49 +01:00
Christian Kellner
be8aafbb90 objectstore: Object.read() for read only access
Provide a way to read the current contents of the object, in a way
the follows the copy-on-write semantics: If `base` is set but the
object has not yet been written to, the `base` content will be
exposed. If no base is set or the object has been written to, the
current (temporary) tree will be exposed. In either way it is done
via a bind mount so it is assured that the contents indeed can only
be read from, but not written to.
The code also currently make sure that there is no write operation
started as long as there is at least one reader.
Additionally, also introduce checks that the object is intact, i.e.
not cleaned up, for all operations that require such a state.
2020-02-28 16:11:49 +01:00
Christian Kellner
c73a28613b objectstore: fix Object._open exception handling
Move the call to os.open outside of the try block so that if an
exception occurs it will be properly propagated to the callers.
2020-02-28 16:11:49 +01:00
Christian Kellner
007488488e objectstore: extract mount code to small helpers
Extract the mount code into small little helpers, intended to be
reused from different places. Adapt ObjectStore to use those.
2020-02-28 16:11:49 +01:00
Christian Kellner
e610fa9659 objectstore: use private bind mounts for get()
Use `--make-private` for the bind mount in `ObjectStore.get`.
2020-02-28 16:11:49 +01:00
Christian Kellner
9b61f50792 objectstore: make Object.open a private method
Analogous to `_path`, it is not possible to identify the intended
mode of the i/o operation from using `open` (whether it is a read
or a write operation) and thus make it an internal method and only
use it for read operations.
2020-02-28 16:11:49 +01:00
Christian Kellner
3258bb62d4 objectstore: make Object.path a private property
Since it is hard to infer the intended modus of the i/o operation,
i.e. whether it is going to be a read or a write from accessing the
`path` property make it an internal method. Do not initialize the
method on property access but return the writable tree, if Object
is initialized, the path to its base tree otherwise.
Adapt all the usage internally: Use `path` for read operations and
initialize the object and then directly use `_tree` for write ops.
2020-02-28 16:11:49 +01:00
Christian Kellner
0ef5de3c94 objectstore: Object stores it base id not path
Instead of storing the base path, store the object id of its base
and resolve it to the path via the ObjectStore whenever needed.
2020-02-28 16:11:49 +01:00
Christian Kellner
6a2a7d99f7 objectstore: unify commit and snapshot code paths
As a result of the previous commits that implement copy on write
semantics, `commit` can now be used to create snapshots. Whenever
an Object is committed, its tree is moved to the store and it is
being reset, i.e. a new clean workdir is created and the old one
discarded. The moved tree is then set as the base of the reset
Object. On the next call to `write` the moved tree will be copied
over and forms the basis of the Object again. Should nobody want
to write to Object after the snapshot, i.e. the `commit`, no copy
will be made.
NB: snapshots/commits will act now act as synchronization points:
if a object with the same treesum, i.e. the very same content
already exists, the move (i.e. `store_tree`) will gracefully fail
and the existing content will be set as the base for Object.
2020-02-28 16:11:49 +01:00
Christian Kellner
39213b7f44 objectstore: copy on write semantics for Object
Since Object knows its base now, the initialization of the tree
with the content of its base can be delayed until the moment
someone wants to actually modify the tree, thus implementing
copy on write semantics. For this a new `write` method is added
that will initialize the base and return the writable tree. It
should be used instead of `path` whenever the a client wants to
write to the tree of the Object.
Adapt the pipeline and the tests to use the new `write` method
in all the appropriate places.
NB: since the intention can not be inferred when using `path`
directly, the Object is still being initialized there.
2020-02-28 16:11:49 +01:00
Christian Kellner
0874b80734 objectstore: Object knows its base
When a new Object is created it can have a `base`, i.e. another
object that is already committed to the store, which is then used
to initialize the tree of the new object. That is, the contents
of the new Object will be based on the contents of the existing.
The initialization of an Object with its base (if any) was done
by the ObjectStore. Move all of that logic inside `Object`:
The Object will store its base, which `Object.init` will use to
initialize itself. Additionally, if `Object.path` is accessed
`init` is being called as well to make sure it is properly
initialized, i.e. the tree initialized with the base content.
2020-02-28 16:11:49 +01:00
Christian Kellner
25b3807a5b objectstore: snapshot takes Object not path
Refactor the `ObjectStore.snapshot` method to take an `Object` not
a plain filesystem tree, so the latter is more encapsulated from
the ObjectStore user (e.g. the pipeline) and prepares a unified
code-path for `snapshot` and `commit` in the future.
2020-02-28 16:11:49 +01:00
Christian Kellner
5deb1be514 objectstore: change Object.move to .store_tree
Now that Object manages its work directory itself, re-create the
latter when the its tree is moved, i.e. when the object is being
committed to the store. This means that after the object has been
written to the store it is in the same state is if it was new and
can be used in the very same way.
If the move itself fails (the rename(2) fails), the tree and its
contents is cleaned up with the reset of the work directory.
Rename the `move` method to `store_tree` to better reflect how the
method should be used, i.e. to store the tree corresponding to the
Object instance.
2020-02-28 16:11:49 +01:00
Christian Kellner
6d14dee9a2 objectstore: object manages its work dir
When a new object is being created, a corresponding temporary
directory is created within the file system tree of the object
store, which shall be called the "work dir". Within that dir a
well-known directory ('tree') is created that then is the root
of the filesystem tree[1] that clients use to store the tree
or the resulting image in.
Previously, the work dir was managed, i.e. created and cleaned
up (via a context manager) by the ObjectStore. Now the Object
itself manages the tree and thus the lifetime of the work dir
is more directly integrated and controlled by it. As a result
the Object itself is now a context manager. On exit of the
context the work dir is cleaned up.

[1] For the assembler this is the output directory that will
    contain the final image.
2020-02-28 16:11:49 +01:00
Christian Kellner
399606528c objectstore: helper to create temp dirs inside the store
Create a small helper method that creates a new temporary directory
of type tempfile.TemporaryDirectory within the store and returns it.
2020-02-28 16:11:49 +01:00
Christian Kellner
d10537da42 objectstore: yield Object not path from .new()
Instead of just returning the path of the temporary object that is
created in .new() the actual instance of the new `Object` is being
returned, which can then provide a richer interface for clients
than a plain directory path.
2020-02-28 16:11:49 +01:00
Christian Kellner
52736169f1 objectstore: Object keeps reference to store
Keep are reference to the parent store, which this object is tied
to. It is currently not yet used directly but is a preparation for
a closer Object and ObjectStore integration that will happen in
commits to follow.
2020-02-28 16:11:49 +01:00
Christian Kellner
19f49e5dc3 objectstore: rename TreeObject to Object
As the name implies, the ObjectStore stores objects, which can be
trees but also everything an Assembler can make of the input tree,
like qcow2 images, tarballs and other non tree-like outputs.
Therefore rename the TreeObject to Object to better reflect that it
is representing any object, not only trees, in the store.
2020-02-28 16:11:49 +01:00
Christian Kellner
3b7c87d563 objectstore: helper to resolve references to paths
Introduce a small helper function to resolve object_id references
into their paths inside the object store and use that throughout
the store.
2020-02-28 16:11:49 +01:00
Christian Kellner
5a61d8c869 objectstore: extract method to open a TreeObject
Extract the opening a TreeObject out of the treesum property so
that the latter is easier to read.
2020-02-06 16:10:35 +01:00
Christian Kellner
ce5719a03f objectstore: move tree-moving code into the tree
The code to move the a TreeObject more naturally belongs to the
TreeObject itself and makes the ObjectStore.commit() method even
easier to read.
2020-02-06 16:10:35 +01:00
Christian Kellner
b5b5e7be29 objectstore: also ignore EEXIST when committing
When the tree is committed to the objects directory of the object-
store, it is done via rename(3). The two possible errors that can
be raised in case that a non-empty tree with the same name already
exist is [EEXIST] or [ENOTEMPTY]. The latter was already ignored
but the former was not. At least on btrfs former will be raised

  File "/home/gicmo/Code/src/osbuild/osbuild/objectstore.py",
    os.rename(tree.root, output_tree)
  FileExistsError: [Errno 17] File exists: 'store/tmpyyi3yvie/tree' -> 'store/objects/…'
2020-02-06 16:10:35 +01:00
Christian Kellner
3a40d31bee objectstore: introduce tree snapshot support
Add a new method to the ObjectStore that takes a path to a file
system tree, which is currently being built, and commits it to
the store and references it via a given object_id.
The tree is copied to a temporary location (co-located in the
store to enable fast copying via reflinks) and then atomically
moved into the ObjectStore's objects path via rename(3).
2020-02-06 16:10:35 +01:00
Christian Kellner
db8618f192 objectstore: extract logic to commit a tree
Extract the code from ObjectStore.new that will commit the filled
tree to the store into its own method so it can be used from a
future method to snapshot trees at random points in time.
2020-02-06 16:10:35 +01:00
Christian Kellner
4831927e84 objectstore: introduce TreeObject
Introduce a small `TreeObject` class that is the representation of
a tree during its construction. It supports calculating its treesum
as well initialize the new tree with an existing one.
2020-02-06 16:10:35 +01: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
f1151a1719 objectstore: clarify ENOTEMPTY handling 2019-09-25 23:50:50 +02:00
Tom Gundersen
679b79c5e5 osbuild: split package into separate files
Import modules between files using the syntax `from . import foobar`,
renaming what used to be `FooBar` to `foobar.FooBar` when moved to a
separate file.

In __init__.py only import what is meant to be public API.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2019-08-21 09:56:50 +04:00