ACK
On Jun 1, 2011, at 6:22 AM, Mark McLoughlin wrote:
Add two new methods to ImageFactory - build_image and push_image.
These
expose the "build image for multiple targets" and "push image to
multiple providers" functionality.
Note that both of these methods take optional image and build ID
parameters. I'm not sure QMF has null parameter values, so we allow
these to be empty strings.
Also add image and build properties to BuildAdaptor to allow the console
client determine the image and/or build IDs if they were allocated by
the agent.
---
Documentation/QMF.markdown | 77 +++++++++++++++++----------
imagefactory/BuildDispatcher.py | 8 ++--
imagefactory/qmfagent/BuildAdaptor.py | 10 +++-
imagefactory/qmfagent/ImageFactory.py | 32 ++++++++++--
imagefactory/qmfagent/ImageFactoryAgent.py | 25 +++++++--
tests/qmfagent/testBuildAdaptor.py | 2 +-
tests/qmfagent/testImageFactory.py | 6 ++-
tests/qmfagent/testImageFactoryAgent.py | 8 ++--
8 files changed, 115 insertions(+), 53 deletions(-)
diff --git a/Documentation/QMF.markdown b/Documentation/QMF.markdown
index d6e6091..ea91cc0 100644
--- a/Documentation/QMF.markdown
+++ b/Documentation/QMF.markdown
@@ -1,6 +1,6 @@
## Introduction ##
-The script imagefactory.py can be used to start a daemon running a QMF agent. Using the
'qpidd' option, the host that qpidd is running on can be set. Currently this
defaults to the default qpidd port on localhost. Future versions will accept an amqp://
URL to fully configure the agent session.
+The script imgfac.py can be used to start a daemon running a QMF agent. Using the
'qpidd' option, the host that qpidd is running on can be set. Currently this
defaults to the default qpidd port on localhost. Future versions will accept an amqp://
URL to fully configure the agent session.
Example usage:
@@ -36,18 +36,18 @@ The imagefactory can then be sent a message to build an image:
Example 3 - Building an image
- In [15]: factory.image("<template></template>",
"mock")
+ In [15]: factory.build_image("", "",
"<template></template>", ["mock"])
Out[15]:
- {'build_adaptor': {'_agent_epoch': 1L,
- '_agent_name':
'redhat.com:imagefactory:5edbf641-78ba-4375-8ec8-f60f555e173a',
- '_object_name':
'build_adaptor:db21dd5e-b3b6-49d1-a432-07f9f2d1c3c5'}}
+ {'build_adaptors': [{'_agent_epoch': 1L,
+ '_agent_name':
'redhat.com:imagefactory:5edbf641-78ba-4375-8ec8-f60f555e173a',
+ '_object_name':
'build_adaptor:db21dd5e-b3b6-49d1-a432-07f9f2d1c3c5'}]}
The console can poll the build_adaptor for build status or receive events listed below.
Example 4 - Querying for build status
- In [20]: response = factory.image("<template></template>",
"mock")
- In [21]: build_addr = DataAddr(response['build_adaptor'])
+ In [20]: response = factory.build_image("", ""
"<template></template>", ["mock"])
+ In [21]: build_addr = DataAddr(response['build_adaptors'][0])
In [22]: query = Query(build_addr)
In [23]: agent.query(query)[0].status
Out[23]: 'PENDING'
@@ -63,40 +63,49 @@ The console can poll the build_adaptor for build status or receive
events listed
name = "ImageFactory"
package = "com.redhat.imagefactory"
#### Methods: ####
-* `image(template, target)`
+* `build_image(image, build, template, targets)`
- method name = "image"
- desc = "Build a new image"
+ method name = "build_image"
+ desc = "Build an image for the given targets"
arguments:
+ name = "image"
+ dtype = SCHEMA_DATA_STRING
+ desc = "the uuid of an existing image, or the empty string"
+ name = "build"
+ dtype = SCHEMA_DATA_STRING
+ desc = "the uuid of an existing build, or the empty string"
name = "template"
dtype = SCHEMA_DATA_STRING
desc = "string of xml, uuid, or url"
- name = "target"
- dtype = SCHEMA_DATA_STRING
- desc = "name of the cloud to target"
+ name = "targets"
+ dtype = SCHEMA_DATA_LIST
+ desc = "names of the clouds to target"
return values:
- name = "build_adaptor"
- dtype = SCHEMA_DATA_MAP
- desc = "the QMF address of the build_adaptor instantiated"
+ name = "build_adaptors"
+ dtype = SCHEMA_DATA_LIST
+ desc = "the QMF addresses of the build_adaptors instantiated"
-* `provider_image(image_id, provider, credentials)`
+* `provider_image(image, build, providers, credentials)`
- method name = "provider_image"
- desc = "Push an image to a provider."
+ method name = "push_image"
+ desc = "Push an image to the given providers."
arguments:
- name = "image_id"
+ name = "image"
dtype = SCHEMA_DATA_STRING
- desc = "the uuid of an image previously built"
- name = "provider"
+ desc = "the uuid of an existing image"
+ name = "build"
dtype = SCHEMA_DATA_STRING
- desc = "the name of the cloud provider, often a region"
- name = credentials"
+ desc = "the uuid of an existing build, or the empty string"
+ name = "providers"
+ dtype = SCHEMA_DATA_LIST
+ desc = "the name of the cloud providers, often target cloud
regions"
+ name = "credentials"
dtype = SCHEMA_DATA_STRING
desc = "an xml string representation of the credentials"
return values:
- name = "build_adaptor"
- dtype = SCHEMA_DATA_MAP
- desc = "the QMF address of the build_adaptor instantiated"
+ name = "build_adaptors"
+ dtype = SCHEMA_DATA_LIST
+ desc = "the QMF addresses of the build_adaptors instantiated"
* `instance_states(class_name)`
@@ -116,6 +125,16 @@ The console can poll the build_adaptor for build status or receive
events listed
package = "com.redhat.imagefactory"
#### Properties: ####
+* Image
+
+ name = "image"
+ dtype = SCHEMA_DATA_STRING
+ desc = "the uuid of the image being built or pushed"
+* Build
+
+ name = "build"
+ dtype = SCHEMA_DATA_STRING
+ desc = "the uuid of the image build being built or pushed"
* Status
name = "status"
@@ -126,11 +145,11 @@ The console can poll the build_adaptor for build status or receive
events listed
name = "percent_complete"
dtype = SCHEMA_DATA_INT
desc = "the estimated percentage through an operation"
-* Image ID
+* Target/Provider Image ID
name = "image_id"
dtype = SCHEMA_DATA_STRING
- desc = "string representation of the assigned uuid"
+ desc = "the uuid of the newly created target or provider image"
#### Methods: ####
* `abort()`
diff --git a/imagefactory/BuildDispatcher.py b/imagefactory/BuildDispatcher.py
index 564b220..da70aa1 100644
--- a/imagefactory/BuildDispatcher.py
+++ b/imagefactory/BuildDispatcher.py
@@ -104,7 +104,7 @@ class BuildDispatcher(object):
builder_thread.start()
@classmethod
- def build_image_for_targets(cls, image_id, build_id, template, targets):
+ def build_image_for_targets(cls, image_id, build_id, template, targets, *args,
**kwargs):
warehouse = cls._get_warehouse()
template = Template(template)
@@ -116,14 +116,14 @@ class BuildDispatcher(object):
dispatchers = []
for target in targets:
- dispatcher = BuildDispatcher(template, target, image_id, build_id)
+ dispatcher = cls(template, target, image_id, build_id, *args, **kwargs)
dispatcher.build_image(watcher)
dispatchers.append(dispatcher)
return dispatchers
@classmethod
- def push_image_to_providers(cls, image_id, build_id, providers, credentials):
+ def push_image_to_providers(cls, image_id, build_id, providers, credentials, *args,
**kwargs):
warehouse = cls._get_warehouse()
if not build_id:
@@ -139,7 +139,7 @@ class BuildDispatcher(object):
template = cls._template_for_target_image_id(warehouse, target_image_id)
- dispatcher = BuildDispatcher(template, target, image_id, build_id)
+ dispatcher = cls(template, target, image_id, build_id, *args, **kwargs)
dispatcher.push_image(target_image_id, provider, credentials, watcher)
dispatchers.append(dispatcher)
diff --git a/imagefactory/qmfagent/BuildAdaptor.py
b/imagefactory/qmfagent/BuildAdaptor.py
index 119dedf..78b0570 100644
--- a/imagefactory/qmfagent/BuildAdaptor.py
+++ b/imagefactory/qmfagent/BuildAdaptor.py
@@ -28,9 +28,11 @@ from imagefactory.BuildDispatcher import BuildDispatcher
class BuildAdaptor(BuildDispatcher):
# 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"))
+ qmf_schema.addProperty(SchemaProperty("build", SCHEMA_DATA_STRING,
desc="UUD of the build"))
qmf_schema.addProperty(SchemaProperty("status", SCHEMA_DATA_STRING,
desc="string representing the status (see instance_states() on ImageFactory)"))
qmf_schema.addProperty(SchemaProperty("percent_complete", SCHEMA_DATA_INT,
desc="the estimated percentage through an operation"))
- qmf_schema.addProperty(SchemaProperty("image_id", SCHEMA_DATA_STRING,
desc="string representation of the assigned uuid"))
+ qmf_schema.addProperty(SchemaProperty("image_id", SCHEMA_DATA_STRING,
desc="UUID of the newly created target or provider image"))
qmf_schema.addMethod(SchemaMethod("abort", desc = "If possible, abort
running build."))
#QMF schema for status change event
@@ -62,14 +64,16 @@ class BuildAdaptor(BuildDispatcher):
"COMPLETED":()
}
+ image_id = props.subprop("qmf_object", "image", "The UUID
of the image.")
+ build_id = props.subprop("qmf_object", "build", "The UUID
of the build.")
status = props.subprop("qmf_object", "status", "The status
property.")
percent_complete = props.subprop("qmf_object",
"percent_complete", "The percent_complete property.")
new_image_id = props.subprop("qmf_object", "image_id", "The
image property.")
qmf_object = props.prop("_qmf_object", "The qmf_object
property.")
- def __init__(self, template, target, agent=None):
+ def __init__(self, template, target, image_id='', build_id='',
agent=None):
self.qmf_object = Data(BuildAdaptor.qmf_schema)
- super(BuildAdaptor, self).__init__(template, target)
+ super(BuildAdaptor, self).__init__(template, target, image_id, build_id)
self.agent = agent
# Builder delegate methods
diff --git a/imagefactory/qmfagent/ImageFactory.py
b/imagefactory/qmfagent/ImageFactory.py
index ba1c11b..a0c2bcc 100644
--- a/imagefactory/qmfagent/ImageFactory.py
+++ b/imagefactory/qmfagent/ImageFactory.py
@@ -19,8 +19,8 @@
import sys
import cqpid
from qmf2 import *
-import BuildAdaptor
import httplib2
+from BuildAdaptor import BuildAdaptor
from imagefactory import props
from imagefactory.ApplicationConfiguration import ApplicationConfiguration
from imagefactory.ImageWarehouse import ImageWarehouse
@@ -34,12 +34,20 @@ class ImageFactory(object):
# QMF schema for ImageFactory
qmf_schema = Schema(SCHEMA_TYPE_DATA, "com.redhat.imagefactory",
"ImageFactory")
- # method for building images
- _build_image_method = SchemaMethod("image", desc="Build a new
image")
+ # method for building an image
+ _build_image_method = SchemaMethod("image", desc="Build a new image
for a given target cloud")
_build_image_method.addArgument(SchemaProperty("template",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="string of xml, uuid, or url"))
_build_image_method.addArgument(SchemaProperty("target",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="name of the cloud to target"))
_build_image_method.addArgument(SchemaProperty("build_adaptor",
SCHEMA_DATA_MAP, direction=DIR_OUT, desc="the QMF address of the build_adaptor
instantiated"))
qmf_schema.addMethod(_build_image_method)
+ # method for building images
+ _build_images_method = SchemaMethod("build_image", desc="Build an
image for the given target clouds")
+ _build_images_method.addArgument(SchemaProperty("image",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="the UUID of an image previously
built"))
+ _build_images_method.addArgument(SchemaProperty("build",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="the UUID of a previous build of the
image"))
+ _build_images_method.addArgument(SchemaProperty("template",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="string of xml, uuid, or url"))
+ _build_images_method.addArgument(SchemaProperty("targets",
SCHEMA_DATA_LIST, direction=DIR_IN, desc="names of the clouds to target"))
+ _build_images_method.addArgument(SchemaProperty("build_adaptors",
SCHEMA_DATA_LIST, direction=DIR_OUT, desc="the QMF addresses of the build_adaptors
instantiated"))
+ qmf_schema.addMethod(_build_images_method)
# method for creating a provider_image from an image
_push_image_method = SchemaMethod("provider_image", desc="Push an
image to a provider.")
_push_image_method.addArgument(SchemaProperty("image_id",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="the uuid of an image previously
built"))
@@ -47,6 +55,14 @@ class ImageFactory(object):
_push_image_method.addArgument(SchemaProperty("credentials",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="an xml string representation of the
credentials"))
_push_image_method.addArgument(SchemaProperty("build_adaptor",
SCHEMA_DATA_MAP, direction=DIR_OUT, desc="the QMF address of the build_adaptor
instantiated"))
qmf_schema.addMethod(_push_image_method)
+ # method for pushing an image to multiple providers
+ _push_images_method = SchemaMethod("push_image", desc="Push an image
to multiple providers.")
+ _push_images_method.addArgument(SchemaProperty("image",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="the UUID of an image previously
built"))
+ _push_images_method.addArgument(SchemaProperty("build",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="the UUID of a previous build of the
image"))
+ _push_images_method.addArgument(SchemaProperty("providers",
SCHEMA_DATA_LIST, direction=DIR_IN, desc="the names of the cloud providers, often
regions"))
+ _push_images_method.addArgument(SchemaProperty("credentials",
SCHEMA_DATA_STRING, direction=DIR_IN, desc="an xml string representation of the
credentials"))
+ _push_images_method.addArgument(SchemaProperty("build_adaptors",
SCHEMA_DATA_LIST, direction=DIR_OUT, desc="the QMF addresses of the build_adaptors
instantiated"))
+ qmf_schema.addMethod(_push_images_method)
# this method will return a representation of the object's finite state machine
_states_method = SchemaMethod("instance_states", desc = "Returns a
dictionary representing the finite state machine for instances.")
_states_method.addArgument(SchemaProperty("class_name", SCHEMA_DATA_STRING,
direction=DIR_IN, desc="the name of the class to query for instance states"))
@@ -79,7 +95,7 @@ class ImageFactory(object):
def image(self,template,target):
template_object = Template(template=template)
- build_adaptor = BuildAdaptor.BuildAdaptor(template_object,target,self.agent)
+ build_adaptor = BuildAdaptor(template_object,target,agent=self.agent)
build_adaptor.build_image()
return build_adaptor
@@ -91,12 +107,18 @@ class ImageFactory(object):
target = image_metadata["target"]
if (template_id and target):
- build_adaptor =
BuildAdaptor.BuildAdaptor(Template(uuid=template_id),target,self.agent)
+ build_adaptor =
BuildAdaptor(Template(uuid=template_id),target,agent=self.agent)
build_adaptor.push_image(target_image_id, provider, credentials)
return build_adaptor
else:
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)
+
+ def push_image(self, image, build, providers, credentials):
+ return BuildAdaptor.push_image_to_providers(image, build, providers,
credentials, self.agent)
+
def instance_states(self, class_name):
"""Returns a dictionary representing the finite state machine for
instances of the class specified."""
module_name = "imagefactory.qmfagent.%s" % (class_name, )
diff --git a/imagefactory/qmfagent/ImageFactoryAgent.py
b/imagefactory/qmfagent/ImageFactoryAgent.py
index e8f1910..73cd1db 100644
--- a/imagefactory/qmfagent/ImageFactoryAgent.py
+++ b/imagefactory/qmfagent/ImageFactoryAgent.py
@@ -79,11 +79,7 @@ class ImageFactoryAgent(AgentHandler):
result = getattr(target_obj, methodName)(**args)
- if ((addr == self.image_factory_addr) and (methodName in ("image",
"provider_image"))):
- build_adaptor_instance_name = "build_adaptor:%s:%s" %
(methodName, result.new_image_id)
- qmf_object_addr = self.session.addData(result.qmf_object,
build_adaptor_instance_name, persistent=True)
- self.managedObjects[repr(qmf_object_addr)] = result
- handle.addReturnArgument("build_adaptor",
qmf_object_addr.asMap())
+ if (self._handle_image_factory_result(handle, methodName, addr, result)):
self.session.methodSuccess(handle)
elif(result and isinstance(result, dict)):
for key in result:
@@ -101,6 +97,25 @@ class ImageFactoryAgent(AgentHandler):
self.log.exception(str(e))
self.session.raiseException(handle, str(e))
+ def _handle_image_factory_result(self, handle, method_name, addr, result):
+ if not addr == self.image_factory_addr:
+ return False
+
+ if method_name in ("image", "provider_image"):
+ handle.addReturnArgument("build_adaptor",
self._add_adaptor(result, method_name))
+ elif method_name in ("build_image", "push_image"):
+ handle.addReturnArgument("build_adaptors", map(lambda ba:
self._add_adaptor(ba, method_name), result))
+ else:
+ return False
+
+ return True
+
+ def _add_adaptor(self, build_adaptor, prefix):
+ build_adaptor_instance_name = "build_adaptor:%s:%s" % (prefix,
build_adaptor.new_image_id)
+ qmf_object_addr = self.session.addData(build_adaptor.qmf_object,
build_adaptor_instance_name, persistent=True)
+ self.managedObjects[repr(qmf_object_addr)] = build_adaptor
+ return qmf_object_addr.asMap()
+
def shutdown(self):
"""
Clean up the session and connection. Cancel the running thread.
diff --git a/tests/qmfagent/testBuildAdaptor.py b/tests/qmfagent/testBuildAdaptor.py
index 49ebb7b..8484ff4 100644
--- a/tests/qmfagent/testBuildAdaptor.py
+++ b/tests/qmfagent/testBuildAdaptor.py
@@ -28,7 +28,7 @@ class TestBuildAdaptor(unittest.TestCase):
pass
def testQMFSchemaDefinition(self):
- expected_schema_properties = ("status", "percent_complete",
"image_id")
+ expected_schema_properties = ("image", "build",
"status", "percent_complete", "image_id")
expected_schema_methods = dict(abort=(), instance_states=("class_name",
"states"))
for schema_property in BuildAdaptor.qmf_schema.getProperties():
self.assertIn(schema_property.getName(), expected_schema_properties)
diff --git a/tests/qmfagent/testImageFactory.py b/tests/qmfagent/testImageFactory.py
index 38c7efc..1f9b121 100644
--- a/tests/qmfagent/testImageFactory.py
+++ b/tests/qmfagent/testImageFactory.py
@@ -31,8 +31,10 @@ class TestImageFactory(unittest.TestCase):
def testQMFSchemaDefinition(self):
expected_schema_methods = dict(image=("template", "target",
"build_adaptor"),
- provider_image=("image_id",
"provider", "credentials", "build_adaptor"),
- instance_states=("class_name",
"states"))
+ build_image=("image",
"build", "template", "targets",
"build_adaptors"),
+ provider_image=("image_id",
"provider", "credentials", "build_adaptor"),
+ push_image=("image", "build",
"providers", "credentials", "build_adaptors"),
+ instance_states=("class_name",
"states"))
for schema_method in ImageFactory.qmf_schema.getMethods():
self.assertIn(schema_method.getName(), expected_schema_methods)
arguments = expected_schema_methods[schema_method.getName()]
diff --git a/tests/qmfagent/testImageFactoryAgent.py
b/tests/qmfagent/testImageFactoryAgent.py
index aa4f37d..f2ed3bc 100644
--- a/tests/qmfagent/testImageFactoryAgent.py
+++ b/tests/qmfagent/testImageFactoryAgent.py
@@ -126,8 +126,8 @@ class MockConsole(ConsoleHandler):
if(str(self.agent_addr).startswith(agent.getName())):
self.agent = agent
self.factory = agent.query("{class:ImageFactory,
package:'com.redhat.imagefactory'}")[0]
- self.build_adaptor_addr_success =
self.factory.image("<template></template>",
"mock")["build_adaptor"]
- self.build_adaptor_addr_fail =
self.factory.image("<template>FAIL</template>",
"mock")["build_adaptor"]
+ self.build_adaptor_addr_success = self.factory.build_image("",
"", "<template></template>",
["mock"])["build_adaptors"][0]
+ self.build_adaptor_addr_fail = self.factory.build_image("",
"", "<template>FAIL</template>",
["mock"])["build_adaptors"][0]
self.image_factory_states =
self.factory.instance_states("ImageFactory")
self.build_adaptor_states =
self.factory.instance_states("BuildAdaptor")
@@ -148,8 +148,8 @@ class MockConsole(ConsoleHandler):
self.build_status_events.append(dict(agent=agent,
data=data.getProperties(), timestamp=timestamp, severity=severity))
if(data.getProperties()["new_status"] ==
"COMPLETED"):
time.sleep(2)
- image_id =
self.build_adaptor_addr_success["_object_name"].split(":")[2]
- self.build_adaptor_addr_push = self.factory.provider_image(image_id,
"mock-provider1", "None")["build_adaptor"]
+ ba =
self.agent.query(Query(DataAddr(self.build_adaptor_addr_success)))[0]
+ self.build_adaptor_addr_push = self.factory.push_image(ba.image,
ba.build, ["mock-provider1"], "None")["build_adaptors"][0]
elif(data.getProperties()["addr"] ==
self.build_adaptor_addr_push):
self.push_status_events.append(dict(agent=agent,
data=data.getProperties(), timestamp=timestamp, severity=severity))
elif(data.getProperties()["event"] == "FAILURE"):
--
1.7.4.4