From f2dc050e4df6ec52a2dad258bb4172ebad47422c Mon Sep 17 00:00:00 2001 From: wanghao Date: Thu, 14 May 2015 15:42:44 +0800 Subject: [PATCH] Introduce Guru Meditation Reports into Cinder 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 --- cinder/cmd/api.py | 3 + cinder/cmd/backup.py | 2 + cinder/cmd/scheduler.py | 2 + cinder/cmd/volume.py | 2 + cinder/openstack/common/report/__init__.py | 25 +++ .../common/report/generators/__init__.py | 21 ++ .../common/report/generators/conf.py | 44 ++++ .../common/report/generators/threading.py | 73 +++++++ .../common/report/generators/version.py | 46 ++++ .../common/report/guru_meditation_report.py | 179 ++++++++++++++++ .../common/report/models/__init__.py | 20 ++ cinder/openstack/common/report/models/base.py | 114 ++++++++++ cinder/openstack/common/report/models/conf.py | 58 +++++ .../common/report/models/threading.py | 100 +++++++++ .../openstack/common/report/models/version.py | 44 ++++ .../report/models/with_default_views.py | 82 +++++++ cinder/openstack/common/report/report.py | 189 ++++++++++++++++ cinder/openstack/common/report/utils.py | 46 ++++ .../openstack/common/report/views/__init__.py | 22 ++ .../common/report/views/jinja_view.py | 125 +++++++++++ .../common/report/views/json/__init__.py | 19 ++ .../common/report/views/json/generic.py | 66 ++++++ .../common/report/views/text/__init__.py | 19 ++ .../common/report/views/text/generic.py | 202 ++++++++++++++++++ .../common/report/views/text/header.py | 52 +++++ .../common/report/views/text/threading.py | 80 +++++++ .../common/report/views/xml/__init__.py | 19 ++ .../common/report/views/xml/generic.py | 85 ++++++++ doc/source/devref/gmr.rst | 90 ++++++++ doc/source/devref/index.rst | 1 + openstack-common.conf | 1 + 31 files changed, 1831 insertions(+) create mode 100644 cinder/openstack/common/report/__init__.py create mode 100644 cinder/openstack/common/report/generators/__init__.py create mode 100644 cinder/openstack/common/report/generators/conf.py create mode 100644 cinder/openstack/common/report/generators/threading.py create mode 100644 cinder/openstack/common/report/generators/version.py create mode 100644 cinder/openstack/common/report/guru_meditation_report.py create mode 100644 cinder/openstack/common/report/models/__init__.py create mode 100644 cinder/openstack/common/report/models/base.py create mode 100644 cinder/openstack/common/report/models/conf.py create mode 100644 cinder/openstack/common/report/models/threading.py create mode 100644 cinder/openstack/common/report/models/version.py create mode 100644 cinder/openstack/common/report/models/with_default_views.py create mode 100644 cinder/openstack/common/report/report.py create mode 100644 cinder/openstack/common/report/utils.py create mode 100644 cinder/openstack/common/report/views/__init__.py create mode 100644 cinder/openstack/common/report/views/jinja_view.py create mode 100644 cinder/openstack/common/report/views/json/__init__.py create mode 100644 cinder/openstack/common/report/views/json/generic.py create mode 100644 cinder/openstack/common/report/views/text/__init__.py create mode 100644 cinder/openstack/common/report/views/text/generic.py create mode 100644 cinder/openstack/common/report/views/text/header.py create mode 100644 cinder/openstack/common/report/views/text/threading.py create mode 100644 cinder/openstack/common/report/views/xml/__init__.py create mode 100644 cinder/openstack/common/report/views/xml/generic.py create mode 100644 doc/source/devref/gmr.rst diff --git a/cinder/cmd/api.py b/cinder/cmd/api.py index 471ed7ee7..418bba983 100644 --- a/cinder/cmd/api.py +++ b/cinder/cmd/api.py @@ -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') diff --git a/cinder/cmd/backup.py b/cinder/cmd/backup.py index 3231aebdd..db823502d 100644 --- a/cinder/cmd/backup.py +++ b/cinder/cmd/backup.py @@ -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() diff --git a/cinder/cmd/scheduler.py b/cinder/cmd/scheduler.py index db862b988..53a820672 100644 --- a/cinder/cmd/scheduler.py +++ b/cinder/cmd/scheduler.py @@ -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() diff --git a/cinder/cmd/volume.py b/cinder/cmd/volume.py index ed99dded3..3c103fe15 100644 --- a/cinder/cmd/volume.py +++ b/cinder/cmd/volume.py @@ -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 index 000000000..b296462c0 --- /dev/null +++ b/cinder/openstack/common/report/__init__.py @@ -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 index 000000000..26474e3ba --- /dev/null +++ b/cinder/openstack/common/report/generators/__init__.py @@ -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 index 000000000..31ccaecbb --- /dev/null +++ b/cinder/openstack/common/report/generators/conf.py @@ -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 index 000000000..4f803e4b7 --- /dev/null +++ b/cinder/openstack/common/report/generators/threading.py @@ -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 index 000000000..05936fc74 --- /dev/null +++ b/cinder/openstack/common/report/generators/version.py @@ -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 index 000000000..9693f9be7 --- /dev/null +++ b/cinder/openstack/common/report/guru_meditation_report.py @@ -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 index 000000000..8430fc45f --- /dev/null +++ b/cinder/openstack/common/report/models/__init__.py @@ -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 index 000000000..90914ffe0 --- /dev/null +++ b/cinder/openstack/common/report/models/base.py @@ -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 ("").format(cl=type(self), + dt=self.data, + vw=type(self.attached_view)) + else: + return ("").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 index 000000000..77e24ab9f --- /dev/null +++ b/cinder/openstack/common/report/models/conf.py @@ -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 index 000000000..15e1fdd02 --- /dev/null +++ b/cinder/openstack/common/report/models/threading.py @@ -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 index 000000000..3666dc210 --- /dev/null +++ b/cinder/openstack/common/report/models/version.py @@ -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 index 000000000..d1a8e42ba --- /dev/null +++ b/cinder/openstack/common/report/models/with_default_views.py @@ -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 index 000000000..77f5d8533 --- /dev/null +++ b/cinder/openstack/common/report/report.py @@ -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 index 000000000..5f1dd1e7c --- /dev/null +++ b/cinder/openstack/common/report/utils.py @@ -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 index 000000000..834f101ce --- /dev/null +++ b/cinder/openstack/common/report/views/__init__.py @@ -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 index 000000000..a6f340e8e --- /dev/null +++ b/cinder/openstack/common/report/views/jinja_view.py @@ -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 index 000000000..2b3933e0c --- /dev/null +++ b/cinder/openstack/common/report/views/json/__init__.py @@ -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 index 000000000..079c01872 --- /dev/null +++ b/cinder/openstack/common/report/views/json/generic.py @@ -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 index 000000000..3f17fdeeb --- /dev/null +++ b/cinder/openstack/common/report/views/text/__init__.py @@ -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 index 000000000..736383305 --- /dev/null +++ b/cinder/openstack/common/report/views/text/generic.py @@ -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 index 000000000..aa4c72a61 --- /dev/null +++ b/cinder/openstack/common/report/views/text/header.py @@ -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 index 000000000..df63b84f3 --- /dev/null +++ b/cinder/openstack/common/report/views/text/threading.py @@ -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 index 000000000..bfcc8511d --- /dev/null +++ b/cinder/openstack/common/report/views/xml/__init__.py @@ -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 index 000000000..96337fafd --- /dev/null +++ b/cinder/openstack/common/report/views/xml/generic.py @@ -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 index 000000000..3b098d9f1 --- /dev/null +++ b/doc/source/devref/gmr.rst @@ -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 diff --git a/doc/source/devref/index.rst b/doc/source/devref/index.rst index 15542dfad..bebafd7e4 100644 --- a/doc/source/devref/index.rst +++ b/doc/source/devref/index.rst @@ -30,6 +30,7 @@ Programming HowTos and Tutorials unit_tests addmethod.openstackapi drivers + gmr Background Concepts for Cinder diff --git a/openstack-common.conf b/openstack-common.conf index 00f22d387..54ebbc53d 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -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 -- 2.45.2