Iterate over images, not labels, when updating

Labels do not map 1:1 to images, so when nodepool was updating
images by iterating over labels, it may have updated an image
more than once.  This was causing duplicate snapshot and DIB
images (though not duplicate DIB files).

Instead, iterate over provider image entries (which are unique)
when deciding which to update.

This includes a small unrelated change to the fake dib selector
used in debugging the problem leading to this patch.

Change-Id: I4e03c1136376b51987e9f2f525ed6613ed4500eb
This commit is contained in:
James E. Blair 2015-02-03 16:34:38 -08:00 committed by Clark Boylan
parent a9c9c60ef1
commit 70b4e065b6
2 changed files with 89 additions and 84 deletions

View File

@ -769,7 +769,7 @@ class DiskImageBuilder(threading.Thread):
cmd = ('disk-image-create -x --no-tmpfs %s -o %s %s' %
(extra_options, out_file_path, img_elements))
if 'fake-dib-image' in filename:
if 'fake-' in filename:
cmd = 'echo ' + cmd
log = logging.getLogger("nodepool.image.build.%s" %
@ -1354,6 +1354,15 @@ class NodePool(threading.Thread):
'{label.name}-{provider.name}-{node_id}-{subnode_id}'
'.slave.openstack.org'
)
# A set of image names that are in use by labels, to be
# used by the image update methods to determine whether
# a given image needs to be updated.
newconfig.images_in_use = set()
for label in newconfig.labels.values():
if label.min_ready >= 0:
newconfig.images_in_use.add(label.image)
return newconfig
def reconfigureDatabase(self, config):
@ -1757,73 +1766,70 @@ class NodePool(threading.Thread):
else:
self.launchNode(session, provider, label, target)
def checkForMissingSnapshotImage(self, session, provider, image):
found = False
for snap_image in session.getSnapshotImages():
if (snap_image.provider_name == provider.name and
snap_image.image_name == image.name and
snap_image.state in [nodedb.READY,
nodedb.BUILDING]):
found = True
if not found:
self.log.warning("Missing image %s on %s" %
(image.name, provider.name))
self.updateImage(session, provider.name, image.name)
def checkForMissingDiskImage(self, session, provider, image):
found = False
for dib_image in session.getDibImages():
if (dib_image.image_name == image.name and
dib_image.state in [nodedb.READY,
nodedb.BUILDING]):
# if image is in ready state, check if image
# file exists in directory, otherwise we need
# to rebuild and delete this buggy image
if (dib_image.state == nodedb.READY and
not os.path.exists(dib_image.filename) and
not 'fake-dib-image' in dib_image.filename):
self.log.warning("Image filename %s does not "
"exist. Removing image" %
dib_image.filename)
self.deleteDibImage(dib_image)
continue
found = True
break
if not found:
# only build the image, we'll recheck again
self.log.warning("Missing disk image %s" % image.name)
self.buildImage(self.config.diskimages[image.name])
else:
found = False
for snap_image in session.getSnapshotImages():
if (snap_image.provider_name == provider.name and
snap_image.image_name == image.name and
snap_image.state in [nodedb.READY,
nodedb.BUILDING]):
found = True
break
if not found:
self.log.warning("Missing image %s on %s" %
(image.name, provider.name))
self.uploadImage(session, provider.name,
image.name)
def checkForMissingImages(self, session):
# If we are missing an image, run the image update function
# outside of its schedule.
self.log.debug("Checking missing images.")
for label in self.config.labels.values():
if label.min_ready < 0:
# Label is configured to be disabled, skip creating the image.
continue
# check if image is there, if not, build it
if label.diskimage_providers:
found = False
for dib_image in session.getDibImages():
if (dib_image.image_name == label.image and
dib_image.state in [nodedb.READY,
nodedb.BUILDING]):
# if image is in ready state, check if image
# file exists in directory, otherwise we need
# to rebuild and delete this buggy image
if (dib_image.state == nodedb.READY and
not os.path.exists(dib_image.filename) and
not 'fake-dib-image' in dib_image.filename):
self.log.warning("Image filename %s does not "
"exist. Removing image" %
dib_image.filename)
self.deleteDibImage(dib_image)
continue
found = True
break
if not found:
# only build the image, we'll recheck again
self.log.warning("Missing disk image %s" % label.image)
self.buildImage(self.config.diskimages[label.image])
for provider in self.config.providers.values():
for image in provider.images.values():
if image.name not in self.config.images_in_use:
continue
if not image.diskimage:
self.checkForMissingSnapshotImage(session, provider, image)
else:
# check for providers, to upload it
for provider_name in label.diskimage_providers.keys():
found = False
for snap_image in session.getSnapshotImages():
if (snap_image.provider_name == provider_name and
snap_image.image_name == label.image and
snap_image.state in [nodedb.READY,
nodedb.BUILDING]):
found = True
break
if not found:
self.log.warning("Missing image %s on %s" %
(label.image, provider_name))
# when we have a READY image, upload it
available_images = \
session.getOrderedReadyDibImages(label.image)
if available_images:
self.uploadImage(session, provider_name,
label.image)
# snapshots
for provider_name in label.snapshot_providers.keys():
found = False
for snap_image in session.getSnapshotImages():
if (snap_image.provider_name == provider_name and
snap_image.image_name == label.image and
snap_image.state in [nodedb.READY,
nodedb.BUILDING]):
found = True
if not found:
self.log.warning("Missing image %s on %s" %
(label.image, provider_name))
self.updateImage(session, provider_name, label.image)
self.checkForMissingDiskImage(session, provider, image)
def _doUpdateImages(self):
try:
@ -1834,34 +1840,33 @@ class NodePool(threading.Thread):
def updateImages(self, session):
self.log.debug("Updating all images.")
# first run the snapshot image updates
for label in self.config.labels.values():
# only update if min_ready not negative
if label.snapshot_providers and label.min_ready >= 0:
self.log.debug("Update snapshot label %s in providers %s" %
(label.name, label.snapshot_providers.keys()))
for provider_name in label.snapshot_providers.keys():
self.updateImage(session, provider_name, label.image)
for provider in self.config.providers.values():
for image in provider.images.values():
if image.name not in self.config.images_in_use:
continue
if image.diskimage:
continue
self.updateImage(session, provider.name, image.name)
needs_build = False
for label in self.config.labels.values():
# If we use diskimages on at least one provider rebuild the image
# before updating it in the provider(s).
if label.diskimage_providers and label.min_ready >= 0:
# check if image is there, if not, build it
self.buildImage(self.config.diskimages[label.image])
needs_build = True
for diskimage in self.config.diskimages.values():
if diskimage.name not in self.config.images_in_use:
continue
self.buildImage(diskimage)
needs_build = True
if needs_build:
# wait for all builds to finish, to have updated images to upload
self.waitForBuiltImages()
# Upload newly built images to all providers that use dib built images
for label in self.config.labels.values():
if label.diskimage_providers and label.min_ready >= 0:
self.log.debug("Upload dib label %s in providers %s" %
(label.name, label.diskimage_providers.keys()))
for provider_name in label.diskimage_providers.keys():
self.uploadImage(session, provider_name, label.image)
for provider in self.config.providers.values():
for image in provider.images.values():
if image.name not in self.config.images_in_use:
continue
if not image.diskimage:
continue
self.uploadImage(session, provider.name, image.name)
def updateImage(self, session, provider_name, image_name):
try:

View File

@ -228,7 +228,7 @@ class UploadImageTask(Task):
return glance
def main(self, client):
if self.args['image_name'].startswith('fake-dib-image'):
if self.args['image_name'].startswith('fake-'):
image = fakeprovider.FakeGlanceClient()
image.update(data='fake')
else: