This change does two things: 1) It adds tests that verify that in the case where images are managed in a central repo, admins may not use the web api of the wrong tenant (ie, a tenant that is not actually managing the builds of these images) to trigger or delete a build or upload. In expanding the tests to include more than one tenant, a previously existing race condition was more reliably observed, so it is fixed: 2) The following race sequence could happen: [1] launcher triggers builds for 2 missing images [2] scheduler begins reporting the image buildset (2 jobs) [2] scheduler adds image build artifact #1 to registry [1] launcher receives signal from zk watch that the image registry was updated and checks for missing images [1] launcher observes that image #2 is still missing and submits an image build trigger [2] scheduler adds image build artifact #2 to registry [2] scheduler removes queue item [2] scheduler processes trigger event, begins new build of image #2 To resolve this, we note that the addition of multiple image artifacts is a critical section. We adjust the creation order so that the sequence is now: * Create artifact #1 with state=None * Create artifact #2 with state=None * Create upload #1 and update artifact #1 to ready * Create upload #2 and update artifact #2 to ready The critical section is now bounded by the condition of having any IBAs with state=None. We now check for that within the launcher and wait for it to clear before we decide if any images are missing. Change-Id: Iad1b68cf8c9cf6bd5f13dab0471e7bf5fd290fbf
100 lines
3.7 KiB
Python
100 lines
3.7 KiB
Python
# Copyright 2024 Acme Gating, LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import collections
|
|
|
|
from zuul.zk.launcher import LockableZKObjectCache
|
|
from zuul.model import ImageBuildArtifact, ImageUpload
|
|
|
|
|
|
class ImageBuildRegistry(LockableZKObjectCache):
|
|
|
|
def __init__(self, zk_client, updated_event=None):
|
|
self.builds_by_image_name = collections.defaultdict(set)
|
|
super().__init__(
|
|
zk_client,
|
|
updated_event,
|
|
root=ImageBuildArtifact.ROOT,
|
|
items_path=ImageBuildArtifact.IMAGES_PATH,
|
|
locks_path=ImageBuildArtifact.LOCKS_PATH,
|
|
zkobject_class=ImageBuildArtifact,
|
|
)
|
|
|
|
def postCacheHook(self, event, data, stat, key, obj):
|
|
exists = key in self._cached_objects
|
|
if exists:
|
|
if obj:
|
|
builds = self.builds_by_image_name[obj.canonical_name]
|
|
builds.add(key)
|
|
else:
|
|
if obj:
|
|
builds = self.builds_by_image_name[obj.canonical_name]
|
|
builds.discard(key)
|
|
super().postCacheHook(event, data, stat, key, obj)
|
|
|
|
def getArtifactsForImage(self, image_canonical_name):
|
|
keys = list(self.builds_by_image_name[image_canonical_name])
|
|
arts = [self._cached_objects.get(key) for key in keys]
|
|
arts = [a for a in arts if a is not None]
|
|
# Sort in a stable order, primarily by timestamp, then format
|
|
# for identical timestamps.
|
|
arts = sorted(arts, key=lambda x: x.format)
|
|
arts = sorted(arts, key=lambda x: x.timestamp)
|
|
return arts
|
|
|
|
def getAllArtifacts(self):
|
|
keys = []
|
|
for image_canonical_name in self.builds_by_image_name.keys():
|
|
keys.extend(self.builds_by_image_name[image_canonical_name])
|
|
arts = [self._cached_objects.get(key) for key in keys]
|
|
arts = [a for a in arts if a is not None]
|
|
# Sort in a stable order, primarily by timestamp, then format
|
|
# for identical timestamps.
|
|
arts = sorted(arts, key=lambda x: x.format)
|
|
arts = sorted(arts, key=lambda x: x.timestamp)
|
|
return arts
|
|
|
|
|
|
class ImageUploadRegistry(LockableZKObjectCache):
|
|
|
|
def __init__(self, zk_client, updated_event=None):
|
|
self.uploads_by_image_name = collections.defaultdict(set)
|
|
super().__init__(
|
|
zk_client,
|
|
updated_event,
|
|
root=ImageUpload.ROOT,
|
|
items_path=ImageUpload.UPLOADS_PATH,
|
|
locks_path=ImageUpload.LOCKS_PATH,
|
|
zkobject_class=ImageUpload,
|
|
)
|
|
|
|
def postCacheHook(self, event, data, stat, key, obj):
|
|
exists = key in self._cached_objects
|
|
if exists:
|
|
if obj:
|
|
uploads = self.uploads_by_image_name[obj.canonical_name]
|
|
uploads.add(key)
|
|
else:
|
|
if obj:
|
|
uploads = self.uploads_by_image_name[obj.canonical_name]
|
|
uploads.discard(key)
|
|
super().postCacheHook(event, data, stat, key, obj)
|
|
|
|
def getUploadsForImage(self, image_canonical_name):
|
|
keys = list(self.uploads_by_image_name[image_canonical_name])
|
|
uploads = [self._cached_objects.get(key) for key in keys]
|
|
uploads = [u for u in uploads if u is not None]
|
|
uploads = sorted(uploads, key=lambda x: x.timestamp)
|
|
return uploads
|