Merge "Support optional post upload hooks"
This commit is contained in:
@@ -446,6 +446,7 @@ Selecting the OpenStack driver adds the following options to the
|
|||||||
launch-retries: 3
|
launch-retries: 3
|
||||||
image-name-format: '{image_name}-{timestamp}'
|
image-name-format: '{image_name}-{timestamp}'
|
||||||
hostname-format: '{label.name}-{provider.name}-{node.id}'
|
hostname-format: '{label.name}-{provider.name}-{node.id}'
|
||||||
|
post-upload-hook: /usr/bin/custom-hook
|
||||||
diskimages:
|
diskimages:
|
||||||
- name: trusty
|
- name: trusty
|
||||||
meta:
|
meta:
|
||||||
@@ -567,6 +568,20 @@ Selecting the OpenStack driver adds the following options to the
|
|||||||
|
|
||||||
Format for image names that are uploaded to providers.
|
Format for image names that are uploaded to providers.
|
||||||
|
|
||||||
|
.. attr:: post-upload-hook
|
||||||
|
:type: string
|
||||||
|
:default: None
|
||||||
|
|
||||||
|
Filename of an optional script that can be called after an image has
|
||||||
|
been uploaded to a provider but before it is taken into use. This is
|
||||||
|
useful to perform last minute validation tests before an image is
|
||||||
|
really used for build nodes. The script will be called as follows:
|
||||||
|
|
||||||
|
``<SCRIPT> <PROVIDER> <EXTERNAL_IMAGE_ID> <LOCAL_IMAGE_FILENAME>``
|
||||||
|
|
||||||
|
If the script returns with result code 0 it is treated as successful
|
||||||
|
otherwise it is treated as failed and the image gets deleted.
|
||||||
|
|
||||||
.. attr:: rate
|
.. attr:: rate
|
||||||
:type: int seconds
|
:type: int seconds
|
||||||
:default: 1
|
:default: 1
|
||||||
|
@@ -1083,6 +1083,42 @@ class UploadWorker(BaseWorker):
|
|||||||
data.state = zk.FAILED
|
data.state = zk.FAILED
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
if provider.post_upload_hook:
|
||||||
|
try:
|
||||||
|
cmd = [
|
||||||
|
provider.post_upload_hook,
|
||||||
|
provider.name,
|
||||||
|
external_id,
|
||||||
|
filename
|
||||||
|
]
|
||||||
|
self.log.info('Running post upload hook %s', cmd)
|
||||||
|
p = subprocess.run(cmd, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE, check=True)
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, subprocess.CalledProcessError):
|
||||||
|
self.log.error('Post upload hook failed with '
|
||||||
|
'exit code %s\nstdout:\n%s\nstderr:\n%s',
|
||||||
|
e.returncode, e.stdout.decode(),
|
||||||
|
e.stderr.decode())
|
||||||
|
else:
|
||||||
|
self.log.exception('Unknown exception during upload hook')
|
||||||
|
|
||||||
|
try:
|
||||||
|
manager.deleteImage(ext_image_name)
|
||||||
|
except Exception:
|
||||||
|
# Image delete failed but we cannot do anything about this
|
||||||
|
# right now so just log the exception.
|
||||||
|
self.log.exception('Unable to delete image "%s"',
|
||||||
|
ext_image_name)
|
||||||
|
data = zk.ImageUpload()
|
||||||
|
data.state = zk.FAILED
|
||||||
|
return data
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
'Post upload hook success with exit code %s\n'
|
||||||
|
'stdout:\n%s\nstderr:\n%s',
|
||||||
|
p.returncode, p.stdout.decode(), p.stderr.decode())
|
||||||
|
|
||||||
if self._statsd:
|
if self._statsd:
|
||||||
dt = int((time.time() - start_time) * 1000)
|
dt = int((time.time() - start_time) * 1000)
|
||||||
key = 'nodepool.image_update.%s.%s' % (image_name,
|
key = 'nodepool.image_update.%s.%s' % (image_name,
|
||||||
|
@@ -235,6 +235,7 @@ class OpenStackProviderConfig(ProviderConfig):
|
|||||||
self.cloud_images = {}
|
self.cloud_images = {}
|
||||||
self.hostname_format = None
|
self.hostname_format = None
|
||||||
self.image_name_format = None
|
self.image_name_format = None
|
||||||
|
self.post_upload_hook = None
|
||||||
super().__init__(provider)
|
super().__init__(provider)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
@@ -250,7 +251,8 @@ class OpenStackProviderConfig(ProviderConfig):
|
|||||||
other.port_cleanup_interval ==
|
other.port_cleanup_interval ==
|
||||||
self.port_cleanup_interval and
|
self.port_cleanup_interval and
|
||||||
other.diskimages == self.diskimages and
|
other.diskimages == self.diskimages and
|
||||||
other.cloud_images == self.cloud_images)
|
other.cloud_images == self.cloud_images and
|
||||||
|
other.post_upload_hook == self.post_upload_hook)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _cloudKwargs(self):
|
def _cloudKwargs(self):
|
||||||
@@ -292,6 +294,7 @@ class OpenStackProviderConfig(ProviderConfig):
|
|||||||
'image-name-format',
|
'image-name-format',
|
||||||
'{image_name}-{timestamp}'
|
'{image_name}-{timestamp}'
|
||||||
)
|
)
|
||||||
|
self.post_upload_hook = self.provider.get('post-upload-hook')
|
||||||
|
|
||||||
default_port_mapping = {
|
default_port_mapping = {
|
||||||
'ssh': 22,
|
'ssh': 22,
|
||||||
@@ -425,6 +428,7 @@ class OpenStackProviderConfig(ProviderConfig):
|
|||||||
'pools': [pool],
|
'pools': [pool],
|
||||||
'diskimages': [provider_diskimage],
|
'diskimages': [provider_diskimage],
|
||||||
'cloud-images': [provider_cloud_images],
|
'cloud-images': [provider_cloud_images],
|
||||||
|
'post-upload-hook': str,
|
||||||
})
|
})
|
||||||
return v.Schema(schema)
|
return v.Schema(schema)
|
||||||
|
|
||||||
|
@@ -77,6 +77,7 @@ providers:
|
|||||||
boot-timeout: 120
|
boot-timeout: 120
|
||||||
rate: 0.001
|
rate: 0.001
|
||||||
port-cleanup-interval: 0
|
port-cleanup-interval: 0
|
||||||
|
post-upload-hook: /usr/bin/upload-hook
|
||||||
diskimages:
|
diskimages:
|
||||||
- name: trusty
|
- name: trusty
|
||||||
pause: False
|
pause: False
|
||||||
|
45
nodepool/tests/fixtures/node_upload_hook.yaml
vendored
Normal file
45
nodepool/tests/fixtures/node_upload_hook.yaml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
elements-dir: .
|
||||||
|
images-dir: '{images_dir}'
|
||||||
|
build-log-dir: '{build_log_dir}'
|
||||||
|
|
||||||
|
zookeeper-servers:
|
||||||
|
- host: {zookeeper_host}
|
||||||
|
port: {zookeeper_port}
|
||||||
|
chroot: {zookeeper_chroot}
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- name: fake-label
|
||||||
|
min-ready: 0
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: fake-provider
|
||||||
|
cloud: fake
|
||||||
|
driver: fake
|
||||||
|
region-name: fake-region
|
||||||
|
rate: 0.0001
|
||||||
|
post-upload-hook: nodepool/tests/post-upload-hook
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
pools:
|
||||||
|
- name: main
|
||||||
|
max-servers: 96
|
||||||
|
availability-zones:
|
||||||
|
- az1
|
||||||
|
labels:
|
||||||
|
- name: fake-label
|
||||||
|
diskimage: fake-image
|
||||||
|
min-ram: 8192
|
||||||
|
flavor-name: 'Fake'
|
||||||
|
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
elements:
|
||||||
|
- fedora
|
||||||
|
- vm
|
||||||
|
release: 21
|
||||||
|
dib-cmd: nodepool/tests/fake-image-create
|
||||||
|
env-vars:
|
||||||
|
TMPDIR: /opt/dib_tmp
|
||||||
|
DIB_IMAGE_CACHE: /opt/dib_cache
|
||||||
|
DIB_CLOUD_IMAGES: http://download.fedoraproject.org/pub/fedora/linux/releases/test/21-Beta/Cloud/Images/x86_64/
|
||||||
|
BASE_IMAGE_FILE: Fedora-Cloud-Base-20141029-21_Beta.x86_64.qcow2
|
20
nodepool/tests/post-upload-hook
Executable file
20
nodepool/tests/post-upload-hook
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "*** post-upload-hook: start"
|
||||||
|
|
||||||
|
echo "arguments:"
|
||||||
|
echo "----"
|
||||||
|
echo $*
|
||||||
|
echo "----"
|
||||||
|
|
||||||
|
PROVIDER=$1
|
||||||
|
IMAGE_ID=$2
|
||||||
|
LOCAL_IMAGE_FILENAME=$3
|
||||||
|
|
||||||
|
STATEFILE="$(dirname $LOCAL_IMAGE_FILENAME)/$(basename $LOCAL_IMAGE_FILENAME).post"
|
||||||
|
|
||||||
|
# Tests might need to know when this process is completed
|
||||||
|
echo "Creating state file $STATEFILE"
|
||||||
|
touch $STATEFILE
|
@@ -447,3 +447,13 @@ class TestNodePoolBuilder(tests.DBTestCase):
|
|||||||
cleanup_mgr.deleteImage = saved_method
|
cleanup_mgr.deleteImage = saved_method
|
||||||
self.waitForUploadRecordDeletion(image.provider_name, image.image_name,
|
self.waitForUploadRecordDeletion(image.provider_name, image.image_name,
|
||||||
image.build_id, image.id)
|
image.build_id, image.id)
|
||||||
|
|
||||||
|
def test_post_upload_hook(self):
|
||||||
|
configfile = self.setup_config('node_upload_hook.yaml')
|
||||||
|
bldr = self.useBuilder(configfile)
|
||||||
|
self.waitForImage('fake-provider', 'fake-image')
|
||||||
|
|
||||||
|
images_dir = bldr._config.imagesdir
|
||||||
|
post_file = os.path.join(
|
||||||
|
images_dir, 'fake-image-0000000001.qcow2.post')
|
||||||
|
self.assertTrue(os.path.exists(post_file), 'Post hook file exists')
|
||||||
|
Reference in New Issue
Block a user