]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Import openstack.common.log and use inside heat infrastructure
authorSteven Dake <sdake@redhat.com>
Sat, 14 Jul 2012 19:18:32 +0000 (12:18 -0700)
committerSteven Dake <sdake@redhat.com>
Sat, 14 Jul 2012 20:51:38 +0000 (13:51 -0700)
Allows heat to match openstack-common log calling conventions such
as using a context object or instance UUID for better traceability.

Change-Id: Idaa6c04270d9d7143c85988d685c0f9e241b635b
Signed-off-by: Steven Dake <sdake@redhat.com>
43 files changed:
bin/heat-api
bin/heat-engine
bin/heat-metadata
heat/api/middleware/version_negotiation.py
heat/api/v1/__init__.py
heat/api/v1/stacks.py
heat/client.py
heat/db/sqlalchemy/session.py
heat/engine/api.py
heat/engine/auth.py
heat/engine/autoscaling.py
heat/engine/checkeddict.py
heat/engine/cloud_watch.py
heat/engine/eip.py
heat/engine/instance.py
heat/engine/loadbalancer.py
heat/engine/manager.py
heat/engine/parser.py
heat/engine/resources.py
heat/engine/security_group.py
heat/engine/stack.py
heat/engine/user.py
heat/engine/volume.py
heat/engine/wait_condition.py
heat/engine/watchrule.py
heat/manager.py
heat/metadata/api/v1/__init__.py
heat/openstack/common/jsonutils.py [new file with mode: 0644]
heat/openstack/common/log.py [new file with mode: 0644]
heat/openstack/common/notifier/__init__.py [new file with mode: 0644]
heat/openstack/common/notifier/api.py [new file with mode: 0644]
heat/openstack/common/notifier/list_notifier.py [new file with mode: 0644]
heat/openstack/common/notifier/log_notifier.py [new file with mode: 0644]
heat/openstack/common/notifier/no_op_notifier.py [new file with mode: 0644]
heat/openstack/common/notifier/rabbit_notifier.py [new file with mode: 0644]
heat/openstack/common/notifier/test_notifier.py [new file with mode: 0644]
heat/rpc/__init__.py
heat/rpc/amqp.py
heat/rpc/common.py
heat/rpc/impl_qpid.py
heat/service.py
heat/utils.py
openstack-common.conf

index 2fe28816bed53ead7a0b54521e10825f1eb3cb33..d588cd0a691a8feac36c4a41bcf048b6e0f9c12c 100755 (executable)
@@ -33,12 +33,13 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
 
 gettext.install('heat', unicode=1)
 
-import logging
 from heat import rpc
 from heat.common import config
 from heat.common import wsgi
 from paste import httpserver
 
+from heat.openstack.common import log as logging
+
 LOG = logging.getLogger('heat.api')
 
 if __name__ == '__main__':
index dba0a6c1bdbd75b9edc8d761e6eb0c20d5636165..9df2929c2ab4c447d0ffa55c5fd109635c540fc1 100755 (executable)
@@ -26,7 +26,8 @@ eventlet.monkey_patch()
 
 import os
 import sys
-import logging
+
+from heat.openstack.common import log as logging
 
 # If ../heat/__init__.py exists, add ../ to Python search path, so that
 # it will override what happens to be installed in /usr/(local/)lib/python...
index fb7802beba3d3c094b495be06fc0d28be187b6d7..cd3a57dcb6c623f539cd5f2052485752a8716303 100755 (executable)
@@ -33,13 +33,14 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
 
 gettext.install('heat', unicode=1)
 
-import logging
 from heat import rpc
 from heat.common import config
 from heat.common import wsgi
 from heat.common import context
 from paste import httpserver
 
+from heat.openstack.common import log as logging
+
 LOG = logging.getLogger('heat.metadata')
 
 
index a4efcc605726e3f92f35d851b888a36c052fa0ef..8f2826df6fd42cfc6720e939d8bde1be15009475 100644 (file)
@@ -19,9 +19,10 @@ and/or Accept headers and attempts to negotiate an API controller to
 return
 """
 
-import logging
 import re
 
+from heat.openstack.common import log as logging
+
 from heat.api import versions
 from heat.common import wsgi
 
index e5f1d1175d2e0d2fa30efd8422eae651a622ed08..9b7fa2dc730888a8c25774317adf9f91eefca043 100644 (file)
@@ -16,7 +16,6 @@
 import json
 import urlparse
 import httplib
-import logging
 import routes
 import gettext
 
@@ -31,6 +30,8 @@ from heat import utils
 from heat.common import context
 from heat.api.v1 import exception
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger(__name__)
 
 
index dfa0e046b2befccc8e89f87284a66a1f998c57dc..1f9010533747f75a78012b2aae538280eee30e6b 100644 (file)
@@ -18,7 +18,6 @@
 """
 import httplib
 import json
-import logging
 import os
 import socket
 import sys
@@ -33,6 +32,7 @@ from heat import utils
 from heat import rpc
 import heat.rpc.common as rpc_common
 
+from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.api.v1.stacks')
 
index 4a5dc1f03c01c17a53c4bdbc55d0f20b0139bdcf..078dc692800df4d2632939d40967e2e8ba937d2a 100644 (file)
@@ -18,11 +18,13 @@ Client classes for callers of a heat system
 """
 
 from lxml import etree
-import logging
 import os
 from heat.common import client as base_client
 from heat.common import exception
 from heat.cloudformations import *
+
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger(__name__)
 
 
index dcdd6efbb12b1755edc5a23158c0c52f11637095..f4f783d155d964406ef9e74b9f04b330639e4d17 100644 (file)
 
 """Session Handling for SQLAlchemy backend."""
 
-import logging
 import sqlalchemy.interfaces
 import sqlalchemy.orm
 from sqlalchemy.exc import DisconnectionError
 
+from heat.openstack.common import log as logging
+
 from heat.db import api as db_api
 from heat.openstack.common import cfg
 
index fc81eb869540389e1f501d7b95114fa89eaeca4d..a87a3fb805bcbd2d13b0b23ad96ac46d37ef1072 100644 (file)
 #    under the License.
 
 import re
-import logging
 from heat.common import utils as heat_utils
 from heat.engine import parser
 
+from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.manager')
 
index c8b27cfdc85ff386a62fa47679d5a52ae9eb1ce6..bd547b0c5692789b8f9fe6a61621f6431ec89b7b 100644 (file)
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import json
 import httplib
 import urlparse
@@ -22,6 +21,7 @@ from novaclient.exceptions import BadRequest
 from novaclient.exceptions import NotFound
 from novaclient.exceptions import AuthorizationFailure
 from heat.common import context
+from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.auth')
 
index 212487904dfda5e99b50cf232e5077e9709f17db..ccecf21e0e61a185e248ecf5d4e374b280fbe04e 100644 (file)
@@ -14,7 +14,6 @@
 #    under the License.
 
 import eventlet
-import logging
 import json
 import os
 
@@ -23,6 +22,8 @@ from heat.db import api as db_api
 from heat.engine import instance
 from heat.engine.resources import Resource
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.autoscaling')
 
 
index 7893a6689403c28de219a5083a615c8dcbc08779..554aab08d3ab284c8d14934cc4bd57d16155e078 100644 (file)
@@ -15,7 +15,8 @@
 
 import collections
 import re
-import logging
+
+from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.checkeddict')
 
index 451986ea9545459f433b449513577494ad450161..68966286c6984fc6f4df03c4ee018123dae79570 100644 (file)
@@ -14,7 +14,6 @@
 #    under the License.
 
 import eventlet
-import logging
 import json
 import os
 
@@ -22,6 +21,8 @@ from heat.common import exception
 from heat.db import api as db_api
 from heat.engine.resources import Resource
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.cloud_watch')
 
 
index 5aa032fabac1fa01108c5d380f4c1a426d080c9c..de22eeae5c51a28b3c4c00ae3831c1c5d1e46b78 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
-
 from heat.common import exception
 from heat.engine.resources import Resource
 from novaclient.exceptions import NotFound
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.eip')
 
 
index a1c794de5fd7c521451fdc5862cf7cb48f59c960..351886f837581ce34f76835d05dc9dfcddd3d7d6 100644 (file)
@@ -14,7 +14,6 @@
 #    under the License.
 
 import eventlet
-import logging
 import os
 import json
 import sys
@@ -26,6 +25,8 @@ import heat
 from heat.engine import resources
 from heat.common import exception
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.instance')
 
 
index fe2705337c8a7469f0d094de638274edf8a96c0a..dfe473695b66dcde1c5b12881e0ce377a50f53c5 100644 (file)
@@ -15,7 +15,6 @@
 
 import urllib2
 import json
-import logging
 
 from heat.common import exception
 from heat.engine import stack
@@ -23,6 +22,8 @@ from heat.db import api as db_api
 from heat.engine import parser
 from novaclient.exceptions import NotFound
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger(__file__)
 
 lb_template = '''
index 9ea607547d35aeb183465401fc38929182223d2a..d0eb2b5cd63bed39e843aa1b59365f773acdf7ca 100644 (file)
@@ -16,7 +16,6 @@
 
 from copy import deepcopy
 import datetime
-import logging
 import webob
 import json
 import urlparse
@@ -33,7 +32,9 @@ from heat.engine import parser
 from heat.engine import resources
 from heat.engine import watchrule
 from heat.engine import auth
+
 from heat.openstack.common import timeutils
+from heat.openstack.common import log as logging
 
 from novaclient.v1_1 import client
 from novaclient.exceptions import BadRequest
index c91792773e525374b4f8f60d0247732366464c66..230b522e2b4e50c0602297aed22594d06a5b052f 100644 (file)
@@ -17,7 +17,6 @@ import eventlet
 import json
 import functools
 import copy
-import logging
 
 from heat.common import exception
 from heat.engine import checkeddict
@@ -25,6 +24,7 @@ from heat.engine import dependencies
 from heat.engine import resources
 from heat.db import api as db_api
 
+from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.parser')
 
index a7a70d8529708fb5d7e8795c189a385592137762..db2608e1bfc19ea4c1adc7e356c404f4e72c8edc 100644 (file)
@@ -15,7 +15,6 @@
 
 import base64
 from datetime import datetime
-import logging
 
 from novaclient.v1_1 import client as nc
 from keystoneclient.v2_0 import client as kc
@@ -26,6 +25,8 @@ from heat.db import api as db_api
 from heat.engine import checkeddict
 from heat.engine import auth
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.resources')
 
 
index edc913ed3e6084026fb9fc238e777bff6ead0c0d..65a01dda5e307b4bd6ba63da7d8a650bce8a8f96 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 from novaclient.exceptions import BadRequest
 from novaclient.exceptions import NotFound
 from heat.common import exception
 from heat.engine.resources import Resource
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.security_group')
 
 
index cbc46aed4c8ebd0aa8e534a914f26dcfe3418e33..145c8d3bdb6aacc3b17eaab5ae217eace3d99d03 100644 (file)
 
 import urllib2
 import json
-import logging
 
 from heat.common import exception
 from heat.engine.resources import Resource
 from heat.db import api as db_api
 from heat.engine import parser
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger(__file__)
 
 
index 083fb077a9b53715edb8d3fd56aad83784ff2130..be09b48534268215e16024945153ce4e4ead58cc 100644 (file)
 #    under the License.
 
 import eventlet
-import logging
 from heat.common import exception
 from heat.engine.resources import Resource
 
+from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.user')
 
index d06c77726ca6e1cb054f78751a241f82c225cdab..dee0debe984f9bc9582917b5740f46dc27ee38d2 100644 (file)
@@ -14,7 +14,7 @@
 #    under the License.
 
 import eventlet
-import logging
+from heat.openstack.common import log as logging
 import re
 
 from heat.common import exception
index a391925afb1c2397fa65220741f2f818c783a46d..fc908a573b41a823367bff71e9cbfe59e6126a65 100644 (file)
 #    under the License.
 
 import eventlet
-import logging
 import json
 
 from heat.common import exception
 from heat.engine import resources
 
+from heat.openstack.common import log as logging
+
 logger = logging.getLogger('heat.engine.wait_condition')
 
 
index e11ccfd19c9c49b8bfbfd033b3b85501b3210be2..4767f5bd0e71a4cec8c20d1af15f05ba12d27db5 100644 (file)
@@ -15,7 +15,7 @@
 
 
 import datetime
-import logging
+from heat.openstack.common import log as logging
 from heat.openstack.common import timeutils
 
 logger = logging.getLogger('heat.engine.watchrule')
index 8940fdc6309cddb12ebc623aa8f6928e94014fea..3739f18ef46686c76294a299f3dc50256587dbc2 100644 (file)
@@ -53,11 +53,12 @@ This module provides Manager, a base class for managers.
 
 """
 
-import logging
-
 from heat import version
 from heat.common import config
 
+from heat.openstack.common import log as logging
+
+
 FLAGS = config.FLAGS
 LOG = logging.getLogger(__name__)
 
index 2a629ef415857893f274879af32ef51c082da6e5..748992917137ec91c2a6d2358faf72825b80635c 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import routes
 
 from heat.common import wsgi
 from heat.metadata.api.v1 import metadata
 
+from heat.openstack.common import log as logging
+
 
 class API(wsgi.Router):
     """
diff --git a/heat/openstack/common/jsonutils.py b/heat/openstack/common/jsonutils.py
new file mode 100644 (file)
index 0000000..11b7e1e
--- /dev/null
@@ -0,0 +1,144 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# 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.
+
+'''
+JSON related utilities.
+
+This module provides a few things:
+
+    1) A handy function for getting an object down to something that can be
+    JSON serialized.  See to_primitive().
+
+    2) Wrappers around loads() and dumps().  The dumps() wrapper will
+    automatically use to_primitive() for you if needed.
+
+    3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
+    is available.
+'''
+
+
+import datetime
+import inspect
+import itertools
+import json
+import xmlrpclib
+
+
+def to_primitive(value, convert_instances=False, level=0):
+    """Convert a complex object into primitives.
+
+    Handy for JSON serialization. We can optionally handle instances,
+    but since this is a recursive function, we could have cyclical
+    data structures.
+
+    To handle cyclical data structures we could track the actual objects
+    visited in a set, but not all objects are hashable. Instead we just
+    track the depth of the object inspections and don't go too deep.
+
+    Therefore, convert_instances=True is lossy ... be aware.
+
+    """
+    nasty = [inspect.ismodule, inspect.isclass, inspect.ismethod,
+             inspect.isfunction, inspect.isgeneratorfunction,
+             inspect.isgenerator, inspect.istraceback, inspect.isframe,
+             inspect.iscode, inspect.isbuiltin, inspect.isroutine,
+             inspect.isabstract]
+    for test in nasty:
+        if test(value):
+            return unicode(value)
+
+    # value of itertools.count doesn't get caught by inspects
+    # above and results in infinite loop when list(value) is called.
+    if type(value) == itertools.count:
+        return unicode(value)
+
+    # FIXME(vish): Workaround for LP bug 852095. Without this workaround,
+    #              tests that raise an exception in a mocked method that
+    #              has a @wrap_exception with a notifier will fail. If
+    #              we up the dependency to 0.5.4 (when it is released) we
+    #              can remove this workaround.
+    if getattr(value, '__module__', None) == 'mox':
+        return 'mock'
+
+    if level > 3:
+        return '?'
+
+    # The try block may not be necessary after the class check above,
+    # but just in case ...
+    try:
+        # It's not clear why xmlrpclib created their own DateTime type, but
+        # for our purposes, make it a datetime type which is explicitly
+        # handled
+        if isinstance(value, xmlrpclib.DateTime):
+            value = datetime.datetime(*tuple(value.timetuple())[:6])
+
+        if isinstance(value, (list, tuple)):
+            o = []
+            for v in value:
+                o.append(to_primitive(v, convert_instances=convert_instances,
+                                      level=level))
+            return o
+        elif isinstance(value, dict):
+            o = {}
+            for k, v in value.iteritems():
+                o[k] = to_primitive(v, convert_instances=convert_instances,
+                                    level=level)
+            return o
+        elif isinstance(value, datetime.datetime):
+            return str(value)
+        elif hasattr(value, 'iteritems'):
+            return to_primitive(dict(value.iteritems()),
+                                convert_instances=convert_instances,
+                                level=level)
+        elif hasattr(value, '__iter__'):
+            return to_primitive(list(value), level)
+        elif convert_instances and hasattr(value, '__dict__'):
+            # Likely an instance of something. Watch for cycles.
+            # Ignore class member vars.
+            return to_primitive(value.__dict__,
+                                convert_instances=convert_instances,
+                                level=level + 1)
+        else:
+            return value
+    except TypeError, e:
+        # Class objects are tricky since they may define something like
+        # __iter__ defined but it isn't callable as list().
+        return unicode(value)
+
+
+def dumps(value, default=to_primitive, **kwargs):
+    return json.dumps(value, default=default, **kwargs)
+
+
+def loads(s):
+    return json.loads(s)
+
+
+def load(s):
+    return json.load(s)
+
+
+try:
+    import anyjson
+except ImportError:
+    pass
+else:
+    anyjson._modules.append((__name__, 'dumps', TypeError,
+                                       'loads', ValueError, 'load'))
+    anyjson.force_implementation(__name__)
diff --git a/heat/openstack/common/log.py b/heat/openstack/common/log.py
new file mode 100644 (file)
index 0000000..e4c808f
--- /dev/null
@@ -0,0 +1,459 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""Openstack logging handler.
+
+This module adds to logging functionality by adding the option to specify
+a context object when calling the various log methods.  If the context object
+is not specified, default formatting is used. Additionally, an instance uuid
+may be passed as part of the log message, which is intended to make it easier
+for admins to find messages related to a specific instance.
+
+It also allows setting of formatting information through conf.
+
+"""
+
+import cStringIO
+import inspect
+import itertools
+import logging
+import logging.config
+import logging.handlers
+import os
+import stat
+import sys
+import traceback
+
+from heat.openstack.common import cfg
+from heat.openstack.common.gettextutils import _
+from heat.openstack.common import jsonutils
+from heat.openstack.common import local
+from heat.openstack.common import notifier
+
+
+log_opts = [
+    cfg.StrOpt('logging_context_format_string',
+               default='%(asctime)s %(levelname)s %(name)s [%(request_id)s '
+                       '%(user_id)s %(project_id)s] %(instance)s'
+                       '%(message)s',
+               help='format string to use for log messages with context'),
+    cfg.StrOpt('logging_default_format_string',
+               default='%(asctime)s %(levelname)s %(name)s [-] %(instance)s'
+                       '%(message)s',
+               help='format string to use for log messages without context'),
+    cfg.StrOpt('logging_debug_format_suffix',
+               default='from (pid=%(process)d) %(funcName)s '
+                       '%(pathname)s:%(lineno)d',
+               help='data to append to log format when level is DEBUG'),
+    cfg.StrOpt('logging_exception_prefix',
+               default='%(asctime)s TRACE %(name)s %(instance)s',
+               help='prefix each line of exception output with this format'),
+    cfg.ListOpt('default_log_levels',
+                default=[
+                  'amqplib=WARN',
+                  'sqlalchemy=WARN',
+                  'boto=WARN',
+                  'suds=INFO',
+                  'keystone=INFO',
+                  'eventlet.wsgi.server=WARN'
+                  ],
+                help='list of logger=LEVEL pairs'),
+    cfg.BoolOpt('publish_errors',
+                default=False,
+                help='publish error events'),
+
+    # NOTE(mikal): there are two options here because sometimes we are handed
+    # a full instance (and could include more information), and other times we
+    # are just handed a UUID for the instance.
+    cfg.StrOpt('instance_format',
+               default='[instance: %(uuid)s] ',
+               help='If an instance is passed with the log message, format '
+                    'it like this'),
+    cfg.StrOpt('instance_uuid_format',
+               default='[instance: %(uuid)s] ',
+               help='If an instance UUID is passed with the log message, '
+                    'format it like this'),
+    ]
+
+
+generic_log_opts = [
+    cfg.StrOpt('logdir',
+               default=None,
+               help='Log output to a per-service log file in named directory'),
+    cfg.StrOpt('logfile',
+               default=None,
+               help='Log output to a named file'),
+    cfg.BoolOpt('use_stderr',
+                default=True,
+                help='Log output to standard error'),
+    cfg.StrOpt('logfile_mode',
+               default='0644',
+               help='Default file mode used when creating log files'),
+    ]
+
+
+CONF = cfg.CONF
+CONF.register_opts(generic_log_opts)
+CONF.register_opts(log_opts)
+
+# our new audit level
+# NOTE(jkoelker) Since we synthesized an audit level, make the logging
+#                module aware of it so it acts like other levels.
+logging.AUDIT = logging.INFO + 1
+logging.addLevelName(logging.AUDIT, 'AUDIT')
+
+
+try:
+    NullHandler = logging.NullHandler
+except AttributeError:  # NOTE(jkoelker) NullHandler added in Python 2.7
+    class NullHandler(logging.Handler):
+        def handle(self, record):
+            pass
+
+        def emit(self, record):
+            pass
+
+        def createLock(self):
+            self.lock = None
+
+
+def _dictify_context(context):
+    if context is None:
+        return None
+    if not isinstance(context, dict) and getattr(context, 'to_dict', None):
+        context = context.to_dict()
+    return context
+
+
+def _get_binary_name():
+    return os.path.basename(inspect.stack()[-1][1])
+
+
+def _get_log_file_path(binary=None):
+    logfile = CONF.log_file or CONF.logfile
+    logdir = CONF.log_dir or CONF.logdir
+
+    if logfile and not logdir:
+        return logfile
+
+    if logfile and logdir:
+        return os.path.join(logdir, logfile)
+
+    if logdir:
+        binary = binary or _get_binary_name()
+        return '%s.log' % (os.path.join(logdir, binary),)
+
+
+class ContextAdapter(logging.LoggerAdapter):
+    warn = logging.LoggerAdapter.warning
+
+    def __init__(self, logger, project_name, version_string):
+        self.logger = logger
+        self.project = project_name
+        self.version = version_string
+
+    def audit(self, msg, *args, **kwargs):
+        self.log(logging.AUDIT, msg, *args, **kwargs)
+
+    def process(self, msg, kwargs):
+        if 'extra' not in kwargs:
+            kwargs['extra'] = {}
+        extra = kwargs['extra']
+
+        context = kwargs.pop('context', None)
+        if not context:
+            context = getattr(local.store, 'context', None)
+        if context:
+            extra.update(_dictify_context(context))
+
+        instance = kwargs.pop('instance', None)
+        instance_extra = ''
+        if instance:
+            instance_extra = CONF.instance_format % instance
+        else:
+            instance_uuid = kwargs.pop('instance_uuid', None)
+            if instance_uuid:
+                instance_extra = (CONF.instance_uuid_format
+                                  % {'uuid': instance_uuid})
+        extra.update({'instance': instance_extra})
+
+        extra.update({"project": self.project})
+        extra.update({"version": self.version})
+        extra['extra'] = extra.copy()
+        return msg, kwargs
+
+
+class JSONFormatter(logging.Formatter):
+    def __init__(self, fmt=None, datefmt=None):
+        # NOTE(jkoelker) we ignore the fmt argument, but its still there
+        #                since logging.config.fileConfig passes it.
+        self.datefmt = datefmt
+
+    def formatException(self, ei, strip_newlines=True):
+        lines = traceback.format_exception(*ei)
+        if strip_newlines:
+            lines = [itertools.ifilter(lambda x: x,
+                                      line.rstrip().splitlines())
+                    for line in lines]
+            lines = list(itertools.chain(*lines))
+        return lines
+
+    def format(self, record):
+        message = {'message': record.getMessage(),
+                   'asctime': self.formatTime(record, self.datefmt),
+                   'name': record.name,
+                   'msg': record.msg,
+                   'args': record.args,
+                   'levelname': record.levelname,
+                   'levelno': record.levelno,
+                   'pathname': record.pathname,
+                   'filename': record.filename,
+                   'module': record.module,
+                   'lineno': record.lineno,
+                   'funcname': record.funcName,
+                   'created': record.created,
+                   'msecs': record.msecs,
+                   'relative_created': record.relativeCreated,
+                   'thread': record.thread,
+                   'thread_name': record.threadName,
+                   'process_name': record.processName,
+                   'process': record.process,
+                   'traceback': None}
+
+        if hasattr(record, 'extra'):
+            message['extra'] = record.extra
+
+        if record.exc_info:
+            message['traceback'] = self.formatException(record.exc_info)
+
+        return jsonutils.dumps(message)
+
+
+class PublishErrorsHandler(logging.Handler):
+    def emit(self, record):
+        if 'list_notifier_drivers' in CONF:
+            if ('heat.openstack.common.notifier.log_notifier' in
+                CONF.list_notifier_drivers):
+                return
+        notifier.api.notify(None, 'error.publisher',
+                                 'error_notification',
+                                 notifier.api.ERROR,
+                                 dict(error=record.msg))
+
+
+def handle_exception(type, value, tb):
+    extra = {}
+    if CONF.verbose:
+        extra['exc_info'] = (type, value, tb)
+    getLogger().critical(str(value), **extra)
+
+
+def setup(product_name):
+    """Setup logging."""
+    sys.excepthook = handle_exception
+
+    if CONF.log_config:
+        try:
+            logging.config.fileConfig(CONF.log_config)
+        except Exception:
+            traceback.print_exc()
+            raise
+    else:
+        _setup_logging_from_conf(product_name)
+
+
+def _find_facility_from_conf():
+    facility_names = logging.handlers.SysLogHandler.facility_names
+    facility = getattr(logging.handlers.SysLogHandler,
+                       CONF.syslog_log_facility,
+                       None)
+
+    if facility is None and CONF.syslog_log_facility in facility_names:
+        facility = facility_names.get(CONF.syslog_log_facility)
+
+    if facility is None:
+        valid_facilities = facility_names.keys()
+        consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON',
+                  'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS',
+                  'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP',
+                  'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3',
+                  'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7']
+        valid_facilities.extend(consts)
+        raise TypeError(_('syslog facility must be one of: %s') %
+                        ', '.join("'%s'" % fac
+                                  for fac in valid_facilities))
+
+    return facility
+
+
+def _setup_logging_from_conf(product_name):
+    log_root = getLogger(product_name).logger
+    for handler in log_root.handlers:
+        log_root.removeHandler(handler)
+
+    if CONF.use_syslog:
+        facility = _find_facility_from_conf()
+        syslog = logging.handlers.SysLogHandler(address='/dev/log',
+                                                facility=facility)
+        log_root.addHandler(syslog)
+
+    logpath = _get_log_file_path()
+    if logpath:
+        filelog = logging.handlers.WatchedFileHandler(logpath)
+        log_root.addHandler(filelog)
+
+        mode = int(CONF.logfile_mode, 8)
+        st = os.stat(logpath)
+        if st.st_mode != (stat.S_IFREG | mode):
+            os.chmod(logpath, mode)
+
+    if CONF.use_stderr:
+        streamlog = ColorHandler()
+        log_root.addHandler(streamlog)
+
+    elif not CONF.log_file:
+        # pass sys.stdout as a positional argument
+        # python2.6 calls the argument strm, in 2.7 it's stream
+        streamlog = logging.StreamHandler(sys.stdout)
+        log_root.addHandler(streamlog)
+
+    if CONF.publish_errors:
+        log_root.addHandler(PublishErrorsHandler(logging.ERROR))
+
+    for handler in log_root.handlers:
+        datefmt = CONF.log_date_format
+        if CONF.log_format:
+            handler.setFormatter(logging.Formatter(fmt=CONF.log_format,
+                                                   datefmt=datefmt))
+        handler.setFormatter(LegacyFormatter(datefmt=datefmt))
+
+    if CONF.verbose or CONF.debug:
+        log_root.setLevel(logging.DEBUG)
+    else:
+        log_root.setLevel(logging.INFO)
+
+    level = logging.NOTSET
+    for pair in CONF.default_log_levels:
+        mod, _sep, level_name = pair.partition('=')
+        level = logging.getLevelName(level_name)
+        logger = logging.getLogger(mod)
+        logger.setLevel(level)
+        for handler in log_root.handlers:
+            logger.addHandler(handler)
+
+    # NOTE(jkoelker) Clear the handlers for the root logger that was setup
+    #                by basicConfig in nova/__init__.py and install the
+    #                NullHandler.
+    root = logging.getLogger()
+    for handler in root.handlers:
+        root.removeHandler(handler)
+    handler = NullHandler()
+    handler.setFormatter(logging.Formatter())
+    root.addHandler(handler)
+
+
+_loggers = {}
+
+
+def getLogger(name='unknown', version='unknown'):
+    if name not in _loggers:
+        _loggers[name] = ContextAdapter(logging.getLogger(name),
+                                        name,
+                                        version)
+    return _loggers[name]
+
+
+class WritableLogger(object):
+    """A thin wrapper that responds to `write` and logs."""
+
+    def __init__(self, logger, level=logging.INFO):
+        self.logger = logger
+        self.level = level
+
+    def write(self, msg):
+        self.logger.log(self.level, msg)
+
+
+class LegacyFormatter(logging.Formatter):
+    """A context.RequestContext aware formatter configured through flags.
+
+    The flags used to set format strings are: logging_context_format_string
+    and logging_default_format_string.  You can also specify
+    logging_debug_format_suffix to append extra formatting if the log level is
+    debug.
+
+    For information about what variables are available for the formatter see:
+    http://docs.python.org/library/logging.html#formatter
+
+    """
+
+    def format(self, record):
+        """Uses contextstring if request_id is set, otherwise default."""
+        if 'instance' not in record.__dict__:
+            record.__dict__['instance'] = ''
+
+        if record.__dict__.get('request_id', None):
+            self._fmt = CONF.logging_context_format_string
+        else:
+            self._fmt = CONF.logging_default_format_string
+
+        if (record.levelno == logging.DEBUG and
+            CONF.logging_debug_format_suffix):
+            self._fmt += " " + CONF.logging_debug_format_suffix
+
+        # Cache this on the record, Logger will respect our formated copy
+        if record.exc_info:
+            record.exc_text = self.formatException(record.exc_info, record)
+        return logging.Formatter.format(self, record)
+
+    def formatException(self, exc_info, record=None):
+        """Format exception output with CONF.logging_exception_prefix."""
+        if not record:
+            return logging.Formatter.formatException(self, exc_info)
+
+        stringbuffer = cStringIO.StringIO()
+        traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
+                                  None, stringbuffer)
+        lines = stringbuffer.getvalue().split('\n')
+        stringbuffer.close()
+
+        if CONF.logging_exception_prefix.find('%(asctime)') != -1:
+            record.asctime = self.formatTime(record, self.datefmt)
+
+        formatted_lines = []
+        for line in lines:
+            pl = CONF.logging_exception_prefix % record.__dict__
+            fl = '%s%s' % (pl, line)
+            formatted_lines.append(fl)
+        return '\n'.join(formatted_lines)
+
+
+class ColorHandler(logging.StreamHandler):
+    LEVEL_COLORS = {
+        logging.DEBUG: '\033[00;32m',  # GREEN
+        logging.INFO: '\033[00;36m',  # CYAN
+        logging.AUDIT: '\033[01;36m',  # BOLD CYAN
+        logging.WARN: '\033[01;33m',  # BOLD YELLOW
+        logging.ERROR: '\033[01;31m',  # BOLD RED
+        logging.CRITICAL: '\033[01;31m',  # BOLD RED
+    }
+
+    def format(self, record):
+        record.color = self.LEVEL_COLORS[record.levelno]
+        return logging.StreamHandler.format(self, record)
diff --git a/heat/openstack/common/notifier/__init__.py b/heat/openstack/common/notifier/__init__.py
new file mode 100644 (file)
index 0000000..482d54e
--- /dev/null
@@ -0,0 +1,14 @@
+# 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.
diff --git a/heat/openstack/common/notifier/api.py b/heat/openstack/common/notifier/api.py
new file mode 100644 (file)
index 0000000..b18fff7
--- /dev/null
@@ -0,0 +1,142 @@
+# 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.
+
+import inspect
+import uuid
+
+from heat.openstack.common import cfg
+from heat.openstack.common import context
+from heat.openstack.common.gettextutils import _
+from heat.openstack.common import importutils
+from heat.openstack.common import jsonutils
+from heat.openstack.common import log as logging
+from heat.openstack.common import timeutils
+
+
+LOG = logging.getLogger(__name__)
+
+notifier_opts = [
+    cfg.StrOpt('notification_driver',
+               default='heat.openstack.common.notifier.no_op_notifier',
+               help='Default driver for sending notifications'),
+    cfg.StrOpt('default_notification_level',
+               default='INFO',
+               help='Default notification level for outgoing notifications'),
+    cfg.StrOpt('default_publisher_id',
+               default='$host',
+               help='Default publisher_id for outgoing notifications'),
+    ]
+
+CONF = cfg.CONF
+CONF.register_opts(notifier_opts)
+
+WARN = 'WARN'
+INFO = 'INFO'
+ERROR = 'ERROR'
+CRITICAL = 'CRITICAL'
+DEBUG = 'DEBUG'
+
+log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL)
+
+
+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]
+
+        ctxt = context.get_context_from_function_and_args(fn, args, kwarg)
+        notify(ctxt,
+               CONF.default_publisher_id,
+               name,
+               CONF.default_notification_level,
+               body)
+        return fn(*args, **kwarg)
+    return wrapped_func
+
+
+def publisher_id(service, host=None):
+    if not host:
+        host = CONF.host
+    return "%s.%s" % (service, host)
+
+
+def notify(context, publisher_id, event_type, priority, payload):
+    """Sends a notification using the specified driver
+
+    :param publisher_id: the source worker_type.host of the message
+    :param event_type:   the literal type of event (ex. Instance Creation)
+    :param priority:     patterned after the enumeration of Python logging
+                         levels in the set (DEBUG, WARN, INFO, ERROR, CRITICAL)
+    :param payload:       A python dictionary of attributes
+
+    Outgoing message format includes the above parameters, and appends the
+    following:
+
+    message_id
+      a UUID representing the id for this notification
+
+    timestamp
+      the GMT timestamp the notification was sent at
+
+    The composite message will be constructed as a dictionary of the above
+    attributes, which will then be sent via the transport mechanism defined
+    by the driver.
+
+    Message example::
+
+        {'message_id': str(uuid.uuid4()),
+         'publisher_id': 'compute.host1',
+         'timestamp': timeutils.utcnow(),
+         'priority': 'WARN',
+         'event_type': 'compute.create_instance',
+         'payload': {'instance_id': 12, ... }}
+
+    """
+    if priority not in log_levels:
+        raise BadPriorityException(
+                 _('%s not in valid priorities') % priority)
+
+    # Ensure everything is JSON serializable.
+    payload = jsonutils.to_primitive(payload, convert_instances=True)
+
+    driver = importutils.import_module(CONF.notification_driver)
+    msg = dict(message_id=str(uuid.uuid4()),
+                   publisher_id=publisher_id,
+                   event_type=event_type,
+                   priority=priority,
+                   payload=payload,
+                   timestamp=str(timeutils.utcnow()))
+    try:
+        driver.notify(context, msg)
+    except Exception, e:
+        LOG.exception(_("Problem '%(e)s' attempting to "
+                        "send to notification system. Payload=%(payload)s") %
+                        locals())
diff --git a/heat/openstack/common/notifier/list_notifier.py b/heat/openstack/common/notifier/list_notifier.py
new file mode 100644 (file)
index 0000000..0a0586e
--- /dev/null
@@ -0,0 +1,117 @@
+# 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.
+
+from heat.openstack.common import cfg
+from heat.openstack.common.gettextutils import _
+from heat.openstack.common import importutils
+from heat.openstack.common import log as logging
+
+
+list_notifier_drivers_opt = cfg.MultiStrOpt('list_notifier_drivers',
+        default=['heat.openstack.common.notifier.no_op_notifier'],
+        help='List of drivers to send notifications')
+
+CONF = cfg.CONF
+CONF.register_opt(list_notifier_drivers_opt)
+
+LOG = logging.getLogger(__name__)
+
+drivers = None
+
+
+class ImportFailureNotifier(object):
+    """Noisily re-raises some exception over-and-over when notify is called."""
+
+    def __init__(self, exception):
+        self.exception = exception
+
+    def notify(self, context, message):
+        raise self.exception
+
+
+def _get_drivers():
+    """Instantiates and returns drivers based on the flag values."""
+    global drivers
+    if drivers is None:
+        drivers = []
+        for notification_driver in CONF.list_notifier_drivers:
+            try:
+                drivers.append(importutils.import_module(notification_driver))
+            except ImportError as e:
+                drivers.append(ImportFailureNotifier(e))
+    return drivers
+
+
+def add_driver(notification_driver):
+    """Add a notification driver at runtime."""
+    # Make sure the driver list is initialized.
+    _get_drivers()
+    if isinstance(notification_driver, basestring):
+        # Load and add
+        try:
+            drivers.append(importutils.import_module(notification_driver))
+        except ImportError as e:
+            drivers.append(ImportFailureNotifier(e))
+    else:
+        # Driver is already loaded; just add the object.
+        drivers.append(notification_driver)
+
+
+def _object_name(obj):
+    name = []
+    if hasattr(obj, '__module__'):
+        name.append(obj.__module__)
+    if hasattr(obj, '__name__'):
+        name.append(obj.__name__)
+    else:
+        name.append(obj.__class__.__name__)
+    return '.'.join(name)
+
+
+def remove_driver(notification_driver):
+    """Remove a notification driver at runtime."""
+    # Make sure the driver list is initialized.
+    _get_drivers()
+    removed = False
+    if notification_driver in drivers:
+        # We're removing an object.  Easy.
+        drivers.remove(notification_driver)
+        removed = True
+    else:
+        # We're removing a driver by name.  Search for it.
+        for driver in drivers:
+            if _object_name(driver) == notification_driver:
+                drivers.remove(driver)
+                removed = True
+
+    if not removed:
+        raise ValueError("Cannot remove; %s is not in list" %
+                         notification_driver)
+
+
+def notify(context, message):
+    """Passes notification to multiple notifiers in a list."""
+    for driver in _get_drivers():
+        try:
+            driver.notify(context, message)
+        except Exception as e:
+            LOG.exception(_("Problem '%(e)s' attempting to send to "
+                            "notification driver %(driver)s."), locals())
+
+
+def _reset_drivers():
+    """Used by unit tests to reset the drivers."""
+    global drivers
+    drivers = None
diff --git a/heat/openstack/common/notifier/log_notifier.py b/heat/openstack/common/notifier/log_notifier.py
new file mode 100644 (file)
index 0000000..0d6090d
--- /dev/null
@@ -0,0 +1,35 @@
+# 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.
+
+
+from heat.openstack.common import cfg
+from heat.openstack.common import jsonutils
+from heat.openstack.common import log as logging
+
+
+CONF = cfg.CONF
+
+
+def notify(_context, message):
+    """Notifies the recipient of the desired event given the model.
+    Log notifications using openstack's default logging system"""
+
+    priority = message.get('priority',
+                           CONF.default_notification_level)
+    priority = priority.lower()
+    logger = logging.getLogger(
+            'heat.openstack.common.notification.%s' %
+            message['event_type'])
+    getattr(logger, priority)(jsonutils.dumps(message))
diff --git a/heat/openstack/common/notifier/no_op_notifier.py b/heat/openstack/common/notifier/no_op_notifier.py
new file mode 100644 (file)
index 0000000..ee1ddbd
--- /dev/null
@@ -0,0 +1,19 @@
+# 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.
+
+
+def notify(_context, message):
+    """Notifies the recipient of the desired event given the model"""
+    pass
diff --git a/heat/openstack/common/notifier/rabbit_notifier.py b/heat/openstack/common/notifier/rabbit_notifier.py
new file mode 100644 (file)
index 0000000..64b00c0
--- /dev/null
@@ -0,0 +1,46 @@
+# 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.
+
+
+from heat.openstack.common import cfg
+from heat.openstack.common import context as req_context
+from heat.openstack.common.gettextutils import _
+from heat.openstack.common import log as logging
+from heat.openstack.common import rpc
+
+LOG = logging.getLogger(__name__)
+
+notification_topic_opt = cfg.ListOpt('notification_topics',
+        default=['notifications', ],
+        help='AMQP topic used for openstack notifications')
+
+CONF = cfg.CONF
+CONF.register_opt(notification_topic_opt)
+
+
+def notify(context, message):
+    """Sends a notification to the RabbitMQ"""
+    if not context:
+        context = req_context.get_admin_context()
+    priority = message.get('priority',
+                           CONF.default_notification_level)
+    priority = priority.lower()
+    for topic in CONF.notification_topics:
+        topic = '%s.%s' % (topic, priority)
+        try:
+            rpc.notify(context, topic, message)
+        except Exception, e:
+            LOG.exception(_("Could not send notification to %(topic)s. "
+                            "Payload=%(message)s"), locals())
diff --git a/heat/openstack/common/notifier/test_notifier.py b/heat/openstack/common/notifier/test_notifier.py
new file mode 100644 (file)
index 0000000..5e34880
--- /dev/null
@@ -0,0 +1,22 @@
+# 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.
+
+
+NOTIFICATIONS = []
+
+
+def notify(_context, message):
+    """Test notifier, stores notifications in memory for unittests."""
+    NOTIFICATIONS.append(message)
index 4ae29bb08767d97603681b11adb4acb62966ed9a..b7cb7876c8891757c4d7ddaa7b068db152dde657 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 from heat.openstack.common import cfg
 from heat.openstack.common import importutils
 
+from heat.openstack.common import log as logging
+
 LOG = logging.getLogger(__name__)
 
 
index 8f02f9471e20ff46078d7c9bd43ff9a9026e8813..8d7989355fdba84651bca70ec23b6d07f2946c2c 100644 (file)
@@ -26,11 +26,12 @@ AMQP, but is deprecated and predates this code.
 """
 
 import inspect
-import logging
 import sys
 import traceback
 import uuid
 
+from heat.openstack.common import log as logging
+
 from eventlet import greenpool
 from eventlet import pools
 
index 28d182475bf36a7ab4fd089c90a5e26e59de7fcb..f0792c807c07d3e26808cebd72fbd0a7a5f3c35f 100644 (file)
 #    under the License.
 
 import copy
-import logging
 
 from heat.openstack.common import cfg
 from heat.openstack.common import exception
+from heat.openstack.common import log as logging
 from heat.common import config
 
-
 LOG = logging.getLogger(__name__)
 
 
index 7602cf1d48841048530fa079effe3f378faf1d12..879e59c12be4d5ffbefd6aece21db0bcf517e957 100644 (file)
@@ -19,7 +19,6 @@ import itertools
 import time
 import uuid
 import json
-import logging
 
 import eventlet
 import greenlet
@@ -31,6 +30,8 @@ from heat.openstack.common import cfg
 from heat.rpc import amqp as rpc_amqp
 from heat.rpc import common as rpc_common
 
+from heat.openstack.common import log as logging
+
 LOG = logging.getLogger(__name__)
 
 
index 9a118ff67c2be1904b420c2fa17dfe3d9a449384..68472b8b94277fc1676e0b1af41c8ae9ba0098b1 100644 (file)
@@ -23,11 +23,11 @@ import inspect
 import os
 
 import eventlet
-import logging
 import greenlet
 
 from heat.openstack.common import cfg
 from heat.openstack.common import importutils
+from heat.openstack.common import log as logging
 
 from heat.common import utils as heat_utils
 from heat.common import exception
index 71770f7769862d13b624693637774b9925d5b9d7..528b40dd43f6d67195b538b9cccb7e9082f124e7 100644 (file)
@@ -20,11 +20,13 @@ import sys
 import base64
 from lxml import etree
 import re
-import logging
 
 from glance import client as glance_client
 from heat.common import exception
 
+from heat.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
 
 SUCCESS = 0
 FAILURE = 1
@@ -39,14 +41,14 @@ def catch_error(action):
                 ret = func(*arguments, **kwargs)
                 return SUCCESS if ret is None else ret
             except exception.NotAuthorized:
-                logging.error("Not authorized to make this request. Check " +
+                LOG.error("Not authorized to make this request. Check " +
                       "your credentials (OS_USERNAME, OS_PASSWORD, " +
                       "OS_TENANT_NAME, OS_AUTH_URL and OS_AUTH_STRATEGY).")
                 return FAILURE
             except exception.ClientConfigurationError:
                 raise
             except exception.KeystoneError, e:
-                logging.error("Keystone did not finish the authentication and "
+                LOG.error("Keystone did not finish the authentication and "
                               "returned the following message:\n\n%s"
                               % e.message)
                 return FAILURE
@@ -54,10 +56,10 @@ def catch_error(action):
                 options = arguments[0]
                 if options.debug:
                     raise
-                logging.error("Failed to %s. Got error:" % action)
+                LOG.error("Failed to %s. Got error:" % action)
                 pieces = unicode(e).split('\n')
                 for piece in pieces:
-                    logging.error(piece)
+                    LOG.error(piece)
                 return FAILURE
 
         return wrapper
index d61ff0b6027493bcd1d42c57d972e3e16698e168..7a14d218eba50a6250ab39fa37abac82a6009637 100644 (file)
@@ -1,7 +1,7 @@
 [DEFAULT]
 
 # The list of modules to copy from openstack-common
-modules=gettextutils,cfg,local,iniparser,utils,exception,timeutils,importutils,setup
+modules=gettextutils,cfg,local,iniparser,utils,exception,timeutils,importutils,setup,log,jsonutils,notifier
 
 # The base module to hold the copy of openstack.common
 base=heat