Load images and storage references in bulk during V1 synthesize

Currently, we perform multiple queries for each layer, making it much slower (especially cross-region)

Fixes #413
This commit is contained in:
Joseph Schorr 2015-09-29 17:53:39 -04:00
parent 78e8aefd45
commit 35c35d9913
4 changed files with 173 additions and 157 deletions

View file

@ -205,6 +205,9 @@ class BaseRegistryMixin(object):
self.assertEquals(response.status_code, expected_code)
return response
def _get_default_images(self):
return [{'id': 'someid', 'contents': 'somecontent'}]
class V1RegistryMixin(BaseRegistryMixin):
def v1_ping(self):
@ -212,20 +215,21 @@ class V1RegistryMixin(BaseRegistryMixin):
class V1RegistryPushMixin(V1RegistryMixin):
def do_push(self, namespace, repository, username, password, images):
def do_push(self, namespace, repository, username, password, images=None):
images = images or self._get_default_images()
auth = (username, password)
# Ping!
self.v1_ping()
# PUT /v1/repositories/{namespace}/{repository}/
data = [{"id": image_id} for image_id, _ in images.iteritems()]
self.conduct('PUT', '/v1/repositories/%s/%s' % (namespace, repository),
data=json.dumps(data), auth=auth,
data=json.dumps(images), auth=auth,
expected_code=201)
last_image_id = None
for image_id, _ in images.iteritems():
for image_data in images:
image_id = image_data['id']
last_image_id = image_id
# PUT /v1/images/{imageID}/json
@ -364,8 +368,10 @@ class V2RegistryMixin(BaseRegistryMixin):
class V2RegistryPushMixin(V2RegistryMixin):
def do_push(self, namespace, repository, username, password, images, tag_name=None,
cancel=False, invalid=False):
def do_push(self, namespace, repository, username, password, images=None, tag_name=None,
cancel=False, invalid=False, expected_manifest_code=202):
images = images or self._get_default_images()
# Ping!
self.v2_ping()
@ -375,30 +381,22 @@ class V2RegistryPushMixin(V2RegistryMixin):
# Build a fake manifest.
tag_name = tag_name or 'latest'
builder = SignedManifestBuilder(namespace, repository, tag_name)
for image_id, contents in images.iteritems():
if isinstance(contents, dict):
full_contents = contents['contents']
else:
full_contents = contents
checksum = 'sha256:' + hashlib.sha256(full_contents).hexdigest()
for image_data in images:
checksum = 'sha256:' + hashlib.sha256(image_data['contents']).hexdigest()
if invalid:
checksum = 'sha256:' + hashlib.sha256('foobarbaz').hexdigest()
builder.add_layer(checksum, json.dumps({'id': image_id, 'data': contents}))
builder.add_layer(checksum, json.dumps(image_data))
# Build the manifest.
manifest = builder.build(_JWK)
# Push the image's layers.
checksums = {}
for image_id, contents in images.iteritems():
chunks = None
if isinstance(contents, dict):
full_contents = contents['contents']
chunks = contents['chunks']
else:
full_contents = contents
for image_data in images:
image_id = image_data['id']
full_contents = image_data['contents']
chunks = image_data.get('chunks')
# Layer data should not yet exist.
checksum = 'sha256:' + hashlib.sha256(full_contents).hexdigest()
@ -414,7 +412,7 @@ class V2RegistryPushMixin(V2RegistryMixin):
# PATCH the image data into the layer.
if chunks is None:
self.conduct('PATCH', location, data=contents, expected_code=204, auth='jwt')
self.conduct('PATCH', location, data=full_contents, expected_code=204, auth='jwt')
else:
for chunk in chunks:
if len(chunk) == 3:
@ -461,7 +459,7 @@ class V2RegistryPushMixin(V2RegistryMixin):
self.assertEquals(response.headers['Content-Length'], str(len(full_contents)))
# Write the manifest.
put_code = 404 if invalid else 202
put_code = 404 if invalid else expected_manifest_code
self.conduct('PUT', '/v2/%s/%s/manifests/%s' % (namespace, repository, tag_name),
data=manifest.bytes, expected_code=put_code,
headers={'Content-Type': 'application/json'}, auth='jwt')
@ -508,11 +506,7 @@ class V2RegistryPullMixin(V2RegistryMixin):
class RegistryTestsMixin(object):
def test_pull_publicrepo_anonymous(self):
# Add a new repository under the public user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('public', 'newrepo', 'public', 'password', images)
self.do_push('public', 'newrepo', 'public', 'password')
self.clearSession()
# First try to pull the (currently private) repo anonymously, which should fail (since it is
@ -530,11 +524,7 @@ class RegistryTestsMixin(object):
def test_pull_publicrepo_devtable(self):
# Add a new repository under the public user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('public', 'newrepo', 'public', 'password', images)
self.do_push('public', 'newrepo', 'public', 'password')
self.clearSession()
# First try to pull the (currently private) repo as devtable, which should fail as it belongs
@ -552,11 +542,7 @@ class RegistryTestsMixin(object):
def test_pull_private_repo(self):
# Add a new repository under the devtable user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
self.do_push('devtable', 'newrepo', 'devtable', 'password')
self.clearSession()
# First try to pull the (currently private) repo as public, which should fail as it belongs
@ -572,11 +558,7 @@ class RegistryTestsMixin(object):
# Turn off anonymous access.
with TestFeature(self, 'ANONYMOUS_ACCESS', False):
# Add a new repository under the public user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('public', 'newrepo', 'public', 'password', images)
self.do_push('public', 'newrepo', 'public', 'password')
self.clearSession()
# First try to pull the (currently private) repo as devtable, which should fail as it belongs
@ -596,11 +578,7 @@ class RegistryTestsMixin(object):
# Turn off anonymous access.
with TestFeature(self, 'ANONYMOUS_ACCESS', False):
# Add a new repository under the public user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('public', 'newrepo', 'public', 'password', images)
self.do_push('public', 'newrepo', 'public', 'password')
self.clearSession()
# First try to pull the (currently private) repo as devtable, which should fail as it belongs
@ -615,11 +593,7 @@ class RegistryTestsMixin(object):
# Turn off anonymous access.
with TestFeature(self, 'ANONYMOUS_ACCESS', False):
# Add a new repository under the public user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('public', 'newrepo', 'public', 'password', images)
self.do_push('public', 'newrepo', 'public', 'password')
self.clearSession()
# First try to pull the (currently private) repo as anonymous, which should fail as it
@ -643,11 +617,7 @@ class RegistryTestsMixin(object):
def test_create_repo_creator_user(self):
images = {
'someid': 'onlyimagehere'
}
self.do_push('buynlarge', 'newrepo', 'creator', 'password', images)
self.do_push('buynlarge', 'newrepo', 'creator', 'password')
# Pull the repository as devtable, which should succeed because the repository is owned by the
# org.
@ -660,11 +630,7 @@ class RegistryTestsMixin(object):
resp = self.conduct('GET', '/api/v1/organization/buynlarge/robots/ownerbot')
robot_token = json.loads(resp.text)['token']
images = {
'someid': 'onlyimagehere'
}
self.do_push('buynlarge', 'newrepo', 'buynlarge+ownerbot', robot_token, images)
self.do_push('buynlarge', 'newrepo', 'buynlarge+ownerbot', robot_token)
# Pull the repository as devtable, which should succeed because the repository is owned by the
# org.
@ -677,11 +643,7 @@ class RegistryTestsMixin(object):
resp = self.conduct('GET', '/api/v1/organization/buynlarge/robots/creatorbot')
robot_token = json.loads(resp.text)['token']
images = {
'someid': 'onlyimagehere'
}
self.do_push('buynlarge', 'newrepo', 'buynlarge+creatorbot', robot_token, images)
self.do_push('buynlarge', 'newrepo', 'buynlarge+creatorbot', robot_token)
# Pull the repository as devtable, which should succeed because the repository is owned by the
# org.
@ -697,27 +659,15 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
""" Tests for V2 registry. """
def test_invalid_push(self):
images = {
'someid': 'onlyimagehere'
}
self.do_push('devtable', 'newrepo', 'devtable', 'password', images, invalid=True)
self.do_push('devtable', 'newrepo', 'devtable', 'password', invalid=True)
def test_cancel_push(self):
images = {
'someid': 'onlyimagehere'
}
self.do_push('devtable', 'newrepo', 'devtable', 'password', images, cancel=True)
self.do_push('devtable', 'newrepo', 'devtable', 'password', cancel=True)
def test_pull_by_checksum(self):
# Add a new repository under the user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
_, digest = self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
_, digest = self.do_push('devtable', 'newrepo', 'devtable', 'password')
# Attempt to pull by digest.
self.do_pull('devtable', 'newrepo', 'devtable', 'password', manifest_id=digest)
@ -725,11 +675,7 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
def test_pull_invalid_image_tag(self):
# Add a new repository under the user, so we have a real repository to pull.
images = {
'someid': 'onlyimagehere'
}
self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
self.do_push('devtable', 'newrepo', 'devtable', 'password')
self.clearSession()
# Attempt to pull the invalid tag.
@ -745,15 +691,16 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
chunk_count = int(math.ceil((len(contents) * 1.0) / chunksize))
chunks = [(index * chunksize, (index + 1)*chunksize) for index in range(chunk_count)]
images = {
'someid': {
images = [
{
'id':'someid',
'contents': contents,
'chunks': chunks
}
}
]
# Push the chunked upload.
self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
# Pull the image back and verify the contents.
blobs = self.do_pull('devtable', 'newrepo', 'devtable', 'password')
@ -765,15 +712,16 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
contents = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(size))
chunks = [(0, 100), (100, size)]
images = {
'someid': {
images = [
{
'id':'someid',
'contents': contents,
'chunks': chunks
}
}
]
# Push the chunked upload.
self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
# Pull the image back and verify the contents.
blobs = self.do_pull('devtable', 'newrepo', 'devtable', 'password')
@ -786,15 +734,16 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
chunks = [(0, 100), (10, size)]
images = {
'someid': {
images = [
{
'id':'someid',
'contents': contents,
'chunks': chunks
}
}
]
# Push the chunked upload.
self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
# Pull the image back and verify the contents.
blobs = self.do_pull('devtable', 'newrepo', 'devtable', 'password')
@ -807,22 +756,66 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
chunks = [(0, 100), (101, size, 416)]
images = {
'someid': {
images = [
{
'id':'someid',
'contents': contents,
'chunks': chunks
}
}
]
# Attempt to push the chunked upload, which should fail.
self.do_push('devtable', 'newrepo', 'devtable', 'password', images)
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
def test_multiple_layers_invalid(self):
# Attempt to push a manifest with an image depending on an unknown base layer.
images = [
{
'id': 'latestid',
'contents': 'the latest image',
'parent': 'baseid',
}
]
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images,
expected_manifest_code=400)
def test_multiple_layers(self):
# Push a manifest with multiple layers.
images = [
{
'id': 'latestid',
'contents': 'the latest image',
'parent': 'baseid',
},
{
'id': 'baseid',
'contents': 'The base image',
}
]
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
def test_multiple_tags(self):
latest_images = [
{
'id': 'latestid',
'contents': 'the latest image'
}
]
foobar_images = [
{
'id': 'foobarid',
'contents': 'the foobar image',
}
]
# Create the repo.
self.do_push('devtable', 'newrepo', 'devtable', 'password', {'someid': 'onlyimagehere'},
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=latest_images,
tag_name='latest')
self.do_push('devtable', 'newrepo', 'devtable', 'password', {'anotherid': 'anotherimage'},
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=foobar_images,
tag_name='foobar')
# Retrieve the tags.
@ -846,6 +839,7 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
self.conduct('GET', '/v2/devtable/doesnotexist/tags/list', auth='jwt', expected_code=401)
class V1PushV2PullRegistryTests(V2RegistryPullMixin, V1RegistryPushMixin, RegistryTestsMixin,
RegistryTestCaseMixin, LiveServerTestCase):
""" Tests for V1 push, V2 pull registry. """