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:
@@ -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
|
||||||
|
@@ -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',
|
||||||
|
@@ -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:
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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__(
|
||||||
|
@@ -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
|
||||||
|
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.
|
# 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()
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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:
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user