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:
parent
246a35b6f9
commit
ad6b8ece18
@ -11,6 +11,9 @@ bind_host = 0.0.0.0
|
||||
# Port the bind the server to
|
||||
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'.
|
||||
log_file = /tmp/murano-api.log
|
||||
|
||||
@ -142,3 +145,14 @@ auth_url = http://localhost:5000/v2.0
|
||||
#key_file =
|
||||
# If set then the server's certificate will not be verified
|
||||
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
|
||||
|
@ -21,6 +21,7 @@ import logging.config
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from oslo.config import cfg
|
||||
from paste import deploy
|
||||
@ -76,6 +77,16 @@ keystone_opts = [
|
||||
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 = [
|
||||
cfg.IntOpt('period', default=5,
|
||||
help=_('Statistics collection interval in minutes.'
|
||||
@ -84,6 +95,9 @@ stats_opt = [
|
||||
|
||||
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.register_opts(paste_deploy_opts, group='paste_deploy')
|
||||
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(neutron_opts, group='neutron')
|
||||
CONF.register_opts(keystone_opts, group='keystone')
|
||||
CONF.register_opts(murano_opts, group='murano')
|
||||
CONF.register_opt(cfg.StrOpt('file_server'))
|
||||
CONF.register_cli_opt(cfg.StrOpt('murano_metadata_url'))
|
||||
CONF.register_cli_opt(metadata_dir)
|
||||
CONF.register_cli_opt(packages_cache)
|
||||
CONF.register_opts(stats_opt, group='stats')
|
||||
|
||||
CONF.import_opt('connection',
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2013 Mirantis Inc.
|
||||
# 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.
|
||||
@ -25,38 +25,40 @@ from muranoapi.common import rpc
|
||||
from muranoapi.dsl import executor
|
||||
from muranoapi.dsl import results_serializer
|
||||
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
|
||||
from muranoapi.openstack.common.gettextutils import _ # noqa
|
||||
from muranoapi.openstack.common import log as logging
|
||||
|
||||
RPC_SERVICE = None
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaskProcessingEndpoint(object):
|
||||
@staticmethod
|
||||
def handle_task(context, task):
|
||||
s_task = token_sanitizer.TokenSanitizer().sanitize(task)
|
||||
log.info(_('Starting processing task: {0}').format(
|
||||
anyjson.dumps(s_task)))
|
||||
LOG.info(_('Starting processing task: {task_desc}').format(
|
||||
task_desc=anyjson.dumps(s_task)))
|
||||
|
||||
env = environment.Environment()
|
||||
env.token = task['token']
|
||||
env.tenant_id = task['tenant_id']
|
||||
|
||||
cl = simple_cloader.SimpleClassLoader(config.CONF.metadata_dir)
|
||||
system_objects.register(cl, config.CONF.metadata_dir)
|
||||
with package_loader.ApiPackageLoader(task['token']) as pkg_loader:
|
||||
class_loader = package_class_loader.PackageClassLoader(pkg_loader)
|
||||
system_objects.register(class_loader, pkg_loader)
|
||||
|
||||
exc = executor.MuranoDslExecutor(cl, env)
|
||||
obj = exc.load(task['model'])
|
||||
exc = executor.MuranoDslExecutor(class_loader, env)
|
||||
obj = exc.load(task['model'])
|
||||
|
||||
if obj is not None:
|
||||
obj.type.invoke('deploy', exc, obj, {})
|
||||
if obj is not None:
|
||||
obj.type.invoke('deploy', exc, obj, {})
|
||||
|
||||
s_res = results_serializer.serialize(obj, exc)
|
||||
rpc.api().process_result(s_res)
|
||||
s_res = results_serializer.serialize(obj, exc)
|
||||
rpc.api().process_result(s_res)
|
||||
|
||||
|
||||
def _prepare_rpc_service(server_id):
|
||||
|
@ -30,18 +30,31 @@ import muranoapi.dsl.typespec as typespec
|
||||
class MuranoClassLoader(object):
|
||||
def __init__(self):
|
||||
self._loaded_types = {}
|
||||
self._packages_cache = {}
|
||||
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):
|
||||
if name in self._loaded_types:
|
||||
return self._loaded_types[name]
|
||||
|
||||
data = self.load_definition(name)
|
||||
if data is None:
|
||||
try:
|
||||
data = self.load_definition(name)
|
||||
package = self._get_package(name)
|
||||
except (exceptions.NoPackageForClassFound, exceptions.NoClassFound):
|
||||
if create_missing:
|
||||
data = {'Name': name}
|
||||
package = None
|
||||
else:
|
||||
raise exceptions.NoClassFound(name)
|
||||
raise
|
||||
|
||||
namespaces = data.get('Namespaces', {})
|
||||
ns_resolver = namespace_resolver.NamespaceResolver(namespaces)
|
||||
@ -55,7 +68,7 @@ class MuranoClassLoader(object):
|
||||
class_parents[i] = self.get_class(full_name)
|
||||
|
||||
type_obj = murano_class.MuranoClass(self, ns_resolver, name,
|
||||
class_parents)
|
||||
package, class_parents)
|
||||
|
||||
properties = data.get('Properties', {})
|
||||
for property_name, property_spec in properties.iteritems():
|
||||
@ -71,6 +84,12 @@ class MuranoClassLoader(object):
|
||||
def load_definition(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
def find_package_name(self, class_name):
|
||||
raise NotImplementedError()
|
||||
|
||||
def load_package(self, class_name):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_root_context(self):
|
||||
return yaql.create_context(True)
|
||||
|
||||
|
@ -36,6 +36,17 @@ class NoClassFound(Exception):
|
||||
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):
|
||||
def __init__(self, name):
|
||||
super(AmbiguousMethodName, self).__init__(
|
||||
|
@ -29,7 +29,9 @@ def classname(name):
|
||||
|
||||
|
||||
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._methods = {}
|
||||
self._namespace_resolver = namespace_resolver
|
||||
@ -51,6 +53,10 @@ class MuranoClass(object):
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def package(self):
|
||||
return self._package
|
||||
|
||||
@property
|
||||
def namespace_resolver(self):
|
||||
return self._namespace_resolver
|
||||
|
27
muranoapi/dsl/murano_package.py
Normal file
27
muranoapi/dsl/murano_package.py
Normal 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
|
53
muranoapi/engine/package_class_loader.py
Normal file
53
muranoapi/engine/package_class_loader.py
Normal 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
|
218
muranoapi/engine/package_loader.py
Normal file
218
muranoapi/engine/package_loader.py
Normal 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)
|
@ -14,21 +14,19 @@
|
||||
# limitations under the License.
|
||||
|
||||
import json as jsonlib
|
||||
import os.path
|
||||
import yaml as yamllib
|
||||
|
||||
import muranoapi.dsl.murano_object as murano_object
|
||||
|
||||
|
||||
class ResourceManager(murano_object.MuranoObject):
|
||||
def initialize(self, base_path, _context, _class):
|
||||
def initialize(self, package_loader, _context, _class):
|
||||
if _class is None:
|
||||
_class = _context.get_data('$')
|
||||
class_name = _class.type.name
|
||||
self._base_path = os.path.join(base_path, class_name, 'resources')
|
||||
self._package = package_loader.get_package(_class.type.package.name)
|
||||
|
||||
def string(self, name):
|
||||
path = os.path.join(self._base_path, name)
|
||||
path = self._package.get_resource(name)
|
||||
with open(path) as file:
|
||||
return file.read()
|
||||
|
||||
|
@ -34,14 +34,14 @@ def _auto_register(class_loader):
|
||||
class_loader.import_class(class_def)
|
||||
|
||||
|
||||
def register(class_loader, path):
|
||||
def register(class_loader, package_loader):
|
||||
_auto_register(class_loader)
|
||||
|
||||
@murano_class.classname('io.murano.system.Resources')
|
||||
class ResourceManagerWrapper(resource_manager.ResourceManager):
|
||||
def initialize(self, _context, _class=None):
|
||||
super(ResourceManagerWrapper, self).initialize(
|
||||
path, _context, _class)
|
||||
package_loader, _context, _class)
|
||||
|
||||
class_loader.import_class(agent.Agent)
|
||||
class_loader.import_class(agent_listener.AgentListener)
|
||||
|
@ -12,6 +12,7 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import imghdr
|
||||
import os
|
||||
import shutil
|
||||
@ -24,6 +25,17 @@ import muranoapi.packages.exceptions as e
|
||||
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):
|
||||
Library = 'Library'
|
||||
Application = 'Application'
|
||||
@ -31,7 +43,8 @@ class PackageTypes(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._full_name = None
|
||||
self._package_type = None
|
||||
@ -42,7 +55,7 @@ class ApplicationPackage(object):
|
||||
self._classes = None
|
||||
self._ui = None
|
||||
self._logo = None
|
||||
self._format = yaml_content.get('Format')
|
||||
self._format = manifest.get('Format')
|
||||
self._ui_cache = None
|
||||
self._raw_ui_cache = None
|
||||
self._logo_cache = None
|
||||
@ -99,6 +112,9 @@ class ApplicationPackage(object):
|
||||
self._load_class(name)
|
||||
return self._classes_cache[name]
|
||||
|
||||
def get_resource(self, name):
|
||||
return os.path.join(self._source_directory, 'Resources', name)
|
||||
|
||||
def validate(self):
|
||||
self._classes_cache.clear()
|
||||
for class_name in self._classes:
|
||||
@ -109,7 +125,7 @@ class ApplicationPackage(object):
|
||||
# Private methods
|
||||
def _load_ui(self, load_yaml=False):
|
||||
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:
|
||||
ui_file = self._ui
|
||||
full_path = os.path.join(self._source_directory, 'UI', ui_file)
|
||||
@ -121,7 +137,8 @@ class ApplicationPackage(object):
|
||||
with open(full_path) as stream:
|
||||
self._raw_ui_cache = stream.read()
|
||||
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:
|
||||
trace = sys.exc_info()[2]
|
||||
raise e.PackageUILoadError(str(ex)), None, trace
|
||||
@ -154,7 +171,7 @@ class ApplicationPackage(object):
|
||||
'definition not found')
|
||||
try:
|
||||
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:
|
||||
trace = sys.exc_info()[2]
|
||||
msg = 'Unable to load class definition due to "{0}"'.format(
|
||||
@ -162,7 +179,8 @@ class ApplicationPackage(object):
|
||||
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}
|
||||
|
||||
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:
|
||||
with open(full_path) as stream:
|
||||
content = yaml.load(stream)
|
||||
content = yaml.load(stream, DummyLoader)
|
||||
except Exception as ex:
|
||||
trace = sys.exc_info()[2]
|
||||
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:
|
||||
raise e.PackageFormatError(
|
||||
'Unknown or missing format version')
|
||||
package = ApplicationPackage(source_directory, content)
|
||||
package = ApplicationPackage(source_directory, content, loader)
|
||||
formats[p_format].load(package, content)
|
||||
if preload:
|
||||
package.validate()
|
||||
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):
|
||||
raise e.PackageLoadError('Unable to find package file')
|
||||
created = False
|
||||
@ -211,7 +230,7 @@ def load_from_file(archive_path, target_dir=None, drop_dir=False):
|
||||
"zip' archive")
|
||||
package = zipfile.ZipFile(archive_path)
|
||||
package.extractall(path=target_dir)
|
||||
return load_from_dir(target_dir, preload=True)
|
||||
return load_from_dir(target_dir, preload=True, loader=loader)
|
||||
finally:
|
||||
if drop_dir:
|
||||
if created:
|
||||
|
@ -114,13 +114,13 @@ class TestClassesManipulation(unittest.TestCase):
|
||||
resolver = mock.Mock(resolve_name=lambda name: name)
|
||||
|
||||
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)
|
||||
|
||||
def test_class_namespace_resolver(self):
|
||||
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)
|
||||
|
||||
@ -131,21 +131,23 @@ class TestClassesManipulation(unittest.TestCase):
|
||||
self.assertEqual([], root_class.parents)
|
||||
|
||||
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)
|
||||
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(
|
||||
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_cl2.parents)
|
||||
|
||||
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)
|
||||
|
||||
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': {}}
|
||||
|
||||
self.assertRaises(TypeError, cls.add_property, **kwargs)
|
||||
@ -153,7 +155,7 @@ class TestClassesManipulation(unittest.TestCase):
|
||||
@unittest.skip
|
||||
def test_add_property_to_class(self):
|
||||
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)
|
||||
|
||||
class_properties = cls.properties
|
||||
@ -190,9 +192,9 @@ class TestClassesManipulation(unittest.TestCase):
|
||||
self.assertEqual(void_prop, child.find_property('Void'))
|
||||
|
||||
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(
|
||||
None, self.resolver, 'DescendantCls', [cls])
|
||||
None, self.resolver, 'DescendantCls', None, [cls])
|
||||
obj = mock.Mock(spec=murano_object.MuranoObject)
|
||||
descendant_obj = mock.Mock(spec=murano_object.MuranoObject)
|
||||
obj.type = cls
|
||||
@ -204,7 +206,7 @@ class TestClassesManipulation(unittest.TestCase):
|
||||
self.assertFalse(descendant_cls.is_compatible(obj))
|
||||
|
||||
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()
|
||||
|
||||
with mock.patch('inspect.getargspec') as spec_mock:
|
||||
@ -214,7 +216,7 @@ class TestClassesManipulation(unittest.TestCase):
|
||||
self.assertTrue(obj.initialize.called)
|
||||
|
||||
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()
|
||||
|
||||
obj = cls.new(None, None, None)
|
||||
@ -241,9 +243,9 @@ class TestObjectsManipulation(unittest.TestCase):
|
||||
pass
|
||||
|
||||
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,
|
||||
'SomeClass', [root])
|
||||
'SomeClass', None, [root])
|
||||
root.new = mock.Mock()
|
||||
init_kwargs = {'theArg': 0}
|
||||
obj = murano_object.MuranoObject(cls, None, None, None)
|
||||
@ -326,13 +328,13 @@ class TestObjectsManipulation(unittest.TestCase):
|
||||
self.assertRaises(LookupError, lambda: obj.conflictProp)
|
||||
|
||||
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, {})
|
||||
|
||||
self.assertRaises(AttributeError, obj.set_property, 'newOne', 10)
|
||||
|
||||
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.cast = mock.Mock(return_value=obj)
|
||||
prop_value = 10
|
||||
@ -403,9 +405,9 @@ class TestObjectsManipulation(unittest.TestCase):
|
||||
self.assertEqual(root_alt, cls_obj_casted2root_alt.type)
|
||||
|
||||
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(
|
||||
None, self.resolver, 'SomeClass', [root])
|
||||
None, self.resolver, 'SomeClass', None, [root])
|
||||
root_obj = root.new(None, None, None)
|
||||
|
||||
self.assertRaises(TypeError, root_obj.cast, cls)
|
||||
|
@ -38,3 +38,4 @@ oslo.messaging>=1.3.0a9
|
||||
|
||||
# not listed in global requirements
|
||||
yaql>=0.2.2,<0.3
|
||||
python-muranoclient==0.4.dev127.g108313a
|
||||
|
Loading…
x
Reference in New Issue
Block a user