Several requirements for storing registry data have been compiled and the
backend layout has been refactored to comply. Specifically, we now store most
data as blobs that are linked from repositories. All data access is traversed
through repositories. Manifest updates are no longer destructive and support
references by digest or tag. Signatures for manifests are now stored externally
to the manifest payload to allow merging of signatures posted at different
time.
The design is detailed in the documentation for pathMapper.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
To smooth initial implementation, uploads were spooled to local file storage,
validated, then pushed to remote storage. That approach was flawed in that it
present easy clustering of registry services that share a remote storage
backend. The original plan was to implement resumable hashes then implement
remote upload storage. After some thought, it was found to be better to get
remote spooling working, then optimize with resumable hashes.
Moving to this approach has tradeoffs: after storing the complete upload
remotely, the node must fetch the content and validate it before moving it to
the final location. This can double bandwidth usage to the remote backend.
Modifying the verification and upload code to store intermediate hashes should
be trivial once the layer digest format has settled.
The largest changes for users of the storage package (mostly the registry app)
are the LayerService interface and the LayerUpload interface. The LayerService
now takes qualified repository names to start and resume uploads. In corallry,
the concept of LayerUploadState has been complete removed, exposing all aspects
of that state as part of the LayerUpload object. The LayerUpload object has
been modified to work as an io.WriteSeeker and includes a StartedAt time, to
allow for upload timeout policies. Finish now only requires a digest, eliding
the requirement for a size parameter.
Resource cleanup has taken a turn for the better. Resources are cleaned up
after successful uploads and during a cancel call. Admittedly, this is probably
not completely where we want to be. It's recommend that we bolster this with a
periodic driver utility script that scans for partial uploads and deletes the
underlying data. As a small benefit, we can leave these around to better
understand how and why these uploads are failing, at the cost of some extra
disk space.
Many other changes follow from the changes above. The webapp needs to be
updated to meet the new interface requirements.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This change updates the path mapper to be able to specify upload management
locations. This includes a startedat file, which contains the RFC3339 formatted
start time of the upload and the actual data file.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This changeset implements a fileWriter type that can be used to managed writes
to remote files in a StorageDriver. Basically, it manages a local seek position
for a remote path. An efficient use of this implementation will write data in
large blocks.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
Since the common package no longer exists, the testutil package is being moved
up to the root. Ideally, we don't have large omnibus packages, like testutil,
but we can fix that in another refactoring round.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
In preparation for removing the common package, the tarsum utilities are being
moved to the more relevant digest package. This functionality will probably go
away in the future, but it's maintained here for the time being.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
To support clustered registry, upload UUIDs must be recognizable by
registries that did not issue the UUID. By creating an HMAC verifiable
upload state token, registries can validate upload requests that other
instances authorized. The tokenProvider interface could also use a redis
store or other system for token handling in the future.
It was probably ill-advised to couple manifest signing and verification to
their respective types. This changeset simply changes them from methods to
functions. These might not even be in this package in the future.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
Because manifests and their signatures are a discrete component of the
registry, we are moving the definitions into a separate package. This causes us
to lose some test coverage, but we can fill this in shortly. No changes have
been made to the external interfaces, but they are likely to come.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
During client implementation, it was found that requiring the size argument
made client implementation more complex. The original benefit of the size
argument was to provide an additional check alongside of tarsum to validate
incoming data. For the purposes of the registry, it has been determined that
tarsum should be enough to validate incoming content.
At this time, the size check is optional but we may consider removing it
completely.
This change updates the backend storage package that consumes StorageDriver to
use the new Stat call, over CurrentSize. It also makes minor updates for using
WriteStream and ReadStream.
Because json.Marshal does compaction on returned results, applications must
directly use SignedManifest.Raw when the marshaled value is required.
Otherwise, the returned manifest will fail signature checks.
This diff removes a few early outs that caused errors to be unreported and
catches a missed error case for signature verification from libtrust. More work
needs to be done around ensuring consistent error handling but this is enough
to make the API work correctly.
This provides compatibility with what is in docker core, ensuring that image
manifests generated here have the same formatting. We'll need to automate this
some how.
Without this copy, the buffer may be re-used in the json package, causing
missing or corrupted content for the long-lived SignedManifest object. By
creating a new buffer, owned by the SignedManifest object, the content remains
stable.
To provide rich error reporting during manifest pushes, the storage layers
verifyManifest stage has been modified to provide the necessary granularity.
Along with this comes with a partial shift to explicit error types, which
represents a small move in larger refactoring of error handling. Signature
methods from libtrust have been added to the various Manifest types to clean up
the verification code.
A primitive deletion implementation for manifests has been added. It only
deletes the manifest file and doesn't attempt to add some of the richer
features request, such as layer cleanup.
Previously, discussions were still ongoing about different storage layouts that
could support various access models. This changeset removes a layer of
indirection that was in place due to earlier designs. Effectively, this both
associates a layer with a named repository and ensures that content cannot be
accessed across repositories. It also moves to rely on tarsum as a true
content-addressable identifier, removing a layer of indirection during blob
resolution.
This change implements the first pass at image manifest storage on top of the
storagedriver. Very similar to LayerService, its much simpler due to less
complexity of pushing and pulling images.
Various components are still missing, such as detailed error reporting on
missing layers during verification, but the base functionality is present.
This changeset move the Manifest type into the storage package to make the type
accessible to client and registry without import cycles. The structure of the
manifest was also changed to accuratle reflect the stages of the signing
process. A straw man Manifest.Sign method has been added to start testing this
concept out but will probably be accompanied by the more import
SignedManifest.Verify method as the security model develops.
This is probably the start of a concerted effort to consolidate types across
the client and server portions of the code base but we may want to see how such
a handy type, like the Manifest and SignedManifest, would work in docker core.
This change separates out the remote file reader functionality from layer
reprsentation data. More importantly, issues with seeking have been fixed and
thoroughly tested.
Mostly, we've made superficial changes to the storage package to start using
the Digest type. Many of the exported interface methods have been changed to
reflect this in addition to changes in the way layer uploads will be initiated.
Further work here is necessary but will come with a separate PR.
This change contains the initial implementation of the LayerService to power
layer push and pulls on the storagedriver. The interfaces presented in this
package will be used by the http application to drive most features around
efficient pulls and resumable pushes.
The file storage/layer.go defines the interface interactions. LayerService is
the root type and supports methods to access Layer and LayerUpload objects.
Pull operations are supported with LayerService.Fetch and push operations are
supported with LayerService.Upload and LayerService.Resume. Reads and writes of
layers are split between Layer and LayerUpload, respectively.
LayerService is implemented internally with the layerStore object, which takes
a storagedriver.StorageDriver and a pathMapper instance.
LayerUploadState is currently exported and will likely continue to be as the
interaction between it and layerUploadStore are better understood. Likely, the
layerUploadStore lifecycle and implementation will be deferred to the
application.
Image pushes pulls will be implemented in a similar manner without the
discrete, persistent upload.
Much of this change is in place to get something running and working. Caveats
of this change include the following:
1. Layer upload state storage is implemented on the local filesystem, separate
from the storage driver. This must be replaced with using the proper backend
and other state storage. This can be removed when we implement resumable
hashing and tarsum calculations to avoid backend roundtrips.
2. Error handling is rather bespoke at this time. The http API implementation
should really dictate the error return structure for the future, so we
intend to refactor this heavily to support these errors. We'd also like to
collect production data to understand how failures happen in the system as
a while before moving to a particular edict around error handling.
3. The layerUploadStore, which manages layer upload storage and state is not
currently exported. This will likely end up being split, with the file
management portion being pointed at the storagedriver and the state storage
elsewhere.
4. Access Control provisions are nearly completely missing from this change.
There are details around how layerindex lookup works that are related with
access controls. As the auth portions of the new API take shape, these
provisions will become more clear.
Please see TODOs for details and individual recommendations.
We've added a path mapper to support simple mapping between path objects used
in the storage layer and the underlying file system. The target of this is to
ensure that paths are only calculated in a single place and their format is
separated from the data that makes up the path components.
This commit only includes spec implementation to support layer reads. Further
specs will come along with their implementations.