Improve logging and prepare for gate

Switched all print statements to use logger

Added ability to halt operations via Ctrl-C, needs cleanup handler

Added realtime logging when using only one thread (matches bash building
scripts behavior)

main is now returning all the build image statuses, so a quick test
script would look like:

import build
res = build.main()
if len(res[0]):
    LOG.error('failure')

Change-Id: Ic8000a96573b011490dc330a4512c77c602ac3d2
Partially-Implements: blueprint build-script
This commit is contained in:
Jeff Peeler 2015-07-13 23:50:06 -04:00
parent 6bca976500
commit 485e1b2539

View File

@ -17,15 +17,16 @@
# TODO(SamYaple): Build only missing images # TODO(SamYaple): Build only missing images
# TODO(SamYaple): Execute the source install script that will pull # TODO(SamYaple): Execute the source install script that will pull
# down and create tarball # down and create tarball
# TODO(SamYaple): Improve logging instead of printing to stdout # TODO(jpeeler): Add clean up handler for SIGINT
from __future__ import print_function
import argparse import argparse
import datetime import datetime
import json import json
import logging
import os import os
import Queue import Queue
import shutil import shutil
import signal
import sys import sys
import tempfile import tempfile
from threading import Thread from threading import Thread
@ -35,13 +36,20 @@ import traceback
import docker import docker
import jinja2 import jinja2
logging.basicConfig()
LOG = logging.getLogger(__name__)
LOG.setLevel(logging.INFO)
signal.signal(signal.SIGINT, signal.SIG_DFL)
class WorkerThread(Thread): class WorkerThread(Thread):
def __init__(self, queue, nocache, keep): def __init__(self, queue, nocache, keep, threads):
self.queue = queue self.queue = queue
self.nocache = nocache self.nocache = nocache
self.forcerm = not keep self.forcerm = not keep
self.threads = threads
self.dc = docker.Client(**docker.utils.kwargs_from_env()) self.dc = docker.Client(**docker.utils.kwargs_from_env())
Thread.__init__(self) Thread.__init__(self)
@ -59,7 +67,7 @@ class WorkerThread(Thread):
self.queue.task_done() self.queue.task_done()
def builder(self, image): def builder(self, image):
print('Processing:', image['name']) LOG.info('Processing: {}'.format(image['name']))
image['status'] = "building" image['status'] = "building"
if (image['parent'] is not None and if (image['parent'] is not None and
@ -81,12 +89,20 @@ class WorkerThread(Thread):
if 'stream' in stream: if 'stream' in stream:
image['logs'] = image['logs'] + stream['stream'] image['logs'] = image['logs'] + stream['stream']
elif 'errorDetail' in stream: if self.threads == 1:
LOG.info('{}:{}'.format(image['name'],
stream['stream'].rstrip()))
if 'errorDetail' in stream:
image['status'] = "error" image['status'] = "error"
LOG.error(stream['errorDetail']['message'])
raise Exception(stream['errorDetail']['message']) raise Exception(stream['errorDetail']['message'])
image['status'] = "built" image['status'] = "built"
print(image['logs'], 'Processed:', image['name'])
if self.threads == 1:
LOG.info('Processed: {}'.format(image['name']))
else:
LOG.info('{}Processed: {}'.format(image['logs'], image['name']))
def argParser(): def argParser():
@ -124,13 +140,18 @@ def argParser():
action='store_true', action='store_true',
default=False) default=False)
parser.add_argument('-T', '--threads', parser.add_argument('-T', '--threads',
help='The number of threads to use while building', help='The number of threads to use while building.'
' (Note: setting to one will allow real time'
' logging.)',
type=int, type=int,
default=8) default=8)
parser.add_argument('--template', parser.add_argument('--template',
help='Create dockerfiles from templates', help='Create dockerfiles from templates',
action='store_true', action='store_true',
default=False) default=False)
parser.add_argument('-d', '--debug',
help='Turn on debugging log level',
action='store_true')
return vars(parser.parse_args()) return vars(parser.parse_args())
@ -148,6 +169,9 @@ class KollaWorker(object):
self.tag = args['tag'] self.tag = args['tag']
self.prefix = self.base + '-' + self.type_ + '-' self.prefix = self.base + '-' + self.type_ + '-'
self.image_statuses_bad = {}
self.image_statuses_good = {}
def setupWorkingDir(self): def setupWorkingDir(self):
"""Creates a working directory for use while building""" """Creates a working directory for use while building"""
ts = time.time() ts = time.time()
@ -158,6 +182,7 @@ class KollaWorker(object):
shutil.copytree(self.templates_dir, self.working_dir) shutil.copytree(self.templates_dir, self.working_dir)
else: else:
shutil.copytree(self.images_dir, self.working_dir) shutil.copytree(self.images_dir, self.working_dir)
LOG.debug('Created working dir: {}'.format(self.working_dir))
def createDockerfiles(self): def createDockerfiles(self):
for path in self.docker_build_paths: for path in self.docker_build_paths:
@ -185,6 +210,9 @@ class KollaWorker(object):
for root, dirs, names in os.walk(path): for root, dirs, names in os.walk(path):
if filename in names: if filename in names:
self.docker_build_paths.append(root) self.docker_build_paths.append(root)
LOG.debug('Found {}'.format(root.split(self.working_dir)[1]))
LOG.debug('Found {} Dockerfiles'.format(len(self.docker_build_paths)))
def cleanup(self): def cleanup(self):
"""Remove temp files""" """Remove temp files"""
@ -203,21 +231,24 @@ class KollaWorker(object):
if image['parent'] is None: if image['parent'] is None:
self.tiers[-1].append(image) self.tiers[-1].append(image)
processed_images.append(image) processed_images.append(image)
LOG.debug('Sorted parentless image: {}'.format(
image['name']))
if len(self.tiers) > 1: if len(self.tiers) > 1:
for parent in self.tiers[-2]: for parent in self.tiers[-2]:
if image['parent'] == parent['fullname']: if image['parent'] == parent['fullname']:
image['parent'] = parent image['parent'] = parent
self.tiers[-1].append(image) self.tiers[-1].append(image)
processed_images.append(image) processed_images.append(image)
LOG.debug('Sorted image {} with parent {}'.format(
image['name'], parent['fullname']))
LOG.debug('===')
# TODO(SamYaple): Improve error handling in this section # TODO(SamYaple): Improve error handling in this section
if not processed_images: if not processed_images:
print('Could not find parent image from some images. Aborting', LOG.warning('Could not find parent image from some images.'
file=sys.stderr) ' Aborting')
for image in images_to_process: for image in images_to_process:
print(image['name'], image['parent'], file=sys.stderr) LOG.warning('{} {}'.format(image['name'], image['parent']))
sys.exit() sys.exit()
# You cannot modify a list while using the list in a for loop as it # You cannot modify a list while using the list in a for loop as it
# will produce unexpected results by messing up the index so we # will produce unexpected results by messing up the index so we
# build a seperate list and remove them here instead # build a seperate list and remove them here instead
@ -225,19 +256,28 @@ class KollaWorker(object):
images_to_process.remove(image) images_to_process.remove(image)
def summary(self): def summary(self):
"""Walk the list of images and check for errors""" """Walk the dictionary of images statuses and print results"""
print("Successfully built images") self.get_image_statuses()
print("=========================") LOG.info("Successfully built images")
LOG.info("=========================")
for name in self.image_statuses_good.keys():
LOG.info(name)
LOG.info("Images that failed to build")
LOG.info("===========================")
for name, status in self.image_statuses_bad.iteritems():
LOG.error('{}\r\t\t\t Failed with status: {}'.format(
name, status))
def get_image_statuses(self):
if len(self.image_statuses_bad) or len(self.image_statuses_good):
return (self.image_statuses_bad, self.image_statuses_good)
for image in self.images: for image in self.images:
if image['status'] == "built": if image['status'] == "built":
print(image['name']) self.image_statuses_good[image['name']] = image['status']
else:
print("\nImages that failed to build") self.image_statuses_bad[image['name']] = image['status']
print("===========================") return (self.image_statuses_bad, self.image_statuses_good)
for image in self.images:
if image['status'] != "built":
print(image['name'], "\r\t\t\t Failed with status:",
image['status'])
def buildImageList(self): def buildImageList(self):
self.images = list() self.images = list()
@ -274,10 +314,11 @@ class KollaWorker(object):
self.sortImages() self.sortImages()
pools = list() pools = list()
for tier in self.tiers: for count, tier in enumerate(self.tiers):
pool = Queue.Queue() pool = Queue.Queue()
for image in tier: for image in tier:
pool.put(image) pool.put(image)
LOG.debug('Tier {}: add image {}'.format(count, image['name']))
pools.append(pool) pools.append(pool)
@ -299,11 +340,13 @@ def push_image(image):
print(stream['stream']) print(stream['stream'])
elif 'errorDetail' in stream: elif 'errorDetail' in stream:
image['status'] = "error" image['status'] = "error"
raise Exception(stream['errorDetail']['message']) LOG.error(stream['errorDetail']['message'])
def main(): def main():
args = argParser() args = argParser()
if args['debug']:
LOG.setLevel(logging.DEBUG)
kolla = KollaWorker(args) kolla = KollaWorker(args)
kolla.setupWorkingDir() kolla.setupWorkingDir()
@ -317,7 +360,8 @@ def main():
# Returns a list of Queues for us to loop through # Returns a list of Queues for us to loop through
for pool in pools: for pool in pools:
for x in xrange(args['threads']): for x in xrange(args['threads']):
WorkerThread(pool, args['no_cache'], args['keep']).start() WorkerThread(pool, args['no_cache'], args['keep'],
args['threads']).start()
# block until queue is empty # block until queue is empty
pool.join() pool.join()
@ -330,5 +374,7 @@ def main():
kolla.summary() kolla.summary()
kolla.cleanup() kolla.cleanup()
return kolla.get_image_statuses()
if __name__ == '__main__': if __name__ == '__main__':
main() main()