diff --git a/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/DemoApp.yaml b/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/DemoApp.yaml index ca44b56b3..73bb4a664 100644 --- a/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/DemoApp.yaml +++ b/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/DemoApp.yaml @@ -23,7 +23,7 @@ Methods: deploy: Body: - - If: !yaql "not bool($.getAttr(deployed))" + - If: not $.getAttr(deployed, false) Then: - $._environment.reporter.report($this, 'Creating VM ') - $securityGroupIngress: diff --git a/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/ImageValidatorMixin.yaml b/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/ImageValidatorMixin.yaml index 062bcb9fa..c5a2319a6 100644 --- a/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/ImageValidatorMixin.yaml +++ b/contrib/plugins/murano_exampleplugin/example-app/io.murano.apps.demo.DemoApp/Classes/ImageValidatorMixin.yaml @@ -14,8 +14,9 @@ Properties: Methods: validateImage: Body: + - $environment: $.find(std:Environment).require() - Try: - - $glance: new('io.murano.extensions.mirantis.example.Glance') + - $glance: new('io.murano.extensions.mirantis.example.Glance', $environment) Catch: With: 'murano.dsl.exceptions.NoPackageForClassFound' Do: diff --git a/contrib/plugins/murano_exampleplugin/murano_exampleplugin/__init__.py b/contrib/plugins/murano_exampleplugin/murano_exampleplugin/__init__.py index 06f4a822e..6031b72ea 100644 --- a/contrib/plugins/murano_exampleplugin/murano_exampleplugin/__init__.py +++ b/contrib/plugins/murano_exampleplugin/murano_exampleplugin/__init__.py @@ -20,20 +20,24 @@ import glanceclient from oslo_config import cfg as config from oslo_log import log as logging - from murano.common import auth_utils - +from murano.dsl import session_local_storage CONF = config.CONF LOG = logging.getLogger(__name__) class GlanceClient(object): - def __init__(self, context): - self.client = self.create_glance_client() + def __init__(self, this): + self._owner = this.find_owner('io.murano.Environment') + + @property + def _client(self): + region = None if self._owner is None else self._owner['region'] + return self.create_glance_client(region) def list(self): - images = self.client.images.list() + images = self._client.images.list() while True: try: image = next(images) @@ -42,7 +46,7 @@ class GlanceClient(object): break def get_by_name(self, name): - images = list(self.client.images.list(filters={"name": name})) + images = list(self._client.images.list(filters={"name": name})) if len(images) > 1: raise AmbiguousNameException(name) elif len(images) == 0: @@ -51,7 +55,7 @@ class GlanceClient(object): return GlanceClient._format(images[0]) def get_by_id(self, imageId): - image = self.client.images.get(imageId) + image = self._client.images.get(imageId) return GlanceClient._format(image) @staticmethod @@ -65,11 +69,13 @@ class GlanceClient(object): def init_plugin(cls): cls.CONF = cfg.init_config(CONF) - def create_glance_client(self): + @staticmethod + @session_local_storage.execution_session_memoize + def create_glance_client(region): LOG.debug("Creating a glance client") params = auth_utils.get_session_client_parameters( - service_type='image', conf=self.CONF) - return glanceclient.Client(self.CONF.api_version, **params) + service_type='image', conf=CONF, region=region) + return glanceclient.Client(CONF.api_version, **params) class AmbiguousNameException(Exception): diff --git a/meta/io.murano/Classes/Environment.yaml b/meta/io.murano/Classes/Environment.yaml index 311af35bc..db0bcccd5 100644 --- a/meta/io.murano/Classes/Environment.yaml +++ b/meta/io.murano/Classes/Environment.yaml @@ -50,9 +50,28 @@ Properties: Contract: $.class(sys:StatusReporter) Usage: Runtime + regionConfigs: + Contract: + $.string(): + agentRabbitMq: + host: $.string().notNull() + port: $.int() or 5672 + login: $.string().notNull() + password: $.string().notNull() + virtual_host: $.string() or '/' + ssl: $.bool() or false + ca_certs: $.string() or '' + Usage: Config + + region: + Contract: $.string() + Usage: InOut + + Methods: initialize: Body: + - $._assignRegions() - $generatedEnvironmentName: $.getAttr(generatedEnvironmentName) - If: $generatedEnvironmentName = null Then: @@ -60,7 +79,7 @@ Methods: - $.setAttr(generatedEnvironmentName, $generatedEnvironmentName) - $this.agentListener: new(sys:AgentListener, name => $generatedEnvironmentName) - $stackDescriptionFormat: 'This stack was generated by Murano for environment {0} (ID: {1})' - - $this.stack: new(sys:HeatStack, + - $this.stack: new(sys:HeatStack, $this, name => 'murano-' + $generatedEnvironmentName, description => $stackDescriptionFormat.format($.name, $.id())) - $this.instanceNotifier: new(sys:InstanceNotifier, environment => $this) @@ -84,3 +103,28 @@ Methods: destroy: Body: - $.stack.delete() + + _assignRegions: + Body: + - $homeRegion: config(home_region) + - If: $.region = null + Then: + $.region: $homeRegion + + - $defaultRegionConfig: + agentRabbitMq: + host: config(rabbitmq, host) + port: config(rabbitmq, port) + login: config(rabbitmq, login) + password: config(rabbitmq, password) + virtual_host: config(rabbitmq, virtual_host) + ssl: config(rabbitmq, ssl) + ca_certs: config(rabbitmq, ca_certs) + + - If: not (null in $.regionConfigs.keys()) + Then: + - $.regionConfigs: $.regionConfigs.set(null => $defaultRegionConfig) + + - If: $homeRegion != null and not ($homeRegion in $.regionConfigs.keys()) + Then: + - $.regionConfigs: $.regionConfigs.set($homeRegion => $defaultRegionConfig) diff --git a/meta/io.murano/Classes/resources/ExistingNeutronNetwork.yaml b/meta/io.murano/Classes/resources/ExistingNeutronNetwork.yaml index 8c56825f5..827b9f749 100644 --- a/meta/io.murano/Classes/resources/ExistingNeutronNetwork.yaml +++ b/meta/io.murano/Classes/resources/ExistingNeutronNetwork.yaml @@ -38,7 +38,8 @@ Properties: Workflow: initialize: Body: - - $._netExplorer: new(sys:NetworkExplorer) + - $environment: $.find(std:Environment).require() + - $._netExplorer: new(sys:NetworkExplorer, $environment) - $._networks: null - $._subnetworks: null - $._ports: null diff --git a/meta/io.murano/Classes/resources/LinuxMuranoInstance.yaml b/meta/io.murano/Classes/resources/LinuxMuranoInstance.yaml index 01bd0bf50..369f138ce 100644 --- a/meta/io.murano/Classes/resources/LinuxMuranoInstance.yaml +++ b/meta/io.murano/Classes/resources/LinuxMuranoInstance.yaml @@ -58,6 +58,7 @@ Methods: generateUserData: Body: - $environment: $.find(std:Environment).require() + - $rabbitMqParams: $environment.regionConfigs.get($environment.region).agentRabbitMq - $resources: new(sys:Resources) - $configFile: $resources.string('Agent-v2.template') - $initScript: $resources.string('linux-init.sh') @@ -66,18 +67,18 @@ Methods: - $muranoAgentService: $resources.string('murano-agent.service') - $muranoAgent: $resources.string('murano-agent') - $configReplacements: - "%RABBITMQ_HOST%": config(rabbitmq, host) - "%RABBITMQ_PORT%": config(rabbitmq, port) - "%RABBITMQ_USER%": config(rabbitmq, login) - "%RABBITMQ_PASSWORD%": config(rabbitmq, password) - "%RABBITMQ_VHOST%": config(rabbitmq, virtual_host) - "%RABBITMQ_SSL%": str(config(rabbitmq, ssl)).toLower() + "%RABBITMQ_HOST%": $rabbitMqParams.host + "%RABBITMQ_PORT%": $rabbitMqParams.port + "%RABBITMQ_USER%": $rabbitMqParams.login + "%RABBITMQ_PASSWORD%": $rabbitMqParams.password + "%RABBITMQ_VHOST%": $rabbitMqParams.virtual_host + "%RABBITMQ_SSL%": str($rabbitMqParams.ssl).toLower() "%RABBITMQ_INPUT_QUEUE%": $.agent.queueName() "%RESULT_QUEUE%": $environment.agentListener.queueName() - $scriptReplacements: "%AGENT_CONFIG_BASE64%": base64encode($configFile.replace($configReplacements)) "%INTERNAL_HOSTNAME%": $.name - "%MURANO_SERVER_ADDRESS%": coalesce(config(file_server), config(rabbitmq, host)) + "%MURANO_SERVER_ADDRESS%": coalesce(config(file_server), $rabbitMqParams.host) "%CA_ROOT_CERT_BASE64%": "" - $muranoReplacements: "%MURANO_AGENT_CONF%": base64encode($muranoAgentConf) diff --git a/meta/io.murano/Classes/resources/NeutronNetwork.yaml b/meta/io.murano/Classes/resources/NeutronNetwork.yaml index c092ebf92..85a94c72b 100644 --- a/meta/io.murano/Classes/resources/NeutronNetwork.yaml +++ b/meta/io.murano/Classes/resources/NeutronNetwork.yaml @@ -53,7 +53,7 @@ Methods: initialize: Body: - $._environment: $.find(std:Environment).require() - - $._netExplorer: new(sys:NetworkExplorer) + - $._netExplorer: new(sys:NetworkExplorer, $._environment) deploy: diff --git a/meta/io.murano/Classes/resources/WindowsInstance.yaml b/meta/io.murano/Classes/resources/WindowsInstance.yaml index 7229033a4..c05a95169 100644 --- a/meta/io.murano/Classes/resources/WindowsInstance.yaml +++ b/meta/io.murano/Classes/resources/WindowsInstance.yaml @@ -38,21 +38,22 @@ Methods: Body: - $resources: new(sys:Resources) - $environment: $.find(std:Environment).require() + - $rabbitMqParams: $environment.regionConfigs.get($environment.region).agentRabbitMq - $configFile: $resources.string('Agent-v1.template') - $initScript: $resources.string('windows-init.ps1') - $configReplacements: - "%RABBITMQ_HOST%": config(rabbitmq, host) - "%RABBITMQ_PORT%": config(rabbitmq, port) - "%RABBITMQ_USER%": config(rabbitmq, login) - "%RABBITMQ_PASSWORD%": config(rabbitmq, password) - "%RABBITMQ_VHOST%": config(rabbitmq, virtual_host) - "%RABBITMQ_SSL%": str(config(rabbitmq, ssl)).toLower() + "%RABBITMQ_HOST%": $rabbitMqParams.host + "%RABBITMQ_PORT%": $rabbitMqParams.port + "%RABBITMQ_USER%": $rabbitMqParams.login + "%RABBITMQ_PASSWORD%": $rabbitMqParams.password + "%RABBITMQ_VHOST%": $rabbitMqParams.virtual_host + "%RABBITMQ_SSL%": str($rabbitMqParams.ssl).toLower() "%RABBITMQ_INPUT_QUEUE%": $.agent.queueName() "%RESULT_QUEUE%": $environment.agentListener.queueName() - $scriptReplacements: "%AGENT_CONFIG_BASE64%": base64encode($configFile.replace($configReplacements)) "%INTERNAL_HOSTNAME%": $.name - "%MURANO_SERVER_ADDRESS%": coalesce(config(file_server), config(rabbitmq, host)) + "%MURANO_SERVER_ADDRESS%": coalesce(config(file_server), $rabbitMqParams.host) "%CA_ROOT_CERT_BASE64%": "" - Return: data: $initScript.replace($scriptReplacements) diff --git a/murano/dsl/dsl.py b/murano/dsl/dsl.py index c53fcd5b7..d0112fdbf 100644 --- a/murano/dsl/dsl.py +++ b/murano/dsl/dsl.py @@ -200,14 +200,17 @@ class MuranoObjectInterface(dsl_types.MuranoObjectInterface): @property def owner(self): - return self.__object.owner + owner = self.__object.owner + if owner is None: + return None + return MuranoObjectInterface(owner, self.__executor) def find_owner(self, type, optional=False): if isinstance(type, six.string_types): type = helpers.get_class(type) elif isinstance(type, dsl_types.MuranoTypeReference): type = type.type - p = self.owner + p = self.__object.owner while p is not None: if type.is_compatible(p): return MuranoObjectInterface(p, self.__executor) diff --git a/murano/dsl/dsl_types.py b/murano/dsl/dsl_types.py index b59259ce9..29f0868b2 100644 --- a/murano/dsl/dsl_types.py +++ b/murano/dsl/dsl_types.py @@ -43,7 +43,7 @@ class PropertyUsages(object): Config = 'Config' Static = 'Static' All = {In, Out, InOut, Runtime, Const, Config, Static} - Writable = {Out, InOut, Runtime, Static} + Writable = {Out, InOut, Runtime, Static, Config} class MethodUsages(object): diff --git a/murano/dsl/helpers.py b/murano/dsl/helpers.py index 902843deb..83ca0a057 100644 --- a/murano/dsl/helpers.py +++ b/murano/dsl/helpers.py @@ -20,6 +20,8 @@ import itertools import re import sys import uuid +import weakref + import eventlet.greenpool import eventlet.greenthread @@ -552,3 +554,11 @@ def list_value(v): if not yaqlutils.is_sequence(v): v = [v] return v + + +def weak_proxy(obj): + if obj is None or isinstance(obj, weakref.ProxyType): + return obj + if isinstance(obj, weakref.ReferenceType): + obj = obj() + return weakref.proxy(obj) diff --git a/murano/dsl/murano_type.py b/murano/dsl/murano_type.py index 485e02a4a..775a615fd 100644 --- a/murano/dsl/murano_type.py +++ b/murano/dsl/murano_type.py @@ -275,7 +275,7 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider): def new(self, owner, object_store, executor, **kwargs): obj = murano_object.MuranoObject( - self, owner, object_store, executor, **kwargs) + self, helpers.weak_proxy(owner), object_store, executor, **kwargs) def initializer(__context, **params): if __context is None: diff --git a/murano/engine/system/heat_stack.py b/murano/engine/system/heat_stack.py index 71700181c..fcf3b06bc 100644 --- a/murano/engine/system/heat_stack.py +++ b/murano/engine/system/heat_stack.py @@ -41,7 +41,7 @@ class HeatStackError(Exception): @dsl.name('io.murano.system.HeatStack') class HeatStack(object): - def __init__(self, name, description=None): + def __init__(self, this, name, description=None): self._name = name self._template = None self._parameters = {} @@ -51,8 +51,7 @@ class HeatStack(object): self._description = description self._last_stack_timestamps = (None, None) self._tags = '' - self._client = self._get_client(CONF.home_region) - pass + self._owner = this.find_owner('io.murano.Environment') @staticmethod def _create_client(session, region_name): @@ -61,16 +60,21 @@ class HeatStack(object): conf=CONF.heat, session=session) return hclient.Client('1', **parameters) + @property + def _client(self): + region = None if self._owner is None else self._owner['region'] + return self._get_client(region) + @staticmethod @session_local_storage.execution_session_memoize def _get_client(region_name): session = auth_utils.get_client_session(conf=CONF.heat) return HeatStack._create_client(session, region_name) - @classmethod - def _get_token_client(cls): + def _get_token_client(self): ks_session = auth_utils.get_token_client_session(conf=CONF.heat) - return cls._create_client(ks_session, CONF.home_region) + region = None if self._owner is None else self._owner['region'] + return self._create_client(ks_session, region) def current(self): if self._template is not None: diff --git a/murano/engine/system/mistralclient.py b/murano/engine/system/mistralclient.py index 28699d01e..2b6cc7a78 100644 --- a/murano/engine/system/mistralclient.py +++ b/murano/engine/system/mistralclient.py @@ -36,8 +36,13 @@ class MistralError(Exception): @dsl.name('io.murano.system.MistralClient') class MistralClient(object): - def __init__(self): - self._client = self._create_client(CONF.home_region) + def __init__(self, this): + self._owner = this.find_owner('io.murano.Environment') + + @property + def _client(self): + region = None if self._owner is None else self._owner['region'] + return self._create_client(region) @staticmethod @session_local_storage.execution_session_memoize diff --git a/murano/engine/system/net_explorer.py b/murano/engine/system/net_explorer.py index 5c9416550..ddd2572bb 100644 --- a/murano/engine/system/net_explorer.py +++ b/murano/engine/system/net_explorer.py @@ -35,12 +35,12 @@ LOG = logging.getLogger(__name__) @dsl.name('io.murano.system.NetworkExplorer') class NetworkExplorer(object): - def __init__(self): + def __init__(self, this): session = helpers.get_execution_session() self._project_id = session.project_id self._settings = CONF.networking self._available_cidrs = self._generate_possible_cidrs() - self._client = self._get_client(CONF.home_region) + self._owner = this.find_owner('io.murano.Environment') @staticmethod @session_local_storage.execution_session_memoize @@ -50,6 +50,11 @@ class NetworkExplorer(object): service_type='network', region=region_name, conf=neutron_settings )) + @property + def _client(self): + region = None if self._owner is None else self._owner['region'] + return self._get_client(region) + # NOTE(starodubcevna): to avoid simultaneous router requests we use retry # decorator with random delay 1-10 seconds between attempts and maximum # delay time 30 seconds. diff --git a/murano/packages/hot_package.py b/murano/packages/hot_package.py index 35e949cb4..f0ce0476a 100644 --- a/murano/packages/hot_package.py +++ b/murano/packages/hot_package.py @@ -281,7 +281,7 @@ class HotPackage(package_base.PackageBase): ] }, {YAQL('$stack'): YAQL( - "new('io.murano.system.HeatStack', " + "new('io.murano.system.HeatStack', $environment, " "name => $.getAttr(generatedHeatStackName))")}, YAQL("$reporter.report($this, " diff --git a/murano/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Classes/MistralShowcaseApp.yaml b/murano/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Classes/MistralShowcaseApp.yaml index 819c6273f..cfe4a0796 100644 --- a/murano/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Classes/MistralShowcaseApp.yaml +++ b/murano/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Classes/MistralShowcaseApp.yaml @@ -20,7 +20,8 @@ Properties: Methods: initialize: Body: - - $this.mistralClient: new(sys:MistralClient) + - $environment: $.find(std:Environment).require() + - $this.mistralClient: new(sys:MistralClient, $environment) deploy: Body: diff --git a/murano/tests/unit/dsl/test_property_access.py b/murano/tests/unit/dsl/test_property_access.py index b66ee82d5..77e485a90 100644 --- a/murano/tests/unit/dsl/test_property_access.py +++ b/murano/tests/unit/dsl/test_property_access.py @@ -113,7 +113,7 @@ class TestPropertyAccess(test_case.DslTestCase): exceptions.NoWriteAccessError, self._runner.on(self._multi_derived). testModifyUsageTestProperty6) - self.assertRaises( - exceptions.NoWriteAccessError, - self._runner.on(self._multi_derived). - testModifyUsageTestProperty7) + self.assertEqual( + 77, + self._runner.on( + self._multi_derived).testModifyUsageTestProperty7()) diff --git a/murano/tests/unit/test_heat_stack.py b/murano/tests/unit/test_heat_stack.py index aa6af6209..bd699ec8c 100644 --- a/murano/tests/unit/test_heat_stack.py +++ b/murano/tests/unit/test_heat_stack.py @@ -36,6 +36,8 @@ class TestHeatStack(base.MuranoTestCase): return_value=self.heat_client_mock) heat_stack.HeatStack._get_client = mock.Mock( return_value=self.heat_client_mock) + self._this = mock.MagicMock() + self._this.owner = None @mock.patch(CLS_NAME + '._wait_state') @mock.patch(CLS_NAME + '._get_status') @@ -44,7 +46,8 @@ class TestHeatStack(base.MuranoTestCase): status_get.return_value = 'NOT_FOUND' wait_st.return_value = {} - hs = heat_stack.HeatStack('test-stack', 'Generated by TestHeatStack') + hs = heat_stack.HeatStack( + self._this, 'test-stack', 'Generated by TestHeatStack') hs._template = {'resources': {'test': 1}} hs._files = {} hs._hot_environment = '' @@ -52,7 +55,8 @@ class TestHeatStack(base.MuranoTestCase): hs._applied = False hs.push() - hs = heat_stack.HeatStack('test-stack', 'Generated by TestHeatStack') + hs = heat_stack.HeatStack( + self._this, 'test-stack', 'Generated by TestHeatStack') hs._template = {'resources': {'test': 1}} hs._files = {} hs._parameters = {} @@ -82,7 +86,7 @@ class TestHeatStack(base.MuranoTestCase): status_get.return_value = 'NOT_FOUND' wait_st.return_value = {} - hs = heat_stack.HeatStack('test-stack', None) + hs = heat_stack.HeatStack(self._this, 'test-stack', None) hs._template = {'resources': {'test': 1}} hs._files = {} hs._hot_environment = '' @@ -112,7 +116,7 @@ class TestHeatStack(base.MuranoTestCase): status_get.return_value = 'NOT_FOUND' wait_st.return_value = {} - hs = heat_stack.HeatStack('test-stack', None) + hs = heat_stack.HeatStack(self._this, 'test-stack', None) hs._description = None hs._template = {'resources': {'test': 1}} hs._files = {"heatFile": "file"} @@ -143,7 +147,7 @@ class TestHeatStack(base.MuranoTestCase): status_get.return_value = 'NOT_FOUND' wait_st.return_value = {} - hs = heat_stack.HeatStack('test-stack', None) + hs = heat_stack.HeatStack(self._this, 'test-stack', None) hs._description = None hs._template = {'resources': {'test': 1}} hs._files = {"heatFile": "file"} @@ -171,7 +175,8 @@ class TestHeatStack(base.MuranoTestCase): def test_update_wrong_template_version(self, current): """Template version other than expected should cause error.""" - hs = heat_stack.HeatStack('test-stack', 'Generated by TestHeatStack') + hs = heat_stack.HeatStack( + self._this, 'test-stack', 'Generated by TestHeatStack') hs._template = {'resources': {'test': 1}} invalid_template = { @@ -208,7 +213,7 @@ class TestHeatStack(base.MuranoTestCase): wait_st.return_value = {} CONF.set_override('stack_tags', ['test-murano', 'murano-tag'], 'heat', enforce_type=True) - hs = heat_stack.HeatStack('test-stack', None) + hs = heat_stack.HeatStack(self._this, 'test-stack', None) hs._description = None hs._template = {'resources': {'test': 1}} hs._files = {}