211 lines
6.1 KiB
Python
Executable File
211 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# 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 io
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
|
|
try:
|
|
import docker
|
|
except ImportError:
|
|
docker = None
|
|
|
|
|
|
DOCKER_BASE_URL = os.environ.get('DOCKER_HOST',
|
|
'unix:///var/run/docker.sock')
|
|
|
|
|
|
DEFAULT_IMAGES_TIMEOUT = 600
|
|
|
|
|
|
DEFAULT_CONTAINERS_TIMEOUT = 120
|
|
|
|
|
|
DEFAULT_POLL_PERIOD = 5
|
|
|
|
|
|
def get_client(log):
|
|
kwargs = {}
|
|
kwargs['base_url'] = DOCKER_BASE_URL
|
|
log.debug('Connecting to %s' % DOCKER_BASE_URL)
|
|
client = docker.Client(**kwargs)
|
|
client._version = client.version()['ApiVersion']
|
|
log.debug('Connected to version %s' % client._version)
|
|
return client
|
|
|
|
|
|
def id_to_pod_name_part(config_id):
|
|
return config_id.replace('-', '')[:15]
|
|
|
|
|
|
def container_pattern(config_id, container_name):
|
|
return r'^/k8s_%s\.[0-9a-z]{8}_%s' % (
|
|
container_name, id_to_pod_name_part(config_id))
|
|
|
|
|
|
def required_images(c):
|
|
containers = c['config'].get('containers', [])
|
|
return set(container['image'] for container in containers)
|
|
|
|
|
|
def required_container_patterns(c):
|
|
config_id = c['id']
|
|
containers = c['config'].get('containers', [])
|
|
return dict((container['name'], container_pattern(
|
|
config_id, container['name'])) for container in containers)
|
|
|
|
|
|
def configure_logging():
|
|
log = logging.getLogger('heat-config')
|
|
log.setLevel('DEBUG')
|
|
formatter = logging.Formatter(
|
|
'[%(asctime)s] (%(name)s) [%(levelname)s] %(message)s')
|
|
|
|
# debug log to stderr
|
|
handler = logging.StreamHandler(sys.stderr)
|
|
handler.setFormatter(formatter)
|
|
log.addHandler(handler)
|
|
|
|
deploy_stdout = io.StringIO()
|
|
handler = logging.StreamHandler(deploy_stdout)
|
|
handler.setFormatter(formatter)
|
|
handler.setLevel('DEBUG')
|
|
log.addHandler(handler)
|
|
|
|
deploy_stderr = io.StringIO()
|
|
handler = logging.StreamHandler(deploy_stderr)
|
|
handler.setFormatter(formatter)
|
|
handler.setLevel('WARN')
|
|
log.addHandler(handler)
|
|
|
|
return log, deploy_stdout, deploy_stderr
|
|
|
|
|
|
def wait_required_images(client, log, images_timeout, poll_period, images):
|
|
log.info(
|
|
'Waiting for images: %s' % ', '.join(images))
|
|
timeout = time.time() + images_timeout
|
|
|
|
def image_prefixes(images):
|
|
for image in images:
|
|
if ':' in image:
|
|
yield image
|
|
else:
|
|
yield '%s:' % image
|
|
|
|
matching_prefixes = list(image_prefixes(images))
|
|
|
|
def image_names(all_images):
|
|
for image in all_images:
|
|
for name in image['RepoTags']:
|
|
yield name
|
|
|
|
while matching_prefixes:
|
|
all_images = list(image_names(client.images()))
|
|
for image_prefix in matching_prefixes:
|
|
for image in all_images:
|
|
if image.startswith(image_prefix):
|
|
log.info('Found image: %s' % image)
|
|
matching_prefixes.remove(image_prefix)
|
|
|
|
if time.time() > timeout:
|
|
raise Exception('Timed out after %s seconds waiting for '
|
|
'matching images: %s' % (
|
|
images_timeout,
|
|
', '.join(matching_prefixes)))
|
|
if poll_period:
|
|
time.sleep(poll_period)
|
|
|
|
|
|
def wait_required_containers(client, log,
|
|
containers_timeout, poll_period,
|
|
container_patterns):
|
|
patterns = container_patterns.values()
|
|
log.info(
|
|
'Waiting for containers matching: %s' % ', '.join(patterns))
|
|
|
|
timeout = time.time() + containers_timeout
|
|
|
|
def containers_names(containers):
|
|
for container in containers:
|
|
for name in container['Names']:
|
|
yield name
|
|
|
|
waiting_for = dict((v, re.compile(v)) for v in patterns)
|
|
while waiting_for:
|
|
for name in containers_names(client.containers()):
|
|
for k, v in waiting_for.items():
|
|
if v.match(name):
|
|
log.info('Pattern %s matches: %s' % (k, name))
|
|
del waiting_for[k]
|
|
break
|
|
if time.time() > timeout:
|
|
raise Exception('Timed out after %s seconds waiting for '
|
|
'matching containers: %s' % (
|
|
containers_timeout,
|
|
', '.join(waiting_for.keys)))
|
|
if poll_period:
|
|
time.sleep(poll_period)
|
|
|
|
|
|
def main(argv=sys.argv, sys_stdin=sys.stdin, sys_stdout=sys.stdout):
|
|
(log, deploy_stdout, deploy_stderr) = configure_logging()
|
|
client = get_client(log)
|
|
|
|
c = json.load(sys.stdin)
|
|
|
|
images_timeout = c['options'].get(
|
|
'images_timeout', DEFAULT_IMAGES_TIMEOUT)
|
|
containers_timeout = c['options'].get(
|
|
'containers_timeout', DEFAULT_CONTAINERS_TIMEOUT)
|
|
poll_period = c['options'].get(
|
|
'poll_period', DEFAULT_POLL_PERIOD)
|
|
|
|
pod_state = 0
|
|
|
|
try:
|
|
wait_required_images(
|
|
client,
|
|
log,
|
|
images_timeout,
|
|
poll_period,
|
|
required_images(c))
|
|
|
|
wait_required_containers(
|
|
client,
|
|
log,
|
|
containers_timeout,
|
|
poll_period,
|
|
required_container_patterns(c))
|
|
|
|
except Exception as ex:
|
|
pod_state = 1
|
|
log.error('An error occurred deploying pod %s' % c['id'])
|
|
log.exception(ex)
|
|
|
|
response = {
|
|
'deploy_stdout': deploy_stdout.getvalue(),
|
|
'deploy_stderr': deploy_stderr.getvalue(),
|
|
'deploy_status_code': pod_state,
|
|
}
|
|
json.dump(response, sys_stdout)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|