initial import for Open Source 🎉
This commit is contained in:
parent
1898c361f3
commit
9c0dd3b722
2048 changed files with 218743 additions and 0 deletions
150
workers/test/test_exportactionlogsworker.py
Normal file
150
workers/test/test_exportactionlogsworker.py
Normal file
|
@ -0,0 +1,150 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import boto
|
||||
|
||||
from httmock import urlmatch, HTTMock
|
||||
from moto import mock_s3_deprecated as mock_s3
|
||||
|
||||
from app import storage as test_storage
|
||||
from data import model, database
|
||||
from data.logs_model import logs_model
|
||||
from storage import S3Storage, StorageContext, DistributedStorage
|
||||
from workers.exportactionlogsworker import ExportActionLogsWorker, POLL_PERIOD_SECONDS
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
_TEST_CONTENT = os.urandom(1024)
|
||||
_TEST_BUCKET = 'some_bucket'
|
||||
_TEST_USER = 'someuser'
|
||||
_TEST_PASSWORD = 'somepassword'
|
||||
_TEST_PATH = 'some/cool/path'
|
||||
_TEST_CONTEXT = StorageContext('nyc', None, None, None, None)
|
||||
|
||||
|
||||
@pytest.fixture(params=['test', 'mock_s3'])
|
||||
def storage_engine(request):
|
||||
if request.param == 'test':
|
||||
yield test_storage
|
||||
else:
|
||||
with mock_s3():
|
||||
# Create a test bucket and put some test content.
|
||||
boto.connect_s3().create_bucket(_TEST_BUCKET)
|
||||
engine = DistributedStorage(
|
||||
{'foo': S3Storage(_TEST_CONTEXT, 'some/path', _TEST_BUCKET, _TEST_USER, _TEST_PASSWORD)},
|
||||
['foo'])
|
||||
yield engine
|
||||
|
||||
|
||||
def test_export_logs_failure(initialized_db):
|
||||
# Make all uploads fail.
|
||||
test_storage.put_content('local_us', 'except_upload', 'true')
|
||||
|
||||
repo = model.repository.get_repository('devtable', 'simple')
|
||||
user = model.user.get_user('devtable')
|
||||
|
||||
worker = ExportActionLogsWorker(None)
|
||||
called = [{}]
|
||||
|
||||
@urlmatch(netloc=r'testcallback')
|
||||
def handle_request(url, request):
|
||||
called[0] = json.loads(request.body)
|
||||
return {'status_code': 200, 'content': '{}'}
|
||||
|
||||
def format_date(datetime):
|
||||
return datetime.strftime("%m/%d/%Y")
|
||||
|
||||
now = datetime.now()
|
||||
with HTTMock(handle_request):
|
||||
with pytest.raises(IOError):
|
||||
worker._process_queue_item({
|
||||
'export_id': 'someid',
|
||||
'repository_id': repo.id,
|
||||
'namespace_id': repo.namespace_user.id,
|
||||
'namespace_name': 'devtable',
|
||||
'repository_name': 'simple',
|
||||
'start_time': format_date(now + timedelta(days=-10)),
|
||||
'end_time': format_date(now + timedelta(days=10)),
|
||||
'callback_url': 'http://testcallback/',
|
||||
'callback_email': None,
|
||||
}, test_storage)
|
||||
|
||||
test_storage.remove('local_us', 'except_upload')
|
||||
|
||||
assert called[0]
|
||||
assert called[0][u'export_id'] == 'someid'
|
||||
assert called[0][u'status'] == 'failed'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('has_logs', [
|
||||
True,
|
||||
False,
|
||||
])
|
||||
def test_export_logs(initialized_db, storage_engine, has_logs):
|
||||
# Delete all existing logs.
|
||||
database.LogEntry3.delete().execute()
|
||||
|
||||
repo = model.repository.get_repository('devtable', 'simple')
|
||||
user = model.user.get_user('devtable')
|
||||
|
||||
now = datetime.now()
|
||||
if has_logs:
|
||||
# Add new logs over a multi-day period.
|
||||
for index in range(-10, 10):
|
||||
logs_model.log_action('push_repo', 'devtable', user, '0.0.0.0', {'index': index},
|
||||
repo, timestamp=now + timedelta(days=index))
|
||||
|
||||
worker = ExportActionLogsWorker(None)
|
||||
called = [{}]
|
||||
|
||||
@urlmatch(netloc=r'testcallback')
|
||||
def handle_request(url, request):
|
||||
called[0] = json.loads(request.body)
|
||||
return {'status_code': 200, 'content': '{}'}
|
||||
|
||||
def format_date(datetime):
|
||||
return datetime.strftime("%m/%d/%Y")
|
||||
|
||||
with HTTMock(handle_request):
|
||||
worker._process_queue_item({
|
||||
'export_id': 'someid',
|
||||
'repository_id': repo.id,
|
||||
'namespace_id': repo.namespace_user.id,
|
||||
'namespace_name': 'devtable',
|
||||
'repository_name': 'simple',
|
||||
'start_time': format_date(now + timedelta(days=-10)),
|
||||
'end_time': format_date(now + timedelta(days=10)),
|
||||
'callback_url': 'http://testcallback/',
|
||||
'callback_email': None,
|
||||
}, storage_engine)
|
||||
|
||||
assert called[0]
|
||||
assert called[0][u'export_id'] == 'someid'
|
||||
assert called[0][u'status'] == 'success'
|
||||
|
||||
url = called[0][u'exported_data_url']
|
||||
|
||||
if url.find('http://localhost:5000/exportedlogs/') == 0:
|
||||
storage_id = url[len('http://localhost:5000/exportedlogs/'):]
|
||||
else:
|
||||
assert url.find('https://some_bucket.s3.amazonaws.com/some/path/exportedactionlogs/') == 0
|
||||
storage_id, _ = url[len('https://some_bucket.s3.amazonaws.com/some/path/exportedactionlogs/'):].split('?')
|
||||
|
||||
created = storage_engine.get_content(storage_engine.preferred_locations,
|
||||
'exportedactionlogs/' + storage_id)
|
||||
created_json = json.loads(created)
|
||||
|
||||
if has_logs:
|
||||
found = set()
|
||||
for log in created_json['logs']:
|
||||
if log.get('terminator'):
|
||||
continue
|
||||
|
||||
found.add(log['metadata']['index'])
|
||||
|
||||
for index in range(-10, 10):
|
||||
assert index in found
|
||||
else:
|
||||
assert created_json['logs'] == [{'terminator': True}]
|
142
workers/test/test_logrotateworker.py
Normal file
142
workers/test/test_logrotateworker.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
import os.path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app import storage
|
||||
|
||||
from data import model
|
||||
from data.database import LogEntry, LogEntry2, LogEntry3
|
||||
from data.logs_model.elastic_logs import INDEX_NAME_PREFIX, INDEX_DATE_FORMAT
|
||||
from data.logs_model.datatypes import AggregatedLogCount, LogEntriesPage, Log
|
||||
from data.logs_model.document_logs_model import DocumentLogsModel
|
||||
from data.logs_model.test.fake_elasticsearch import FAKE_ES_HOST, fake_elasticsearch
|
||||
from data.logs_model.table_logs_model import TableLogsModel
|
||||
from data.logs_model.combined_model import CombinedLogsModel
|
||||
from data.logs_model.inmemory_model import InMemoryModel
|
||||
|
||||
from data.logs_model import LogsModelProxy
|
||||
|
||||
from util.timedeltastring import convert_to_timedelta
|
||||
from workers.logrotateworker import LogRotateWorker, SAVE_PATH, SAVE_LOCATION
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def clear_db_logs(initialized_db):
|
||||
LogEntry.delete().execute()
|
||||
LogEntry2.delete().execute()
|
||||
LogEntry3.delete().execute()
|
||||
|
||||
|
||||
def combined_model():
|
||||
return CombinedLogsModel(TableLogsModel(), InMemoryModel())
|
||||
|
||||
|
||||
def es_model():
|
||||
return DocumentLogsModel(producer='elasticsearch', elasticsearch_config={
|
||||
'host': FAKE_ES_HOST,
|
||||
'port': 12345,
|
||||
})
|
||||
|
||||
@pytest.fixture()
|
||||
def fake_es():
|
||||
with fake_elasticsearch():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(params=[TableLogsModel, es_model, InMemoryModel, combined_model])
|
||||
def logs_model(request, clear_db_logs, fake_es):
|
||||
model = request.param()
|
||||
with patch('data.logs_model.logs_model', model):
|
||||
with patch('workers.logrotateworker.logs_model', model):
|
||||
yield model
|
||||
|
||||
|
||||
def _lookup_logs(logs_model, start_time, end_time, **kwargs):
|
||||
logs_found = []
|
||||
page_token = None
|
||||
while True:
|
||||
found = logs_model.lookup_logs(start_time, end_time, page_token=page_token, **kwargs)
|
||||
logs_found.extend(found.logs)
|
||||
page_token = found.next_page_token
|
||||
if not found.logs or not page_token:
|
||||
break
|
||||
|
||||
assert len(logs_found) == len(set(logs_found))
|
||||
return logs_found
|
||||
|
||||
|
||||
def test_logrotateworker(logs_model):
|
||||
worker = LogRotateWorker()
|
||||
days = 90
|
||||
start_timestamp = datetime(2019, 1, 1)
|
||||
|
||||
# Make sure there are no existing logs
|
||||
found = _lookup_logs(logs_model, start_timestamp - timedelta(days=1000), start_timestamp + timedelta(days=1000))
|
||||
assert not found
|
||||
|
||||
# Create some logs
|
||||
for day in range(0, days):
|
||||
logs_model.log_action('push_repo', namespace_name='devtable', repository_name='simple',
|
||||
ip='1.2.3.4', timestamp=start_timestamp-timedelta(days=day))
|
||||
|
||||
# Ensure there are logs.
|
||||
logs = _lookup_logs(logs_model,
|
||||
start_timestamp - timedelta(days=1000),
|
||||
start_timestamp + timedelta(days=1000))
|
||||
|
||||
assert len(logs) == days
|
||||
|
||||
# Archive all the logs.
|
||||
assert worker._perform_archiving(start_timestamp + timedelta(days=1))
|
||||
|
||||
# Ensure all the logs were archived.
|
||||
found = _lookup_logs(logs_model, start_timestamp - timedelta(days=1000), start_timestamp + timedelta(days=1000))
|
||||
assert not found
|
||||
|
||||
|
||||
def test_logrotateworker_with_cutoff(logs_model):
|
||||
days = 60
|
||||
start_timestamp = datetime(2019, 1, 1)
|
||||
|
||||
# Make sure there are no existing logs
|
||||
found = _lookup_logs(logs_model, start_timestamp - timedelta(days=365), start_timestamp + timedelta(days=365))
|
||||
assert not found
|
||||
|
||||
# Create a new set of logs/indices.
|
||||
for day in range(0, days):
|
||||
logs_model.log_action('push_repo', namespace_name='devtable', repository_name='simple',
|
||||
ip='1.2.3.4', timestamp=start_timestamp+timedelta(days=day))
|
||||
|
||||
# Get all logs
|
||||
logs = _lookup_logs(logs_model,
|
||||
start_timestamp - timedelta(days=days-1),
|
||||
start_timestamp + timedelta(days=days+1))
|
||||
|
||||
assert len(logs) == days
|
||||
|
||||
# Set the cutoff datetime to be the midpoint of the logs
|
||||
midpoint = logs[0:len(logs)/2]
|
||||
assert midpoint
|
||||
assert len(midpoint) < len(logs)
|
||||
|
||||
worker = LogRotateWorker()
|
||||
cutoff_date = midpoint[-1].datetime
|
||||
|
||||
# Archive the indices at or older than the cutoff date
|
||||
archived_files = worker._perform_archiving(cutoff_date)
|
||||
|
||||
# Ensure the eariler logs were archived
|
||||
found = _lookup_logs(logs_model, start_timestamp, cutoff_date-timedelta(seconds=1))
|
||||
assert not found
|
||||
|
||||
# Check that the files were written to storage
|
||||
for archived_file in archived_files:
|
||||
assert storage.exists([SAVE_LOCATION], os.path.join(SAVE_PATH, archived_file))
|
||||
|
||||
# If current model uses ES, check that the indices were also deleted
|
||||
if isinstance(logs_model, DocumentLogsModel):
|
||||
assert len(logs_model.list_indices()) == days - (len(logs) / 2)
|
||||
for index in logs_model.list_indices():
|
||||
dt = datetime.strptime(index[len(INDEX_NAME_PREFIX):], INDEX_DATE_FORMAT)
|
||||
assert dt >= cutoff_date
|
12
workers/test/test_repositoryactioncounter.py
Normal file
12
workers/test/test_repositoryactioncounter.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from data import model, database
|
||||
from workers.repositoryactioncounter import RepositoryActionCountWorker
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
def test_repositoryactioncount(app):
|
||||
database.RepositoryActionCount.delete().execute()
|
||||
database.RepositorySearchScore.delete().execute()
|
||||
|
||||
rac = RepositoryActionCountWorker()
|
||||
while rac._count_repository_actions():
|
||||
continue
|
171
workers/test/test_storagereplication.py
Normal file
171
workers/test/test_storagereplication.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
import hashlib
|
||||
|
||||
import pytest
|
||||
|
||||
from data import model, database
|
||||
from storage.basestorage import StoragePaths
|
||||
from storage.fakestorage import FakeStorage
|
||||
from storage.distributedstorage import DistributedStorage
|
||||
from workers.storagereplication import (StorageReplicationWorker, JobException,
|
||||
WorkerUnhealthyException)
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def storage_user(app):
|
||||
user = model.user.get_user('devtable')
|
||||
database.UserRegion.create(user=user,
|
||||
location=database.ImageStorageLocation.get(name='local_us'))
|
||||
database.UserRegion.create(user=user,
|
||||
location=database.ImageStorageLocation.get(name='local_eu'))
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def storage_paths():
|
||||
return StoragePaths()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def replication_worker():
|
||||
return StorageReplicationWorker(None)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def storage():
|
||||
return DistributedStorage({'local_us': FakeStorage('local'), 'local_eu': FakeStorage('local')},
|
||||
['local_us'])
|
||||
|
||||
|
||||
def test_storage_replication_v1(storage_user, storage_paths, replication_worker, storage, app):
|
||||
# Add a storage entry with a V1 path.
|
||||
v1_storage = model.storage.create_v1_storage('local_us')
|
||||
content_path = storage_paths.v1_image_layer_path(v1_storage.uuid)
|
||||
storage.put_content(['local_us'], content_path, 'some content')
|
||||
|
||||
# Call replicate on it and verify it replicates.
|
||||
replication_worker.replicate_storage(storage_user, v1_storage.uuid, storage)
|
||||
|
||||
# Ensure that the data was replicated to the other "region".
|
||||
assert storage.get_content(['local_eu'], content_path) == 'some content'
|
||||
|
||||
locations = model.storage.get_storage_locations(v1_storage.uuid)
|
||||
assert len(locations) == 2
|
||||
|
||||
|
||||
def test_storage_replication_cas(storage_user, storage_paths, replication_worker, storage, app):
|
||||
# Add a storage entry with a CAS path.
|
||||
content_checksum = 'sha256:' + hashlib.sha256('some content').hexdigest()
|
||||
cas_storage = database.ImageStorage.create(cas_path=True, content_checksum=content_checksum)
|
||||
|
||||
location = database.ImageStorageLocation.get(name='local_us')
|
||||
database.ImageStoragePlacement.create(storage=cas_storage, location=location)
|
||||
|
||||
content_path = storage_paths.blob_path(cas_storage.content_checksum)
|
||||
storage.put_content(['local_us'], content_path, 'some content')
|
||||
|
||||
# Call replicate on it and verify it replicates.
|
||||
replication_worker.replicate_storage(storage_user, cas_storage.uuid, storage)
|
||||
|
||||
# Ensure that the data was replicated to the other "region".
|
||||
assert storage.get_content(['local_eu'], content_path) == 'some content'
|
||||
|
||||
locations = model.storage.get_storage_locations(cas_storage.uuid)
|
||||
assert len(locations) == 2
|
||||
|
||||
|
||||
def test_storage_replication_missing_base(storage_user, storage_paths, replication_worker, storage,
|
||||
app):
|
||||
# Add a storage entry with a CAS path.
|
||||
content_checksum = 'sha256:' + hashlib.sha256('some content').hexdigest()
|
||||
cas_storage = database.ImageStorage.create(cas_path=True, content_checksum=content_checksum)
|
||||
|
||||
location = database.ImageStorageLocation.get(name='local_us')
|
||||
database.ImageStoragePlacement.create(storage=cas_storage, location=location)
|
||||
|
||||
# Attempt to replicate storage. This should fail because the layer is missing from the base
|
||||
# storage.
|
||||
with pytest.raises(JobException):
|
||||
replication_worker.replicate_storage(storage_user, cas_storage.uuid, storage,
|
||||
backoff_check=False)
|
||||
|
||||
# Ensure the storage location count remains 1. This is technically inaccurate, but that's okay
|
||||
# as we still require at least one location per storage.
|
||||
locations = model.storage.get_storage_locations(cas_storage.uuid)
|
||||
assert len(locations) == 1
|
||||
|
||||
|
||||
def test_storage_replication_copy_error(storage_user, storage_paths, replication_worker, storage,
|
||||
app):
|
||||
# Add a storage entry with a CAS path.
|
||||
content_checksum = 'sha256:' + hashlib.sha256('some content').hexdigest()
|
||||
cas_storage = database.ImageStorage.create(cas_path=True, content_checksum=content_checksum)
|
||||
|
||||
location = database.ImageStorageLocation.get(name='local_us')
|
||||
database.ImageStoragePlacement.create(storage=cas_storage, location=location)
|
||||
|
||||
content_path = storage_paths.blob_path(cas_storage.content_checksum)
|
||||
storage.put_content(['local_us'], content_path, 'some content')
|
||||
|
||||
# Tell storage to break copying.
|
||||
storage.put_content(['local_us'], 'break_copying', 'true')
|
||||
|
||||
# Attempt to replicate storage. This should fail because the write fails.
|
||||
with pytest.raises(JobException):
|
||||
replication_worker.replicate_storage(storage_user, cas_storage.uuid, storage,
|
||||
backoff_check=False)
|
||||
|
||||
# Ensure the storage location count remains 1.
|
||||
locations = model.storage.get_storage_locations(cas_storage.uuid)
|
||||
assert len(locations) == 1
|
||||
|
||||
|
||||
def test_storage_replication_copy_didnot_copy(storage_user, storage_paths, replication_worker,
|
||||
storage, app):
|
||||
# Add a storage entry with a CAS path.
|
||||
content_checksum = 'sha256:' + hashlib.sha256('some content').hexdigest()
|
||||
cas_storage = database.ImageStorage.create(cas_path=True, content_checksum=content_checksum)
|
||||
|
||||
location = database.ImageStorageLocation.get(name='local_us')
|
||||
database.ImageStoragePlacement.create(storage=cas_storage, location=location)
|
||||
|
||||
content_path = storage_paths.blob_path(cas_storage.content_checksum)
|
||||
storage.put_content(['local_us'], content_path, 'some content')
|
||||
|
||||
# Tell storage to fake copying (i.e. not actually copy the data).
|
||||
storage.put_content(['local_us'], 'fake_copying', 'true')
|
||||
|
||||
# Attempt to replicate storage. This should fail because the copy doesn't actually do the copy.
|
||||
with pytest.raises(JobException):
|
||||
replication_worker.replicate_storage(storage_user, cas_storage.uuid, storage,
|
||||
backoff_check=False)
|
||||
|
||||
# Ensure the storage location count remains 1.
|
||||
locations = model.storage.get_storage_locations(cas_storage.uuid)
|
||||
assert len(locations) == 1
|
||||
|
||||
|
||||
def test_storage_replication_copy_unhandled_exception(storage_user, storage_paths,
|
||||
replication_worker, storage, app):
|
||||
# Add a storage entry with a CAS path.
|
||||
content_checksum = 'sha256:' + hashlib.sha256('some content').hexdigest()
|
||||
cas_storage = database.ImageStorage.create(cas_path=True, content_checksum=content_checksum)
|
||||
|
||||
location = database.ImageStorageLocation.get(name='local_us')
|
||||
database.ImageStoragePlacement.create(storage=cas_storage, location=location)
|
||||
|
||||
content_path = storage_paths.blob_path(cas_storage.content_checksum)
|
||||
storage.put_content(['local_us'], content_path, 'some content')
|
||||
|
||||
# Tell storage to raise an exception when copying.
|
||||
storage.put_content(['local_us'], 'except_copying', 'true')
|
||||
|
||||
# Attempt to replicate storage. This should fail because the copy raises an unhandled exception.
|
||||
with pytest.raises(WorkerUnhealthyException):
|
||||
replication_worker.replicate_storage(storage_user, cas_storage.uuid, storage,
|
||||
backoff_check=False)
|
||||
|
||||
# Ensure the storage location count remains 1.
|
||||
locations = model.storage.get_storage_locations(cas_storage.uuid)
|
||||
assert len(locations) == 1
|
281
workers/test/test_tagbackfillworker.py
Normal file
281
workers/test/test_tagbackfillworker.py
Normal file
|
@ -0,0 +1,281 @@
|
|||
from app import docker_v2_signing_key
|
||||
from data import model
|
||||
from data.database import (TagManifestLabelMap, TagManifestToManifest, Manifest, ManifestBlob,
|
||||
ManifestLegacyImage, ManifestLabel, TagManifest, RepositoryTag, Image,
|
||||
TagManifestLabel, Tag, TagToRepositoryTag, Repository,
|
||||
ImageStorage)
|
||||
from image.docker.schema1 import DockerSchema1ManifestBuilder
|
||||
from workers.tagbackfillworker import backfill_tag, _backfill_manifest
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def clear_rows(initialized_db):
|
||||
# Remove all new-style rows so we can backfill.
|
||||
TagToRepositoryTag.delete().execute()
|
||||
Tag.delete().execute()
|
||||
TagManifestLabelMap.delete().execute()
|
||||
ManifestLabel.delete().execute()
|
||||
ManifestBlob.delete().execute()
|
||||
ManifestLegacyImage.delete().execute()
|
||||
TagManifestToManifest.delete().execute()
|
||||
Manifest.delete().execute()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('clear_all_rows', [
|
||||
True,
|
||||
False,
|
||||
])
|
||||
def test_tagbackfillworker(clear_all_rows, initialized_db):
|
||||
# Remove the new-style rows so we can backfill.
|
||||
TagToRepositoryTag.delete().execute()
|
||||
Tag.delete().execute()
|
||||
|
||||
if clear_all_rows:
|
||||
TagManifestLabelMap.delete().execute()
|
||||
ManifestLabel.delete().execute()
|
||||
ManifestBlob.delete().execute()
|
||||
ManifestLegacyImage.delete().execute()
|
||||
TagManifestToManifest.delete().execute()
|
||||
Manifest.delete().execute()
|
||||
|
||||
found_dead_tag = False
|
||||
|
||||
for repository_tag in list(RepositoryTag.select()):
|
||||
# Backfill the tag.
|
||||
assert backfill_tag(repository_tag)
|
||||
|
||||
# Ensure if we try again, the backfill is skipped.
|
||||
assert not backfill_tag(repository_tag)
|
||||
|
||||
# Ensure that we now have the expected tag rows.
|
||||
tag_to_repo_tag = TagToRepositoryTag.get(repository_tag=repository_tag)
|
||||
tag = tag_to_repo_tag.tag
|
||||
assert tag.name == repository_tag.name
|
||||
assert tag.repository == repository_tag.repository
|
||||
assert not tag.hidden
|
||||
assert tag.reversion == repository_tag.reversion
|
||||
|
||||
if repository_tag.lifetime_start_ts is None:
|
||||
assert tag.lifetime_start_ms is None
|
||||
else:
|
||||
assert tag.lifetime_start_ms == (repository_tag.lifetime_start_ts * 1000)
|
||||
|
||||
if repository_tag.lifetime_end_ts is None:
|
||||
assert tag.lifetime_end_ms is None
|
||||
else:
|
||||
assert tag.lifetime_end_ms == (repository_tag.lifetime_end_ts * 1000)
|
||||
found_dead_tag = True
|
||||
|
||||
assert tag.manifest
|
||||
|
||||
# Ensure that we now have the expected manifest rows.
|
||||
try:
|
||||
tag_manifest = TagManifest.get(tag=repository_tag)
|
||||
except TagManifest.DoesNotExist:
|
||||
continue
|
||||
|
||||
map_row = TagManifestToManifest.get(tag_manifest=tag_manifest)
|
||||
assert not map_row.broken
|
||||
|
||||
manifest_row = map_row.manifest
|
||||
assert manifest_row.manifest_bytes == tag_manifest.json_data
|
||||
assert manifest_row.digest == tag_manifest.digest
|
||||
assert manifest_row.repository == tag_manifest.tag.repository
|
||||
|
||||
assert tag.manifest == map_row.manifest
|
||||
|
||||
legacy_image = ManifestLegacyImage.get(manifest=manifest_row).image
|
||||
assert tag_manifest.tag.image == legacy_image
|
||||
|
||||
expected_storages = {tag_manifest.tag.image.storage.id}
|
||||
for parent_image_id in tag_manifest.tag.image.ancestor_id_list():
|
||||
expected_storages.add(Image.get(id=parent_image_id).storage_id)
|
||||
|
||||
found_storages = {manifest_blob.blob_id for manifest_blob
|
||||
in ManifestBlob.select().where(ManifestBlob.manifest == manifest_row)}
|
||||
assert expected_storages == found_storages
|
||||
|
||||
# Ensure the labels were copied over.
|
||||
tmls = list(TagManifestLabel.select().where(TagManifestLabel.annotated == tag_manifest))
|
||||
expected_labels = {tml.label_id for tml in tmls}
|
||||
found_labels = {m.label_id for m
|
||||
in ManifestLabel.select().where(ManifestLabel.manifest == manifest_row)}
|
||||
assert found_labels == expected_labels
|
||||
|
||||
# Verify at the repository level.
|
||||
for repository in list(Repository.select()):
|
||||
tags = RepositoryTag.select().where(RepositoryTag.repository == repository,
|
||||
RepositoryTag.hidden == False)
|
||||
oci_tags = Tag.select().where(Tag.repository == repository)
|
||||
assert len(tags) == len(oci_tags)
|
||||
assert {t.name for t in tags} == {t.name for t in oci_tags}
|
||||
|
||||
for tag in tags:
|
||||
tag_manifest = TagManifest.get(tag=tag)
|
||||
ttr = TagToRepositoryTag.get(repository_tag=tag)
|
||||
manifest = ttr.tag.manifest
|
||||
|
||||
assert tag_manifest.json_data == manifest.manifest_bytes
|
||||
assert tag_manifest.digest == manifest.digest
|
||||
assert tag.image == ManifestLegacyImage.get(manifest=manifest).image
|
||||
assert tag.lifetime_start_ts == (ttr.tag.lifetime_start_ms / 1000)
|
||||
|
||||
if tag.lifetime_end_ts:
|
||||
assert tag.lifetime_end_ts == (ttr.tag.lifetime_end_ms / 1000)
|
||||
else:
|
||||
assert ttr.tag.lifetime_end_ms is None
|
||||
|
||||
assert found_dead_tag
|
||||
|
||||
|
||||
def test_manifestbackfillworker_broken_manifest(clear_rows, initialized_db):
|
||||
# Delete existing tag manifest so we can reuse the tag.
|
||||
TagManifestLabel.delete().execute()
|
||||
TagManifest.delete().execute()
|
||||
|
||||
# Add a broken manifest.
|
||||
broken_manifest = TagManifest.create(json_data='wat?', digest='sha256:foobar',
|
||||
tag=RepositoryTag.get())
|
||||
|
||||
# Ensure the backfill works.
|
||||
assert _backfill_manifest(broken_manifest)
|
||||
|
||||
# Ensure the mapping is marked as broken.
|
||||
map_row = TagManifestToManifest.get(tag_manifest=broken_manifest)
|
||||
assert map_row.broken
|
||||
|
||||
manifest_row = map_row.manifest
|
||||
assert manifest_row.manifest_bytes == broken_manifest.json_data
|
||||
assert manifest_row.digest == broken_manifest.digest
|
||||
assert manifest_row.repository == broken_manifest.tag.repository
|
||||
|
||||
legacy_image = ManifestLegacyImage.get(manifest=manifest_row).image
|
||||
assert broken_manifest.tag.image == legacy_image
|
||||
|
||||
|
||||
def test_manifestbackfillworker_mislinked_manifest(clear_rows, initialized_db):
|
||||
""" Tests that a manifest whose image is mislinked will have its storages relinked properly. """
|
||||
# Delete existing tag manifest so we can reuse the tag.
|
||||
TagManifestLabel.delete().execute()
|
||||
TagManifest.delete().execute()
|
||||
|
||||
repo = model.repository.get_repository('devtable', 'complex')
|
||||
tag_v30 = model.tag.get_active_tag('devtable', 'gargantuan', 'v3.0')
|
||||
tag_v50 = model.tag.get_active_tag('devtable', 'gargantuan', 'v5.0')
|
||||
|
||||
# Add a mislinked manifest, by having its layer point to a blob in v3.0 but its image
|
||||
# be the v5.0 image.
|
||||
builder = DockerSchema1ManifestBuilder('devtable', 'gargantuan', 'sometag')
|
||||
builder.add_layer(tag_v30.image.storage.content_checksum, '{"id": "foo"}')
|
||||
manifest = builder.build(docker_v2_signing_key)
|
||||
|
||||
mislinked_manifest = TagManifest.create(json_data=manifest.bytes.as_encoded_str(),
|
||||
digest=manifest.digest,
|
||||
tag=tag_v50)
|
||||
|
||||
# Backfill the manifest and ensure its proper content checksum was linked.
|
||||
assert _backfill_manifest(mislinked_manifest)
|
||||
|
||||
map_row = TagManifestToManifest.get(tag_manifest=mislinked_manifest)
|
||||
assert not map_row.broken
|
||||
|
||||
manifest_row = map_row.manifest
|
||||
legacy_image = ManifestLegacyImage.get(manifest=manifest_row).image
|
||||
assert legacy_image == tag_v50.image
|
||||
|
||||
manifest_blobs = list(ManifestBlob.select().where(ManifestBlob.manifest == manifest_row))
|
||||
assert len(manifest_blobs) == 1
|
||||
assert manifest_blobs[0].blob.content_checksum == tag_v30.image.storage.content_checksum
|
||||
|
||||
|
||||
def test_manifestbackfillworker_mislinked_invalid_manifest(clear_rows, initialized_db):
|
||||
""" Tests that a manifest whose image is mislinked will attempt to have its storages relinked
|
||||
properly. """
|
||||
# Delete existing tag manifest so we can reuse the tag.
|
||||
TagManifestLabel.delete().execute()
|
||||
TagManifest.delete().execute()
|
||||
|
||||
repo = model.repository.get_repository('devtable', 'complex')
|
||||
tag_v50 = model.tag.get_active_tag('devtable', 'gargantuan', 'v5.0')
|
||||
|
||||
# Add a mislinked manifest, by having its layer point to an invalid blob but its image
|
||||
# be the v5.0 image.
|
||||
builder = DockerSchema1ManifestBuilder('devtable', 'gargantuan', 'sometag')
|
||||
builder.add_layer('sha256:deadbeef', '{"id": "foo"}')
|
||||
manifest = builder.build(docker_v2_signing_key)
|
||||
|
||||
broken_manifest = TagManifest.create(json_data=manifest.bytes.as_encoded_str(),
|
||||
digest=manifest.digest,
|
||||
tag=tag_v50)
|
||||
|
||||
# Backfill the manifest and ensure it is marked as broken.
|
||||
assert _backfill_manifest(broken_manifest)
|
||||
|
||||
map_row = TagManifestToManifest.get(tag_manifest=broken_manifest)
|
||||
assert map_row.broken
|
||||
|
||||
manifest_row = map_row.manifest
|
||||
legacy_image = ManifestLegacyImage.get(manifest=manifest_row).image
|
||||
assert legacy_image == tag_v50.image
|
||||
|
||||
manifest_blobs = list(ManifestBlob.select().where(ManifestBlob.manifest == manifest_row))
|
||||
assert len(manifest_blobs) == 0
|
||||
|
||||
|
||||
def test_manifestbackfillworker_repeat_digest(clear_rows, initialized_db):
|
||||
""" Tests that a manifest with a shared digest will be properly linked. """
|
||||
# Delete existing tag manifest so we can reuse the tag.
|
||||
TagManifestLabel.delete().execute()
|
||||
TagManifest.delete().execute()
|
||||
|
||||
repo = model.repository.get_repository('devtable', 'gargantuan')
|
||||
tag_v30 = model.tag.get_active_tag('devtable', 'gargantuan', 'v3.0')
|
||||
tag_v50 = model.tag.get_active_tag('devtable', 'gargantuan', 'v5.0')
|
||||
|
||||
# Build a manifest and assign it to both tags (this is allowed in the old model).
|
||||
builder = DockerSchema1ManifestBuilder('devtable', 'gargantuan', 'sometag')
|
||||
builder.add_layer('sha256:deadbeef', '{"id": "foo"}')
|
||||
manifest = builder.build(docker_v2_signing_key)
|
||||
|
||||
manifest_1 = TagManifest.create(json_data=manifest.bytes.as_encoded_str(), digest=manifest.digest,
|
||||
tag=tag_v30)
|
||||
manifest_2 = TagManifest.create(json_data=manifest.bytes.as_encoded_str(), digest=manifest.digest,
|
||||
tag=tag_v50)
|
||||
|
||||
# Backfill "both" manifests and ensure both are pointed to by a single resulting row.
|
||||
assert _backfill_manifest(manifest_1)
|
||||
assert _backfill_manifest(manifest_2)
|
||||
|
||||
map_row1 = TagManifestToManifest.get(tag_manifest=manifest_1)
|
||||
map_row2 = TagManifestToManifest.get(tag_manifest=manifest_2)
|
||||
|
||||
assert map_row1.manifest == map_row2.manifest
|
||||
|
||||
|
||||
def test_manifest_backfill_broken_tag(clear_rows, initialized_db):
|
||||
""" Tests backfilling a broken tag. """
|
||||
# Delete existing tag manifest so we can reuse the tag.
|
||||
TagManifestLabel.delete().execute()
|
||||
TagManifest.delete().execute()
|
||||
|
||||
# Create a tag with an image referenced missing parent images.
|
||||
repo = model.repository.get_repository('devtable', 'gargantuan')
|
||||
broken_image = Image.create(docker_image_id='foo', repository=repo, ancestors='/348723847234/',
|
||||
storage=ImageStorage.get())
|
||||
broken_image_tag = RepositoryTag.create(repository=repo, image=broken_image, name='broken')
|
||||
|
||||
# Backfill the tag.
|
||||
assert backfill_tag(broken_image_tag)
|
||||
|
||||
# Ensure we backfilled, even though we reference a broken manifest.
|
||||
tag_manifest = TagManifest.get(tag=broken_image_tag)
|
||||
|
||||
map_row = TagManifestToManifest.get(tag_manifest=tag_manifest)
|
||||
manifest = map_row.manifest
|
||||
assert manifest.manifest_bytes == tag_manifest.json_data
|
||||
|
||||
tag = TagToRepositoryTag.get(repository_tag=broken_image_tag).tag
|
||||
assert tag.name == 'broken'
|
||||
assert tag.manifest == manifest
|
Reference in a new issue