]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Introduce Guru Meditation Reports into Cinder
authorwanghao <wanghao749@huawei.com>
Thu, 14 May 2015 07:42:44 +0000 (15:42 +0800)
committerwanghao <wanghao749@huawei.com>
Tue, 16 Jun 2015 04:01:28 +0000 (12:01 +0800)
This commit integrates functionality from the
`openstack.common.report` module into Cinder.
This enables Cinder services to receive SIGUSR1
and print a Guru Meditation Report to stderr.
The required modules were added to
'openstack-common.conf' as well.

It is essentially a copy from implementation of
nova side.

Change-Id: I5bbdc0f97db9b0ebd7b48e50ab7869e2ca33aead
Implements: blueprint guru-meditation-report

31 files changed:
cinder/cmd/api.py
cinder/cmd/backup.py
cinder/cmd/scheduler.py
cinder/cmd/volume.py
cinder/openstack/common/report/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/generators/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/generators/conf.py [new file with mode: 0644]
cinder/openstack/common/report/generators/threading.py [new file with mode: 0644]
cinder/openstack/common/report/generators/version.py [new file with mode: 0644]
cinder/openstack/common/report/guru_meditation_report.py [new file with mode: 0644]
cinder/openstack/common/report/models/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/models/base.py [new file with mode: 0644]
cinder/openstack/common/report/models/conf.py [new file with mode: 0644]
cinder/openstack/common/report/models/threading.py [new file with mode: 0644]
cinder/openstack/common/report/models/version.py [new file with mode: 0644]
cinder/openstack/common/report/models/with_default_views.py [new file with mode: 0644]
cinder/openstack/common/report/report.py [new file with mode: 0644]
cinder/openstack/common/report/utils.py [new file with mode: 0644]
cinder/openstack/common/report/views/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/views/jinja_view.py [new file with mode: 0644]
cinder/openstack/common/report/views/json/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/views/json/generic.py [new file with mode: 0644]
cinder/openstack/common/report/views/text/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/views/text/generic.py [new file with mode: 0644]
cinder/openstack/common/report/views/text/header.py [new file with mode: 0644]
cinder/openstack/common/report/views/text/threading.py [new file with mode: 0644]
cinder/openstack/common/report/views/xml/__init__.py [new file with mode: 0644]
cinder/openstack/common/report/views/xml/generic.py [new file with mode: 0644]
doc/source/devref/gmr.rst [new file with mode: 0644]
doc/source/devref/index.rst
openstack-common.conf

index 471ed7ee700bf2af994f0a09e23e7ab4c40a7372..418bba98331ed66798cba885dcb6ab363457bbb3 100644 (file)
@@ -35,6 +35,7 @@ i18n.enable_lazy()
 
 # Need to register global_opts
 from cinder.common import config  # noqa
+from cinder.openstack.common.report import guru_meditation_report as gmr
 from cinder import rpc
 from cinder import service
 from cinder import utils
@@ -51,6 +52,8 @@ def main():
     logging.setup(CONF, "cinder")
     utils.monkey_patch()
 
+    gmr.TextGuruMeditation.setup_autorun(version)
+
     rpc.init(CONF)
     launcher = service.process_launcher()
     server = service.WSGIService('osapi_volume')
index 3231aebdd24c6a929c61cb011137d017faaafcbe..db823502d390e2e0a5627ff16a9fda76c8e75cca 100644 (file)
@@ -33,6 +33,7 @@ i18n.enable_lazy()
 
 # Need to register global_opts
 from cinder.common import config  # noqa
+from cinder.openstack.common.report import guru_meditation_report as gmr
 from cinder import service
 from cinder import utils
 from cinder import version
@@ -46,6 +47,7 @@ def main():
          version=version.version_string())
     logging.setup(CONF, "cinder")
     utils.monkey_patch()
+    gmr.TextGuruMeditation.setup_autorun(version)
     server = service.Service.create(binary='cinder-backup')
     service.serve(server)
     service.wait()
index db862b988e84949e4ce1bae565aed9dddcba616c..53a820672565990ffda61174df8d719dfba85a54 100644 (file)
@@ -33,6 +33,7 @@ i18n.enable_lazy()
 
 # Need to register global_opts
 from cinder.common import config  # noqa
+from cinder.openstack.common.report import guru_meditation_report as gmr
 from cinder import service
 from cinder import utils
 from cinder import version
@@ -46,6 +47,7 @@ def main():
          version=version.version_string())
     logging.setup(CONF, "cinder")
     utils.monkey_patch()
+    gmr.TextGuruMeditation.setup_autorun(version)
     server = service.Service.create(binary='cinder-scheduler')
     service.serve(server)
     service.wait()
index ed99dded34384ab4f37cbf5457a776471854b5bc..3c103fe15a8b6678e5d08189ab8a6d51b66367fd 100644 (file)
@@ -44,6 +44,7 @@ i18n.enable_lazy()
 # Need to register global_opts
 from cinder.common import config  # noqa
 from cinder.db import api as session
+from cinder.openstack.common.report import guru_meditation_report as gmr
 from cinder import service
 from cinder import utils
 from cinder import version
@@ -62,6 +63,7 @@ def main():
          version=version.version_string())
     logging.setup(CONF, "cinder")
     utils.monkey_patch()
+    gmr.TextGuruMeditation.setup_autorun(version)
     launcher = service.get_launcher()
     if CONF.enabled_backends:
         for backend in CONF.enabled_backends:
diff --git a/cinder/openstack/common/report/__init__.py b/cinder/openstack/common/report/__init__.py
new file mode 100644 (file)
index 0000000..b296462
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides a way to generate serializable reports
+
+This package/module provides mechanisms for defining reports
+which may then be serialized into various data types.  Each
+report ( :class:`openstack.common.report.report.BasicReport` )
+is composed of one or more report sections
+( :class:`openstack.common.report.report.BasicSection` ),
+which contain generators which generate data models
+( :class:`openstack.common.report.models.base.ReportModels` ),
+which are then serialized by views.
+"""
\ No newline at end of file
diff --git a/cinder/openstack/common/report/generators/__init__.py b/cinder/openstack/common/report/generators/__init__.py
new file mode 100644 (file)
index 0000000..26474e3
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Data Model Generators
+
+This module defines classes for generating data models
+( :class:`openstack.common.report.models.base.ReportModel` ).
+A generator is any object which is callable with no parameters
+and returns a data model.
+""" 
\ No newline at end of file
diff --git a/cinder/openstack/common/report/generators/conf.py b/cinder/openstack/common/report/generators/conf.py
new file mode 100644 (file)
index 0000000..31ccaec
--- /dev/null
@@ -0,0 +1,44 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Openstack config generators
+
+This module defines a class for configuration
+generators for generating the model in
+:mod:`openstack.common.report.models.conf`.
+"""
+
+from oslo_config import cfg
+
+import cinder.openstack.common.report.models.conf as cm
+
+
+class ConfigReportGenerator(object):
+    """A Configuration Data Generator
+
+    This generator returns
+    :class:`openstack.common.report.models.conf.ConfigModel` ,
+    by default using the configuration options stored
+    in :attr:`oslo_config.cfg.CONF`, which is where
+    Openstack stores everything.
+
+    :param cnf: the configuration option object
+    :type cnf: :class:`oslo.config.cfg.ConfigOpts`
+    """
+
+    def __init__(self, cnf=cfg.CONF):
+        self.conf_obj = cnf
+
+    def __call__(self):
+        return cm.ConfigModel(self.conf_obj)
diff --git a/cinder/openstack/common/report/generators/threading.py b/cinder/openstack/common/report/generators/threading.py
new file mode 100644 (file)
index 0000000..4f803e4
--- /dev/null
@@ -0,0 +1,73 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides thread-related generators
+
+This module defines classes for threading-related
+generators for generating the models in
+:mod:`openstack.common.report.models.threading`.
+"""
+
+import sys
+
+import greenlet
+
+import cinder.openstack.common.report.models.threading as tm
+from cinder.openstack.common.report.models import with_default_views as mwdv
+import cinder.openstack.common.report.utils as rutils
+import cinder.openstack.common.report.views.text.generic as text_views
+
+
+class ThreadReportGenerator(object):
+    """A Thread Data Generator
+
+    This generator returns a collection of
+    :class:`openstack.common.report.models.threading.ThreadModel`
+    objects by introspecting the current python state using
+    :func:`sys._current_frames()` .
+    """
+
+    def __call__(self):
+        threadModels = [
+            tm.ThreadModel(thread_id, stack)
+            for thread_id, stack in sys._current_frames().items()
+        ]
+
+        thread_pairs = dict(zip(range(len(threadModels)), threadModels))
+        return mwdv.ModelWithDefaultViews(thread_pairs,
+                                          text_view=text_views.MultiView())
+
+
+class GreenThreadReportGenerator(object):
+    """A Green Thread Data Generator
+
+    This generator returns a collection of
+    :class:`openstack.common.report.models.threading.GreenThreadModel`
+    objects by introspecting the current python garbage collection
+    state, and sifting through for :class:`greenlet.greenlet` objects.
+
+    .. seealso::
+
+        Function :func:`openstack.common.report.utils._find_objects`
+    """
+
+    def __call__(self):
+        threadModels = [
+            tm.GreenThreadModel(gr.gr_frame)
+            for gr in rutils._find_objects(greenlet.greenlet)
+        ]
+
+        thread_pairs = dict(zip(range(len(threadModels)), threadModels))
+        return mwdv.ModelWithDefaultViews(thread_pairs,
+                                          text_view=text_views.MultiView())
diff --git a/cinder/openstack/common/report/generators/version.py b/cinder/openstack/common/report/generators/version.py
new file mode 100644 (file)
index 0000000..05936fc
--- /dev/null
@@ -0,0 +1,46 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Openstack version generators
+
+This module defines a class for Openstack
+version and package information
+generators for generating the model in
+:mod:`openstack.common.report.models.version`.
+"""
+
+import cinder.openstack.common.report.models.version as vm
+
+
+class PackageReportGenerator(object):
+    """A Package Information Data Generator
+
+    This generator returns
+    :class:`openstack.common.report.models.version.PackageModel`,
+    extracting data from the given version object, which should follow
+    the general format defined in Nova's version information (i.e. it
+    should contain the methods vendor_string, product_string, and
+    version_string_with_package).
+
+    :param version_object: the version information object
+    """
+
+    def __init__(self, version_obj):
+        self.version_obj = version_obj
+
+    def __call__(self):
+        return vm.PackageModel(
+            self.version_obj.vendor_string(),
+            self.version_obj.product_string(),
+            self.version_obj.version_string_with_package())
diff --git a/cinder/openstack/common/report/guru_meditation_report.py b/cinder/openstack/common/report/guru_meditation_report.py
new file mode 100644 (file)
index 0000000..9693f9b
--- /dev/null
@@ -0,0 +1,179 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Guru Meditation Report
+
+This module defines the actual OpenStack Guru Meditation Report class.
+
+This can be used in the OpenStack command definition files.
+For example, in a nova command module (under nova/cmd):
+
+.. code-block:: python
+   :emphasize-lines: 8,9,10
+
+   CONF = cfg.CONF
+   # maybe import some options here...
+
+   def main():
+       config.parse_args(sys.argv)
+       logging.setup('blah')
+
+       TextGuruMeditation.register_section('Some Special Section',
+                                           special_section_generator)
+       TextGuruMeditation.setup_autorun(version_object)
+
+       server = service.Service.create(binary='some-service',
+                                       topic=CONF.some_service_topic)
+       service.serve(server)
+       service.wait()
+
+Then, you can do
+
+.. code-block:: bash
+
+   $ kill -USR1 $SERVICE_PID
+
+and get a Guru Meditation Report in the file or terminal
+where stderr is logged for that given service.
+"""
+
+from __future__ import print_function
+
+import signal
+import sys
+
+from cinder.openstack.common.report.generators import conf as cgen
+from cinder.openstack.common.report.generators import threading as tgen
+from cinder.openstack.common.report.generators import version as pgen
+from cinder.openstack.common.report import report
+
+
+class GuruMeditation(object):
+    """A Guru Meditation Report Mixin/Base Class
+
+    This class is a base class for Guru Meditation Reports.
+    It provides facilities for registering sections and
+    setting up functionality to auto-run the report on
+    a certain signal.
+
+    This class should always be used in conjunction with
+    a Report class via multiple inheritance.  It should
+    always come first in the class list to ensure the
+    MRO is correct.
+    """
+
+    def __init__(self, version_obj, *args, **kwargs):
+        self.version_obj = version_obj
+
+        super(GuruMeditation, self).__init__(*args, **kwargs)
+        self.start_section_index = len(self.sections)
+
+    @classmethod
+    def register_section(cls, section_title, generator):
+        """Register a New Section
+
+        This method registers a persistent section for the current
+        class.
+
+        :param str section_title: the title of the section
+        :param generator: the generator for the section
+        """
+
+        try:
+            cls.persistent_sections.append([section_title, generator])
+        except AttributeError:
+            cls.persistent_sections = [[section_title, generator]]
+
+    @classmethod
+    def setup_autorun(cls, version, signum=signal.SIGUSR1):
+        """Set Up Auto-Run
+
+        This method sets up the Guru Meditation Report to automatically
+        get dumped to stderr when the given signal is received.
+
+        :param version: the version object for the current product
+        :param signum: the signal to associate with running the report
+        """
+
+        signal.signal(signum, lambda *args: cls.handle_signal(version, *args))
+
+    @classmethod
+    def handle_signal(cls, version, *args):
+        """The Signal Handler
+
+        This method (indirectly) handles receiving a registered signal and
+        dumping the Guru Meditation Report to stderr.  This method is designed
+        to be curried into a proper signal handler by currying out the version
+        parameter.
+
+        :param version: the version object for the current product
+        """
+
+        try:
+            res = cls(version).run()
+        except Exception:
+            print("Unable to run Guru Meditation Report!",
+                  file=sys.stderr)
+        else:
+            print(res, file=sys.stderr)
+
+    def _readd_sections(self):
+        del self.sections[self.start_section_index:]
+
+        self.add_section('Package',
+                         pgen.PackageReportGenerator(self.version_obj))
+
+        self.add_section('Threads',
+                         tgen.ThreadReportGenerator())
+
+        self.add_section('Green Threads',
+                         tgen.GreenThreadReportGenerator())
+
+        self.add_section('Configuration',
+                         cgen.ConfigReportGenerator())
+
+        try:
+            for section_title, generator in self.persistent_sections:
+                self.add_section(section_title, generator)
+        except AttributeError:
+            pass
+
+    def run(self):
+        self._readd_sections()
+        return super(GuruMeditation, self).run()
+
+
+# GuruMeditation must come first to get the correct MRO
+class TextGuruMeditation(GuruMeditation, report.TextReport):
+    """A Text Guru Meditation Report
+
+    This report is the basic human-readable Guru Meditation Report
+
+    It contains the following sections by default
+    (in addition to any registered persistent sections):
+
+    - Package Information
+
+    - Threads List
+
+    - Green Threads List
+
+    - Configuration Options
+
+    :param version_obj: the version object for the current product
+    """
+
+    def __init__(self, version_obj):
+        super(TextGuruMeditation, self).__init__(version_obj,
+                                                 'Guru Meditation')
diff --git a/cinder/openstack/common/report/models/__init__.py b/cinder/openstack/common/report/models/__init__.py
new file mode 100644 (file)
index 0000000..8430fc4
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides data models
+
+This module provides both the base data model,
+as well as several predefined specific data models
+to be used in reports.
+"""
\ No newline at end of file
diff --git a/cinder/openstack/common/report/models/base.py b/cinder/openstack/common/report/models/base.py
new file mode 100644 (file)
index 0000000..90914ff
--- /dev/null
@@ -0,0 +1,114 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides the base report model
+
+This module defines a class representing the basic report
+data model from which all data models should inherit (or
+at least implement similar functionality).  Data models
+store unserialized data generated by generators during
+the report serialization process.
+"""
+
+import collections as col
+import copy
+
+
+class ReportModel(col.MutableMapping):
+    """A Report Data Model
+
+    A report data model contains data generated by some
+    generator method or class.  Data may be read or written
+    using dictionary-style access, and may be read (but not
+    written) using object-member-style access.  Additionally,
+    a data model may have an associated view.  This view is
+    used to serialize the model when str() is called on the
+    model.  An appropriate object for a view is callable with
+    a single parameter: the model to be serialized.
+
+    :param data: a dictionary of data to initially associate with the model
+    :param attached_view: a view object to attach to this model
+    """
+
+    def __init__(self, data=None, attached_view=None):
+        self.attached_view = attached_view
+        self.data = data or {}
+
+    def __str__(self):
+        self_cpy = copy.deepcopy(self)
+        for key in self_cpy:
+            if getattr(self_cpy[key], 'attached_view', None) is not None:
+                self_cpy[key] = str(self_cpy[key])
+
+        if self.attached_view is not None:
+            return self.attached_view(self_cpy)
+        else:
+            raise Exception("Cannot stringify model: no attached view")
+
+    def __repr__(self):
+        if self.attached_view is not None:
+            return ("<Model {cl.__module__}.{cl.__name__} {dt}"
+                    " with view {vw.__module__}."
+                    "{vw.__name__}>").format(cl=type(self),
+                                             dt=self.data,
+                                             vw=type(self.attached_view))
+        else:
+            return ("<Model {cl.__module__}.{cl.__name__} {dt}"
+                    " with no view>").format(cl=type(self),
+                                             dt=self.data)
+
+    def __getitem__(self, attrname):
+        return self.data[attrname]
+
+    def __setitem__(self, attrname, attrval):
+        self.data[attrname] = attrval
+
+    def __delitem__(self, attrname):
+        del self.data[attrname]
+
+    def __contains__(self, key):
+        return self.data.__contains__(key)
+
+    def __getattr__(self, attrname):
+        try:
+            return self.data[attrname]
+        except KeyError:
+            raise AttributeError(
+                "'{cl}' object has no attribute '{an}'".format(
+                    cl=type(self).__name__, an=attrname
+                )
+            )
+
+    def __len__(self):
+        return len(self.data)
+
+    def __iter__(self):
+        return self.data.__iter__()
+
+    def set_current_view_type(self, tp):
+        """Set the current view type
+
+        This method attempts to set the current view
+        type for this model and all submodels by calling
+        itself recursively on all values (and ignoring the
+        ones that are not themselves models)
+
+        :param tp: the type of the view ('text', 'json', 'xml', etc)
+        """
+
+        for key in self:
+            try:
+                self[key].set_current_view_type(tp)
+            except AttributeError:
+                pass
diff --git a/cinder/openstack/common/report/models/conf.py b/cinder/openstack/common/report/models/conf.py
new file mode 100644 (file)
index 0000000..77e24ab
--- /dev/null
@@ -0,0 +1,58 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Openstack Configuration Model
+
+This module defines a class representing the data
+model for :mod:`oslo.config` configuration options
+"""
+
+import cinder.openstack.common.report.models.with_default_views as mwdv
+import cinder.openstack.common.report.views.text.generic as generic_text_views
+
+
+class ConfigModel(mwdv.ModelWithDefaultViews):
+    """A Configuration Options Model
+
+    This model holds data about a set of configuration options
+    from :mod:`oslo.config`.  It supports both the default group
+    of options and named option groups.
+
+    :param conf_obj: a configuration object
+    :type conf_obj: :class:`oslo.config.cfg.ConfigOpts`
+    """
+
+    def __init__(self, conf_obj):
+        kv_view = generic_text_views.KeyValueView(dict_sep=": ",
+                                                  before_dict='')
+        super(ConfigModel, self).__init__(text_view=kv_view)
+
+        def opt_title(optname, co):
+            return co._opts[optname]['opt'].name
+
+        self['default'] = dict(
+            (opt_title(optname, conf_obj), conf_obj[optname])
+            for optname in conf_obj._opts
+        )
+
+        groups = {}
+        for groupname in conf_obj._groups:
+            group_obj = conf_obj._groups[groupname]
+            curr_group_opts = dict(
+                (opt_title(optname, group_obj), conf_obj[groupname][optname])
+                for optname in group_obj._opts
+            )
+            groups[group_obj.name] = curr_group_opts
+
+        self.update(groups)
diff --git a/cinder/openstack/common/report/models/threading.py b/cinder/openstack/common/report/models/threading.py
new file mode 100644 (file)
index 0000000..15e1fdd
--- /dev/null
@@ -0,0 +1,100 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides threading and stack-trace models
+
+This module defines classes representing thread, green
+thread, and stack trace data models
+"""
+
+import traceback
+
+import cinder.openstack.common.report.models.with_default_views as mwdv
+import cinder.openstack.common.report.views.text.threading as text_views
+
+
+class StackTraceModel(mwdv.ModelWithDefaultViews):
+    """A Stack Trace Model
+
+    This model holds data from a python stack trace,
+    commonly extracted from running thread information
+
+    :param stack_state: the python stack_state object
+    """
+
+    def __init__(self, stack_state):
+        super(StackTraceModel, self).__init__(
+            text_view=text_views.StackTraceView())
+
+        if (stack_state is not None):
+            self['lines'] = [
+                {'filename': fn, 'line': ln, 'name': nm, 'code': cd}
+                for fn, ln, nm, cd in traceback.extract_stack(stack_state)
+            ]
+
+            if stack_state.f_exc_type is not None:
+                self['root_exception'] = {
+                    'type': stack_state.f_exc_type,
+                    'value': stack_state.f_exc_value
+                }
+            else:
+                self['root_exception'] = None
+        else:
+            self['lines'] = []
+            self['root_exception'] = None
+
+
+class ThreadModel(mwdv.ModelWithDefaultViews):
+    """A Thread Model
+
+    This model holds data for information about an
+    individual thread.  It holds both a thread id,
+    as well as a stack trace for the thread
+
+    .. seealso::
+
+        Class :class:`StackTraceModel`
+
+    :param int thread_id: the id of the thread
+    :param stack: the python stack state for the current thread
+    """
+
+    # threadId, stack in sys._current_frams().items()
+    def __init__(self, thread_id, stack):
+        super(ThreadModel, self).__init__(text_view=text_views.ThreadView())
+
+        self['thread_id'] = thread_id
+        self['stack_trace'] = StackTraceModel(stack)
+
+
+class GreenThreadModel(mwdv.ModelWithDefaultViews):
+    """A Green Thread Model
+
+    This model holds data for information about an
+    individual thread.  Unlike the thread model,
+    it holds just a stack trace, since green threads
+    do not have thread ids.
+
+    .. seealso::
+
+        Class :class:`StackTraceModel`
+
+    :param stack: the python stack state for the green thread
+    """
+
+    # gr in greenpool.coroutines_running  --> gr.gr_frame
+    def __init__(self, stack):
+        super(GreenThreadModel, self).__init__(
+            {'stack_trace': StackTraceModel(stack)},
+            text_view=text_views.GreenThreadView())
diff --git a/cinder/openstack/common/report/models/version.py b/cinder/openstack/common/report/models/version.py
new file mode 100644 (file)
index 0000000..3666dc2
--- /dev/null
@@ -0,0 +1,44 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Openstack Version Info Model
+
+This module defines a class representing the data
+model for Openstack package and version information
+"""
+
+import cinder.openstack.common.report.models.with_default_views as mwdv
+import cinder.openstack.common.report.views.text.generic as generic_text_views
+
+
+class PackageModel(mwdv.ModelWithDefaultViews):
+    """A Package Information Model
+
+    This model holds information about the current
+    package.  It contains vendor, product, and version
+    information.
+
+    :param str vendor: the product vendor
+    :param str product: the product name
+    :param str version: the product version
+    """
+
+    def __init__(self, vendor, product, version):
+        super(PackageModel, self).__init__(
+            text_view=generic_text_views.KeyValueView()
+        )
+
+        self['vendor'] = vendor
+        self['product'] = product
+        self['version'] = version
diff --git a/cinder/openstack/common/report/models/with_default_views.py b/cinder/openstack/common/report/models/with_default_views.py
new file mode 100644 (file)
index 0000000..d1a8e42
--- /dev/null
@@ -0,0 +1,82 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+import cinder.openstack.common.report.models.base as base_model
+import cinder.openstack.common.report.views.json.generic as jsonviews
+import cinder.openstack.common.report.views.text.generic as textviews
+import cinder.openstack.common.report.views.xml.generic as xmlviews
+
+
+class ModelWithDefaultViews(base_model.ReportModel):
+    """A Model With Default Views of Various Types
+
+    A model with default views has several predefined views,
+    each associated with a given type.  This is often used for
+    when a submodel should have an attached view, but the view
+    differs depending on the serialization format
+
+    Paramaters are as the superclass, with the exception
+    of any parameters ending in '_view': these parameters
+    get stored as default views.
+
+    The default 'default views' are
+
+    text
+        :class:`openstack.common.views.text.generic.KeyValueView`
+    xml
+        :class:`openstack.common.views.xml.generic.KeyValueView`
+    json
+        :class:`openstack.common.views.json.generic.KeyValueView`
+
+    .. function:: to_type()
+
+       ('type' is one of the 'default views' defined for this model)
+       Serializes this model using the default view for 'type'
+
+       :rtype: str
+       :returns: this model serialized as 'type'
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.views = {
+            'text': textviews.KeyValueView(),
+            'json': jsonviews.KeyValueView(),
+            'xml': xmlviews.KeyValueView()
+        }
+
+        newargs = copy.copy(kwargs)
+        for k in kwargs:
+            if k.endswith('_view'):
+                self.views[k[:-5]] = kwargs[k]
+                del newargs[k]
+        super(ModelWithDefaultViews, self).__init__(*args, **newargs)
+
+    def set_current_view_type(self, tp):
+        self.attached_view = self.views[tp]
+        super(ModelWithDefaultViews, self).set_current_view_type(tp)
+
+    def __getattr__(self, attrname):
+        if attrname[:3] == 'to_':
+            if self.views[attrname[3:]] is not None:
+                return lambda: self.views[attrname[3:]](self)
+            else:
+                raise NotImplementedError(_(
+                    "Model %(module)s.%(name)s does not have a default view "
+                    "for %(tp)s"), {'module': type(self).__module__,
+                                    'name': type(self).__name__,
+                                    'tp': attrname[3:]})
+        else:
+            return super(ModelWithDefaultViews, self).__getattr__(attrname)
diff --git a/cinder/openstack/common/report/report.py b/cinder/openstack/common/report/report.py
new file mode 100644 (file)
index 0000000..77f5d85
--- /dev/null
@@ -0,0 +1,189 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Report classes
+
+This module defines various classes representing
+reports and report sections.  All reports take the
+form of a report class containing various report sections.
+"""
+
+import cinder.openstack.common.report.views.text.header as header_views
+
+
+class BasicReport(object):
+    """A Basic Report
+
+    A Basic Report consists of a collection of :class:`ReportSection`
+    objects, each of which contains a top-level model and generator.
+    It collects these sections into a cohesive report which may then
+    be serialized by calling :func:`run`
+    """
+
+    def __init__(self):
+        self.sections = []
+        self._state = 0
+
+    def add_section(self, view, generator, index=None):
+        """Add a section to the report
+
+        This method adds a section with the given view and
+        generator to the report.  An index may be specified to
+        insert the section at a given location in the list;
+        If no index is specified, the section is appended to the
+        list.  The view is called on the model which results from
+        the generator when the report is run.  A generator is simply
+        a method or callable object which takes no arguments and
+        returns a :class:`openstack.common.report.models.base.ReportModel`
+        or similar object.
+
+        :param view: the top-level view for the section
+        :param generator: the method or class which generates the model
+        :param index: the index at which to insert the section
+                      (or None to append it)
+        :type index: int or None
+        """
+
+        if index is None:
+            self.sections.append(ReportSection(view, generator))
+        else:
+            self.sections.insert(index, ReportSection(view, generator))
+
+    def run(self):
+        """Run the report
+
+        This method runs the report, having each section generate
+        its data and serialize itself before joining the sections
+        together.  The BasicReport accomplishes the joining
+        by joining the serialized sections together with newlines.
+
+        :rtype: str
+        :returns: the serialized report
+        """
+
+        return "\n".join(str(sect) for sect in self.sections)
+
+
+class ReportSection(object):
+    """A Report Section
+
+    A report section contains a generator and a top-level view.
+    When something attempts to serialize the section by calling
+    str() on it, the section runs the generator and calls the view
+    on the resulting model.
+
+    .. seealso::
+
+       Class :class:`BasicReport`
+           :func:`BasicReport.add_section`
+
+    :param view: the top-level view for this section
+    :param generator: the generator for this section
+                      (any callable object which takes
+                       no parameters and returns a data model)
+    """
+
+    def __init__(self, view, generator):
+        self.view = view
+        self.generator = generator
+
+    def __str__(self):
+        return self.view(self.generator())
+
+
+class ReportOfType(BasicReport):
+    """A Report of a Certain Type
+
+    A ReportOfType has a predefined type associated with it.
+    This type is automatically propagated down to the each of
+    the sections upon serialization by wrapping the generator
+    for each section.
+
+    .. seealso::
+
+       Class :class:`openstack.common.report.models.with_default_view.ModelWithDefaultView` # noqa
+          (the entire class)
+
+       Class :class:`openstack.common.report.models.base.ReportModel`
+           :func:`openstack.common.report.models.base.ReportModel.set_current_view_type` # noqa
+
+    :param str tp: the type of the report
+    """
+
+    def __init__(self, tp):
+        self.output_type = tp
+        super(ReportOfType, self).__init__()
+
+    def add_section(self, view, generator, index=None):
+        def with_type(gen):
+            def newgen():
+                res = gen()
+                try:
+                    res.set_current_view_type(self.output_type)
+                except AttributeError:
+                    pass
+
+                return res
+            return newgen
+
+        super(ReportOfType, self).add_section(
+            view,
+            with_type(generator),
+            index
+        )
+
+
+class TextReport(ReportOfType):
+    """A Human-Readable Text Report
+
+    This class defines a report that is designed to be read by a human
+    being.  It has nice section headers, and a formatted title.
+
+    :param str name: the title of the report
+    """
+
+    def __init__(self, name):
+        super(TextReport, self).__init__('text')
+        self.name = name
+        # add a title with a generator that creates an empty result model
+        self.add_section(name, lambda: ('|' * 72) + "\n\n")
+
+    def add_section(self, heading, generator, index=None):
+        """Add a section to the report
+
+        This method adds a section with the given title, and
+        generator to the report.  An index may be specified to
+        insert the section at a given location in the list;
+        If no index is specified, the section is appended to the
+        list.  The view is called on the model which results from
+        the generator when the report is run.  A generator is simply
+        a method or callable object which takes no arguments and
+        returns a :class:`openstack.common.report.models.base.ReportModel`
+        or similar object.
+
+        The model is told to serialize as text (if possible) at serialization
+        time by wrapping the generator.  The view model's attached view
+        (if any) is wrapped in a
+        :class:`openstack.common.report.views.text.header.TitledView`
+
+        :param str heading: the title for the section
+        :param generator: the method or class which generates the model
+        :param index: the index at which to insert the section
+                      (or None to append)
+        :type index: int or None
+        """
+
+        super(TextReport, self).add_section(header_views.TitledView(heading),
+                                            generator,
+                                            index)
diff --git a/cinder/openstack/common/report/utils.py b/cinder/openstack/common/report/utils.py
new file mode 100644 (file)
index 0000000..5f1dd1e
--- /dev/null
@@ -0,0 +1,46 @@
+#  Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Various utilities for report generation
+
+This module includes various utilities
+used in generating reports.
+"""
+
+import gc
+
+
+class StringWithAttrs(str):
+    """A String that can have arbitrary attributes
+    """
+
+    pass
+
+
+def _find_objects(t):
+    """Find Objects in the GC State
+
+    This horribly hackish method locates objects of a
+    given class in the current python instance's garbage
+    collection state.  In case you couldn't tell, this is
+    horribly hackish, but is necessary for locating all
+    green threads, since they don't keep track of themselves
+    like normal threads do in python.
+
+    :param class t: the class of object to locate
+    :rtype: list
+    :returns: a list of objects of the given type
+    """
+
+    return [o for o in gc.get_objects() if isinstance(o, t)]
diff --git a/cinder/openstack/common/report/views/__init__.py b/cinder/openstack/common/report/views/__init__.py
new file mode 100644 (file)
index 0000000..834f101
--- /dev/null
@@ -0,0 +1,22 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides predefined views
+
+This module provides a collection of predefined views
+for use in reports.  It is separated by type (xml, json, or text).
+Each type contains a submodule called 'generic' containing
+several basic, universal views for that type.  There is also
+a predefined view that utilizes Jinja.
+"""
\ No newline at end of file
diff --git a/cinder/openstack/common/report/views/jinja_view.py b/cinder/openstack/common/report/views/jinja_view.py
new file mode 100644 (file)
index 0000000..a6f340e
--- /dev/null
@@ -0,0 +1,125 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides Jinja Views
+
+This module provides views that utilize the Jinja templating
+system for serialization.  For more information on Jinja, please
+see http://jinja.pocoo.org/ .
+"""
+
+import jinja2
+
+
+class JinjaView(object):
+    """A Jinja View
+
+    This view renders the given model using the provided Jinja
+    template.  The template can be given in various ways.
+    If the `VIEw_TEXT` property is defined, that is used as template.
+    Othewise, if a `path` parameter is passed to the constructor, that
+    is used to load a file containing the template.  If the `path`
+    parameter is None, the `text` parameter is used as the template.
+
+    The leading newline character and trailing newline character are stripped
+    from the template (provided they exist).  Baseline indentation is
+    also stripped from each line.  The baseline indentation is determined by
+    checking the indentation of the first line, after stripping off the leading
+    newline (if any).
+
+    :param str path: the path to the Jinja template
+    :param str text: the text of the Jinja template
+    """
+
+    def __init__(self, path=None, text=None):
+        try:
+            self._text = self.VIEW_TEXT
+        except AttributeError:
+            if path is not None:
+                with open(path, 'r') as f:
+                    self._text = f.read()
+            elif text is not None:
+                self._text = text
+            else:
+                self._text = ""
+
+        if self._text[0] == "\n":
+            self._text = self._text[1:]
+
+        newtext = self._text.lstrip()
+        amt = len(self._text) - len(newtext)
+        if (amt > 0):
+            base_indent = self._text[0:amt]
+            lines = self._text.splitlines()
+            newlines = []
+            for line in lines:
+                if line.startswith(base_indent):
+                    newlines.append(line[amt:])
+                else:
+                    newlines.append(line)
+            self._text = "\n".join(newlines)
+
+        if self._text[-1] == "\n":
+            self._text = self._text[:-1]
+
+        self._regentemplate = True
+        self._templatecache = None
+
+    def __call__(self, model):
+        return self.template.render(**model)
+
+    @property
+    def template(self):
+        """Get the Compiled Template
+
+        Gets the compiled template, using a cached copy if possible
+        (stored in attr:`_templatecache`) or otherwise recompiling
+        the template if the compiled template is not present or is
+        invalid (due to attr:`_regentemplate` being set to True).
+
+        :returns: the compiled Jinja template
+        :rtype: :class:`jinja2.Template`
+        """
+
+        if self._templatecache is None or self._regentemplate:
+            self._templatecache = jinja2.Template(self._text)
+            self._regentemplate = False
+
+        return self._templatecache
+
+    def _gettext(self):
+        """Get the Template Text
+
+        Gets the text of the current template
+
+        :returns: the text of the Jinja template
+        :rtype: str
+        """
+
+        return self._text
+
+    def _settext(self, textval):
+        """Set the Template Text
+
+        Sets the text of the current template, marking it
+        for recompilation next time the compiled template
+        is retrived via attr:`template` .
+
+        :param str textval: the new text of the Jinja template
+        """
+
+        self._text = textval
+        self.regentemplate = True
+
+    text = property(_gettext, _settext)
diff --git a/cinder/openstack/common/report/views/json/__init__.py b/cinder/openstack/common/report/views/json/__init__.py
new file mode 100644 (file)
index 0000000..2b3933e
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides basic JSON views
+
+This module provides several basic views which serialize
+models into JSON.
+""" 
\ No newline at end of file
diff --git a/cinder/openstack/common/report/views/json/generic.py b/cinder/openstack/common/report/views/json/generic.py
new file mode 100644 (file)
index 0000000..079c018
--- /dev/null
@@ -0,0 +1,66 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides generic JSON views
+
+This modules defines several basic views for serializing
+data to JSON.  Submodels that have already been serialized
+as JSON may have their string values marked with `__is_json__
+= True` using :class:`openstack.common.report.utils.StringWithAttrs`
+(each of the classes within this module does this automatically,
+and non-naive serializers check for this attribute and handle
+such strings specially)
+"""
+
+import copy
+
+from oslo_serialization import jsonutils as json
+
+import cinder.openstack.common.report.utils as utils
+
+
+class BasicKeyValueView(object):
+    """A Basic Key-Value JSON View
+
+    This view performs a naive serialization of a model
+    into JSON by simply calling :func:`json.dumps` on the model
+    """
+
+    def __call__(self, model):
+        res = utils.StringWithAttrs(json.dumps(model.data))
+        res.__is_json__ = True
+        return res
+
+
+class KeyValueView(object):
+    """A Key-Value JSON View
+
+    This view performs advanced serialization to a model
+    into JSON.  It does so by first checking all values to
+    see if they are marked as JSON.  If so, they are deserialized
+    using :func:`json.loads`.  Then, the copy of the model with all
+    JSON deserialized is reserialized into proper nested JSON using
+    :func:`json.dumps`.
+    """
+
+    def __call__(self, model):
+        # this part deals with subviews that were already serialized
+        cpy = copy.deepcopy(model)
+        for key, valstr in model.items():
+            if getattr(valstr, '__is_json__', False):
+                cpy[key] = json.loads(valstr)
+
+        res = utils.StringWithAttrs(json.dumps(cpy.data))
+        res.__is_json__ = True
+        return res
diff --git a/cinder/openstack/common/report/views/text/__init__.py b/cinder/openstack/common/report/views/text/__init__.py
new file mode 100644 (file)
index 0000000..3f17fde
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides basic text views
+
+This module provides several basic views which serialize
+models into human-readable text.
+"""
\ No newline at end of file
diff --git a/cinder/openstack/common/report/views/text/generic.py b/cinder/openstack/common/report/views/text/generic.py
new file mode 100644 (file)
index 0000000..7363833
--- /dev/null
@@ -0,0 +1,202 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides generic text views
+
+This modules provides several generic views for
+serializing models into human-readable text.
+"""
+
+import collections as col
+
+import six
+
+
+class MultiView(object):
+    """A Text View Containing Multiple Views
+
+    This view simply serializes each
+    value in the data model, and then
+    joins them with newlines (ignoring
+    the key values altogether).  This is
+    useful for serializing lists of models
+    (as array-like dicts).
+    """
+
+    def __call__(self, model):
+        res = [str(model[key]) for key in model]
+        return "\n".join(res)
+
+
+class BasicKeyValueView(object):
+    """A Basic Key-Value Text View
+
+    This view performs a naive serialization of a model into
+    text using a basic key-value method, where each
+    key-value pair is rendered as "key = str(value)"
+    """
+
+    def __call__(self, model):
+        res = ""
+        for key in model:
+            res += "{key} = {value}\n".format(key=key, value=model[key])
+
+        return res
+
+
+class KeyValueView(object):
+    """A Key-Value Text View
+
+    This view performs an advanced serialization of a model
+    into text by following the following set of rules:
+
+    key : text
+        key = text
+
+    rootkey : Mapping
+        ::
+
+            rootkey =
+              serialize(key, value)
+
+    key : Sequence
+        ::
+
+            key =
+              serialize(item)
+
+    :param str indent_str: the string used to represent one "indent"
+    :param str key_sep: the separator to use between keys and values
+    :param str dict_sep: the separator to use after a dictionary root key
+    :param str list_sep: the separator to use after a list root key
+    :param str anon_dict: the "key" to use when there is a dict in a list
+                          (does not automatically use the dict separator)
+    :param before_dict: content to place on the line(s) before the a dict
+                        root key (use None to avoid inserting an extra line)
+    :type before_dict: str or None
+    :param before_list: content to place on the line(s) before the a list
+                        root key (use None to avoid inserting an extra line)
+    :type before_list: str or None
+    """
+
+    def __init__(self,
+                 indent_str='  ',
+                 key_sep=' = ',
+                 dict_sep=' = ',
+                 list_sep=' = ',
+                 anon_dict='[dict]',
+                 before_dict=None,
+                 before_list=None):
+        self.indent_str = indent_str
+        self.key_sep = key_sep
+        self.dict_sep = dict_sep
+        self.list_sep = list_sep
+        self.anon_dict = anon_dict
+        self.before_dict = before_dict
+        self.before_list = before_list
+
+    def __call__(self, model):
+        def serialize(root, rootkey, indent):
+            res = []
+            if rootkey is not None:
+                res.append((self.indent_str * indent) + rootkey)
+
+            if isinstance(root, col.Mapping):
+                if rootkey is None and indent > 0:
+                    res.append((self.indent_str * indent) + self.anon_dict)
+                elif rootkey is not None:
+                    res[0] += self.dict_sep
+                    if self.before_dict is not None:
+                        res.insert(0, self.before_dict)
+
+                for key in root:
+                    res.extend(serialize(root[key], key, indent + 1))
+            elif (isinstance(root, col.Sequence) and
+                    not isinstance(root, six.string_types)):
+                if rootkey is not None:
+                    res[0] += self.list_sep
+                    if self.before_list is not None:
+                        res.insert(0, self.before_list)
+
+                for val in root:
+                    res.extend(serialize(val, None, indent + 1))
+            else:
+                str_root = str(root)
+                if '\n' in str_root:
+                    # we are in a submodel
+                    if rootkey is not None:
+                        res[0] += self.dict_sep
+
+                    list_root = [(self.indent_str * (indent + 1)) + line
+                                 for line in str_root.split('\n')]
+                    res.extend(list_root)
+                else:
+                    # just a normal key or list entry
+                    try:
+                        res[0] += self.key_sep + str_root
+                    except IndexError:
+                        res = [(self.indent_str * indent) + str_root]
+
+            return res
+
+        return "\n".join(serialize(model, None, -1))
+
+
+class TableView(object):
+    """A Basic Table Text View
+
+    This view performs serialization of data into a basic table with
+    predefined column names and mappings.  Column width is auto-calculated
+    evenly, column values are automatically truncated accordingly.  Values
+    are centered in the columns.
+
+    :param [str] column_names: the headers for each of the columns
+    :param [str] column_values: the item name to match each column to in
+                                each row
+    :param str table_prop_name: the name of the property within the model
+                                containing the row models
+    """
+
+    def __init__(self, column_names, column_values, table_prop_name):
+        self.table_prop_name = table_prop_name
+        self.column_names = column_names
+        self.column_values = column_values
+        self.column_width = (72 - len(column_names) + 1) / len(column_names)
+
+        column_headers = "|".join(
+            "{ch[" + str(n) + "]: ^" + str(self.column_width) + "}"
+            for n in range(len(column_names))
+        )
+
+        # correct for float-to-int roundoff error
+        test_fmt = column_headers.format(ch=column_names)
+        if len(test_fmt) < 72:
+            column_headers += ' ' * (72 - len(test_fmt))
+
+        vert_divider = '-' * 72
+        self.header_fmt_str = column_headers + "\n" + vert_divider + "\n"
+
+        self.row_fmt_str = "|".join(
+            "{cv[" + str(n) + "]: ^" + str(self.column_width) + "}"
+            for n in range(len(column_values))
+        )
+
+    def __call__(self, model):
+        res = self.header_fmt_str.format(ch=self.column_names)
+        for raw_row in model[self.table_prop_name]:
+            row = [str(raw_row[prop_name]) for prop_name in self.column_values]
+            # double format is in case we have roundoff error
+            res += '{0: <72}\n'.format(self.row_fmt_str.format(cv=row))
+
+        return res
diff --git a/cinder/openstack/common/report/views/text/header.py b/cinder/openstack/common/report/views/text/header.py
new file mode 100644 (file)
index 0000000..aa4c72a
--- /dev/null
@@ -0,0 +1,52 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Text Views With Headers
+
+This package defines several text views with headers
+"""
+
+
+class HeaderView(object):
+    """A Text View With a Header
+
+    This view simply serializes the model and places the given
+    header on top.
+
+    :param header: the header (can be anything on which str() can be called)
+    """
+
+    def __init__(self, header):
+        self.header = header
+
+    def __call__(self, model):
+        return str(self.header) + "\n" + str(model)
+
+
+class TitledView(HeaderView):
+    """A Text View With a Title
+
+    This view simply serializes the model, and places
+    a preformatted header containing the given title
+    text on top.  The title text can be up to 64 characters
+    long.
+
+    :param str title:  the title of the view
+    """
+
+    FORMAT_STR = ('=' * 72) + "\n===={0: ^64}====\n" + ('=' * 72)
+
+    def __init__(self, title):
+        super(TitledView, self).__init__(self.FORMAT_STR.format(title))
+
diff --git a/cinder/openstack/common/report/views/text/threading.py b/cinder/openstack/common/report/views/text/threading.py
new file mode 100644 (file)
index 0000000..df63b84
--- /dev/null
@@ -0,0 +1,80 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides thread and stack-trace views
+
+This module provides a collection of views for
+visualizing threads, green threads, and stack traces
+in human-readable form.
+"""
+
+import cinder.openstack.common.report.views.jinja_view as jv
+
+
+class StackTraceView(jv.JinjaView):
+    """A Stack Trace View
+
+    This view displays stack trace models defined by
+    :class:`openstack.common.report.models.threading.StackTraceModel`
+    """
+
+    VIEW_TEXT = (
+        "{% if root_exception is not none %}"
+        "Exception: {{ root_exception }}\n"
+        "------------------------------------\n"
+        "\n"
+        "{% endif %}"
+        "{% for line in lines %}\n"
+        "{{ line.filename }}:{{ line.line }} in {{ line.name }}\n"
+        "    {% if line.code is not none %}"
+        "`{{ line.code }}`"
+        "{% else %}"
+        "(source not found)"
+        "{% endif %}\n"
+        "{% else %}\n"
+        "No Traceback!\n"
+        "{% endfor %}"
+    )
+
+
+class GreenThreadView(object):
+    """A Green Thread View
+
+    This view displays a green thread provided by the data
+    model :class:`openstack.common.report.models.threading.GreenThreadModel`  # noqa
+    """
+
+    FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}"
+
+    def __call__(self, model):
+        return self.FORMAT_STR.format(
+            thread_str=" Green Thread ",
+            stack_trace=model.stack_trace
+        )
+
+
+class ThreadView(object):
+    """A Thread Collection View
+
+    This view displays a python thread provided by the data
+    model :class:`openstack.common.report.models.threading.ThreadModel`  # noqa
+    """
+
+    FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}"
+
+    def __call__(self, model):
+        return self.FORMAT_STR.format(
+            thread_str=" Thread #{0} ".format(model.thread_id),
+            stack_trace=model.stack_trace
+        )
diff --git a/cinder/openstack/common/report/views/xml/__init__.py b/cinder/openstack/common/report/views/xml/__init__.py
new file mode 100644 (file)
index 0000000..bfcc851
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides basic XML views
+
+This module provides several basic views which serialize
+models into XML.
+"""
\ No newline at end of file
diff --git a/cinder/openstack/common/report/views/xml/generic.py b/cinder/openstack/common/report/views/xml/generic.py
new file mode 100644 (file)
index 0000000..96337fa
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright 2013 Red Hat, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""Provides generic XML views
+
+This modules defines several basic views for serializing
+data to XML.  Submodels that have already been serialized
+as XML may have their string values marked with `__is_xml__
+= True` using :class:`openstack.common.report.utils.StringWithAttrs`
+(each of the classes within this module does this automatically,
+and non-naive serializers check for this attribute and handle
+such strings specially)
+"""
+
+import collections as col
+import copy
+import xml.etree.ElementTree as ET
+
+import six
+
+import cinder.openstack.common.report.utils as utils
+
+
+class KeyValueView(object):
+    """A Key-Value XML View
+
+    This view performs advanced serialization of a data model
+    into XML.  It first deserializes any values marked as XML so
+    that they can be properly reserialized later.  It then follows
+    the following rules to perform serialization:
+
+    key : text/xml
+        The tag name is the key name, and the contents are the text or xml
+    key : Sequence
+        A wrapper tag is created with the key name, and each item is placed
+        in an 'item' tag
+    key : Mapping
+        A wrapper tag is created with the key name, and the serialize is called
+        on each key-value pair (such that each key gets its own tag)
+
+    :param str wrapper_name: the name of the top-level element
+    """
+
+    def __init__(self, wrapper_name="model"):
+        self.wrapper_name = wrapper_name
+
+    def __call__(self, model):
+        # this part deals with subviews that were already serialized
+        cpy = copy.deepcopy(model)
+        for key, valstr in model.items():
+            if getattr(valstr, '__is_xml__', False):
+                cpy[key] = ET.fromstring(valstr)
+
+        def serialize(rootmodel, rootkeyname):
+            res = ET.Element(rootkeyname)
+
+            if isinstance(rootmodel, col.Mapping):
+                for key in rootmodel:
+                    res.append(serialize(rootmodel[key], key))
+            elif (isinstance(rootmodel, col.Sequence)
+                    and not isinstance(rootmodel, six.string_types)):
+                for val in rootmodel:
+                    res.append(serialize(val, 'item'))
+            elif ET.iselement(rootmodel):
+                res.append(rootmodel)
+            else:
+                res.text = str(rootmodel)
+
+            return res
+
+        res = utils.StringWithAttrs(ET.tostring(serialize(cpy,
+                                                          self.wrapper_name)))
+        res.__is_xml__ = True
+        return res
diff --git a/doc/source/devref/gmr.rst b/doc/source/devref/gmr.rst
new file mode 100644 (file)
index 0000000..3b098d9
--- /dev/null
@@ -0,0 +1,90 @@
+..
+      Copyright (c) 2013 OpenStack Foundation
+      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.
+
+Guru Meditation Reports
+=======================
+
+Cinder contains a mechanism whereby developers and system administrators can
+generate a report about the state of a running Cinder executable.
+This report is called a *Guru Meditation Report* (*GMR* for short).
+
+Generating a GMR
+----------------
+
+A *GMR* can be generated by sending the *USR1* signal to any Cinder process
+with support (see below).
+The *GMR* will then be outputted standard error for that particular process.
+
+For example, suppose that ``cinder-api`` has process id ``8675``, and was run
+with ``2>/var/log/cinder/cinder-api-err.log``.
+Then, ``kill -USR1 8675`` will trigger the Guru Meditation report to be printed
+to ``/var/log/cinder/cinder-api-err.log``.
+
+Structure of a GMR
+------------------
+
+The *GMR* is designed to be extensible; any particular executable may add
+its own sections.  However, the base *GMR* consists of several sections:
+
+Package
+  Shows information about the package to which this process belongs,
+  including version information
+
+Threads
+  Shows stack traces and thread ids for each of the threads within this process
+
+Green Threads
+  Shows stack traces for each of the green threads within this process
+  (green threads don't have thread ids)
+
+Configuration
+  Lists all the configuration options currently accessible via the CONF object
+  for the current process
+
+Adding Support for GMRs to New Executables
+------------------------------------------
+
+Adding support for a *GMR* to a given executable is fairly easy.
+
+First import the module (currently residing in oslo-incubator), as well as the
+Cinder version module:
+
+.. code-block:: python
+
+      from cinder.openstack.common.report import guru_meditation_report as gmr
+      from cinder import version
+
+Then, register any additional sections (optional):
+
+.. code-block:: python
+
+      TextGuruMeditation.register_section('Some Special Section',
+                                          some_section_generator)
+
+Finally (under main), before running the "main loop" of the executable
+(usually ``service.server(server)`` or something similar), register the *GMR*
+hook:
+
+.. code-block:: python
+
+      TextGuruMeditation.setup_autorun(version)
+
+Extending the GMR
+-----------------
+
+As mentioned above, additional sections can be added to the GMR for a
+particular executable.  For more information, see the inline documentation
+under :mod:`cinder.openstack.common.report`
\ No newline at end of file
index 15542dfad29e7f1b322d21d7268e7f4a56e5faaf..bebafd7e41d63f42fab37bdade0d86c487b3722e 100644 (file)
@@ -30,6 +30,7 @@ Programming HowTos and Tutorials
     unit_tests
     addmethod.openstackapi
     drivers
+    gmr
 
 
 Background Concepts for Cinder
index 00f22d387be5467c03f5e6d0ea3f0925705917ce..54ebbc53d1e5a618d122846200c20543659b7880 100644 (file)
@@ -16,6 +16,7 @@ module=scheduler.filters
 module=scheduler.weights
 module=service
 module=versionutils
+module=report
 
 # The base module to hold the copy of openstack.common
 base=cinder