CORD-1138 create corebuilder unit test framework; add corebuilder exceptions
Change-Id: I405e1cbc7652f9f70f2a7105ea43526e8604a30d
diff --git a/xos/tools/corebuilder/README.md b/xos/tools/corebuilder/README.md
new file mode 100644
index 0000000..0813fcc
--- /dev/null
+++ b/xos/tools/corebuilder/README.md
@@ -0,0 +1,28 @@
+# Corebuilder #
+
+Corebuilder is a build tool that is used to aid in generating the `xosproject/xos-ui` container image from the `xosproject/xos` container image. It does this by creating a `BUILD` tree that can be used to layer models and other files on top of the code base that is present in `xosproject/xos`. It's intended that corebuilder is run once to generate the BUILD tree, and then docker is run to generate the `xosproject/xos-ui` container image.
+
+TODO: Say something about how Corebuilder integrates xproto here.
+
+## Running Corebuilder ##
+
+Corebuilder is usually containerized and run from inside its container. See `Dockerfile.corebuilder` in `containers/xos` for reference on how the Corebuilder container is built. Running the corebuilder container usually requires a few docker volume mounts be used. For example, to run from the CORD dev node:
+
+ docker run -it --rm -v /cord:/opt/cord -v /cord//orchestration/xos/containers/xos/BUILD:/opt/xos_corebuilder/BUILD /bin/bash docker-registry:5000/xosproject/xos-corebuilder:candidate <list of onboarding recipe pathnames>
+
+In a typical CORD build, Corebuilder will be run automatically by the `xos-core-build` role of `platform-install`.
+
+## Running Unit Tests ##
+
+The corebuilder container image includes the dependencies necessary to run the tool. If this container image is constructed, then the unit tests may be run as follows:
+
+ docker run -it --rm --entrypoint python docker-registry:5000/xosproject/xos-corebuilder:candidate ./corebuilder_test.py
+
+If the container image is not available, then the local environment can be setup to run the unit test directly.
+
+1. Install the necessary python pip dependencies. See `containers/xos/pip_requirements.txt` for reference. At minimum, `tosca-parser==0.7.0` should be installed.
+2. Create a directory, `custom_types` in the directory where `corebuilder_test.py` is to be run from.
+3. Copy the contents of `xos/tosca/custom_types` to the `custom_types` directory created in step 2.
+4. Execute `corebuilder_test.py`
+
+
diff --git a/xos/tools/corebuilder/corebuilder.py b/xos/tools/corebuilder/corebuilder.py
index c2a584a..6e00962 100644
--- a/xos/tools/corebuilder/corebuilder.py
+++ b/xos/tools/corebuilder/corebuilder.py
@@ -39,6 +39,7 @@
/opt/cord/orchestration/xos_libraries/ng-xos-lib/ng-xos-lib-onboard.yaml
"""
+import argparse
import os
import pdb
import shutil
@@ -55,6 +56,24 @@
if not os.path.exists(pathname):
os.makedirs(pathname)
+class CoreBuilderException(Exception):
+ pass
+
+class CoreBuilderMissingRecipeException(CoreBuilderException):
+ pass
+
+class CoreBuilderMalformedValueException(CoreBuilderException):
+ pass
+
+class CoreBuilderMalformedUrlException(CoreBuilderException):
+ pass
+
+class CoreBuilderMissingResourceException(CoreBuilderException):
+ pass
+
+class CoreBuilderUnknownResourceException(CoreBuilderException):
+ pass
+
class XOSCoreBuilder(object):
def __init__(self, recipe_list, parent_dir=None):
# TOSCA will look for imports using a relative path from where the
@@ -74,6 +93,8 @@
self.app_names = []
for recipe in recipe_list:
+ if not os.path.exists(recipe):
+ raise CoreBuilderMissingRecipeException("Onboarding Recipe %s does not exist" % recipe)
tosca_yaml = open(recipe).read()
self.execute_recipe(tosca_yaml)
@@ -149,7 +170,7 @@
# Library works just like ServiceController
self.execute_servicecontroller(nodetemplate)
else:
- raise Exception("Nodetemplate %s's type %s is not a known resource" % (nodetemplate.name, nodetemplate.type))
+ raise CoreBuilderUnknownResourceException("Nodetemplate %s's type %s is not a known resource" % (nodetemplate.name, nodetemplate.type))
def execute_servicecontroller(self, nodetemplate):
service_name = nodetemplate.name
@@ -193,9 +214,9 @@
if lhs=="subdirectory":
subdirectory=rhs
else:
- raise Exception("Malformed value %s" % value)
+ raise CoreBuilderMalformedValueException("Malformed value %s in resource %s of recipe %s" % (v, k, nodetemplate.name))
else:
- raise Exception("Malformed value %s" % value)
+ raise CoreBuilderMalformedValueException("Malformed value %s in resource %s of recipe %s" % (v, k, nodetemplate.name))
src_fn = parts[-1]
# apply base_url to src_fn
@@ -204,13 +225,13 @@
# ensure that it's a file:// url
if not src_fn.startswith("file://"):
- raise Exception("%s does not start with file://" % src_fn)
+ raise CoreBuilderMalformedUrlException("Resource `%s: %s` of recipe %s does not start with file://" % (k, src_fn, nodetemplate.name))
src_fn = src_fn[7:]
src_fn = self.fixup_path(src_fn)
if not os.path.exists(src_fn):
- raise Exception("%s does not exist" % src_fn)
+ raise CoreBuilderMissingResourceException("Resource '%s: %s' of recipe %s does not exist" % (k, src_fn, nodetemplate.name))
dest_dir = self.get_dest_dir(k, service_name)
dest_fn = os.path.join(dest_dir, subdirectory, os.path.basename(src_fn))
@@ -266,12 +287,40 @@
makedirs_if_noexist(os.path.dirname(app_list_fn))
file(app_list_fn, "w").write("\n".join(["services.%s" % x for x in self.app_names])+"\n")
-def main():
- if len(sys.argv)<=1:
- print >> sys.stderr, "Syntax: corebuilder.py [recipe1, recipe2, ...]"
+def parse_args():
+ parser = argparse.ArgumentParser()
+
+ _help = 'enable verbose logging'
+ parser.add_argument('-v', '--verbose',
+ dest='verbose',
+ action='store_true',
+ default=False,
+ help=_help)
+
+ _help = 'list of onboarding recipe filenames'
+ parser.add_argument('recipe_names',
+ metavar="RECIPE",
+ nargs='+',
+ help=_help)
+
+ args = parser.parse_args()
+
+ return args
- builder = XOSCoreBuilder(sys.argv[1:])
- builder.build()
+def main():
+ global options
+
+ options = parse_args()
+
+ try:
+ builder = XOSCoreBuilder(options.recipe_names)
+ builder.build()
+ except CoreBuilderException, e:
+ if options.verbose:
+ traceback.print_exc()
+ else:
+ print >> sys.stderr, "Error:", str(e)
+ sys.exit(-1)
if __name__ == "__main__":
main()
diff --git a/xos/tools/corebuilder/corebuilder_test.py b/xos/tools/corebuilder/corebuilder_test.py
new file mode 100644
index 0000000..6bbf03b
--- /dev/null
+++ b/xos/tools/corebuilder/corebuilder_test.py
@@ -0,0 +1,157 @@
+import shutil
+import unittest
+from corebuilder import *
+
+class TestCoreBuilder(unittest.TestCase):
+ def setUp(self):
+ if not os.path.exists("/tmp/fake_library"):
+ os.mkdir("/tmp/fake_library")
+ file("/tmp/fake_library/fake.js","w").write("stuff")
+
+ def tearDown(self):
+ if os.path.exists("/tmp/fake_library"):
+ shutil.rmtree("/tmp/fake_library")
+ if os.path.exists("/opt/xos_corebuilder/BUILD/opt"):
+ shutil.rmtree("/opt/xos_corebuilder/BUILD/opt")
+ if os.path.exists("/tmp/recipe"):
+ os.remove("/tmp/recipe")
+
+ #--------------------------------------------------------------------------
+ # test_XOSCoreBuilder_init
+ #--------------------------------------------------------------------------
+
+ def test_XOSCoreBuilder_init(self):
+ recipe = \
+"""tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard fake library
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ library#fake:
+ type: tosca.nodes.Library
+ properties:
+ base_url: file:///tmp/fake_library/
+ vendor_js: fake.js
+"""
+ file("/tmp/recipe", "w").write(recipe)
+
+ CoreBuilderMissingResourceException(XOSCoreBuilder, ["/tmp/recipe"])
+
+ #--------------------------------------------------------------------------
+ # test_bad_recipe_name
+ #--------------------------------------------------------------------------
+
+ def test_bad_recipe_name(self):
+ self.assertRaises(CoreBuilderMissingRecipeException,
+ XOSCoreBuilder,
+ ["/tmp/does_not_exit"])
+
+ #--------------------------------------------------------------------------
+ # test_missing_resource
+ #--------------------------------------------------------------------------
+
+ def test_missing_resource(self):
+ recipe = \
+"""tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard fake library
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ library#fake:
+ type: tosca.nodes.Library
+ properties:
+ base_url: file:///tmp/fake_library/
+ vendor_js: missing_fake.js
+"""
+ file("/tmp/recipe", "w").write(recipe)
+ self.assertRaises(CoreBuilderMissingResourceException,
+ XOSCoreBuilder,
+ ["/tmp/recipe"])
+
+ #--------------------------------------------------------------------------
+ # test_malformed_url
+ #--------------------------------------------------------------------------
+
+ def test_malformed_url(self):
+ recipe = \
+"""tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard fake library
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ library#fake:
+ type: tosca.nodes.Library
+ properties:
+ base_url: http:///tmp/fake_library/
+ vendor_js: fake.js
+"""
+ file("/tmp/recipe", "w").write(recipe)
+
self.assertRaises(CoreBuilderMalformedUrlException,
+ XOSCoreBuilder,
+ ["/tmp/recipe"])
+
+ #--------------------------------------------------------------------------
+ # test_malformed_value
+ #--------------------------------------------------------------------------
+
+ def test_malformed_value(self):
+ recipe = \
+"""tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard fake library
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ library#fake:
+ type: tosca.nodes.Library
+ properties:
+ base_url: file:///tmp/fake_library/
+ vendor_js: badvalue=bad fake.js
+"""
+ file("/tmp/recipe", "w").write(recipe)
+
self.assertRaises(CoreBuilderMalformedValueException,
+ XOSCoreBuilder,
+ ["/tmp/recipe"])
+
+ #--------------------------------------------------------------------------
+ # test_unknown_resource
+ #--------------------------------------------------------------------------
+
+ def test_unknown_resource(self):
+ recipe = \
+"""tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard fake library
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ library#fake:
+ type: tosca.nodes.Slice
+"""
+ file("/tmp/recipe", "w").write(recipe)
+
self.assertRaises(CoreBuilderUnknownResourceException,
+ XOSCoreBuilder,
+ ["/tmp/recipe"])
+
+if __name__ == '__main__':
+ unittest.main()
+
+