diff options
| author | Nachi Ueno <ueno.nachi@lab.ntt.co.jp> | 2011-08-23 20:04:53 +0000 |
|---|---|---|
| committer | Tarmac <> | 2011-08-23 20:04:53 +0000 |
| commit | 474d394ebb077bb2cfc778d67ab4b1e3ccc7ceb0 (patch) | |
| tree | c3ef409ab9a567f8646a8278433e2b41256a3753 | |
| parent | 731d4e7f7631e8a8a303bef7779e3f0e513332ae (diff) | |
| parent | 6bbef7627200f6c6ef27b5ae5c9b114e8e6d0f52 (diff) | |
| download | nova-474d394ebb077bb2cfc778d67ab4b1e3ccc7ceb0.tar.gz nova-474d394ebb077bb2cfc778d67ab4b1e3ccc7ceb0.tar.xz nova-474d394ebb077bb2cfc778d67ab4b1e3ccc7ceb0.zip | |
I added notifications decorator for each API call using monkey_patching.
By this merge, users can get API call notification from any modules.
| -rwxr-xr-x | bin/nova-api | 1 | ||||
| -rwxr-xr-x | bin/nova-api-ec2 | 1 | ||||
| -rwxr-xr-x | bin/nova-api-os | 1 | ||||
| -rwxr-xr-x | bin/nova-compute | 1 | ||||
| -rwxr-xr-x | bin/nova-network | 1 | ||||
| -rwxr-xr-x | bin/nova-objectstore | 1 | ||||
| -rwxr-xr-x | bin/nova-scheduler | 3 | ||||
| -rwxr-xr-x | bin/nova-volume | 1 | ||||
| -rw-r--r-- | nova/flags.py | 9 | ||||
| -rw-r--r-- | nova/notifier/api.py | 27 | ||||
| -rw-r--r-- | nova/tests/monkey_patch_example/__init__.py | 33 | ||||
| -rw-r--r-- | nova/tests/monkey_patch_example/example_a.py | 29 | ||||
| -rw-r--r-- | nova/tests/monkey_patch_example/example_b.py | 30 | ||||
| -rw-r--r-- | nova/tests/test_notifier.py | 21 | ||||
| -rw-r--r-- | nova/tests/test_utils.py | 45 | ||||
| -rw-r--r-- | nova/utils.py | 41 |
16 files changed, 245 insertions, 0 deletions
diff --git a/bin/nova-api b/bin/nova-api index 38e2624d8..d8635978e 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -45,6 +45,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() servers = [] for api in flags.FLAGS.enabled_apis: servers.append(service.WSGIService(api)) diff --git a/bin/nova-api-ec2 b/bin/nova-api-ec2 index df50f713d..9f82a69e4 100755 --- a/bin/nova-api-ec2 +++ b/bin/nova-api-ec2 @@ -41,6 +41,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() server = service.WSGIService('ec2') service.serve(server) service.wait() diff --git a/bin/nova-api-os b/bin/nova-api-os index 374e850ea..83a808987 100755 --- a/bin/nova-api-os +++ b/bin/nova-api-os @@ -41,6 +41,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() server = service.WSGIService('osapi') service.serve(server) service.wait() diff --git a/bin/nova-compute b/bin/nova-compute index 5239fae72..0c69a8129 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -43,6 +43,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() server = service.Service.create(binary='nova-compute') service.serve(server) service.wait() diff --git a/bin/nova-network b/bin/nova-network index 57759d30a..0f1482515 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -43,6 +43,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() server = service.Service.create(binary='nova-network') service.serve(server) service.wait() diff --git a/bin/nova-objectstore b/bin/nova-objectstore index c7a76e120..757301c24 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -49,6 +49,7 @@ if __name__ == '__main__': utils.default_flagfile() FLAGS(sys.argv) logging.setup() + utils.monkey_patch() router = s3server.S3Application(FLAGS.buckets_path) server = wsgi.Server("S3 Objectstore", router, diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 2e168cbc6..c1033a304 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -22,6 +22,7 @@ import eventlet eventlet.monkey_patch() +import gettext import os import sys @@ -33,6 +34,7 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) +gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -43,6 +45,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() server = service.Service.create(binary='nova-scheduler') service.serve(server) service.wait() diff --git a/bin/nova-volume b/bin/nova-volume index 5405aebbb..8caa0f44a 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -43,6 +43,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() server = service.Service.create(binary='nova-volume') service.serve(server) service.wait() diff --git a/nova/flags.py b/nova/flags.py index ce5356723..95000df1b 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -404,3 +404,12 @@ DEFINE_string('root_helper', 'sudo', 'Command prefix to use for running commands as root') DEFINE_bool('use_ipv6', False, 'use ipv6') + +DEFINE_bool('monkey_patch', False, + 'Whether to log monkey patching') + +DEFINE_list('monkey_patch_modules', + ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator', + 'nova.compute.api:nova.notifier.api.notify_decorator'], + 'Module list representing monkey ' + 'patched module and decorator') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index e18f3e280..6ef4a050e 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -25,6 +25,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('default_notification_level', 'INFO', 'Default notification level for outgoing notifications') +flags.DEFINE_string('default_publisher_id', FLAGS.host, + 'Default publisher_id for outgoing notifications') + WARN = 'WARN' INFO = 'INFO' @@ -39,6 +42,30 @@ class BadPriorityException(Exception): pass +def notify_decorator(name, fn): + """ decorator for notify which is used from utils.monkey_patch() + + :param name: name of the function + :param function: - object of the function + :returns: function -- decorated function + + """ + def wrapped_func(*args, **kwarg): + body = {} + body['args'] = [] + body['kwarg'] = {} + for arg in args: + body['args'].append(arg) + for key in kwarg: + body['kwarg'][key] = kwarg[key] + notify(FLAGS.default_publisher_id, + name, + FLAGS.default_notification_level, + body) + return fn(*args, **kwarg) + return wrapped_func + + def publisher_id(service, host=None): if not host: host = FLAGS.host diff --git a/nova/tests/monkey_patch_example/__init__.py b/nova/tests/monkey_patch_example/__init__.py new file mode 100644 index 000000000..25cf9ccfe --- /dev/null +++ b/nova/tests/monkey_patch_example/__init__.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# 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. +"""Example Module for testing utils.monkey_patch().""" + + +CALLED_FUNCTION = [] + + +def example_decorator(name, function): + """ decorator for notify which is used from utils.monkey_patch() + + :param name: name of the function + :param function: - object of the function + :returns: function -- decorated function + """ + def wrapped_func(*args, **kwarg): + CALLED_FUNCTION.append(name) + return function(*args, **kwarg) + return wrapped_func diff --git a/nova/tests/monkey_patch_example/example_a.py b/nova/tests/monkey_patch_example/example_a.py new file mode 100644 index 000000000..21e79bcb0 --- /dev/null +++ b/nova/tests/monkey_patch_example/example_a.py @@ -0,0 +1,29 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# 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. +"""Example Module A for testing utils.monkey_patch().""" + + +def example_function_a(): + return 'Example function' + + +class ExampleClassA(): + def example_method(self): + return 'Example method' + + def example_method_add(self, arg1, arg2): + return arg1 + arg2 diff --git a/nova/tests/monkey_patch_example/example_b.py b/nova/tests/monkey_patch_example/example_b.py new file mode 100644 index 000000000..9d8f6d339 --- /dev/null +++ b/nova/tests/monkey_patch_example/example_b.py @@ -0,0 +1,30 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +"""Example Module B for testing utils.monkey_patch().""" + + +def example_function_b(): + return 'Example function' + + +class ExampleClassB(): + def example_method(self): + return 'Example method' + + def example_method_add(self, arg1, arg2): + return arg1 + arg2 diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 64b799a2c..7de3a4a99 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -134,3 +134,24 @@ class NotifierTestCase(test.TestCase): self.assertEqual(msg['event_type'], 'error_notification') self.assertEqual(msg['priority'], 'ERROR') self.assertEqual(msg['payload']['error'], 'foo') + + def test_send_notification_by_decorator(self): + self.notify_called = False + + def example_api(arg1, arg2): + return arg1 + arg2 + + example_api = nova.notifier.api.notify_decorator( + 'example_api', + example_api) + + def mock_notify(cls, *args): + self.notify_called = True + + self.stubs.Set(nova.notifier.no_op_notifier, 'notify', + mock_notify) + + class Mock(object): + pass + self.assertEqual(3, example_api(1, 2)) + self.assertEqual(self.notify_called, True) diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 28e366a8e..1ba794a1a 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -18,6 +18,7 @@ import datetime import os import tempfile +import nova from nova import exception from nova import test from nova import utils @@ -394,3 +395,47 @@ class ToPrimitiveTestCase(test.TestCase): self.assertTrue(ret[0].startswith(u"<module 'datetime' from ")) self.assertTrue(ret[1].startswith(u'<function foo at 0x')) self.assertEquals(ret[2], u'<built-in function dir>') + + +class MonkeyPatchTestCase(test.TestCase): + """Unit test for utils.monkey_patch().""" + def setUp(self): + super(MonkeyPatchTestCase, self).setUp() + self.example_package = 'nova.tests.monkey_patch_example.' + self.flags( + monkey_patch=True, + monkey_patch_modules=[self.example_package + 'example_a' + ':' + + self.example_package + 'example_decorator']) + + def test_monkey_patch(self): + utils.monkey_patch() + nova.tests.monkey_patch_example.CALLED_FUNCTION = [] + from nova.tests.monkey_patch_example import example_a, example_b + + self.assertEqual('Example function', example_a.example_function_a()) + exampleA = example_a.ExampleClassA() + exampleA.example_method() + ret_a = exampleA.example_method_add(3, 5) + self.assertEqual(ret_a, 8) + + self.assertEqual('Example function', example_b.example_function_b()) + exampleB = example_b.ExampleClassB() + exampleB.example_method() + ret_b = exampleB.example_method_add(3, 5) + + self.assertEqual(ret_b, 8) + package_a = self.example_package + 'example_a.' + self.assertTrue(package_a + 'example_function_a' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) + + self.assertTrue(package_a + 'ExampleClassA.example_method' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) + self.assertTrue(package_a + 'ExampleClassA.example_method_add' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) + package_b = self.example_package + 'example_b.' + self.assertFalse(package_b + 'example_function_b' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) + self.assertFalse(package_b + 'ExampleClassB.example_method' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) + self.assertFalse(package_b + 'ExampleClassB.example_method_add' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) diff --git a/nova/utils.py b/nova/utils.py index fc4bbd53b..21e6221b2 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -35,6 +35,7 @@ import sys import time import types import uuid +import pyclbr from xml.sax import saxutils from eventlet import event @@ -860,3 +861,43 @@ def is_valid_ipv4(address): except ValueError: return False return True + + +def monkey_patch(): + """ If the Flags.monkey_patch set as True, + this functuion patches a decorator + for all functions in specified modules. + You can set decorators for each modules + using FLAGS.monkey_patch_modules. + The format is "Module path:Decorator function". + Example: 'nova.api.ec2.cloud:nova.notifier.api.notify_decorator' + + Parameters of the decorator is as follows. + (See nova.notifier.api.notify_decorator) + + name - name of the function + function - object of the function + """ + # If FLAGS.monkey_patch is not True, this function do nothing. + if not FLAGS.monkey_patch: + return + # Get list of modules and decorators + for module_and_decorator in FLAGS.monkey_patch_modules: + module, decorator_name = module_and_decorator.split(':') + # import decorator function + decorator = import_class(decorator_name) + __import__(module) + # Retrieve module information using pyclbr + module_data = pyclbr.readmodule_ex(module) + for key in module_data.keys(): + # set the decorator for the class methods + if isinstance(module_data[key], pyclbr.Class): + clz = import_class("%s.%s" % (module, key)) + for method, func in inspect.getmembers(clz, inspect.ismethod): + setattr(clz, method,\ + decorator("%s.%s.%s" % (module, key, method), func)) + # set the decorator for the function + if isinstance(module_data[key], pyclbr.Function): + func = import_class("%s.%s" % (module, key)) + setattr(sys.modules[module], key,\ + decorator("%s.%s" % (module, key), func)) |
