
This change adds a plugin interface so that driver can be loaded dynamically. Instead of importing each driver in the launcher, provider_manager and config, the Drivers class discovers and loads driver from the driver directory. This change also adds a reset() method to the driver Config interface to reset the os_client_config reference when reloading the OpenStack driver. Change-Id: Ia347aa2501de0e05b2a7dd014c4daf1b0a4e0fb5
300 lines
13 KiB
Python
300 lines
13 KiB
Python
# Copyright (C) 2015 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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 os
|
|
import uuid
|
|
import fixtures
|
|
|
|
from nodepool import builder, exceptions, tests
|
|
from nodepool.driver import Drivers
|
|
from nodepool.driver.fake import provider as fakeprovider
|
|
from nodepool import zk
|
|
|
|
|
|
class TestNodepoolBuilderDibImage(tests.BaseTestCase):
|
|
def test_from_path(self):
|
|
image = builder.DibImageFile.from_path(
|
|
'/foo/bar/myid1234.qcow2')
|
|
self.assertEqual(image.image_id, 'myid1234')
|
|
self.assertEqual(image.extension, 'qcow2')
|
|
|
|
def test_from_image_id(self):
|
|
tempdir = fixtures.TempDir()
|
|
self.useFixture(tempdir)
|
|
image_path = os.path.join(tempdir.path, 'myid1234.qcow2')
|
|
open(image_path, 'w')
|
|
|
|
images = builder.DibImageFile.from_image_id(tempdir.path, 'myid1234')
|
|
self.assertEqual(len(images), 1)
|
|
|
|
image = images[0]
|
|
self.assertEqual(image.image_id, 'myid1234')
|
|
self.assertEqual(image.extension, 'qcow2')
|
|
|
|
def test_from_id_multiple(self):
|
|
tempdir = fixtures.TempDir()
|
|
self.useFixture(tempdir)
|
|
image_path_1 = os.path.join(tempdir.path, 'myid1234.qcow2')
|
|
image_path_2 = os.path.join(tempdir.path, 'myid1234.raw')
|
|
open(image_path_1, 'w')
|
|
open(image_path_2, 'w')
|
|
|
|
images = builder.DibImageFile.from_image_id(tempdir.path, 'myid1234')
|
|
images = sorted(images, key=lambda x: x.extension)
|
|
self.assertEqual(len(images), 2)
|
|
|
|
self.assertEqual(images[0].extension, 'qcow2')
|
|
self.assertEqual(images[1].extension, 'raw')
|
|
|
|
def test_from_images_dir(self):
|
|
tempdir = fixtures.TempDir()
|
|
self.useFixture(tempdir)
|
|
image_path_1 = os.path.join(tempdir.path, 'myid1234.qcow2')
|
|
image_path_2 = os.path.join(tempdir.path, 'myid1234.raw')
|
|
open(image_path_1, 'w')
|
|
open(image_path_2, 'w')
|
|
|
|
images = builder.DibImageFile.from_images_dir(tempdir.path)
|
|
images = sorted(images, key=lambda x: x.extension)
|
|
self.assertEqual(len(images), 2)
|
|
|
|
self.assertEqual(images[0].image_id, 'myid1234')
|
|
self.assertEqual(images[0].extension, 'qcow2')
|
|
self.assertEqual(images[1].image_id, 'myid1234')
|
|
self.assertEqual(images[1].extension, 'raw')
|
|
|
|
def test_to_path(self):
|
|
image = builder.DibImageFile('myid1234', 'qcow2')
|
|
self.assertEqual(image.to_path('/imagedir'),
|
|
'/imagedir/myid1234.qcow2')
|
|
self.assertEqual(image.to_path('/imagedir/'),
|
|
'/imagedir/myid1234.qcow2')
|
|
self.assertEqual(image.to_path('/imagedir/', False),
|
|
'/imagedir/myid1234')
|
|
|
|
image = builder.DibImageFile('myid1234')
|
|
self.assertRaises(exceptions.BuilderError, image.to_path, '/imagedir/')
|
|
|
|
|
|
class TestNodePoolBuilder(tests.DBTestCase):
|
|
|
|
def test_start_stop(self):
|
|
config = self.setup_config('node.yaml')
|
|
nb = builder.NodePoolBuilder(config)
|
|
nb.cleanup_interval = .5
|
|
nb.build_interval = .1
|
|
nb.upload_interval = .1
|
|
nb.start()
|
|
nb.stop()
|
|
|
|
def test_builder_id_file(self):
|
|
configfile = self.setup_config('node.yaml')
|
|
self.useBuilder(configfile)
|
|
path = os.path.join(self._config_images_dir.path, 'builder_id.txt')
|
|
|
|
# Validate the unique ID file exists and contents are what we expect
|
|
self.assertTrue(os.path.exists(path))
|
|
with open(path, "r") as f:
|
|
the_id = f.read()
|
|
obj = uuid.UUID(the_id, version=4)
|
|
self.assertEqual(the_id, str(obj))
|
|
|
|
def test_image_upload_fail(self):
|
|
"""Test that image upload fails are handled properly."""
|
|
|
|
# Now swap out the upload fake so that the next uploads fail
|
|
fake_client = fakeprovider.FakeUploadFailCloud(times_to_fail=1)
|
|
|
|
def get_fake_client(*args, **kwargs):
|
|
return fake_client
|
|
|
|
self.useFixture(fixtures.MockPatchObject(
|
|
Drivers.get('fake')['provider'], '_getClient',
|
|
get_fake_client))
|
|
|
|
configfile = self.setup_config('node.yaml')
|
|
pool = self.useNodepool(configfile, watermark_sleep=1)
|
|
# NOTE(pabelanger): Disable CleanupWorker thread for nodepool-builder
|
|
# as we currently race it to validate our failed uploads.
|
|
self.useBuilder(configfile, cleanup_interval=0)
|
|
pool.start()
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
nodes = self.waitForNodes('fake-label')
|
|
self.assertEqual(len(nodes), 1)
|
|
|
|
newest_builds = self.zk.getMostRecentBuilds(1, 'fake-image',
|
|
state=zk.READY)
|
|
self.assertEqual(1, len(newest_builds))
|
|
|
|
uploads = self.zk.getUploads('fake-image', newest_builds[0].id,
|
|
'fake-provider', states=[zk.FAILED])
|
|
self.assertEqual(1, len(uploads))
|
|
|
|
def test_provider_addition(self):
|
|
configfile = self.setup_config('node.yaml')
|
|
self.useBuilder(configfile)
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
self.replace_config(configfile, 'node_two_provider.yaml')
|
|
self.waitForImage('fake-provider2', 'fake-image')
|
|
|
|
def test_provider_removal(self):
|
|
configfile = self.setup_config('node_two_provider.yaml')
|
|
self.useBuilder(configfile)
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
self.waitForImage('fake-provider2', 'fake-image')
|
|
image = self.zk.getMostRecentImageUpload('fake-provider', 'fake-image')
|
|
self.replace_config(configfile, 'node_two_provider_remove.yaml')
|
|
self.waitForImageDeletion('fake-provider2', 'fake-image')
|
|
image2 = self.zk.getMostRecentImageUpload('fake-provider',
|
|
'fake-image')
|
|
self.assertEqual(image, image2)
|
|
|
|
def test_image_addition(self):
|
|
configfile = self.setup_config('node.yaml')
|
|
self.useBuilder(configfile)
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
self.replace_config(configfile, 'node_two_image.yaml')
|
|
self.waitForImage('fake-provider', 'fake-image2')
|
|
|
|
def test_image_removal(self):
|
|
configfile = self.setup_config('node_two_image.yaml')
|
|
self.useBuilder(configfile)
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
self.waitForImage('fake-provider', 'fake-image2')
|
|
self.replace_config(configfile, 'node_two_image_remove.yaml')
|
|
self.waitForImageDeletion('fake-provider', 'fake-image2')
|
|
self.waitForBuildDeletion('fake-image2', '0000000001')
|
|
|
|
def test_image_rebuild_age(self):
|
|
self._test_image_rebuild_age()
|
|
|
|
def _test_image_rebuild_age(self, expire=86400):
|
|
configfile = self.setup_config('node.yaml')
|
|
self.useBuilder(configfile)
|
|
build = self.waitForBuild('fake-image', '0000000001')
|
|
image = self.waitForImage('fake-provider', 'fake-image')
|
|
# Expire rebuild-age (default: 1day) to force a new build.
|
|
build.state_time -= expire
|
|
with self.zk.imageBuildLock('fake-image', blocking=True, timeout=1):
|
|
self.zk.storeBuild('fake-image', build, '0000000001')
|
|
self.waitForBuild('fake-image', '0000000002')
|
|
self.waitForImage('fake-provider', 'fake-image', [image])
|
|
builds = self.zk.getBuilds('fake-image', zk.READY)
|
|
self.assertEqual(len(builds), 2)
|
|
return (build, image)
|
|
|
|
def test_image_rotation(self):
|
|
# Expire rebuild-age (2days), to avoid problems when expiring 2 images.
|
|
self._test_image_rebuild_age(expire=172800)
|
|
build = self.waitForBuild('fake-image', '0000000002')
|
|
# Expire rebuild-age (default: 1day) to force a new build.
|
|
build.state_time -= 86400
|
|
with self.zk.imageBuildLock('fake-image', blocking=True, timeout=1):
|
|
self.zk.storeBuild('fake-image', build, '0000000002')
|
|
self.waitForBuildDeletion('fake-image', '0000000001')
|
|
self.waitForBuild('fake-image', '0000000003')
|
|
builds = self.zk.getBuilds('fake-image', zk.READY)
|
|
self.assertEqual(len(builds), 2)
|
|
|
|
def test_image_rotation_invalid_external_name(self):
|
|
# NOTE(pabelanger): We are forcing fake-image to leak in fake-provider.
|
|
# We do this to test our CleanupWorker will properly delete diskimage
|
|
# builds from the HDD. For this test, we don't care about the leaked
|
|
# image.
|
|
#
|
|
# Ensure we have a total of 3 diskimages on disk, so we can confirm
|
|
# nodepool-builder will properly purge the 1 diskimage build leaving a
|
|
# total of 2 diskimages on disk at all times.
|
|
|
|
# Expire rebuild-age (2days), to avoid problems when expiring 2 images.
|
|
build001, image001 = self._test_image_rebuild_age(expire=172800)
|
|
build002 = self.waitForBuild('fake-image', '0000000002')
|
|
|
|
# Make sure 2rd diskimage build was uploaded.
|
|
image002 = self.waitForImage('fake-provider', 'fake-image', [image001])
|
|
self.assertEqual(image002.build_id, '0000000002')
|
|
|
|
# Delete external name / id so we can test exception handlers.
|
|
upload = self.zk.getUploads(
|
|
'fake-image', '0000000001', 'fake-provider', zk.READY)[0]
|
|
upload.external_name = None
|
|
upload.external_id = None
|
|
with self.zk.imageUploadLock(upload.image_name, upload.build_id,
|
|
upload.provider_name, blocking=True,
|
|
timeout=1):
|
|
self.zk.storeImageUpload(upload.image_name, upload.build_id,
|
|
upload.provider_name, upload, upload.id)
|
|
|
|
# Expire rebuild-age (default: 1day) to force a new build.
|
|
build002.state_time -= 86400
|
|
with self.zk.imageBuildLock('fake-image', blocking=True, timeout=1):
|
|
self.zk.storeBuild('fake-image', build002, '0000000002')
|
|
self.waitForBuildDeletion('fake-image', '0000000001')
|
|
|
|
# Make sure fake-image for fake-provider is removed from zookeeper.
|
|
upload = self.zk.getUploads(
|
|
'fake-image', '0000000001', 'fake-provider')
|
|
self.assertEqual(len(upload), 0)
|
|
self.waitForBuild('fake-image', '0000000003')
|
|
|
|
# Ensure we only have 2 builds on disk.
|
|
builds = self.zk.getBuilds('fake-image', zk.READY)
|
|
self.assertEqual(len(builds), 2)
|
|
|
|
# Make sure 3rd diskimage build was uploaded.
|
|
image003 = self.waitForImage(
|
|
'fake-provider', 'fake-image', [image001, image002])
|
|
self.assertEqual(image003.build_id, '0000000003')
|
|
|
|
def test_cleanup_hard_upload_fails(self):
|
|
configfile = self.setup_config('node.yaml')
|
|
self.useBuilder(configfile)
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
|
|
upload = self.zk.getUploads('fake-image', '0000000001',
|
|
'fake-provider', zk.READY)[0]
|
|
|
|
# Store a new ZK node as UPLOADING to represent a hard fail
|
|
upload.state = zk.UPLOADING
|
|
|
|
with self.zk.imageUploadLock(upload.image_name, upload.build_id,
|
|
upload.provider_name, blocking=True,
|
|
timeout=1):
|
|
upnum = self.zk.storeImageUpload(upload.image_name,
|
|
upload.build_id,
|
|
upload.provider_name,
|
|
upload)
|
|
|
|
# Now it should disappear from the current build set of uploads
|
|
self.waitForUploadRecordDeletion(upload.provider_name,
|
|
upload.image_name,
|
|
upload.build_id,
|
|
upnum)
|
|
|
|
def test_cleanup_failed_image_build(self):
|
|
configfile = self.setup_config('node_diskimage_fail.yaml')
|
|
self.useBuilder(configfile)
|
|
# NOTE(pabelanger): We are racing here, but don't really care. We just
|
|
# need our first image build to fail.
|
|
self.replace_config(configfile, 'node.yaml')
|
|
self.waitForImage('fake-provider', 'fake-image')
|
|
# Make sure our cleanup worker properly removes the first build.
|
|
self.waitForBuildDeletion('fake-image', '0000000001')
|
|
|
|
def test_diskimage_build_only(self):
|
|
configfile = self.setup_config('node_diskimage_only.yaml')
|
|
self.useBuilder(configfile)
|
|
self.waitForBuild('fake-image', '0000000001')
|