]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Sync 'report' from oslo-incubator
authorEric Harney <eharney@redhat.com>
Wed, 17 Jun 2015 20:00:35 +0000 (16:00 -0400)
committerEric Harney <eharney@redhat.com>
Wed, 17 Jun 2015 20:34:22 +0000 (16:34 -0400)
Needed to fix guru meditation report for Windows.
We landed an outdated 'report' module recently.

Add report.[generators,models,views] to
openstack-common.conf.

Oslo source: c4c7dd28 Updated from global requirements

Closes-Bug: #1286528

Change-Id: Ib33542718176241663c0e61605acbf4e941e2ca9

29 files changed:
cinder/openstack/common/report/__init__.py
cinder/openstack/common/report/generators/__init__.py
cinder/openstack/common/report/generators/conf.py
cinder/openstack/common/report/generators/process.py [new file with mode: 0644]
cinder/openstack/common/report/generators/threading.py
cinder/openstack/common/report/generators/version.py
cinder/openstack/common/report/guru_meditation_report.py
cinder/openstack/common/report/models/__init__.py
cinder/openstack/common/report/models/base.py
cinder/openstack/common/report/models/conf.py
cinder/openstack/common/report/models/process.py [new file with mode: 0644]
cinder/openstack/common/report/models/threading.py
cinder/openstack/common/report/models/version.py
cinder/openstack/common/report/models/with_default_views.py
cinder/openstack/common/report/report.py
cinder/openstack/common/report/utils.py
cinder/openstack/common/report/views/__init__.py
cinder/openstack/common/report/views/jinja_view.py
cinder/openstack/common/report/views/json/__init__.py
cinder/openstack/common/report/views/json/generic.py
cinder/openstack/common/report/views/text/__init__.py
cinder/openstack/common/report/views/text/generic.py
cinder/openstack/common/report/views/text/header.py
cinder/openstack/common/report/views/text/process.py [new file with mode: 0644]
cinder/openstack/common/report/views/text/threading.py
cinder/openstack/common/report/views/xml/__init__.py
cinder/openstack/common/report/views/xml/generic.py
openstack-common.conf
requirements.txt

index b296462c0835914c9cb86e72af649dac162a50ad..35390ecd3924b30daaea0c5194e05e84a00ad448 100644 (file)
@@ -22,4 +22,4 @@ is composed of one or more report sections
 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
+"""
index 26474e3bac1559ee7539b00668d35feccae2212a..68473f22d5aea553ae9c57af9ead76fd083c1d17 100644 (file)
@@ -18,4 +18,4 @@ 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
+"""
index 31ccaecbbc187ea94da85d46f516427ea4322ec4..c6a916bfee1ae203e1baef03fe4f649a233bbbc7 100644 (file)
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-"""Provides Openstack config generators
+"""Provides OpenStack config generators
 
 This module defines a class for configuration
 generators for generating the model in
@@ -21,20 +21,20 @@ generators for generating the model in
 
 from oslo_config import cfg
 
-import cinder.openstack.common.report.models.conf as cm
+from cinder.openstack.common.report.models import conf as cm
 
 
 class ConfigReportGenerator(object):
     """A Configuration Data Generator
 
     This generator returns
-    :class:`openstack.common.report.models.conf.ConfigModel` ,
+    :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.
+    OpenStack stores everything.
 
     :param cnf: the configuration option object
-    :type cnf: :class:`oslo.config.cfg.ConfigOpts`
+    :type cnf: :class:`oslo_config.cfg.ConfigOpts`
     """
 
     def __init__(self, cnf=cfg.CONF):
diff --git a/cinder/openstack/common/report/generators/process.py b/cinder/openstack/common/report/generators/process.py
new file mode 100644 (file)
index 0000000..57afb7a
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright 2014 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 process-data generators
+
+This modules defines a class for generating
+process data by way of the psutil package.
+"""
+
+import os
+
+import psutil
+
+from cinder.openstack.common.report.models import process as pm
+
+
+class ProcessReportGenerator(object):
+    """A Process Data Generator
+
+    This generator returns a
+    :class:`openstack.common.report.models.process.ProcessModel`
+    based on the current process (which will also include
+    all subprocesses, recursively) using the :class:`psutil.Process` class`.
+    """
+
+    def __call__(self):
+        return pm.ProcessModel(psutil.Process(os.getpid()))
index 4f803e4b7fb11a31aaf93a9813cb0d7b5f0c92f5..10c07503f95ca33c37c7c26e7272cee029c4d87a 100644 (file)
@@ -19,14 +19,17 @@ generators for generating the models in
 :mod:`openstack.common.report.models.threading`.
 """
 
+from __future__ import absolute_import
+
 import sys
+import threading
 
 import greenlet
 
-import cinder.openstack.common.report.models.threading as tm
+from cinder.openstack.common.report.models import 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
+from cinder.openstack.common.report import utils as rutils
+from cinder.openstack.common.report.views.text import generic as text_views
 
 
 class ThreadReportGenerator(object):
@@ -35,17 +38,28 @@ class ThreadReportGenerator(object):
     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()` .
+    :func:`sys._current_frames()` .  Its constructor may optionally
+    be passed a frame object.  This frame object will be interpreted
+    as the actual stack trace for the current thread, and, come generation
+    time, will be used to replace the stack trace of the thread in which
+    this code is running.
     """
 
+    def __init__(self, curr_thread_traceback=None):
+        self.traceback = curr_thread_traceback
+
     def __call__(self):
-        threadModels = [
-            tm.ThreadModel(thread_id, stack)
+        threadModels = dict(
+            (thread_id, tm.ThreadModel(thread_id, stack))
             for thread_id, stack in sys._current_frames().items()
-        ]
+        )
+
+        if self.traceback is not None:
+            curr_thread_id = threading.current_thread().ident
+            threadModels[curr_thread_id] = tm.ThreadModel(curr_thread_id,
+                                                          self.traceback)
 
-        thread_pairs = dict(zip(range(len(threadModels)), threadModels))
-        return mwdv.ModelWithDefaultViews(thread_pairs,
+        return mwdv.ModelWithDefaultViews(threadModels,
                                           text_view=text_views.MultiView())
 
 
@@ -68,6 +82,5 @@ class GreenThreadReportGenerator(object):
             for gr in rutils._find_objects(greenlet.greenlet)
         ]
 
-        thread_pairs = dict(zip(range(len(threadModels)), threadModels))
-        return mwdv.ModelWithDefaultViews(thread_pairs,
+        return mwdv.ModelWithDefaultViews(threadModels,
                                           text_view=text_views.MultiView())
index 05936fc745896ebd0c0ac6bc998b8ea2a1ec0fbb..77e1b3b807dda174c32020c5f6056d24996c6fbf 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-"""Provides Openstack version generators
+"""Provides OpenStack version generators
 
-This module defines a class for Openstack
+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
+from cinder.openstack.common.report.models import version as vm
 
 
 class PackageReportGenerator(object):
@@ -40,7 +40,21 @@ class PackageReportGenerator(object):
         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())
+        if hasattr(self.version_obj, "vendor_string"):
+            vendor_string = self.version_obj.vendor_string()
+        else:
+            vendor_string = None
+
+        if hasattr(self.version_obj, "product_string"):
+            product_string = self.version_obj.product_string()
+        else:
+            product_string = None
+
+        if hasattr(self.version_obj, "version_string_with_package"):
+            version_string_with_package = self.version_obj.\
+                version_string_with_package()
+        else:
+            version_string_with_package = None
+
+        return vm.PackageModel(vendor_string, product_string,
+                               version_string_with_package)
index 9693f9be783a4eef4d1d081e372934b80d6f3114..dd8af3924eb4be7eb5e470c9d3de20847d49c3fd 100644 (file)
@@ -14,7 +14,8 @@
 
 """Provides Guru Meditation Report
 
-This module defines the actual OpenStack Guru Meditation Report class.
+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):
@@ -50,10 +51,15 @@ where stderr is logged for that given service.
 
 from __future__ import print_function
 
+import inspect
+import os
 import signal
 import sys
 
+from oslo_utils import timeutils
+
 from cinder.openstack.common.report.generators import conf as cgen
+from cinder.openstack.common.report.generators import process as prgen
 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
@@ -73,8 +79,11 @@ class GuruMeditation(object):
     MRO is correct.
     """
 
-    def __init__(self, version_obj, *args, **kwargs):
+    timestamp_fmt = "%Y%m%d%H%M%S"
+
+    def __init__(self, version_obj, sig_handler_tb=None, *args, **kwargs):
         self.version_obj = version_obj
+        self.traceback = sig_handler_tb
 
         super(GuruMeditation, self).__init__(*args, **kwargs)
         self.start_section_index = len(self.sections)
@@ -96,37 +105,69 @@ class GuruMeditation(object):
             cls.persistent_sections = [[section_title, generator]]
 
     @classmethod
-    def setup_autorun(cls, version, signum=signal.SIGUSR1):
+    def setup_autorun(cls, version, service_name=None,
+                      log_dir=None, signum=None):
         """Set Up Auto-Run
 
         This method sets up the Guru Meditation Report to automatically
-        get dumped to stderr when the given signal is received.
+        get dumped to stderr or a file in a given dir when the given signal
+        is received.
 
         :param version: the version object for the current product
+        :param service_name: this program name used to construct logfile name
+        :param logdir: path to a log directory where to create a file
         :param signum: the signal to associate with running the report
         """
 
-        signal.signal(signum, lambda *args: cls.handle_signal(version, *args))
+        if not signum and hasattr(signal, 'SIGUSR1'):
+            # SIGUSR1 is not supported on all platforms
+            signum = signal.SIGUSR1
+
+        if signum:
+            signal.signal(signum,
+                          lambda sn, tb: cls.handle_signal(
+                              version, service_name, log_dir, tb))
 
     @classmethod
-    def handle_signal(cls, version, *args):
+    def handle_signal(cls, version, service_name, log_dir, traceback):
         """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
+        dumping the Guru Meditation Report to stderr or a file in a given dir.
+        If service name and log dir are not None, the report will be dumped to
+        a file named $service_name_gurumeditation_$current_time in the log_dir
+        directory.
+        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
+        :param service_name: this program name used to construct logfile name
+        :param logdir: path to a log directory where to create a file
+        :param traceback: the traceback provided to the signal handler
         """
 
         try:
-            res = cls(version).run()
+            res = cls(version, traceback).run()
         except Exception:
             print("Unable to run Guru Meditation Report!",
                   file=sys.stderr)
         else:
-            print(res, file=sys.stderr)
+            if log_dir:
+                service_name = service_name or os.path.basename(
+                    inspect.stack()[-1][1])
+                filename = "%s_gurumeditation_%s" % (
+                    service_name, timeutils.utcnow().strftime(
+                        cls.timestamp_fmt))
+                filepath = os.path.join(log_dir, filename)
+                try:
+                    with open(filepath, "w") as dumpfile:
+                        dumpfile.write(res)
+                except Exception:
+                    print("Unable to dump Guru Meditation Report to file %s" %
+                          (filepath,), file=sys.stderr)
+            else:
+                print(res, file=sys.stderr)
 
     def _readd_sections(self):
         del self.sections[self.start_section_index:]
@@ -135,11 +176,14 @@ class GuruMeditation(object):
                          pgen.PackageReportGenerator(self.version_obj))
 
         self.add_section('Threads',
-                         tgen.ThreadReportGenerator())
+                         tgen.ThreadReportGenerator(self.traceback))
 
         self.add_section('Green Threads',
                          tgen.GreenThreadReportGenerator())
 
+        self.add_section('Processes',
+                         prgen.ProcessReportGenerator())
+
         self.add_section('Configuration',
                          cgen.ConfigReportGenerator())
 
@@ -169,11 +213,15 @@ class TextGuruMeditation(GuruMeditation, report.TextReport):
 
     - Green Threads List
 
+    - Process List
+
     - Configuration Options
 
     :param version_obj: the version object for the current product
+    :param traceback: an (optional) frame object providing the actual
+                      traceback for the current thread
     """
 
-    def __init__(self, version_obj):
-        super(TextGuruMeditation, self).__init__(version_obj,
+    def __init__(self, version_obj, traceback=None):
+        super(TextGuruMeditation, self).__init__(version_obj, traceback,
                                                  'Guru Meditation')
index 8430fc45f3e7fba7c33c8d4bbb0ae54163ecd677..7bfed3d882be9cfb8fad27fb5d9ff9b7f139dd11 100644 (file)
@@ -17,4 +17,4 @@
 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
+"""
index 90914ffe07147d41f1d15e28b13aa610683f5240..d840c5b514b54da6c2f789480136f8e975065a92 100644 (file)
@@ -24,6 +24,8 @@ the report serialization process.
 import collections as col
 import copy
 
+import six
+
 
 class ReportModel(col.MutableMapping):
     """A Report Data Model
@@ -37,13 +39,29 @@ class ReportModel(col.MutableMapping):
     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
+    If present, the object passed in as data will be transformed
+    into a standard python dict.  For mappings, this is fairly
+    straightforward.  For sequences, the indices become keys
+    and the items become values.
+
+    :param data: a sequence or mapping of data to 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 {}
+
+        if data is not None:
+            if isinstance(data, col.Mapping):
+                self.data = dict(data)
+            elif isinstance(data, col.Sequence):
+                # convert a list [a, b, c] to a dict {0: a, 1: b, 2: c}
+                self.data = dict(enumerate(data))
+            else:
+                raise TypeError('Data for the model must be a sequence '
+                                'or mapping.')
+        else:
+            self.data = {}
 
     def __str__(self):
         self_cpy = copy.deepcopy(self)
@@ -81,9 +99,16 @@ class ReportModel(col.MutableMapping):
         return self.data.__contains__(key)
 
     def __getattr__(self, attrname):
+        # Needed for deepcopy in Python3. That will avoid an infinite loop
+        # in __getattr__ .
+        if 'data' not in self.__dict__:
+            self.data = {}
+
         try:
             return self.data[attrname]
         except KeyError:
+            # we don't have that key in data, and the
+            # model class doesn't have that attribute
             raise AttributeError(
                 "'{cl}' object has no attribute '{an}'".format(
                     cl=type(self).__name__, an=attrname
@@ -96,19 +121,42 @@ class ReportModel(col.MutableMapping):
     def __iter__(self):
         return self.data.__iter__()
 
-    def set_current_view_type(self, tp):
+    def set_current_view_type(self, tp, visited=None):
         """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)
+        itself recursively on all values, traversing
+        intervening sequences and mappings when possible,
+        and ignoring all other objects.
 
         :param tp: the type of the view ('text', 'json', 'xml', etc)
+        :param visited: a set of object ids for which the corresponding objects
+                        have already had their view type set
         """
 
-        for key in self:
-            try:
-                self[key].set_current_view_type(tp)
-            except AttributeError:
-                pass
+        if visited is None:
+            visited = set()
+
+        def traverse_obj(obj):
+            oid = id(obj)
+
+            # don't die on recursive structures,
+            # and don't treat strings like sequences
+            if oid in visited or isinstance(obj, six.string_types):
+                return
+
+            visited.add(oid)
+
+            if hasattr(obj, 'set_current_view_type'):
+                obj.set_current_view_type(tp, visited=visited)
+
+            if isinstance(obj, col.Sequence):
+                for item in obj:
+                    traverse_obj(item)
+
+            elif isinstance(obj, col.Mapping):
+                for val in six.itervalues(obj):
+                    traverse_obj(val)
+
+        traverse_obj(self)
index 77e24ab9f8fa226f294de3f9eef1fa84b0ff95c5..f692d3c76f6bc38e2c9cab732c49744155b9eecf 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-"""Provides Openstack Configuration Model
+"""Provides OpenStack Configuration Model
 
 This module defines a class representing the data
-model for :mod:`oslo.config` configuration options
+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
+from cinder.openstack.common.report.models import with_default_views as mwdv
+from cinder.openstack.common.report.views.text import 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
+    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`
+    :type conf_obj: :class:`oslo_config.cfg.ConfigOpts`
     """
 
     def __init__(self, conf_obj):
@@ -41,8 +41,15 @@ class ConfigModel(mwdv.ModelWithDefaultViews):
         def opt_title(optname, co):
             return co._opts[optname]['opt'].name
 
+        def opt_value(opt_obj, value):
+            if opt_obj['opt'].secret:
+                return '***'
+            else:
+                return value
+
         self['default'] = dict(
-            (opt_title(optname, conf_obj), conf_obj[optname])
+            (opt_title(optname, conf_obj),
+             opt_value(conf_obj._opts[optname], conf_obj[optname]))
             for optname in conf_obj._opts
         )
 
@@ -50,9 +57,10 @@ class ConfigModel(mwdv.ModelWithDefaultViews):
         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
-            )
+                (opt_title(optname, group_obj),
+                 opt_value(group_obj._opts[optname],
+                           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/process.py b/cinder/openstack/common/report/models/process.py
new file mode 100644 (file)
index 0000000..22d1e98
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright 2014 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 process model
+
+This module defines a class representing a process,
+potentially with subprocesses.
+"""
+
+import cinder.openstack.common.report.models.with_default_views as mwdv
+import cinder.openstack.common.report.views.text.process as text_views
+
+
+class ProcessModel(mwdv.ModelWithDefaultViews):
+    """A Process Model
+
+    This model holds data about a process,
+    including references to any subprocesses
+
+    :param process: a :class:`psutil.Process` object
+    """
+
+    def __init__(self, process):
+        super(ProcessModel, self).__init__(
+            text_view=text_views.ProcessView())
+
+        self['pid'] = process.pid
+        self['parent_pid'] = process.ppid
+        if hasattr(process, 'uids'):
+            self['uids'] = {'real': process.uids.real,
+                            'effective': process.uids.effective,
+                            'saved': process.uids.saved}
+        else:
+            self['uids'] = {'real': None,
+                            'effective': None,
+                            'saved': None}
+
+        if hasattr(process, 'gids'):
+            self['gids'] = {'real': process.gids.real,
+                            'effective': process.gids.effective,
+                            'saved': process.gids.saved}
+        else:
+            self['gids'] = {'real': None,
+                            'effective': None,
+                            'saved': None}
+
+        self['username'] = process.username
+        self['command'] = process.cmdline
+        self['state'] = process.status
+
+        self['children'] = [ProcessModel(pr) for pr in process.get_children()]
index 15e1fdd02f1eb756c80cd747a87e51efc676cf98..94ccfcd987daf67bd17668458702ccb65258ce85 100644 (file)
@@ -20,8 +20,8 @@ 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
+from cinder.openstack.common.report.models import with_default_views as mwdv
+from cinder.openstack.common.report.views.text import threading as text_views
 
 
 class StackTraceModel(mwdv.ModelWithDefaultViews):
@@ -42,12 +42,12 @@ class StackTraceModel(mwdv.ModelWithDefaultViews):
                 {'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:
+            # FIXME(flepied): under Python3 f_exc_type doesn't exist
+            # anymore so we lose information about exceptions
+            if getattr(stack_state, 'f_exc_type', None) is not None:
                 self['root_exception'] = {
                     'type': stack_state.f_exc_type,
-                    'value': stack_state.f_exc_value
-                }
+                    'value': stack_state.f_exc_value}
             else:
                 self['root_exception'] = None
         else:
index 3666dc210677c94c38bedf2e83bd3ef8c245eef0..7602ce2a296964c99ac6ac44817ffd67de7557ea 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-"""Provides Openstack Version Info Model
+"""Provides OpenStack Version Info Model
 
 This module defines a class representing the data
-model for Openstack package and version information
+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
+from cinder.openstack.common.report.models import with_default_views as mwdv
+from cinder.openstack.common.report.views.text import generic as generic_text_views
 
 
 class PackageModel(mwdv.ModelWithDefaultViews):
index d1a8e42ba5f675ed67fd8591be8c6986fe2d1bc8..43f6cbe8bde1fff1f22a7e7ff6e7bef9e279d2f9 100644 (file)
 
 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
+from cinder.openstack.common.report.models import base as base_model
+from cinder.openstack.common.report.views.json import generic as jsonviews
+from cinder.openstack.common.report.views.text import generic as textviews
+from cinder.openstack.common.report.views.xml import generic as xmlviews
 
 
 class ModelWithDefaultViews(base_model.ReportModel):
@@ -28,18 +28,18 @@ class ModelWithDefaultViews(base_model.ReportModel):
     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
+    Parameters are as the superclass, except for 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`
+        :class:`openstack.common.report.views.text.generic.KeyValueView`
     xml
-        :class:`openstack.common.views.xml.generic.KeyValueView`
+        :class:`openstack.common.report.views.xml.generic.KeyValueView`
     json
-        :class:`openstack.common.views.json.generic.KeyValueView`
+        :class:`openstack.common.report.views.json.generic.KeyValueView`
 
     .. function:: to_type()
 
@@ -64,19 +64,18 @@ class ModelWithDefaultViews(base_model.ReportModel):
                 del newargs[k]
         super(ModelWithDefaultViews, self).__init__(*args, **newargs)
 
-    def set_current_view_type(self, tp):
+    def set_current_view_type(self, tp, visited=None):
         self.attached_view = self.views[tp]
-        super(ModelWithDefaultViews, self).set_current_view_type(tp)
+        super(ModelWithDefaultViews, self).set_current_view_type(tp, visited)
 
     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:]})
+                raise NotImplementedError((
+                    "Model {cn.__module__}.{cn.__name__} does not have" +
+                    " a default view for "
+                    "{tp}").format(cn=type(self), tp=attrname[3:]))
         else:
             return super(ModelWithDefaultViews, self).__getattr__(attrname)
index 77f5d8533e7926b94fcf8796acabea98342f4877..96b070afdd5c543c46736e96305d535c2fc26fe8 100644 (file)
 
 """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.
+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
+from cinder.openstack.common.report.views.text import header as header_views
 
 
 class BasicReport(object):
@@ -28,7 +28,7 @@ class BasicReport(object):
     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`
+    be serialized by calling :func:`run`.
     """
 
     def __init__(self):
@@ -78,10 +78,9 @@ class BasicReport(object):
 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.
+    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::
 
@@ -90,8 +89,7 @@ class ReportSection(object):
 
     :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)
+      (any callable object which takes no parameters and returns a data model)
     """
 
     def __init__(self, view, generator):
index 5f1dd1e7c8580859f031c6d315ce1de13a424a45..fb71e36ae91fd4e45eb12b954e7206d682de5765 100644 (file)
@@ -1,4 +1,4 @@
-#  Copyright 2013 Red Hat, Inc.
+# 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
index 834f101cec8aa64dbabe2b755bb476d938e16116..612959b2e0e19266df56781360e661e392e5fc7a 100644 (file)
@@ -19,4 +19,4 @@ 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
+"""
index a6f340e8e0ad5cadc2a08755356768fc11559ae7..5f57dc34ab8128470b34233b444bc4e2c6752f06 100644 (file)
@@ -19,6 +19,8 @@ system for serialization.  For more information on Jinja, please
 see http://jinja.pocoo.org/ .
 """
 
+import copy
+
 import jinja2
 
 
@@ -79,6 +81,16 @@ class JinjaView(object):
     def __call__(self, model):
         return self.template.render(**model)
 
+    def __deepcopy__(self, memodict):
+        res = object.__new__(JinjaView)
+        res._text = copy.deepcopy(self._text, memodict)
+
+        # regenerate the template on a deepcopy
+        res._regentemplate = True
+        res._templatecache = None
+
+        return res
+
     @property
     def template(self):
         """Get the Compiled Template
index 2b3933e0c8af9569868e4537e0850827a25ae0b8..47bd33b6f98cb576d0a8baded7f166c92356e9cb 100644 (file)
@@ -16,4 +16,4 @@
 
 This module provides several basic views which serialize
 models into JSON.
-""" 
\ No newline at end of file
+"""
index 079c018727bca7c29970d26063a5676fd6c8173d..f2769e31c53e8673c75c49a911c45aea5b0a06eb 100644 (file)
@@ -27,7 +27,7 @@ import copy
 
 from oslo_serialization import jsonutils as json
 
-import cinder.openstack.common.report.utils as utils
+from cinder.openstack.common.report import utils as utils
 
 
 class BasicKeyValueView(object):
@@ -57,10 +57,10 @@ class KeyValueView(object):
     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)
+        for key in model.keys():
+            if getattr(model[key], '__is_json__', False):
+                cpy[key] = json.loads(model[key])
 
-        res = utils.StringWithAttrs(json.dumps(cpy.data))
+        res = utils.StringWithAttrs(json.dumps(cpy.data, sort_keys=True))
         res.__is_json__ = True
         return res
index 3f17fdeeb1d3f38e2d22aeed51c558747335ce73..c0974844365b00744c71cfcc8272061d4b2f04e8 100644 (file)
@@ -16,4 +16,4 @@
 
 This module provides several basic views which serialize
 models into human-readable text.
-"""
\ No newline at end of file
+"""
index 7363833058aeab6f09d2b868a963094b0ab33414..3b30a07079c9e24dd7b8b728fbe85d82e441f031 100644 (file)
@@ -120,7 +120,7 @@ class KeyValueView(object):
                     if self.before_dict is not None:
                         res.insert(0, self.before_dict)
 
-                for key in root:
+                for key in sorted(root):
                     res.extend(serialize(root[key], key, indent + 1))
             elif (isinstance(root, col.Sequence) and
                     not isinstance(root, six.string_types)):
@@ -129,7 +129,7 @@ class KeyValueView(object):
                     if self.before_list is not None:
                         res.insert(0, self.before_list)
 
-                for val in root:
+                for val in sorted(root, key=str):
                     res.extend(serialize(val, None, indent + 1))
             else:
                 str_root = str(root)
@@ -172,7 +172,7 @@ class TableView(object):
         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)
+        self.column_width = (72 - len(column_names) + 1) // len(column_names)
 
         column_headers = "|".join(
             "{ch[" + str(n) + "]: ^" + str(self.column_width) + "}"
index aa4c72a610665759ea12203b35cfa1f0e7298766..58d06c0d3ef3f0a96096c488e3b53cf9f785d8be 100644 (file)
@@ -49,4 +49,3 @@ class TitledView(HeaderView):
 
     def __init__(self, title):
         super(TitledView, self).__init__(self.FORMAT_STR.format(title))
-
diff --git a/cinder/openstack/common/report/views/text/process.py b/cinder/openstack/common/report/views/text/process.py
new file mode 100644 (file)
index 0000000..c7d7f12
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright 2014 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 process view
+
+This module provides a view for
+visualizing processes in human-readable formm
+"""
+
+import cinder.openstack.common.report.views.jinja_view as jv
+
+
+class ProcessView(jv.JinjaView):
+    """A Process View
+
+    This view displays process models defined by
+    :class:`openstack.common.report.models.process.ProcessModel`
+    """
+
+    VIEW_TEXT = (
+        "Process {{ pid }} (under {{ parent_pid }}) "
+        "[ run by: {{ username }} ({{ uids.real|default('unknown uid') }}),"
+        " state: {{ state }} ]\n"
+        "{% for child in children %}"
+        "    {{ child }}"
+        "{% endfor %}"
+    )
index df63b84f30945de8dcef8304bd6d1043199bd111..c4a11c07997d3a00ecf0a233811b42fded154c88 100644 (file)
@@ -19,7 +19,7 @@ visualizing threads, green threads, and stack traces
 in human-readable form.
 """
 
-import cinder.openstack.common.report.views.jinja_view as jv
+from cinder.openstack.common.report.views import jinja_view as jv
 
 
 class StackTraceView(jv.JinjaView):
@@ -52,7 +52,7 @@ 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
+    model :class:`openstack.common.report.models.threading.GreenThreadModel`
     """
 
     FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}"
index bfcc8511da9498badc7a301040f08743ddb66700..a40fec984d3aff9f373cd101e4d1f1ca79ea7726 100644 (file)
@@ -16,4 +16,4 @@
 
 This module provides several basic views which serialize
 models into XML.
-"""
\ No newline at end of file
+"""
index 96337fafda81bde0b468265da8ba4e1456c5b91d..59c3f4861070c35dc65b1ecd1c632af6e7e6ffd3 100644 (file)
@@ -29,7 +29,7 @@ import xml.etree.ElementTree as ET
 
 import six
 
-import cinder.openstack.common.report.utils as utils
+from cinder.openstack.common.report import utils as utils
 
 
 class KeyValueView(object):
@@ -66,11 +66,11 @@ class KeyValueView(object):
             res = ET.Element(rootkeyname)
 
             if isinstance(rootmodel, col.Mapping):
-                for key in rootmodel:
+                for key in sorted(rootmodel):
                     res.append(serialize(rootmodel[key], key))
             elif (isinstance(rootmodel, col.Sequence)
                     and not isinstance(rootmodel, six.string_types)):
-                for val in rootmodel:
+                for val in sorted(rootmodel, key=str):
                     res.append(serialize(val, 'item'))
             elif ET.iselement(rootmodel):
                 res.append(rootmodel)
@@ -79,7 +79,9 @@ class KeyValueView(object):
 
             return res
 
-        res = utils.StringWithAttrs(ET.tostring(serialize(cpy,
-                                                          self.wrapper_name)))
+        str_ = ET.tostring(serialize(cpy,
+                                     self.wrapper_name),
+                           encoding="utf-8").decode("utf-8")
+        res = utils.StringWithAttrs(str_)
         res.__is_xml__ = True
         return res
index 7c134b77352ce4b5de0469ee041529f2a3dccbf9..467a13d16d91b50547156215f041f82b2fb3d0d0 100644 (file)
@@ -15,6 +15,12 @@ module=scheduler.weights
 module=service
 module=versionutils
 module=report
+module=report.generators
+module=report.models
+module=report.views
+module=report.views.json
+module=report.views.text
+module=report.views.xml
 
 # The base module to hold the copy of openstack.common
 base=cinder
index d78ca2256f2db0188a9417744879248fe922c820..0e3b5c0b50eb3e93f5f9453971228dbb6b421e83 100644 (file)
@@ -28,6 +28,7 @@ osprofiler>=0.3.0                       # Apache-2.0
 paramiko>=1.13.0
 Paste
 PasteDeploy>=1.5.0
+psutil>=1.1.1,<2.0.0
 pycrypto>=2.6
 pyparsing>=2.0.1
 python-barbicanclient>=3.0.1