Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

registry package rewrite #851

Merged
merged 13 commits into from
Dec 19, 2017
Merged

registry package rewrite #851

merged 13 commits into from
Dec 19, 2017

Conversation

squaremo
Copy link
Member

@squaremo squaremo commented Nov 21, 2017

Rewrite the registry code to remove some historical baggage and

These two --

-- I am punting on, since they are to do with images for other OS/Arch combinations, and this needs more thought. Although we do handle manifestlist documents now (such as you get from nats:nanoserver), if there's no Linux/amd64 image it'll still result in an incomplete set of repo metadata.

Value: val,
Expiration: expiry,
Value: append(expiryBytes, v...),
Expiration: int32(expiry),

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

@squaremo squaremo force-pushed the image-reg-caching branch 2 times, most recently from 321694c to 5514161 Compare November 30, 2017 18:16
@squaremo squaremo changed the title [WIP] registry rewrite registry package rewrite Nov 30, 2017
Copy link
Contributor

@samb1729 samb1729 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM. Some comments might be out of place because of files being moved after the commit I looked at.

type RateLimiters struct {
RPS, Burst int
perHost map[string]*rate.Limiter
mx sync.Mutex

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

@@ -96,13 +69,13 @@ func (reg *registry) tagsToRepository(client Client, id image.Name, tags []strin
toFetch := make(chan string, len(tags))
fetched := make(chan result, len(tags))

for i := 0; i < reg.connections; i++ {
for i := 0; i < 100; i++ {

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

logger: logger,
// GetImage gets the manifest of a specific image ref, from its
// registry.
func (c *Cache) GetImage(id image.Ref) (image.Info, error) {

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

@@ -43,7 +42,7 @@ type backlogItem struct {
func (w *Warmer) Loop(stop <-chan struct{}, wg *sync.WaitGroup, imagesToFetchFunc func() ImageCreds) {
defer wg.Done()

if w.Logger == nil || w.ClientFactory == nil || w.Expiry == 0 || w.Writer == nil || w.Reader == nil {
if w.Logger == nil || w.ClientFactory == nil || w.Expiry == 0 || w.Cache == nil {

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

@@ -151,12 +146,12 @@ func (w *Warmer) warm(id image.Name, creds Credentials) {
// See if we have the manifest already cached
// We don't want to re-download a manifest again.
newID := id.ToRef(tag)
key, err := cache.NewManifestKey(newID.CanonicalRef())
key := cache.NewManifestKey(newID.CanonicalRef())
if err != nil {

This comment was marked as abuse.

This comment was marked as abuse.

// value (show the images, but also indicate there's a problem, for
// example).
type ImageRepository struct {
LastError string

This comment was marked as abuse.

This comment was marked as abuse.


// TODO(michael): can we type switch? Not sure how dependable the
// underlying types are.
switch mt {

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

registry/mock.go Outdated
type ManifestFunc func(id image.Ref) (image.Info, error)
type TagsFunc func(id image.Name) ([]string, error)
type ManifestFunc func(ref string) (image.Info, error)
type TagsFunc func() ([]string, error)

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

Copy link
Contributor

@tamarakaufler tamarakaufler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No ground breaking thoughts just a possible idea for a tiny cleanup

remoteDuration.With(
LabelRequestKind, RequestKindMetadata,
fluxmetrics.LabelSuccess, strconv.FormatBool(err == nil),
).Observe(time.Since(start).Seconds())
return
}

func (m *instrumentedClient) Tags(id image.Name) (res []string, err error) {
func (m *instrumentedClient) Tags(ctx context.Context) (res []string, err error) {

This comment was marked as abuse.

This comment was marked as abuse.

// Client is a remote registry client for a particular image
// repository (e.g., for quay.io/weaveworks/flux). It is an interface
// so we can wrap it in instrumentation, write fake implementations,
// and so on.

This comment was marked as abuse.

This comment was marked as abuse.

squaremo and others added 13 commits December 18, 2017 15:41
 * make rate limiters a struct rather than a global
 * remove some constructor procedures in favour of literals

This is mainly to tidy some things up before making more drastic
changes.
It used to be the case that flux used a pull-through cache for image
metadata. The cache was practically, but not logically, necessary. It
made sense then for there to be a registry Client interface with two
implementations: the implementation that looked at the cache, and the
implementation that went and fetched the metadata from a registry.

But we changed this such that we only ever look in the cache when
querying, and refresh from registries in the background. Querying can
just use cache operations, and only refreshing needs to fetch metadata
from registries.

This commit removes some of the layers that existed only because of
the erstwhile equivalence of the two clients. Specifically: it removes
the registry struct, which used a (cache) client factory to get a
(cache) Client which it then used to assemble image
manifests. Instead, the cache client just assembles the manifests
itself.

Consequently, the cache client factory can also go. I have kept the
remote client factory around for now, as the work it does to construct
a client has to live somewhere.
It's not necessary to repackage results from the docker regsitry
library.
No point in having separate fields (in fact, it'd be weird if you
supplied two different implementations, or two clients pointed at
different places.)
 - we fetch the whole entry each time, so just return both of the
   expiry and value
 - don't encode with JSON, since the byte array will end up being
   encoded as `[byte0, byte1, byte2, ...]`
There are two components that read or write to the cache. The first is
the cache warmer, which refreshes the list of tags for an image
repository in the cache, and for each tag, writes the image metadata
(the layer created date) to the cache; the second is `registry.Cache`
(an implementation of `registry.Registry`), which assembles a full
list of `image.Info` by first reading the tags from the cache, then
for each tag, reading the metadata.

There are two problems with this:

 - the `registry.Cache` does a lot of reads to re-assemble what the
   warmer already had all in one place; and,
 - if the warmer fails to fetch a single manifest, the Cache cannot
   assemble an answer at all.

This commit changes the warmer so that it writes whole results, and
`registry.Cache` so that it reads whole results. This solves the first
problem. The writing is done as a ratchet: we only overwrite the
result when we have managed to get all the metadata. This solves the
second problem.

A consequence is that we no longer need to store the tags separately,
as they are an intermediate result.
This moves the "cache" (looking less like a cache every day) into its
own package, with the memcached implementation a subpackage therein.
 - remove `Stop` method from cache client interface; it is particular
   to this client
 - memcacheClient -> MemcacheClient so it can be returned as such
 - explain why the expiry goes in two places
The package github.com/docker/distribution/registry/client (now) has
types and procedures for fetching image metadata.

This means we can get rid of a lot of the workarounds we were using to
patch `docker-registry-client` so that e.g., it works with quay.io.

I have changed the interfaces around a bit, since we usually need to
request image manifests "in a straight line", reusing the same
authorisation, and it makes sense to construct a client for each such
series of requests.

There are a few things we can keep track of across series of requests:
specifically, the challenges we've seen from each host. So it's still
useful to have a "factory" to hold that information, as well as other
commonalities like rate limiting.
Replace the `context.TODO()`s given to with a context passed in.

Also: the values returned by the docker distribution registry client
will be one of the types in
github.com/docker/distribution/manifest/{schema1,schema2,manifestlist},
so instead of dispatching on the media type and doing the
deserialisation ourselves, just dispatch on the value's type.
These fields help with detecting when

 - references with different tags point at the same image (e.g.,
 `latest` vs whatever)
 - the same reference fetched at different times refer to different
   images (e.g., `latest` now vs `latest` before)

NB I have not attempted to use the fields as above -- I've just made
sure they are populated.
 - rate_limiter_test.go tested that contexts were not shared between
   transports; but we no longer implement the transport under test.
 - the use of memcached has changed
 - removed some spurious cache warmer tests
 - move the mock registry objects (which are handy elsewhere) to
   registry/mock
 - remove the tests that check if the registry assembles manifests
   from individual cache entries; it no longer does that
 - check that the cache warmer populates the cache, then the registry
   can read the result
 - move and rewrite the registry integration test

and a change made /en passant/:

 - supply the registry cache expiry argument to the cache and use it
   - and don't supply it to the warmer, which doesn't use it
The cache.Warmer struct has some mandatory fields and some
optional. With a constructor we can better enforce this.

In particular, the cache client implementation, which is now mandatory
but was still described as optional in examples and help text.

We can simplify (and in examples, omit) the default values by assuming
memcached is in the same namespace.
@squaremo squaremo merged commit 5245a0c into master Dec 19, 2017
@squaremo squaremo deleted the image-reg-caching branch December 19, 2017 11:28
@squaremo squaremo mentioned this pull request Dec 22, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants