Use an image object, recorder object and status constants

Instead of using raw dicts and passing data around via
dictionaries (which makes it really hard to figure out
what is in those dictionaries at any point) prefer to
use objects. That way people can actually understand what
the object is supposed to be, vs guessing and/or having to
decipher its usage.

The same goes for raw string constants, prefer using
named constants instead.

Closes-Bug: #1586475

Change-Id: Ide179dc6593c50696d47a2d3d4cd000f343855d4
This commit is contained in:
Joshua Harlow 2016-05-26 13:38:21 -07:00
parent e78ae9bc61
commit 7c6990ccec
2 changed files with 197 additions and 143 deletions

View File

@ -80,6 +80,51 @@ class KollaRpmSetupUnknownConfig(Exception):
pass pass
# Image status constants.
#
# TODO(harlowja): use enum lib in the future??
STATUS_CONNECTION_ERROR = 'connection_error'
STATUS_PUSH_ERROR = 'push_error'
STATUS_ERROR = 'error'
STATUS_PARENT_ERROR = 'parent_error'
STATUS_BUILT = 'built'
STATUS_BUILDING = 'building'
STATUS_UNMATCHED = 'unmatched'
STATUS_MATCHED = 'matched'
STATUS_UNPROCESSED = 'unprocessed'
# All error status constants.
STATUS_ERRORS = (STATUS_CONNECTION_ERROR, STATUS_PUSH_ERROR,
STATUS_ERROR, STATUS_PARENT_ERROR)
class Recorder(object):
"""Recorder/buffer of (unicode) log lines for eventual display."""
def __init__(self):
self._lines = []
def write(self, text=""):
if isinstance(text, six.text_type):
self._lines.append(text)
elif isinstance(text, six.binary_type):
self._lines.append(text.decode('utf8'))
elif isinstance(text, Recorder):
self._lines.extend(text._lines)
else:
self.write(text=str(text))
def clear(self):
self._lines = []
def __iter__(self):
for line in self._lines:
yield line
def __str__(self):
return u"\n".join(self._lines)
def docker_client(): def docker_client():
try: try:
docker_kwargs = docker.utils.kwargs_from_env() docker_kwargs = docker.utils.kwargs_from_env()
@ -90,6 +135,28 @@ def docker_client():
sys.exit(1) sys.exit(1)
class Image(object):
def __init__(self, name, canonical_name, path, parent_name='',
status=STATUS_UNPROCESSED, parent=None, source=None):
self.name = name
self.canonical_name = canonical_name
self.path = path
self.status = status
self.parent = parent
self.source = source
self.parent_name = parent_name
self.logs = Recorder()
self.push_logs = Recorder()
self.children = []
self.plugins = []
def __repr__(self):
return ("Image(%s, %s, %s, parent_name=%s,"
" status=%s, parent=%s, source=%s)") % (
self.name, self.canonical_name, self.path,
self.parent_name, self.status, self.parent, self.source)
class PushIntoQueueTask(task.Task): class PushIntoQueueTask(task.Task):
"""Task that pushes some other task into a queue.""" """Task that pushes some other task into a queue."""
@ -119,41 +186,41 @@ class PushTask(task.Task):
@property @property
def name(self): def name(self):
return 'PushTask(%s)' % self.image['name'] return 'PushTask(%s)' % self.image.name
def run(self): def run(self):
image = self.image image = self.image
try: try:
LOG.debug('%s:Try to push the image', image['name']) LOG.debug('%s:Try to push the image', image.name)
self.push_image(image) self.push_image(image)
except requests_exc.ConnectionError: except requests_exc.ConnectionError:
LOG.exception('%s:Make sure Docker is running and that you' LOG.exception('%s:Make sure Docker is running and that you'
' have the correct privileges to run Docker' ' have the correct privileges to run Docker'
' (root)', image['name']) ' (root)', image.name)
image['status'] = "connection_error" image.status = STATUS_CONNECTION_ERROR
except Exception: except Exception:
LOG.exception('%s:Unknown error when pushing', image['name']) LOG.exception('%s:Unknown error when pushing', image.name)
image['status'] = "push_error" image.status = STATUS_PUSH_ERROR
finally: finally:
if "error" not in image['status']: if (image.status not in STATUS_ERRORS
LOG.info('%s:Pushed successfully', image['name']) and image.status != STATUS_UNPROCESSED):
LOG.info('%s:Pushed successfully', image.name)
self.success = True self.success = True
else: else:
self.success = False self.success = False
def push_image(self, image): def push_image(self, image):
image['push_logs'] = str() image.push_logs.clear()
for response in self.dc.push(image.canonical_name,
for response in self.dc.push(image['fullname'],
stream=True, stream=True,
insecure_registry=True): insecure_registry=True):
stream = json.loads(response) stream = json.loads(response)
if 'stream' in stream: if 'stream' in stream:
image['push_logs'] = image['logs'] + stream['stream'] image.push_logs.write(image.logs)
image.push_logs.write(stream['stream'])
LOG.info('%s', stream['stream']) LOG.info('%s', stream['stream'])
elif 'errorDetail' in stream: elif 'errorDetail' in stream:
image['status'] = "error" image.status = STATUS_ERROR
LOG.error(stream['errorDetail']['message']) LOG.error(stream['errorDetail']['message'])
@ -171,11 +238,11 @@ class BuildTask(task.Task):
@property @property
def name(self): def name(self):
return 'BuildTask(%s)' % self.image['name'] return 'BuildTask(%s)' % self.image.name
def run(self): def run(self):
self.builder(self.image) self.builder(self.image)
if self.image['status'] == 'built': if self.image.status == STATUS_BUILT:
self.success = True self.success = True
@property @property
@ -189,24 +256,24 @@ class BuildTask(task.Task):
PushTask(self.conf, self.image), PushTask(self.conf, self.image),
self.push_queue), self.push_queue),
]) ])
if self.image['children'] and self.success: if self.image.children and self.success:
for image in self.image['children']: for image in self.image.children:
followups.append(BuildTask(self.conf, image, self.push_queue)) followups.append(BuildTask(self.conf, image, self.push_queue))
return followups return followups
def process_source(self, image, source): def process_source(self, image, source):
dest_archive = os.path.join(image['path'], source['name'] + '-archive') dest_archive = os.path.join(image.path, source['name'] + '-archive')
if source.get('type') == 'url': if source.get('type') == 'url':
LOG.debug("%s:Getting archive from %s", image['name'], LOG.debug("%s:Getting archive from %s", image.name,
source['source']) source['source'])
try: try:
r = requests.get(source['source'], timeout=self.conf.timeout) r = requests.get(source['source'], timeout=self.conf.timeout)
except requests_exc.Timeout: except requests_exc.Timeout:
LOG.exception('Request timed out while getting archive' LOG.exception('Request timed out while getting archive'
' from %s', source['source']) ' from %s', source['source'])
image['status'] = "error" image.status = STATUS_ERROR
image['logs'] = str() image.logs.clear()
return return
if r.status_code == 200: if r.status_code == 200:
@ -214,34 +281,34 @@ class BuildTask(task.Task):
f.write(r.content) f.write(r.content)
else: else:
LOG.error('%s:Failed to download archive: status_code %s', LOG.error('%s:Failed to download archive: status_code %s',
image['name'], r.status_code) image.name, r.status_code)
image['status'] = "error" image.status = STATUS_ERROR
return return
elif source.get('type') == 'git': elif source.get('type') == 'git':
clone_dir = '{}-{}'.format(dest_archive, clone_dir = '{}-{}'.format(dest_archive,
source['reference'].replace('/', '-')) source['reference'].replace('/', '-'))
try: try:
LOG.debug("%s:Cloning from %s", image['name'], LOG.debug("%s:Cloning from %s", image.name,
source['source']) source['source'])
git.Git().clone(source['source'], clone_dir) git.Git().clone(source['source'], clone_dir)
git.Git(clone_dir).checkout(source['reference']) git.Git(clone_dir).checkout(source['reference'])
reference_sha = git.Git(clone_dir).rev_parse('HEAD') reference_sha = git.Git(clone_dir).rev_parse('HEAD')
LOG.debug("%s:Git checkout by reference %s (%s)", LOG.debug("%s:Git checkout by reference %s (%s)",
image['name'], source['reference'], reference_sha) image.name, source['reference'], reference_sha)
except Exception as e: except Exception as e:
LOG.error("%s:Failed to get source from git", image['name']) LOG.error("%s:Failed to get source from git", image.name)
LOG.error("%s:Error:%s", image['name'], str(e)) LOG.error("%s:Error:%s", image.name, str(e))
# clean-up clone folder to retry # clean-up clone folder to retry
shutil.rmtree(clone_dir) shutil.rmtree(clone_dir)
image['status'] = "error" image.status = STATUS_ERROR
return return
with tarfile.open(dest_archive, 'w') as tar: with tarfile.open(dest_archive, 'w') as tar:
tar.add(clone_dir, arcname=os.path.basename(clone_dir)) tar.add(clone_dir, arcname=os.path.basename(clone_dir))
elif source.get('type') == 'local': elif source.get('type') == 'local':
LOG.debug("%s:Getting local archive from %s", image['name'], LOG.debug("%s:Getting local archive from %s", image.name,
source['source']) source['source'])
if os.path.isdir(source['source']): if os.path.isdir(source['source']):
with tarfile.open(dest_archive, 'w') as tar: with tarfile.open(dest_archive, 'w') as tar:
@ -251,9 +318,9 @@ class BuildTask(task.Task):
shutil.copyfile(source['source'], dest_archive) shutil.copyfile(source['source'], dest_archive)
else: else:
LOG.error("%s:Wrong source type '%s'", image['name'], LOG.error("%s:Wrong source type '%s'", image.name,
source.get('type')) source.get('type'))
image['status'] = "error" image.status = STATUS_ERROR
return return
# Set time on destination archive to epoch 0 # Set time on destination archive to epoch 0
@ -279,31 +346,30 @@ class BuildTask(task.Task):
return buildargs return buildargs
def builder(self, image): def builder(self, image):
LOG.debug('%s:Processing', image['name']) LOG.debug('%s:Processing', image.name)
if image['status'] == 'unmatched': if image.status == STATUS_UNMATCHED:
return return
if (image['parent'] is not None and if (image.parent is not None and
image['parent']['status'] in ['error', 'parent_error', image.parent.status in STATUS_ERRORS):
'connection_error']):
LOG.error('%s:Parent image error\'d with message "%s"', LOG.error('%s:Parent image error\'d with message "%s"',
image['name'], image['parent']['status']) image.name, image.parent.status)
image['status'] = "parent_error" image.status = STATUS_PARENT_ERROR
return return
image['status'] = "building" image.status = STATUS_BUILDING
LOG.info('%s:Building', image['name']) LOG.info('%s:Building', image.name)
if 'source' in image and 'source' in image['source']: if image.source and 'source' in image.source:
self.process_source(image, image['source']) self.process_source(image, image.source)
if image['status'] == "error": if image.status in STATUS_ERRORS:
return return
plugin_archives = list() plugin_archives = list()
plugins_path = os.path.join(image['path'], 'plugins') plugins_path = os.path.join(image.path, 'plugins')
for plugin in image['plugins']: for plugin in image.plugins:
archive_path = self.process_source(image, plugin) archive_path = self.process_source(image, plugin)
if image['status'] == "error": if image.status in STATUS_ERRORS:
return return
plugin_archives.append(archive_path) plugin_archives.append(archive_path)
if plugin_archives: if plugin_archives:
@ -320,19 +386,19 @@ class BuildTask(task.Task):
else: else:
LOG.error('Failed to create directory %s: %s', LOG.error('Failed to create directory %s: %s',
plugins_path, e) plugins_path, e)
image['status'] = "error" image.status = STATUS_CONNECTION_ERROR
return return
with tarfile.open(os.path.join(image['path'], 'plugins-archive'), with tarfile.open(os.path.join(image.path, 'plugins-archive'),
'w') as tar: 'w') as tar:
tar.add(plugins_path, arcname='plugins') tar.add(plugins_path, arcname='plugins')
# Pull the latest image for the base distro only # Pull the latest image for the base distro only
pull = True if image['parent'] is None else False pull = True if image.parent is None else False
image['logs'] = str() image.logs.clear()
buildargs = self.update_buildargs() buildargs = self.update_buildargs()
for response in self.dc.build(path=image['path'], for response in self.dc.build(path=image.path,
tag=image['fullname'], tag=image.canonical_name,
nocache=self.nocache, nocache=self.nocache,
rm=True, rm=True,
pull=pull, pull=pull,
@ -341,23 +407,23 @@ class BuildTask(task.Task):
stream = json.loads(response.decode('utf-8')) stream = json.loads(response.decode('utf-8'))
if 'stream' in stream: if 'stream' in stream:
image['logs'] = image['logs'] + stream['stream'] image.logs.write(stream['stream'])
for line in stream['stream'].split('\n'): for line in stream['stream'].split('\n'):
if line: if line:
LOG.info('%s:%s', image['name'], line) LOG.info('%s:%s', image.name, line)
if 'errorDetail' in stream: if 'errorDetail' in stream:
image['status'] = "error" image.status = STATUS_ERROR
LOG.error('%s:Error\'d with the following message', LOG.error('%s:Error\'d with the following message',
image['name']) image.name)
for line in stream['errorDetail']['message'].split('\n'): for line in stream['errorDetail']['message'].split('\n'):
if line: if line:
LOG.error('%s:%s', image['name'], line) LOG.error('%s:%s', image.name, line)
return return
image['status'] = "built" image.status = STATUS_BUILT
LOG.info('%s:Built', image['name']) LOG.info('%s:Built', image.name)
class WorkerThread(threading.Thread): class WorkerThread(threading.Thread):
@ -606,31 +672,31 @@ class KollaWorker(object):
if filter_: if filter_:
patterns = re.compile(r"|".join(filter_).join('()')) patterns = re.compile(r"|".join(filter_).join('()'))
for image in self.images: for image in self.images:
if image['status'] == 'matched': if image.status == STATUS_MATCHED:
continue continue
if re.search(patterns, image['name']): if re.search(patterns, image.name):
image['status'] = 'matched' image.status = STATUS_MATCHED
while (image['parent'] is not None and while (image.parent is not None and
image['parent']['status'] != 'matched'): image.parent.status != STATUS_MATCHED):
image = image['parent'] image = image.parent
image['status'] = 'matched' image.status = STATUS_MATCHED
LOG.debug('%s:Matched regex', image['name']) LOG.debug('%s:Matched regex', image.name)
else: else:
image['status'] = 'unmatched' image.status = STATUS_UNMATCHED
else: else:
for image in self.images: for image in self.images:
image['status'] = 'matched' image.status = STATUS_MATCHED
def summary(self): def summary(self):
"""Walk the dictionary of images statuses and print results""" """Walk the dictionary of images statuses and print results"""
# For debug we print the logs again if the image error'd. This is to # For debug we print the logs again if the image error'd. This is to
# to help us debug and it will be extra helpful in the gate. # to help us debug and it will be extra helpful in the gate.
for image in self.images: for image in self.images:
if image['status'] == 'error': if image.status in STATUS_ERRORS:
LOG.debug("%s:Failed with the following logs", image['name']) LOG.debug("%s:Failed with the following logs", image.name)
for line in image['logs'].split('\n'): for line in image.logs:
if line: if line:
LOG.debug("%s:%s", image['name'], ''.join(line)) LOG.debug("%s:%s", image.name, line)
self.get_image_statuses() self.get_image_statuses()
@ -660,12 +726,12 @@ class KollaWorker(object):
self.image_statuses_good, self.image_statuses_good,
self.image_statuses_unmatched) self.image_statuses_unmatched)
for image in self.images: for image in self.images:
if image['status'] == "built": if image.status == STATUS_BUILT:
self.image_statuses_good[image['name']] = image['status'] self.image_statuses_good[image.name] = image.status
elif image['status'] == "unmatched": elif image.status == STATUS_UNMATCHED:
self.image_statuses_unmatched[image['name']] = image['status'] self.image_statuses_unmatched[image.name] = image.status
else: else:
self.image_statuses_bad[image['name']] = image['status'] self.image_statuses_bad[image.name] = image.status
return (self.image_statuses_bad, return (self.image_statuses_bad,
self.image_statuses_good, self.image_statuses_good,
self.image_statuses_unmatched) self.image_statuses_unmatched)
@ -689,33 +755,26 @@ class KollaWorker(object):
with open(os.path.join(path, 'Dockerfile')) as f: with open(os.path.join(path, 'Dockerfile')) as f:
content = f.read() content = f.read()
image = dict() image_name = os.path.basename(path)
image['status'] = "unprocessed" canonical_name = (self.namespace + '/' + self.image_prefix +
image['name'] = os.path.basename(path) image_name + ':' + self.tag)
image['fullname'] = self.namespace + '/' + self.image_prefix + \ image = Image(image_name, canonical_name, path,
image['name'] + ':' + self.tag parent_name=content.split(' ')[1].split('\n')[0])
image['path'] = path
image['parent_name'] = content.split(' ')[1].split('\n')[0]
if not image['parent_name'].startswith(self.namespace + '/'):
image['parent'] = None
image['children'] = list()
image['plugins'] = list()
if self.install_type == 'source': if self.install_type == 'source':
# NOTE(jeffrey4l): register the opts if the section didn't # NOTE(jeffrey4l): register the opts if the section didn't
# register in the kolla/common/config.py file # register in the kolla/common/config.py file
if image['name'] not in self.conf._groups: if image.name not in self.conf._groups:
self.conf.register_opts(common_config.get_source_opts(), self.conf.register_opts(common_config.get_source_opts(),
image['name']) image.name)
image['source'] = process_source_installation(image, image.source = process_source_installation(image, image.name)
image['name'])
for plugin in [match.group(0) for match in for plugin in [match.group(0) for match in
(re.search('{}-plugin-.+'.format(image['name']), (re.search('{}-plugin-.+'.format(image.name),
section) for section in section) for section in
self.conf.list_all_sections()) if match]: self.conf.list_all_sections()) if match]:
self.conf.register_opts(common_config.get_source_opts(), self.conf.register_opts(common_config.get_source_opts(),
plugin) plugin)
image['plugins'].append( image.plugins.append(
process_source_installation(image, plugin)) process_source_installation(image, plugin))
self.images.append(image) self.images.append(image)
@ -724,25 +783,25 @@ class KollaWorker(object):
dot = graphviz.Digraph(comment='Docker Images Dependency') dot = graphviz.Digraph(comment='Docker Images Dependency')
dot.body.extend(['rankdir=LR']) dot.body.extend(['rankdir=LR'])
for image in self.images: for image in self.images:
if image['status'] not in ['matched']: if image.status not in [STATUS_MATCHED]:
continue continue
dot.node(image['name']) dot.node(image.name)
if image['parent'] is not None: if image.parent is not None:
dot.edge(image['parent']['name'], image['name']) dot.edge(image.parent.name, image.name)
with open(to_file, 'w') as f: with open(to_file, 'w') as f:
f.write(dot.source) f.write(dot.source)
def list_images(self): def list_images(self):
for count, image in enumerate(self.images): for count, image in enumerate(self.images):
print(count + 1, ':', image['name']) print(count + 1, ':', image.name)
def list_dependencies(self): def list_dependencies(self):
match = False match = False
for image in self.images: for image in self.images:
if image['status'] in ['matched']: if image.status in [STATUS_MATCHED]:
match = True match = True
if image['parent'] is None: if image.parent is None:
base = image base = image
if not match: if not match:
print('Nothing matched!') print('Nothing matched!')
@ -751,18 +810,17 @@ class KollaWorker(object):
def list_children(images, ancestry): def list_children(images, ancestry):
children = ancestry.values()[0] children = ancestry.values()[0]
for item in images: for item in images:
if item['status'] not in ['matched']: if item.status not in [STATUS_MATCHED]:
continue continue
if not item.children:
if not item['children']: children.append(item.name)
children.append(item['name'])
else: else:
newparent = {item['name']: []} newparent = {item.name: []}
children.append(newparent) children.append(newparent)
list_children(item['children'], newparent) list_children(item.children, newparent)
ancestry = {base['name']: []} ancestry = {base.name: []}
list_children(base['children'], ancestry) list_children(base.children, ancestry)
pprint.pprint(ancestry) pprint.pprint(ancestry)
def find_parents(self): def find_parents(self):
@ -770,13 +828,13 @@ class KollaWorker(object):
sort_images = dict() sort_images = dict()
for image in self.images: for image in self.images:
sort_images[image['fullname']] = image sort_images[image.canonical_name] = image
for parent_name, parent in sort_images.items(): for parent_name, parent in sort_images.items():
for image in sort_images.values(): for image in sort_images.values():
if image['parent_name'] == parent_name: if image.parent_name == parent_name:
parent['children'].append(image) parent.children.append(image)
image['parent'] = parent image.parent = parent
def build_queue(self, push_queue): def build_queue(self, push_queue):
"""Organizes Queue list """Organizes Queue list
@ -791,9 +849,9 @@ class KollaWorker(object):
queue = six.moves.queue.Queue() queue = six.moves.queue.Queue()
for image in self.images: for image in self.images:
if image['parent'] is None: if image.parent is None:
queue.put(BuildTask(self.conf, image, push_queue)) queue.put(BuildTask(self.conf, image, push_queue))
LOG.debug('%s:Added image to queue', image['name']) LOG.debug('%s:Added image to queue', image.name)
return queue return queue

View File

@ -10,6 +10,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import fixtures import fixtures
import itertools import itertools
import mock import mock
@ -20,24 +21,18 @@ from kolla.cmd import build
from kolla.tests import base from kolla.tests import base
FAKE_IMAGE = { FAKE_IMAGE = build.Image('image-base', 'image-base:latest',
'name': 'image-base', '/fake/path', parent_name=None,
'status': 'matched', parent=None, status=build.STATUS_MATCHED)
'parent': None,
'parent_name': None,
'path': '/fake/path',
'plugins': [],
'fullname': 'image-base:latest',
}
class TasksTest(base.TestCase): class TasksTest(base.TestCase):
def setUp(self): def setUp(self):
super(TasksTest, self).setUp() super(TasksTest, self).setUp()
self.image = FAKE_IMAGE.copy() self.image = copy.deepcopy(FAKE_IMAGE)
# NOTE(jeffrey4l): use a real, temporary dir # NOTE(jeffrey4l): use a real, temporary dir
self.image['path'] = self.useFixture(fixtures.TempDir()).path self.image.path = self.useFixture(fixtures.TempDir()).path
@mock.patch.dict(os.environ, clear=True) @mock.patch.dict(os.environ, clear=True)
@mock.patch('docker.Client') @mock.patch('docker.Client')
@ -45,7 +40,7 @@ class TasksTest(base.TestCase):
pusher = build.PushTask(self.conf, self.image) pusher = build.PushTask(self.conf, self.image)
pusher.run() pusher.run()
mock_client().push.assert_called_once_with( mock_client().push.assert_called_once_with(
self.image['fullname'], stream=True, insecure_registry=True) self.image.canonical_name, stream=True, insecure_registry=True)
@mock.patch.dict(os.environ, clear=True) @mock.patch.dict(os.environ, clear=True)
@mock.patch('docker.Client') @mock.patch('docker.Client')
@ -55,7 +50,7 @@ class TasksTest(base.TestCase):
builder.run() builder.run()
mock_client().build.assert_called_once_with( mock_client().build.assert_called_once_with(
path=self.image['path'], tag=self.image['fullname'], path=self.image.path, tag=self.image.canonical_name,
nocache=False, rm=True, pull=True, forcerm=True, nocache=False, rm=True, pull=True, forcerm=True,
buildargs=None) buildargs=None)
@ -74,7 +69,7 @@ class TasksTest(base.TestCase):
builder.run() builder.run()
mock_client().build.assert_called_once_with( mock_client().build.assert_called_once_with(
path=self.image['path'], tag=self.image['fullname'], path=self.image.path, tag=self.image.canonical_name,
nocache=False, rm=True, pull=True, forcerm=True, nocache=False, rm=True, pull=True, forcerm=True,
buildargs=build_args) buildargs=build_args)
@ -92,7 +87,7 @@ class TasksTest(base.TestCase):
builder.run() builder.run()
mock_client().build.assert_called_once_with( mock_client().build.assert_called_once_with(
path=self.image['path'], tag=self.image['fullname'], path=self.image.path, tag=self.image.canonical_name,
nocache=False, rm=True, pull=True, forcerm=True, nocache=False, rm=True, pull=True, forcerm=True,
buildargs=build_args) buildargs=build_args)
@ -112,7 +107,7 @@ class TasksTest(base.TestCase):
builder.run() builder.run()
mock_client().build.assert_called_once_with( mock_client().build.assert_called_once_with(
path=self.image['path'], tag=self.image['fullname'], path=self.image.path, tag=self.image.canonical_name,
nocache=False, rm=True, pull=True, forcerm=True, nocache=False, rm=True, pull=True, forcerm=True,
buildargs=build_args) buildargs=build_args)
@ -121,7 +116,7 @@ class TasksTest(base.TestCase):
@mock.patch('docker.Client') @mock.patch('docker.Client')
@mock.patch('requests.get') @mock.patch('requests.get')
def test_requests_get_timeout(self, mock_get, mock_client): def test_requests_get_timeout(self, mock_get, mock_client):
self.image['source'] = { self.image.source = {
'source': 'http://fake/source', 'source': 'http://fake/source',
'type': 'url', 'type': 'url',
'name': 'fake-image-base' 'name': 'fake-image-base'
@ -129,12 +124,12 @@ class TasksTest(base.TestCase):
push_queue = mock.Mock() push_queue = mock.Mock()
builder = build.BuildTask(self.conf, self.image, push_queue) builder = build.BuildTask(self.conf, self.image, push_queue)
mock_get.side_effect = requests.exceptions.Timeout mock_get.side_effect = requests.exceptions.Timeout
get_result = builder.process_source(self.image, self.image['source']) get_result = builder.process_source(self.image, self.image.source)
self.assertIsNone(get_result) self.assertIsNone(get_result)
self.assertEqual(self.image['status'], 'error') self.assertEqual(self.image.status, build.STATUS_ERROR)
self.assertEqual(self.image['logs'], str()) self.assertEqual(str(self.image.logs), str())
mock_get.assert_called_once_with(self.image['source']['source'], mock_get.assert_called_once_with(self.image.source['source'],
timeout=120) timeout=120)
self.assertFalse(builder.success) self.assertFalse(builder.success)
@ -146,8 +141,8 @@ class KollaWorkerTest(base.TestCase):
def setUp(self): def setUp(self):
super(KollaWorkerTest, self).setUp() super(KollaWorkerTest, self).setUp()
image = FAKE_IMAGE.copy() image = copy.deepcopy(FAKE_IMAGE)
image['status'] = None image.status = None
self.images = [image] self.images = [image]
def test_supported_base_type(self): def test_supported_base_type(self):
@ -188,14 +183,15 @@ class KollaWorkerTest(base.TestCase):
'type': 'git' 'type': 'git'
} }
for image in kolla.images: for image in kolla.images:
if image['name'] == 'neutron-server': if image.name == 'neutron-server':
self.assertEqual(image['plugins'][0], expected_plugin) self.assertEqual(image.plugins[0], expected_plugin)
break break
else: else:
self.fail('Can not find the expected neutron arista plugin') self.fail('Can not find the expected neutron arista plugin')
def _get_matched_images(self, images): def _get_matched_images(self, images):
return [image for image in images if image['status'] == 'matched'] return [image for image in images
if image.status == build.STATUS_MATCHED]
def test_without_profile(self): def test_without_profile(self):
kolla = build.KollaWorker(self.conf) kolla = build.KollaWorker(self.conf)