Sync 'report' from oslo-incubator

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
This commit is contained in:
Eric Harney 2015-06-17 16:00:35 -04:00
parent 8b581e0b01
commit 7dae430e81
29 changed files with 405 additions and 119 deletions

View 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.
"""
"""

View 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.
"""
"""

View 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):

View File

@ -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()))

View 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 __call__(self):
threadModels = [
tm.ThreadModel(thread_id, stack)
for thread_id, stack in sys._current_frames().items()
]
def __init__(self, curr_thread_traceback=None):
self.traceback = curr_thread_traceback
thread_pairs = dict(zip(range(len(threadModels)), threadModels))
return mwdv.ModelWithDefaultViews(thread_pairs,
def __call__(self):
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)
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())

View File

@ -12,15 +12,15 @@
# 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)

View 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')

View 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.
"""
"""

View 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)

View File

@ -12,25 +12,25 @@
# 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)

View File

@ -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()]

View 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:

View File

@ -12,14 +12,14 @@
# 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):

View File

@ -14,10 +14,10 @@
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)

View File

@ -14,12 +14,12 @@
"""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):

View 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

View 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.
"""
"""

View 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

View File

@ -16,4 +16,4 @@
This module provides several basic views which serialize
models into JSON.
"""
"""

View 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

View File

@ -16,4 +16,4 @@
This module provides several basic views which serialize
models into human-readable text.
"""
"""

View 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) + "}"

View File

@ -49,4 +49,3 @@ class TitledView(HeaderView):
def __init__(self, title):
super(TitledView, self).__init__(self.FORMAT_STR.format(title))

View File

@ -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 %}"
)

View 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}"

View File

@ -16,4 +16,4 @@
This module provides several basic views which serialize
models into XML.
"""
"""

View 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

View 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

View 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