A job is essentially what we're modelling here - e.g. it has status and percent_complete properties. --- imagefactory/BuildDispatcher.py | 105 ++++---------------------------- imagefactory/BuildJob.py | 106 +++++++++++++++++++++++++++++++++ imagefactory/qmfagent/BuildAdaptor.py | 4 +- imagefactory/qmfagent/ImageFactory.py | 5 +- tests/testBuildDispatcher.py | 41 ------------- tests/testBuildJob.py | 41 +++++++++++++ 6 files changed, 165 insertions(+), 137 deletions(-) create mode 100644 imagefactory/BuildJob.py delete mode 100644 tests/testBuildDispatcher.py create mode 100644 tests/testBuildJob.py
diff --git a/imagefactory/BuildDispatcher.py b/imagefactory/BuildDispatcher.py index 4bae177..a1cfc9d 100644 --- a/imagefactory/BuildDispatcher.py +++ b/imagefactory/BuildDispatcher.py @@ -15,15 +15,10 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html.
-import sys import libxml2 -import logging import os.path -import props -from threading import Thread, Lock -from imagefactory.builders import * from imagefactory.ApplicationConfiguration import ApplicationConfiguration -from imagefactory.ImageFactoryException import ImageFactoryException +from imagefactory.BuildJob import BuildJob from imagefactory.ImageWarehouse import ImageWarehouse from imagefactory.Template import Template
@@ -35,82 +30,8 @@ from imagefactory.Template import Template
class BuildDispatcher(object):
- template = props.prop("_template", "The template property.") - target = props.prop("_target", "The target property.") - image_id = props.prop("_image_id", "The UUID of the image.") - build_id = props.prop("_build_id", "The UUID of the build.") - provider = props.prop("_provider", "The provider being pushed to.") - status = props.prop("_status", "The status property.") - percent_complete = props.prop("_percent_complete", "The percent_complete property.") - new_image_id = props.prop("_new_image_id" "The image property.") - - def __init__(self, template, target, image_id = '', build_id = ''): - super(BuildDispatcher, self).__init__() - - self.log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__)) - - self.template = template if isinstance(template, Template) else Template(template) - self.target = target - self.image_id = image_id - self.build_id = build_id - self.provider = None - self.status = "New" - self.percent_complete = 0 - self._watcher = None - - self._builder = self._get_builder() - self._builder.delegate = self - - self.new_image_id = self._builder.new_image_id - - def build_image(self, watcher=None): - self._watcher = watcher - kwargs = dict(build_id=self.build_id) - self._start_builder_thread("build_image", arg_dict=kwargs) - - def push_image(self, target_image_id, provider, credentials, watcher=None): - self._watcher = watcher - self.provider = provider - kwargs = dict(target_image_id=target_image_id, provider=provider, credentials=credentials) - self._start_builder_thread("push_image", arg_dict=kwargs) - - def abort(self): - self._builder.abort() - - def builder_did_update_status(self, builder, old_status, new_status): - self.status = new_status - if self.status == "COMPLETED" and self._watcher: - self._watcher.completed() - self._watcher = None - - def builder_did_update_percentage(self, builder, original_percentage, new_percentage): - self.percent_complete = new_percentage - - def builder_did_fail(self, builder, failure_type, failure_info): - pass - - def _get_builder(self): - builder_class = MockBuilder.MockBuilder - if (self.target != "mock"): # If target is mock always run mock builder regardless of template - os_name = self.__class__._xml_node(self.template.xml, '/template/os/name') - class_name = "%sBuilder" % (os_name, ) - try: - module_name = "imagefactory.builders.%s" % (class_name, ) - __import__(module_name) - builder_class = getattr(sys.modules[module_name], class_name) - except AttributeError, e: - self.log.exception("CAUGHT EXCEPTION: %s \n Could not find builder class for %s, returning MockBuilder!", e, os_name) - - return builder_class(self.template, self.target) - - def _start_builder_thread(self, method_name, arg_dict={}): - thread_name = "%s.%s()" % (self.new_image_id, method_name) - # using args to pass the method we want to call on the target object. - builder_thread = Thread(target = self._builder, name=thread_name, args=(method_name), kwargs=arg_dict) - builder_thread.start() - @classmethod - def build_image_for_targets(cls, image_id, build_id, template, targets, *args, **kwargs): + def build_image_for_targets(cls, image_id, build_id, template, targets, job_cls = BuildJob, *args, **kwargs): warehouse = cls._get_warehouse()
template = Template(template) @@ -120,16 +41,16 @@ class BuildDispatcher(object):
watcher = BuildWatcher(image_id, build_id, len(targets), warehouse)
- dispatchers = [] + jobs = [] for target in targets: - dispatcher = cls(template, target, image_id, build_id, *args, **kwargs) - dispatcher.build_image(watcher) - dispatchers.append(dispatcher) + job = job_cls(template, target, image_id, build_id, *args, **kwargs) + job.build_image(watcher) + jobs.append(job)
- return dispatchers + return jobs
@classmethod - def push_image_to_providers(cls, image_id, build_id, providers, credentials, *args, **kwargs): + def push_image_to_providers(cls, image_id, build_id, providers, credentials, job_cls = BuildJob, *args, **kwargs): warehouse = cls._get_warehouse()
if not build_id: @@ -137,7 +58,7 @@ class BuildDispatcher(object):
watcher = PushWatcher(image_id, build_id, len(providers), warehouse)
- dispatchers = [] + jobs = [] for provider in providers: target = cls._map_provider_to_target(provider)
@@ -145,11 +66,11 @@ class BuildDispatcher(object):
template = cls._template_for_target_image_id(warehouse, target_image_id)
- dispatcher = cls(template, target, image_id, build_id, *args, **kwargs) - dispatcher.push_image(target_image_id, provider, credentials, watcher) - dispatchers.append(dispatcher) + job = job_cls(template, target, image_id, build_id, *args, **kwargs) + job.push_image(target_image_id, provider, credentials, watcher) + jobs.append(job)
- return dispatchers + return jobs
@classmethod def _get_warehouse(cls): diff --git a/imagefactory/BuildJob.py b/imagefactory/BuildJob.py new file mode 100644 index 0000000..a5db89a --- /dev/null +++ b/imagefactory/BuildJob.py @@ -0,0 +1,106 @@ +# Copyright (C) 2010-2011 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import libxml2 +import logging +import props +import sys +import threading +from imagefactory.builders import * +from imagefactory.Template import Template + +class BuildJob(object): + + template = props.prop("_template", "The template property.") + target = props.prop("_target", "The target property.") + image_id = props.prop("_image_id", "The UUID of the image.") + build_id = props.prop("_build_id", "The UUID of the build.") + provider = props.prop("_provider", "The provider being pushed to.") + status = props.prop("_status", "The status property.") + percent_complete = props.prop("_percent_complete", "The percent_complete property.") + new_image_id = props.prop("_new_image_id" "The image property.") + + def __init__(self, template, target, image_id = '', build_id = ''): + super(BuildJob, self).__init__() + + self.log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__)) + + self.template = template if isinstance(template, Template) else Template(template) + self.target = target + self.image_id = image_id + self.build_id = build_id + self.provider = None + self.status = "New" + self.percent_complete = 0 + self._watcher = None + + self._builder = self._get_builder() + self._builder.delegate = self + + self.new_image_id = self._builder.new_image_id + + def build_image(self, watcher=None): + self._watcher = watcher + kwargs = dict(build_id=self.build_id) + self._start_builder_thread("build_image", arg_dict=kwargs) + + def push_image(self, target_image_id, provider, credentials, watcher=None): + self._watcher = watcher + self.provider = provider + kwargs = dict(target_image_id=target_image_id, provider=provider, credentials=credentials) + self._start_builder_thread("push_image", arg_dict=kwargs) + + def abort(self): + self._builder.abort() + + def builder_did_update_status(self, builder, old_status, new_status): + self.status = new_status + if self.status == "COMPLETED" and self._watcher: + self._watcher.completed() + self._watcher = None + + def builder_did_update_percentage(self, builder, original_percentage, new_percentage): + self.percent_complete = new_percentage + + def builder_did_fail(self, builder, failure_type, failure_info): + pass + + def _get_builder(self): + builder_class = MockBuilder.MockBuilder + if (self.target != "mock"): # If target is mock always run mock builder regardless of template + os_name = self._xml_node(self.template.xml, '/template/os/name') + class_name = "%sBuilder" % (os_name, ) + try: + module_name = "imagefactory.builders.%s" % (class_name, ) + __import__(module_name) + builder_class = getattr(sys.modules[module_name], class_name) + except AttributeError, e: + self.log.exception("CAUGHT EXCEPTION: %s \n Could not find builder class for %s, returning MockBuilder!", e, os_name) + + return builder_class(self.template, self.target) + + def _start_builder_thread(self, method_name, arg_dict={}): + thread_name = "%s.%s()" % (self.new_image_id, method_name) + # using args to pass the method we want to call on the target object. + builder_thread = threading.Thread(target = self._builder, name=thread_name, args=(method_name), kwargs=arg_dict) + builder_thread.start() + + def _xml_node(self, xml, xpath): + nodes = libxml2.parseDoc(xml).xpathEval(xpath) + if not nodes: + return None + return nodes[0].content diff --git a/imagefactory/qmfagent/BuildAdaptor.py b/imagefactory/qmfagent/BuildAdaptor.py index 78b0570..e042759 100644 --- a/imagefactory/qmfagent/BuildAdaptor.py +++ b/imagefactory/qmfagent/BuildAdaptor.py @@ -23,9 +23,9 @@ from qmf2 import * from threading import Thread, Lock from imagefactory import props from imagefactory.builders import * -from imagefactory.BuildDispatcher import BuildDispatcher +from imagefactory.BuildJob import BuildJob
-class BuildAdaptor(BuildDispatcher): +class BuildAdaptor(BuildJob): # QMF schema for BuildAdaptor qmf_schema = Schema(SCHEMA_TYPE_DATA, "com.redhat.imagefactory", "BuildAdaptor") qmf_schema.addProperty(SchemaProperty("image", SCHEMA_DATA_STRING, desc="UUID of the image")) diff --git a/imagefactory/qmfagent/ImageFactory.py b/imagefactory/qmfagent/ImageFactory.py index c5e64fa..460883c 100644 --- a/imagefactory/qmfagent/ImageFactory.py +++ b/imagefactory/qmfagent/ImageFactory.py @@ -23,6 +23,7 @@ import httplib2 from BuildAdaptor import BuildAdaptor from imagefactory import props from imagefactory.ApplicationConfiguration import ApplicationConfiguration +from imagefactory.BuildDispatcher import BuildDispatcher from imagefactory.ImageWarehouse import ImageWarehouse from imagefactory.Singleton import Singleton from imagefactory.Template import Template @@ -107,10 +108,10 @@ class ImageFactory(Singleton): raise RuntimeError("Could not return build_adaptor!\nimage_metadata: %s\ntemplate_id: %s\ntemplate: %s\n" % (image_metadata, template_id, target))
def build_image(self, image, build, template, targets): - return BuildAdaptor.build_image_for_targets(image, build, template, targets, self.agent) + return BuildDispatcher.build_image_for_targets(image, build, template, targets, BuildAdaptor, self.agent)
def push_image(self, image, build, providers, credentials): - return BuildAdaptor.push_image_to_providers(image, build, providers, credentials, self.agent) + return BuildDispatcher.push_image_to_providers(image, build, providers, credentials, BuildAdaptor, self.agent)
def instance_states(self, class_name): """Returns a dictionary representing the finite state machine for instances of the class specified.""" diff --git a/tests/testBuildDispatcher.py b/tests/testBuildDispatcher.py deleted file mode 100644 index 226da12..0000000 --- a/tests/testBuildDispatcher.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -# Copyright (C) 2010-2011 Red Hat, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -import unittest -import logging -from imagefactory import BuildDispatcher -from imagefactory.builders import MockBuilder - -class testBuildDispatcher(unittest.TestCase): - def setUp(self): - logging.basicConfig(level=logging.NOTSET, format='%(asctime)s %(levelname)s %(name)s pid(%(process)d) Message: %(message)s', filename='/tmp/imagefactory-unittests.log') - - def tearDown(self): - pass - - def testInstantiateMockBuilder(self): - template_xml = "<template><name>f14jeos</name><os><name>Fedora</name></os></template>" - dispatcher = BuildDispatcher.BuildDispatcher(template_xml, "mock") - self.assertIsInstance(dispatcher._builder, MockBuilder.MockBuilder) - self.assertEqual(dispatcher.template.xml, template_xml) - self.assertEqual(dispatcher.target, "mock") - -if __name__ == '__main__': - unittest.main() diff --git a/tests/testBuildJob.py b/tests/testBuildJob.py new file mode 100644 index 0000000..28c75e6 --- /dev/null +++ b/tests/testBuildJob.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# Copyright (C) 2010-2011 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import unittest +import logging +from imagefactory import BuildJob +from imagefactory.builders import MockBuilder + +class testBuildJob(unittest.TestCase): + def setUp(self): + logging.basicConfig(level=logging.NOTSET, format='%(asctime)s %(levelname)s %(name)s pid(%(process)d) Message: %(message)s', filename='/tmp/imagefactory-unittests.log') + + def tearDown(self): + pass + + def testInstantiateMockBuilder(self): + template_xml = "<template><name>f14jeos</name><os><name>Fedora</name></os></template>" + job = BuildJob.BuildJob(template_xml, "mock") + self.assertIsInstance(job._builder, MockBuilder.MockBuilder) + self.assertEqual(job.template.xml, template_xml) + self.assertEqual(job.target, "mock") + +if __name__ == '__main__': + unittest.main()