Support packages in dsl and engine

* Added package property to MuranoClass
   * Added new class loader: PackageClassLoader
   * Support loading packages from API and file-system
   * Extended ApplicationPackage to support getting resources from packages
   * Rewritten ResourceLoader to support loading resources from packages

Co-Authored-By: Serg Melikyan <smelikyan@mirantis.com>
Change-Id: I47e70f960104f78433c285411328f315638186da
This commit is contained in:
Ekaterina Fedorova
2014-04-04 16:58:06 +04:00
parent 246a35b6f9
commit ad6b8ece18
14 changed files with 439 additions and 53 deletions

View File

@@ -11,6 +11,9 @@ bind_host = 0.0.0.0
# Port the bind the server to # Port the bind the server to
bind_port = 8082 bind_port = 8082
# Directory to store application package cache
# packages_cache =
# Set up logging. Make sure the user has permissions to write to this file! To use syslog just set use_syslog parameter value to 'True'. # Set up logging. Make sure the user has permissions to write to this file! To use syslog just set use_syslog parameter value to 'True'.
log_file = /tmp/murano-api.log log_file = /tmp/murano-api.log
@@ -142,3 +145,14 @@ auth_url = http://localhost:5000/v2.0
#key_file = #key_file =
# If set then the server's certificate will not be verified # If set then the server's certificate will not be verified
insecure = False insecure = False
[murano_opts]
url = http:/localhost:8082
# Optional CA cert file to use in SSL connections
#cacert =
# Optional PEM-formatted certificate chain file
#cert_file =
# Optional PEM-formatted file that contains the private key
#key_file =
# If set then the server's certificate will not be verified
insecure = False

View File

@@ -21,6 +21,7 @@ import logging.config
import logging.handlers import logging.handlers
import os import os
import sys import sys
import tempfile
from oslo.config import cfg from oslo.config import cfg
from paste import deploy from paste import deploy
@@ -76,6 +77,16 @@ keystone_opts = [
cfg.StrOpt('key_file') cfg.StrOpt('key_file')
] ]
murano_opts = [
cfg.StrOpt('url', help=_('Optional murano url in format '
'like http://0.0.0.0:8082')),
cfg.BoolOpt('insecure', default=False),
cfg.StrOpt('cacert'),
cfg.StrOpt('cert_file'),
cfg.StrOpt('key_file'),
cfg.StrOpt('endpoint_type', default='publicURL')
]
stats_opt = [ stats_opt = [
cfg.IntOpt('period', default=5, cfg.IntOpt('period', default=5,
help=_('Statistics collection interval in minutes.' help=_('Statistics collection interval in minutes.'
@@ -84,6 +95,9 @@ stats_opt = [
metadata_dir = cfg.StrOpt('metadata-dir', default='./meta') metadata_dir = cfg.StrOpt('metadata-dir', default='./meta')
temp_pkg_cache = os.path.join(tempfile.gettempdir(), 'murano-packages-cache')
packages_cache = cfg.StrOpt('packages-cache', default=temp_pkg_cache)
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(paste_deploy_opts, group='paste_deploy') CONF.register_opts(paste_deploy_opts, group='paste_deploy')
CONF.register_cli_opts(bind_opts) CONF.register_cli_opts(bind_opts)
@@ -92,9 +106,11 @@ CONF.register_opts(rabbit_opts, group='rabbitmq')
CONF.register_opts(heat_opts, group='heat') CONF.register_opts(heat_opts, group='heat')
CONF.register_opts(neutron_opts, group='neutron') CONF.register_opts(neutron_opts, group='neutron')
CONF.register_opts(keystone_opts, group='keystone') CONF.register_opts(keystone_opts, group='keystone')
CONF.register_opts(murano_opts, group='murano')
CONF.register_opt(cfg.StrOpt('file_server')) CONF.register_opt(cfg.StrOpt('file_server'))
CONF.register_cli_opt(cfg.StrOpt('murano_metadata_url')) CONF.register_cli_opt(cfg.StrOpt('murano_metadata_url'))
CONF.register_cli_opt(metadata_dir) CONF.register_cli_opt(metadata_dir)
CONF.register_cli_opt(packages_cache)
CONF.register_opts(stats_opt, group='stats') CONF.register_opts(stats_opt, group='stats')
CONF.import_opt('connection', CONF.import_opt('connection',

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2013 Mirantis Inc. # Copyright (c) 2014 Mirantis Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -25,31 +25,33 @@ from muranoapi.common import rpc
from muranoapi.dsl import executor from muranoapi.dsl import executor
from muranoapi.dsl import results_serializer from muranoapi.dsl import results_serializer
from muranoapi.engine import environment from muranoapi.engine import environment
from muranoapi.engine import simple_cloader from muranoapi.engine import package_class_loader
from muranoapi.engine import package_loader
import muranoapi.engine.system.system_objects as system_objects import muranoapi.engine.system.system_objects as system_objects
from muranoapi.openstack.common.gettextutils import _ # noqa from muranoapi.openstack.common.gettextutils import _ # noqa
from muranoapi.openstack.common import log as logging from muranoapi.openstack.common import log as logging
RPC_SERVICE = None RPC_SERVICE = None
log = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class TaskProcessingEndpoint(object): class TaskProcessingEndpoint(object):
@staticmethod @staticmethod
def handle_task(context, task): def handle_task(context, task):
s_task = token_sanitizer.TokenSanitizer().sanitize(task) s_task = token_sanitizer.TokenSanitizer().sanitize(task)
log.info(_('Starting processing task: {0}').format( LOG.info(_('Starting processing task: {task_desc}').format(
anyjson.dumps(s_task))) task_desc=anyjson.dumps(s_task)))
env = environment.Environment() env = environment.Environment()
env.token = task['token'] env.token = task['token']
env.tenant_id = task['tenant_id'] env.tenant_id = task['tenant_id']
cl = simple_cloader.SimpleClassLoader(config.CONF.metadata_dir) with package_loader.ApiPackageLoader(task['token']) as pkg_loader:
system_objects.register(cl, config.CONF.metadata_dir) class_loader = package_class_loader.PackageClassLoader(pkg_loader)
system_objects.register(class_loader, pkg_loader)
exc = executor.MuranoDslExecutor(cl, env) exc = executor.MuranoDslExecutor(class_loader, env)
obj = exc.load(task['model']) obj = exc.load(task['model'])
if obj is not None: if obj is not None:

View File

@@ -30,18 +30,31 @@ import muranoapi.dsl.typespec as typespec
class MuranoClassLoader(object): class MuranoClassLoader(object):
def __init__(self): def __init__(self):
self._loaded_types = {} self._loaded_types = {}
self._packages_cache = {}
principal_objects.register(self) principal_objects.register(self)
def _get_package(self, class_name):
package_name = self.find_package_name(class_name)
if package_name is None:
raise exceptions.NoPackageForClassFound(class_name)
if package_name not in self._packages_cache:
package = self.load_package(package_name)
self._packages_cache[package_name] = package
return self._packages_cache[package_name]
def get_class(self, name, create_missing=False): def get_class(self, name, create_missing=False):
if name in self._loaded_types: if name in self._loaded_types:
return self._loaded_types[name] return self._loaded_types[name]
try:
data = self.load_definition(name) data = self.load_definition(name)
if data is None: package = self._get_package(name)
except (exceptions.NoPackageForClassFound, exceptions.NoClassFound):
if create_missing: if create_missing:
data = {'Name': name} data = {'Name': name}
package = None
else: else:
raise exceptions.NoClassFound(name) raise
namespaces = data.get('Namespaces', {}) namespaces = data.get('Namespaces', {})
ns_resolver = namespace_resolver.NamespaceResolver(namespaces) ns_resolver = namespace_resolver.NamespaceResolver(namespaces)
@@ -55,7 +68,7 @@ class MuranoClassLoader(object):
class_parents[i] = self.get_class(full_name) class_parents[i] = self.get_class(full_name)
type_obj = murano_class.MuranoClass(self, ns_resolver, name, type_obj = murano_class.MuranoClass(self, ns_resolver, name,
class_parents) package, class_parents)
properties = data.get('Properties', {}) properties = data.get('Properties', {})
for property_name, property_spec in properties.iteritems(): for property_name, property_spec in properties.iteritems():
@@ -71,6 +84,12 @@ class MuranoClassLoader(object):
def load_definition(self, name): def load_definition(self, name):
raise NotImplementedError() raise NotImplementedError()
def find_package_name(self, class_name):
raise NotImplementedError()
def load_package(self, class_name):
raise NotImplementedError()
def create_root_context(self): def create_root_context(self):
return yaql.create_context(True) return yaql.create_context(True)

View File

@@ -36,6 +36,17 @@ class NoClassFound(Exception):
super(NoClassFound, self).__init__('Class %s is not found' % name) super(NoClassFound, self).__init__('Class %s is not found' % name)
class NoPackageFound(Exception):
def __init__(self, name):
super(NoPackageFound, self).__init__('Package %s is not found' % name)
class NoPackageForClassFound(Exception):
def __init__(self, name):
super(NoPackageForClassFound, self).__init__('Package for class %s '
'is not found' % name)
class AmbiguousMethodName(Exception): class AmbiguousMethodName(Exception):
def __init__(self, name): def __init__(self, name):
super(AmbiguousMethodName, self).__init__( super(AmbiguousMethodName, self).__init__(

View File

@@ -29,7 +29,9 @@ def classname(name):
class MuranoClass(object): class MuranoClass(object):
def __init__(self, class_loader, namespace_resolver, name, parents=None): def __init__(self, class_loader, namespace_resolver, name, package,
parents=None):
self._package = package
self._class_loader = class_loader self._class_loader = class_loader
self._methods = {} self._methods = {}
self._namespace_resolver = namespace_resolver self._namespace_resolver = namespace_resolver
@@ -51,6 +53,10 @@ class MuranoClass(object):
def name(self): def name(self):
return self._name return self._name
@property
def package(self):
return self._package
@property @property
def namespace_resolver(self): def namespace_resolver(self):
return self._namespace_resolver return self._namespace_resolver

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class MuranoPackage(object):
def __init__(self):
super(MuranoPackage, self).__init__()
self._name = None
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value

View File

@@ -0,0 +1,53 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo.config import cfg
from muranoapi.dsl import class_loader
from muranoapi.dsl import exceptions
from muranoapi.dsl import murano_package
from muranoapi.engine.system import yaql_functions
from muranoapi.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class PackageClassLoader(class_loader.MuranoClassLoader):
def __init__(self, package_loader):
self.package_loader = package_loader
self._packages_cache = {}
super(PackageClassLoader, self).__init__()
def load_definition(self, name):
try:
package = self.package_loader.get_package_by_class(name)
return package.get_class(name)
except Exception:
raise exceptions.NoClassFound(name)
def load_package(self, name):
package = murano_package.MuranoPackage()
package.name = name
return package
def find_package_name(self, class_name):
app_pkg = self.package_loader.get_package_by_class(class_name)
return None if app_pkg is None else app_pkg.full_name
def create_root_context(self):
context = super(PackageClassLoader, self).create_root_context()
yaql_functions.register(context)
return context

View File

@@ -0,0 +1,218 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import os
import shutil
import tempfile
import uuid
from keystoneclient.v2_0 import client as keystoneclient
from muranoclient.common import exceptions as muranoclient_exc
from muranoclient.v1 import client as muranoclient
import six
import yaml
from muranoapi.common import config
from muranoapi.dsl import exceptions
from muranoapi.dsl import yaql_expression
from muranoapi.openstack.common import log as logging
from muranoapi.packages import application_package as app_pkg
from muranoapi.packages import exceptions as pkg_exc
LOG = logging.getLogger(__name__)
class YaqlYamlLoader(yaml.Loader):
pass
def yaql_constructor(loader, node):
value = loader.construct_scalar(node)
return yaql_expression.YaqlExpression(value)
yaml.add_constructor(u'!yaql', yaql_constructor, YaqlYamlLoader)
yaml.add_implicit_resolver(u'!yaql', yaql_expression.YaqlExpression,
Loader=YaqlYamlLoader)
class PackageLoader(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def get_package(self, name):
pass
@abc.abstractmethod
def get_package_by_class(self, name):
pass
class ApiPackageLoader(PackageLoader):
def __init__(self, token_id):
self._cache_directory = self._get_cache_directory()
self._client = self._get_murano_client(token_id)
def get_package_by_class(self, name):
filter_opts = {'class_name': name, 'limit': 1}
try:
package_definition = self._get_definition(filter_opts)
return self._get_package_by_definition(package_definition)
except(LookupError, pkg_exc.PackageLoadError):
raise exceptions.NoPackageForClassFound(name)
def get_package(self, name):
filter_opts = {'fqn': name, 'limit': 1}
try:
package_definition = self._get_definition(filter_opts)
return self._get_package_by_definition(package_definition)
except(LookupError, pkg_exc.PackageLoadError):
raise exceptions.NoPackageFound(name)
@staticmethod
def _get_cache_directory():
directory = os.path.join(config.CONF.packages_cache, str(uuid.uuid4()))
directory = os.path.abspath(directory)
os.makedirs(directory)
LOG.debug('Cache for package loader is located at: '
'{0}'.format(directory))
return directory
@staticmethod
def _get_murano_client(token_id):
murano_settings = config.CONF.murano
endpoint_url = murano_settings.url
if endpoint_url is None:
keystone_settings = config.CONF.keystone
keystone_client = keystoneclient.Client(
endpoint=keystone_settings.auth_url,
cacert=keystone_settings.ca_file or None,
cert=keystone_settings.cert_file or None,
key=keystone_settings.key_file or None,
insecure=keystone_settings.insecure
)
endpoint_url = keystone_client.url_for(
service_type='murano',
endpoint_type=murano_settings.endpoint_type
)
return muranoclient.Client(
endpoint=endpoint_url,
key_file=murano_settings.key_file or None,
cacert=murano_settings.cacert or None,
cert_file=murano_settings.cert_file or None,
insecure=murano_settings.insecure,
token=token_id
)
def _get_definition(self, filter_opts):
try:
packages = self._client.packages.filter(**filter_opts)
if not packages:
LOG.debug('There are no packages matching filter '
'{0}'.format(filter_opts))
# TODO(smelikyan): This exception should be replaced with one
# defined in python-muranoclient
raise LookupError()
return packages[0]
except muranoclient_exc.HTTPException:
LOG.debug('Failed to get package definition from repository')
raise LookupError()
def _get_package_by_definition(self, package_def):
package_id = package_def.id
package_name = package_def.fully_qualified_name
package_directory = os.path.join(self._cache_directory, package_name)
if os.path.exists(package_directory):
try:
return app_pkg.load_from_dir(package_directory, preload=True,
loader=YaqlYamlLoader)
except pkg_exc.PackageLoadError:
LOG.exception('Unable to load package from cache. Clean-up...')
shutil.rmtree(package_directory, ignore_errors=True)
try:
package_data = self._client.packages.download(package_id)
except muranoclient_exc.HTTPException:
LOG.exception('Unable to download '
'package with id {0}'.format(package_id))
raise pkg_exc.PackageLoadError()
try:
with tempfile.NamedTemporaryFile(delete=False) as package_file:
package_file.write(package_data)
return app_pkg.load_from_file(
package_file.name,
target_dir=package_directory,
drop_dir=False,
loader=YaqlYamlLoader
)
except IOError:
LOG.exception('Unable to write package file')
raise pkg_exc.PackageLoadError()
finally:
try:
os.remove(package_file.name)
except OSError:
pass
def cleanup(self):
shutil.rmtree(self._cache_directory, ignore_errors=True)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
return False
class DirectoryPackageLoader(PackageLoader):
def __init__(self, base_path):
self._base_path = base_path
self._processed_entries = set()
self._packages_by_class = {}
self._packages_by_name = {}
self._build_index()
def get_package(self, name):
return self._packages_by_name.get(name)
def get_package_by_class(self, name):
return self._packages_by_class.get(name)
def _build_index(self):
for entry in os.listdir(self._base_path):
if not os.path.isdir(entry) or entry in self._processed_entries:
continue
try:
package = app_pkg.load_from_dir(entry, preload=True,
loader=YaqlYamlLoader)
except pkg_exc.PackageLoadError:
LOG.exception('Unable to load package from path: '
'{0}'.format(entry))
continue
for c in package.classes:
self._packages_by_class[c] = package
self._packages_by_name[package.full_name] = package
self._processed_entries.add(entry)

View File

@@ -14,21 +14,19 @@
# limitations under the License. # limitations under the License.
import json as jsonlib import json as jsonlib
import os.path
import yaml as yamllib import yaml as yamllib
import muranoapi.dsl.murano_object as murano_object import muranoapi.dsl.murano_object as murano_object
class ResourceManager(murano_object.MuranoObject): class ResourceManager(murano_object.MuranoObject):
def initialize(self, base_path, _context, _class): def initialize(self, package_loader, _context, _class):
if _class is None: if _class is None:
_class = _context.get_data('$') _class = _context.get_data('$')
class_name = _class.type.name self._package = package_loader.get_package(_class.type.package.name)
self._base_path = os.path.join(base_path, class_name, 'resources')
def string(self, name): def string(self, name):
path = os.path.join(self._base_path, name) path = self._package.get_resource(name)
with open(path) as file: with open(path) as file:
return file.read() return file.read()

View File

@@ -34,14 +34,14 @@ def _auto_register(class_loader):
class_loader.import_class(class_def) class_loader.import_class(class_def)
def register(class_loader, path): def register(class_loader, package_loader):
_auto_register(class_loader) _auto_register(class_loader)
@murano_class.classname('io.murano.system.Resources') @murano_class.classname('io.murano.system.Resources')
class ResourceManagerWrapper(resource_manager.ResourceManager): class ResourceManagerWrapper(resource_manager.ResourceManager):
def initialize(self, _context, _class=None): def initialize(self, _context, _class=None):
super(ResourceManagerWrapper, self).initialize( super(ResourceManagerWrapper, self).initialize(
path, _context, _class) package_loader, _context, _class)
class_loader.import_class(agent.Agent) class_loader.import_class(agent.Agent)
class_loader.import_class(agent_listener.AgentListener) class_loader.import_class(agent_listener.AgentListener)

View File

@@ -12,6 +12,7 @@
# implied. # implied.
# 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 imghdr import imghdr
import os import os
import shutil import shutil
@@ -24,6 +25,17 @@ import muranoapi.packages.exceptions as e
import muranoapi.packages.versions.v1 import muranoapi.packages.versions.v1
class DummyLoader(yaml.Loader):
pass
def yaql_constructor(loader, node):
value = loader.construct_scalar(node)
return value
yaml.add_constructor(u'!yaql', yaql_constructor, DummyLoader)
class PackageTypes(object): class PackageTypes(object):
Library = 'Library' Library = 'Library'
Application = 'Application' Application = 'Application'
@@ -31,7 +43,8 @@ class PackageTypes(object):
class ApplicationPackage(object): class ApplicationPackage(object):
def __init__(self, source_directory, yaml_content): def __init__(self, source_directory, manifest, loader=DummyLoader):
self.yaml_loader = loader
self._source_directory = source_directory self._source_directory = source_directory
self._full_name = None self._full_name = None
self._package_type = None self._package_type = None
@@ -42,7 +55,7 @@ class ApplicationPackage(object):
self._classes = None self._classes = None
self._ui = None self._ui = None
self._logo = None self._logo = None
self._format = yaml_content.get('Format') self._format = manifest.get('Format')
self._ui_cache = None self._ui_cache = None
self._raw_ui_cache = None self._raw_ui_cache = None
self._logo_cache = None self._logo_cache = None
@@ -99,6 +112,9 @@ class ApplicationPackage(object):
self._load_class(name) self._load_class(name)
return self._classes_cache[name] return self._classes_cache[name]
def get_resource(self, name):
return os.path.join(self._source_directory, 'Resources', name)
def validate(self): def validate(self):
self._classes_cache.clear() self._classes_cache.clear()
for class_name in self._classes: for class_name in self._classes:
@@ -109,7 +125,7 @@ class ApplicationPackage(object):
# Private methods # Private methods
def _load_ui(self, load_yaml=False): def _load_ui(self, load_yaml=False):
if self._raw_ui_cache and load_yaml: if self._raw_ui_cache and load_yaml:
self._ui_cache = yaml.load(self._raw_ui_cache) self._ui_cache = yaml.load(self._raw_ui_cache, self.yaml_loader)
else: else:
ui_file = self._ui ui_file = self._ui
full_path = os.path.join(self._source_directory, 'UI', ui_file) full_path = os.path.join(self._source_directory, 'UI', ui_file)
@@ -121,7 +137,8 @@ class ApplicationPackage(object):
with open(full_path) as stream: with open(full_path) as stream:
self._raw_ui_cache = stream.read() self._raw_ui_cache = stream.read()
if load_yaml: if load_yaml:
self._ui_cache = yaml.load(self._raw_ui_cache) self._ui_cache = yaml.load(self._raw_ui_cache,
self.yaml_loader)
except Exception as ex: except Exception as ex:
trace = sys.exc_info()[2] trace = sys.exc_info()[2]
raise e.PackageUILoadError(str(ex)), None, trace raise e.PackageUILoadError(str(ex)), None, trace
@@ -154,7 +171,7 @@ class ApplicationPackage(object):
'definition not found') 'definition not found')
try: try:
with open(full_path) as stream: with open(full_path) as stream:
self._classes_cache[name] = yaml.load(stream) self._classes_cache[name] = yaml.load(stream, self.yaml_loader)
except Exception as ex: except Exception as ex:
trace = sys.exc_info()[2] trace = sys.exc_info()[2]
msg = 'Unable to load class definition due to "{0}"'.format( msg = 'Unable to load class definition due to "{0}"'.format(
@@ -162,7 +179,8 @@ class ApplicationPackage(object):
raise e.PackageClassLoadError(name, msg), None, trace raise e.PackageClassLoadError(name, msg), None, trace
def load_from_dir(source_directory, filename='manifest.yaml', preload=False): def load_from_dir(source_directory, filename='manifest.yaml', preload=False,
loader=DummyLoader):
formats = {'1.0': muranoapi.packages.versions.v1} formats = {'1.0': muranoapi.packages.versions.v1}
if not os.path.isdir(source_directory) or not os.path.exists( if not os.path.isdir(source_directory) or not os.path.exists(
@@ -174,7 +192,7 @@ def load_from_dir(source_directory, filename='manifest.yaml', preload=False):
try: try:
with open(full_path) as stream: with open(full_path) as stream:
content = yaml.load(stream) content = yaml.load(stream, DummyLoader)
except Exception as ex: except Exception as ex:
trace = sys.exc_info()[2] trace = sys.exc_info()[2]
raise e.PackageLoadError( raise e.PackageLoadError(
@@ -184,14 +202,15 @@ def load_from_dir(source_directory, filename='manifest.yaml', preload=False):
if not p_format or p_format not in formats: if not p_format or p_format not in formats:
raise e.PackageFormatError( raise e.PackageFormatError(
'Unknown or missing format version') 'Unknown or missing format version')
package = ApplicationPackage(source_directory, content) package = ApplicationPackage(source_directory, content, loader)
formats[p_format].load(package, content) formats[p_format].load(package, content)
if preload: if preload:
package.validate() package.validate()
return package return package
def load_from_file(archive_path, target_dir=None, drop_dir=False): def load_from_file(archive_path, target_dir=None, drop_dir=False,
loader=DummyLoader):
if not os.path.isfile(archive_path): if not os.path.isfile(archive_path):
raise e.PackageLoadError('Unable to find package file') raise e.PackageLoadError('Unable to find package file')
created = False created = False
@@ -211,7 +230,7 @@ def load_from_file(archive_path, target_dir=None, drop_dir=False):
"zip' archive") "zip' archive")
package = zipfile.ZipFile(archive_path) package = zipfile.ZipFile(archive_path)
package.extractall(path=target_dir) package.extractall(path=target_dir)
return load_from_dir(target_dir, preload=True) return load_from_dir(target_dir, preload=True, loader=loader)
finally: finally:
if drop_dir: if drop_dir:
if created: if created:

View File

@@ -114,13 +114,13 @@ class TestClassesManipulation(unittest.TestCase):
resolver = mock.Mock(resolve_name=lambda name: name) resolver = mock.Mock(resolve_name=lambda name: name)
def test_class_name(self): def test_class_name(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
self.assertEqual(ROOT_CLASS, cls.name) self.assertEqual(ROOT_CLASS, cls.name)
def test_class_namespace_resolver(self): def test_class_namespace_resolver(self):
resolver = ns_resolver.NamespaceResolver({}) resolver = ns_resolver.NamespaceResolver({})
cls = murano_class.MuranoClass(None, resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, resolver, ROOT_CLASS, None)
self.assertEqual(resolver, cls.namespace_resolver) self.assertEqual(resolver, cls.namespace_resolver)
@@ -131,21 +131,23 @@ class TestClassesManipulation(unittest.TestCase):
self.assertEqual([], root_class.parents) self.assertEqual([], root_class.parents)
def test_non_root_class_resolves_parents(self): def test_non_root_class_resolves_parents(self):
root_cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) root_cls = murano_class.MuranoClass(None, self.resolver,
ROOT_CLASS, None)
class_loader = mock.Mock(get_class=lambda name: root_cls) class_loader = mock.Mock(get_class=lambda name: root_cls)
desc_cl1 = murano_class.MuranoClass(class_loader, self.resolver, 'Obj') desc_cl1 = murano_class.MuranoClass(class_loader, self.resolver,
'Obj', None)
desc_cl2 = murano_class.MuranoClass( desc_cl2 = murano_class.MuranoClass(
class_loader, self.resolver, 'Obj', [root_cls]) class_loader, self.resolver, 'Obj', None, [root_cls])
self.assertEqual([root_cls], desc_cl1.parents) self.assertEqual([root_cls], desc_cl1.parents)
self.assertEqual([root_cls], desc_cl2.parents) self.assertEqual([root_cls], desc_cl2.parents)
def test_class_initial_properties(self): def test_class_initial_properties(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
self.assertEqual([], cls.properties) self.assertEqual([], cls.properties)
def test_fails_add_incompatible_property_to_class(self): def test_fails_add_incompatible_property_to_class(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
kwargs = {'name': 'sampleProperty', 'property_typespec': {}} kwargs = {'name': 'sampleProperty', 'property_typespec': {}}
self.assertRaises(TypeError, cls.add_property, **kwargs) self.assertRaises(TypeError, cls.add_property, **kwargs)
@@ -153,7 +155,7 @@ class TestClassesManipulation(unittest.TestCase):
@unittest.skip @unittest.skip
def test_add_property_to_class(self): def test_add_property_to_class(self):
prop = typespec.PropertySpec({'Default': 1}, self.resolver) prop = typespec.PropertySpec({'Default': 1}, self.resolver)
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
cls.add_property('firstPrime', prop) cls.add_property('firstPrime', prop)
class_properties = cls.properties class_properties = cls.properties
@@ -190,9 +192,9 @@ class TestClassesManipulation(unittest.TestCase):
self.assertEqual(void_prop, child.find_property('Void')) self.assertEqual(void_prop, child.find_property('Void'))
def test_class_is_compatible(self): def test_class_is_compatible(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
descendant_cls = murano_class.MuranoClass( descendant_cls = murano_class.MuranoClass(
None, self.resolver, 'DescendantCls', [cls]) None, self.resolver, 'DescendantCls', None, [cls])
obj = mock.Mock(spec=murano_object.MuranoObject) obj = mock.Mock(spec=murano_object.MuranoObject)
descendant_obj = mock.Mock(spec=murano_object.MuranoObject) descendant_obj = mock.Mock(spec=murano_object.MuranoObject)
obj.type = cls obj.type = cls
@@ -204,7 +206,7 @@ class TestClassesManipulation(unittest.TestCase):
self.assertFalse(descendant_cls.is_compatible(obj)) self.assertFalse(descendant_cls.is_compatible(obj))
def test_new_method_calls_initialize(self): def test_new_method_calls_initialize(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
cls.object_class = mock.Mock() cls.object_class = mock.Mock()
with mock.patch('inspect.getargspec') as spec_mock: with mock.patch('inspect.getargspec') as spec_mock:
@@ -214,7 +216,7 @@ class TestClassesManipulation(unittest.TestCase):
self.assertTrue(obj.initialize.called) self.assertTrue(obj.initialize.called)
def test_new_method_not_calls_initialize(self): def test_new_method_not_calls_initialize(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
cls.object_class = mock.Mock() cls.object_class = mock.Mock()
obj = cls.new(None, None, None) obj = cls.new(None, None, None)
@@ -241,9 +243,9 @@ class TestObjectsManipulation(unittest.TestCase):
pass pass
def test_object_parent_properties_initialization(self): def test_object_parent_properties_initialization(self):
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
cls = murano_class.MuranoClass(None, self.resolver, cls = murano_class.MuranoClass(None, self.resolver,
'SomeClass', [root]) 'SomeClass', None, [root])
root.new = mock.Mock() root.new = mock.Mock()
init_kwargs = {'theArg': 0} init_kwargs = {'theArg': 0}
obj = murano_object.MuranoObject(cls, None, None, None) obj = murano_object.MuranoObject(cls, None, None, None)
@@ -326,13 +328,13 @@ class TestObjectsManipulation(unittest.TestCase):
self.assertRaises(LookupError, lambda: obj.conflictProp) self.assertRaises(LookupError, lambda: obj.conflictProp)
def test_fails_setting_undeclared_property(self): def test_fails_setting_undeclared_property(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
obj = cls.new(None, None, None, {}) obj = cls.new(None, None, None, {})
self.assertRaises(AttributeError, obj.set_property, 'newOne', 10) self.assertRaises(AttributeError, obj.set_property, 'newOne', 10)
def test_set_undeclared_property_as_internal(self): def test_set_undeclared_property_as_internal(self):
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
obj = cls.new(None, None, None, {}) obj = cls.new(None, None, None, {})
obj.cast = mock.Mock(return_value=obj) obj.cast = mock.Mock(return_value=obj)
prop_value = 10 prop_value = 10
@@ -403,9 +405,9 @@ class TestObjectsManipulation(unittest.TestCase):
self.assertEqual(root_alt, cls_obj_casted2root_alt.type) self.assertEqual(root_alt, cls_obj_casted2root_alt.type)
def test_fails_object_down_cast(self): def test_fails_object_down_cast(self):
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS) root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
cls = murano_class.MuranoClass( cls = murano_class.MuranoClass(
None, self.resolver, 'SomeClass', [root]) None, self.resolver, 'SomeClass', None, [root])
root_obj = root.new(None, None, None) root_obj = root.new(None, None, None)
self.assertRaises(TypeError, root_obj.cast, cls) self.assertRaises(TypeError, root_obj.cast, cls)

View File

@@ -38,3 +38,4 @@ oslo.messaging>=1.3.0a9
# not listed in global requirements # not listed in global requirements
yaql>=0.2.2,<0.3 yaql>=0.2.2,<0.3
python-muranoclient==0.4.dev127.g108313a