From e2bea76426cb1026b26f0fd932a8a01d942436e5 Mon Sep 17 00:00:00 2001 From: Steve McLellan Date: Fri, 25 Jul 2014 17:59:33 -0500 Subject: [PATCH] Allow murano-agent to be disabled In some circumstances murano-agent isn't required (e.g. in environments where heat SW config is capable alone of performing configuration). In this case it's not necessary to have the additional overhead of rabbitMQ connections for the AgentListener that will never receive a message. Patch adds a config option 'disable_murano_agent' that no-ops AgentLister.start() and raises an exception on Agent._send() Change-Id: I565caaae21925c48f2a0adea18036239cac91c77 Implements: blueprint disable-murano-agent --- etc/murano/murano.conf.sample | 10 +++ murano/common/config.py | 7 ++ murano/engine/system/agent.py | 36 ++++++-- murano/engine/system/agent_listener.py | 24 ++++++ murano/tests/dsl/meta/AgentListenerTests.yaml | 16 ++++ murano/tests/dsl/meta/AgentTests.yaml | 16 ++++ murano/tests/dsl/test_agent.py | 82 +++++++++++++++++++ 7 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 murano/tests/dsl/meta/AgentListenerTests.yaml create mode 100644 murano/tests/dsl/meta/AgentTests.yaml create mode 100644 murano/tests/dsl/test_agent.py diff --git a/etc/murano/murano.conf.sample b/etc/murano/murano.conf.sample index eafdc2588..4363549d8 100644 --- a/etc/murano/murano.conf.sample +++ b/etc/murano/murano.conf.sample @@ -270,6 +270,16 @@ #db_max_retries=20 +[engine] + +# +# Options defined in murano.common.config +# + +# Disallow the use of murano-agent (boolean value) +#disable_murano_agent=false + + [heat] # diff --git a/murano/common/config.py b/murano/common/config.py index 2f34208e4..81902482b 100644 --- a/murano/common/config.py +++ b/murano/common/config.py @@ -168,6 +168,12 @@ stats_opt = [ 'Default value is 5 minutes.')) ] +engine_opts = [ + cfg.BoolOpt('disable_murano_agent', default=False, + help=_('Disallow the use of murano-agent')) +] + +# TODO(sjmc7): move into engine opts? metadata_dir = cfg.StrOpt('metadata-dir', default='./meta', help='Metadata dir') @@ -196,6 +202,7 @@ 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_opts(engine_opts, group='engine') CONF.register_opt(cfg.StrOpt('file_server')) CONF.register_cli_opt(cfg.StrOpt('murano_metadata_url')) CONF.register_cli_opt(metadata_dir) diff --git a/murano/engine/system/agent.py b/murano/engine/system/agent.py index 4d1b29df1..fb9ed553d 100644 --- a/murano/engine/system/agent.py +++ b/murano/engine/system/agent.py @@ -20,7 +20,9 @@ import types import uuid import eventlet.event +import logging +import murano.common.config as config import murano.common.messaging as messaging import murano.dsl.murano_class as murano_class import murano.dsl.murano_object as murano_object @@ -28,6 +30,9 @@ import murano.dsl.yaql_expression as yaql_expression import murano.engine.system.common as common +LOG = logging.getLogger(__name__) + + class AgentException(Exception): pass @@ -35,19 +40,36 @@ class AgentException(Exception): @murano_class.classname('io.murano.system.Agent') class Agent(murano_object.MuranoObject): def initialize(self, _context, host): - environment = yaql_expression.YaqlExpression( + self._enabled = False + if config.CONF.engine.disable_murano_agent: + LOG.debug("murano-agent is disabled by the server") + return + + self._environment = self._get_environment(_context) + self._enabled = True + self._queue = str('e%s-h%s' % ( + self._environment.object_id, host.object_id)).lower() + + def _get_environment(self, _context): + return yaql_expression.YaqlExpression( "$host.find('io.murano.Environment').require()" ).evaluate(_context) - self._queue = str('e%s-h%s' % ( - environment.object_id, host.object_id)).lower() - self._environment = environment + @property + def enabled(self): + return self._enabled def queueName(self): return self._queue - def _send(self, template, wait_results): + def _check_enabled(self): + if config.CONF.engine.disable_murano_agent: + raise AgentException( + "Use of murano-agent is disallowed " + "by the server configuration") + def _send(self, template, wait_results): + """Send a message over the MQ interface""" msg_id = template.get('ID', uuid.uuid4().hex) if wait_results: event = eventlet.event.Event() @@ -77,17 +99,21 @@ class Agent(murano_object.MuranoObject): return None def call(self, template, resources): + self._check_enabled() plan = self.buildExecutionPlan(template, resources) return self._send(plan, True) def send(self, template, resources): + self._check_enabled() plan = self.buildExecutionPlan(template, resources) return self._send(plan, False) def callRaw(self, plan): + self._check_enabled() return self._send(plan, True) def sendRaw(self, plan): + self._check_enabled() return self._send(plan, False) def _process_v1_result(self, result): diff --git a/murano/engine/system/agent_listener.py b/murano/engine/system/agent_listener.py index 75849cf02..c3694f2f6 100644 --- a/murano/engine/system/agent_listener.py +++ b/murano/engine/system/agent_listener.py @@ -15,6 +15,7 @@ import eventlet +import murano.common.config as config import murano.dsl.murano_class as murano_class import murano.dsl.murano_object as murano_object import murano.engine.system.common as common @@ -24,26 +25,49 @@ from murano.openstack.common import log as logging LOG = logging.getLogger(__name__) +class AgentListenerException(Exception): + pass + + @murano_class.classname('io.murano.system.AgentListener') class AgentListener(murano_object.MuranoObject): def initialize(self, _context, name): + self._enabled = False + if config.CONF.engine.disable_murano_agent: + return + self._enabled = True self._results_queue = str('-execution-results-%s' % name.lower()) self._subscriptions = {} self._receive_thread = None + @property + def enabled(self): + return self._enabled + def queueName(self): return self._results_queue def start(self): + if config.CONF.engine.disable_murano_agent: + # Noop + LOG.debug("murano-agent is disabled by the server") + return + if self._receive_thread is None: self._receive_thread = eventlet.spawn(self._receive) def stop(self): + # _receive_thread will be None if agent is disabled if self._receive_thread is not None: self._receive_thread.kill() self._receive_thread = None def subscribe(self, message_id, event): + if config.CONF.engine.disable_murano_agent: + raise AgentListenerException( + "Use of murano-agent is disallowed " + "by the server configuration") + self._subscriptions[message_id] = event def _receive(self): diff --git a/murano/tests/dsl/meta/AgentListenerTests.yaml b/murano/tests/dsl/meta/AgentListenerTests.yaml new file mode 100644 index 000000000..dd089d502 --- /dev/null +++ b/murano/tests/dsl/meta/AgentListenerTests.yaml @@ -0,0 +1,16 @@ +Name: AgentListenerTests + +Namespaces: + sys: io.murano.system + +Properties: + agentListener: + Contract: $.class(sys:AgentListener) + Usage: Runtime + + +Methods: + testAgentListener: + Body: + - $.agentListener: new(sys:AgentListener, name => 'hello') + - Return: $.agentListener diff --git a/murano/tests/dsl/meta/AgentTests.yaml b/murano/tests/dsl/meta/AgentTests.yaml new file mode 100644 index 000000000..8e1a74c5b --- /dev/null +++ b/murano/tests/dsl/meta/AgentTests.yaml @@ -0,0 +1,16 @@ +Name: AgentTests + +Namespaces: + sys: io.murano.system + +Properties: + agent: + Contract: $.class(sys:Agent) + Usage: Runtime + + +Methods: + testAgent: + Body: + - $.agent: new(sys:Agent, host => $) + - Return: $.agent diff --git a/murano/tests/dsl/test_agent.py b/murano/tests/dsl/test_agent.py new file mode 100644 index 000000000..89380a77b --- /dev/null +++ b/murano/tests/dsl/test_agent.py @@ -0,0 +1,82 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 mock + +from murano.engine.system import agent +from murano.engine.system import agent_listener +from murano.tests.dsl.foundation import object_model as om +from murano.tests.dsl.foundation import test_case + + +class TestAgentListener(test_case.DslTestCase): + def setUp(self): + super(TestAgentListener, self).setUp() + + # Register Agent class + self.class_loader.import_class(agent_listener.AgentListener) + model = om.Object( + 'AgentListenerTests') + self.runner = self.new_runner(model) + + def test_listener_enabled(self): + self.override_config('disable_murano_agent', False, 'engine') + al = self.runner.testAgentListener() + self.assertTrue(al.enabled) + al.subscribe('msgid', 'event') + self.assertEqual({'msgid': 'event'}, al._subscriptions) + + def test_listener_disabled(self): + self.override_config('disable_murano_agent', True, 'engine') + al = self.runner.testAgentListener() + self.assertFalse(al.enabled) + self.assertRaises(agent_listener.AgentListenerException, + al.subscribe, 'msgid', 'event') + + +class TestAgent(test_case.DslTestCase): + def setUp(self): + super(TestAgent, self).setUp() + + # Register Agent class + self.class_loader.import_class(agent.Agent) + model = om.Object( + 'AgentTests') + self.runner = self.new_runner(model) + + def test_agent_enabled(self): + self.override_config('disable_murano_agent', False, 'engine') + m = mock.MagicMock() + # Necessary because otherwise there'll be an Environment lookup + agent_cls = 'murano.engine.system.agent.Agent' + with mock.patch(agent_cls + '._get_environment') as f: + f.return_value = m + a = self.runner.testAgent() + self.assertTrue(a.enabled) + self.assertEqual(m, a._environment) + + with mock.patch(agent_cls + '._send') as s: + s.return_value = mock.MagicMock() + a.sendRaw({}) + s.assert_called_with({}, False) + + def test_agent_disabled(self): + self.override_config('disable_murano_agent', True, 'engine') + a = self.runner.testAgent() + self.assertFalse(a.enabled) + self.assertRaises(agent.AgentException, a.call, {}, None) + self.assertRaises(agent.AgentException, a.send, {}, None) + self.assertRaises(agent.AgentException, a.callRaw, {}) + self.assertRaises(agent.AgentException, a.sendRaw, {})