diff --git a/designate/cmd/agent.py b/designate/cmd/agent.py index 739afe097..abd957e73 100644 --- a/designate/cmd/agent.py +++ b/designate/cmd/agent.py @@ -30,6 +30,7 @@ CONF.import_opt('workers', 'designate.agent', group='service:agent') def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') + utils.setup_gmr(log_dir=cfg.CONF.log_dir) server = agent_service.Service() service.serve(server, workers=CONF['service:agent'].workers) diff --git a/designate/cmd/api.py b/designate/cmd/api.py index 22b5bf394..16f7c7d82 100644 --- a/designate/cmd/api.py +++ b/designate/cmd/api.py @@ -31,6 +31,7 @@ CONF.import_opt('workers', 'designate.api', group='service:api') def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') + utils.setup_gmr(log_dir=cfg.CONF.log_dir) rpc.init(CONF) diff --git a/designate/cmd/central.py b/designate/cmd/central.py index b5bc5a1cc..7b9682c8f 100644 --- a/designate/cmd/central.py +++ b/designate/cmd/central.py @@ -30,6 +30,7 @@ CONF.import_opt('workers', 'designate.central', group='service:central') def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') + utils.setup_gmr(log_dir=cfg.CONF.log_dir) server = central.Service() service.serve(server, workers=CONF['service:central'].workers) diff --git a/designate/cmd/manage.py b/designate/cmd/manage.py index 428f86529..adbc4e33f 100644 --- a/designate/cmd/manage.py +++ b/designate/cmd/manage.py @@ -119,6 +119,7 @@ def main(): print(_('Please re-run designate-manage as root.')) sys.exit(2) + utils.setup_gmr(log_dir=cfg.CONF.log_dir) fn = CONF.category.action_fn fn_args = fetch_func_args(fn) diff --git a/designate/cmd/mdns.py b/designate/cmd/mdns.py index 43cc4d5f8..08777d226 100644 --- a/designate/cmd/mdns.py +++ b/designate/cmd/mdns.py @@ -30,6 +30,7 @@ CONF.import_opt('workers', 'designate.mdns', group='service:mdns') def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') + utils.setup_gmr(log_dir=cfg.CONF.log_dir) server = mdns_service.Service() service.serve(server, workers=CONF['service:mdns'].workers) diff --git a/designate/cmd/pool_manager.py b/designate/cmd/pool_manager.py index 7ed7ff0ed..d00b6ed71 100644 --- a/designate/cmd/pool_manager.py +++ b/designate/cmd/pool_manager.py @@ -31,6 +31,7 @@ CONF.import_opt('workers', 'designate.pool_manager', def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') + utils.setup_gmr(log_dir=cfg.CONF.log_dir) server = pool_manager_service.Service() service.serve(server, workers=CONF['service:pool_manager'].workers) diff --git a/designate/cmd/sink.py b/designate/cmd/sink.py index 0b57fdef5..75773cb95 100644 --- a/designate/cmd/sink.py +++ b/designate/cmd/sink.py @@ -30,6 +30,7 @@ CONF.import_opt('workers', 'designate.sink', group='service:sink') def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') + utils.setup_gmr(log_dir=cfg.CONF.log_dir) server = sink_service.Service() service.serve(server, workers=CONF['service:sink'].workers) diff --git a/designate/openstack/common/report/__init__.py b/designate/openstack/common/report/__init__.py new file mode 100644 index 000000000..35390ecd3 --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/generators/__init__.py b/designate/openstack/common/report/generators/__init__.py new file mode 100644 index 000000000..68473f22d --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/generators/conf.py b/designate/openstack/common/report/generators/conf.py new file mode 100644 index 000000000..de57f352b --- /dev/null +++ b/designate/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 + +from designate.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`, + 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/designate/openstack/common/report/generators/process.py b/designate/openstack/common/report/generators/process.py new file mode 100644 index 000000000..32329da85 --- /dev/null +++ b/designate/openstack/common/report/generators/process.py @@ -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 designate.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())) diff --git a/designate/openstack/common/report/generators/threading.py b/designate/openstack/common/report/generators/threading.py new file mode 100644 index 000000000..5eb26f693 --- /dev/null +++ b/designate/openstack/common/report/generators/threading.py @@ -0,0 +1,86 @@ +# 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`. +""" + +from __future__ import absolute_import + +import sys +import threading + +import greenlet + +from designate.openstack.common.report.models import threading as tm +from designate.openstack.common.report.models import with_default_views as mwdv +from designate.openstack.common.report import utils as rutils +from designate.openstack.common.report.views.text import 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()` . Its constructor may optionally + be passed a frame object. This frame object will be interpreted + as the actual stack trace for the current thread, and, come generation + time, will be used to replace the stack trace of the thread in which + this code is running. + """ + + def __init__(self, curr_thread_traceback=None): + self.traceback = curr_thread_traceback + + def __call__(self): + threadModels = 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()) + + +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) + ] + + return mwdv.ModelWithDefaultViews(threadModels, + text_view=text_views.MultiView()) diff --git a/designate/openstack/common/report/generators/version.py b/designate/openstack/common/report/generators/version.py new file mode 100644 index 000000000..a51c6ad3c --- /dev/null +++ b/designate/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`. +""" + +from designate.openstack.common.report.models import 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/designate/openstack/common/report/guru_meditation_report.py b/designate/openstack/common/report/guru_meditation_report.py new file mode 100644 index 000000000..8eca7055a --- /dev/null +++ b/designate/openstack/common/report/guru_meditation_report.py @@ -0,0 +1,226 @@ +# 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 inspect +import os +import signal +import sys + +from oslo_utils import timeutils + +from designate.openstack.common.report.generators import conf as cgen +from designate.openstack.common.report.generators import process as prgen +from designate.openstack.common.report.generators import threading as tgen +from designate.openstack.common.report.generators import version as pgen +from designate.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. + """ + + 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) + + @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, 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 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 + """ + + 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, service_name, log_dir, traceback): + """The Signal Handler + + This method (indirectly) handles receiving a registered signal and + 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, traceback).run() + except Exception: + print("Unable to run Guru Meditation Report!", + file=sys.stderr) + else: + if log_dir: + service_name = service_name or os.path.basename( + inspect.stack()[-1][1]) + filename = "%s_gurumeditation_%s" % ( + service_name, timeutils.strtime(fmt=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:] + + self.add_section('Package', + pgen.PackageReportGenerator(self.version_obj)) + + self.add_section('Threads', + tgen.ThreadReportGenerator(self.traceback)) + + self.add_section('Green Threads', + tgen.GreenThreadReportGenerator()) + + self.add_section('Processes', + prgen.ProcessReportGenerator()) + + 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 + + - 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, traceback=None): + super(TextGuruMeditation, self).__init__(version_obj, traceback, + 'Guru Meditation') diff --git a/designate/openstack/common/report/models/__init__.py b/designate/openstack/common/report/models/__init__.py new file mode 100644 index 000000000..7bfed3d88 --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/models/base.py b/designate/openstack/common/report/models/base.py new file mode 100644 index 000000000..d840c5b51 --- /dev/null +++ b/designate/openstack/common/report/models/base.py @@ -0,0 +1,162 @@ +# 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 + +import six + + +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. + + 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 + + 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) + 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): + # 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 + ) + ) + + def __len__(self): + return len(self.data) + + def __iter__(self): + return self.data.__iter__() + + 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, 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 + """ + + 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) diff --git a/designate/openstack/common/report/models/conf.py b/designate/openstack/common/report/models/conf.py new file mode 100644 index 000000000..1f1434fb9 --- /dev/null +++ b/designate/openstack/common/report/models/conf.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 OpenStack Configuration Model + +This module defines a class representing the data +model for :mod:`oslo_config` configuration options +""" + +from designate.openstack.common.report.models import with_default_views as mwdv +from designate.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 + 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 + + def opt_value(opt_obj, value): + if opt_obj['opt'].secret: + return '***' + else: + return value + + self['default'] = dict( + (opt_title(optname, conf_obj), + opt_value(conf_obj._opts[optname], 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), + opt_value(group_obj._opts[optname], + conf_obj[groupname][optname])) + for optname in group_obj._opts) + groups[group_obj.name] = curr_group_opts + + self.update(groups) diff --git a/designate/openstack/common/report/models/process.py b/designate/openstack/common/report/models/process.py new file mode 100644 index 000000000..7694c998f --- /dev/null +++ b/designate/openstack/common/report/models/process.py @@ -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 designate.openstack.common.report.models.with_default_views as mwdv +import designate.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()] diff --git a/designate/openstack/common/report/models/threading.py b/designate/openstack/common/report/models/threading.py new file mode 100644 index 000000000..581c90e0e --- /dev/null +++ b/designate/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 + +from designate.openstack.common.report.models import with_default_views as mwdv +from designate.openstack.common.report.views.text import 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) + ] + # 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} + 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/designate/openstack/common/report/models/version.py b/designate/openstack/common/report/models/version.py new file mode 100644 index 000000000..b8c46da0e --- /dev/null +++ b/designate/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 +""" + +from designate.openstack.common.report.models import with_default_views as mwdv +from designate.openstack.common.report.views.text import 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/designate/openstack/common/report/models/with_default_views.py b/designate/openstack/common/report/models/with_default_views.py new file mode 100644 index 000000000..8775637ec --- /dev/null +++ b/designate/openstack/common/report/models/with_default_views.py @@ -0,0 +1,81 @@ +# 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 + +from designate.openstack.common.report.models import base as base_model +from designate.openstack.common.report.views.json import generic as jsonviews +from designate.openstack.common.report.views.text import generic as textviews +from designate.openstack.common.report.views.xml import 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 + + 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.report.views.text.generic.KeyValueView` + xml + :class:`openstack.common.report.views.xml.generic.KeyValueView` + json + :class:`openstack.common.report.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, visited=None): + self.attached_view = self.views[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 {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) diff --git a/designate/openstack/common/report/report.py b/designate/openstack/common/report/report.py new file mode 100644 index 000000000..a7203de63 --- /dev/null +++ b/designate/openstack/common/report/report.py @@ -0,0 +1,187 @@ +# 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. +""" + +from designate.openstack.common.report.views.text import 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/designate/openstack/common/report/utils.py b/designate/openstack/common/report/utils.py new file mode 100644 index 000000000..fb71e36ae --- /dev/null +++ b/designate/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/designate/openstack/common/report/views/__init__.py b/designate/openstack/common/report/views/__init__.py new file mode 100644 index 000000000..612959b2e --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/views/jinja_view.py b/designate/openstack/common/report/views/jinja_view.py new file mode 100644 index 000000000..5f57dc34a --- /dev/null +++ b/designate/openstack/common/report/views/jinja_view.py @@ -0,0 +1,137 @@ +# 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 copy + +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) + + 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 + + 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/designate/openstack/common/report/views/json/__init__.py b/designate/openstack/common/report/views/json/__init__.py new file mode 100644 index 000000000..47bd33b6f --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/views/json/generic.py b/designate/openstack/common/report/views/json/generic.py new file mode 100644 index 000000000..a7347b9be --- /dev/null +++ b/designate/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 + +from designate.openstack.common.report import 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 in model.keys(): + if getattr(model[key], '__is_json__', False): + cpy[key] = json.loads(model[key]) + + res = utils.StringWithAttrs(json.dumps(cpy.data, sort_keys=True)) + res.__is_json__ = True + return res diff --git a/designate/openstack/common/report/views/text/__init__.py b/designate/openstack/common/report/views/text/__init__.py new file mode 100644 index 000000000..c09748443 --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/views/text/generic.py b/designate/openstack/common/report/views/text/generic.py new file mode 100644 index 000000000..3b30a0707 --- /dev/null +++ b/designate/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 sorted(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 sorted(root, key=str): + 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/designate/openstack/common/report/views/text/header.py b/designate/openstack/common/report/views/text/header.py new file mode 100644 index 000000000..58d06c0d3 --- /dev/null +++ b/designate/openstack/common/report/views/text/header.py @@ -0,0 +1,51 @@ +# 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/designate/openstack/common/report/views/text/process.py b/designate/openstack/common/report/views/text/process.py new file mode 100644 index 000000000..157bc26b9 --- /dev/null +++ b/designate/openstack/common/report/views/text/process.py @@ -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 designate.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 %}" + ) diff --git a/designate/openstack/common/report/views/text/threading.py b/designate/openstack/common/report/views/text/threading.py new file mode 100644 index 000000000..2775d4822 --- /dev/null +++ b/designate/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. +""" + +from designate.openstack.common.report.views import 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` + """ + + 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/designate/openstack/common/report/views/xml/__init__.py b/designate/openstack/common/report/views/xml/__init__.py new file mode 100644 index 000000000..a40fec984 --- /dev/null +++ b/designate/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. +""" diff --git a/designate/openstack/common/report/views/xml/generic.py b/designate/openstack/common/report/views/xml/generic.py new file mode 100644 index 000000000..81da9588e --- /dev/null +++ b/designate/openstack/common/report/views/xml/generic.py @@ -0,0 +1,87 @@ +# 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 + +from designate.openstack.common.report import 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 sorted(rootmodel): + res.append(serialize(rootmodel[key], key)) + elif (isinstance(rootmodel, col.Sequence) + and not isinstance(rootmodel, six.string_types)): + for val in sorted(rootmodel, key=str): + res.append(serialize(val, 'item')) + elif ET.iselement(rootmodel): + res.append(rootmodel) + else: + res.text = str(rootmodel) + + return res + + str_ = ET.tostring(serialize(cpy, + self.wrapper_name), + encoding="utf-8").decode("utf-8") + res = utils.StringWithAttrs(str_) + res.__is_xml__ = True + return res diff --git a/designate/utils.py b/designate/utils.py index 91b1bd7e1..2f1016957 100644 --- a/designate/utils.py +++ b/designate/utils.py @@ -28,6 +28,8 @@ from oslo_log import log as logging from oslo_utils import timeutils from designate import exceptions +from designate.openstack.common.report import guru_meditation_report as gmr +from designate import version as designate_version LOG = logging.getLogger(__name__) @@ -375,3 +377,7 @@ def cache_result(function): cache[0] = result return result return wrapper + + +def setup_gmr(log_dir=None): + gmr.TextGuruMeditation.setup_autorun(designate_version, log_dir=log_dir) diff --git a/designate/version.py b/designate/version.py index a965b5024..751e4152a 100644 --- a/designate/version.py +++ b/designate/version.py @@ -15,4 +15,26 @@ # under the License. import pbr.version +DESIGNATE_VENDOR = "OpenStack Foundation" +DESIGNATE_PRODUCT = "OpenStack Designate" + version_info = pbr.version.VersionInfo('designate') + + +def vendor_string(): + return DESIGNATE_VENDOR + + +def product_string(): + return DESIGNATE_PRODUCT + + +def package_string(): + return None + + +def version_string_with_package(): + if package_string() is None: + return version_info.version_string() + else: + return "%s-%s" % (version_info.version_string(), package_string()) diff --git a/doc/source/gmr.rst b/doc/source/gmr.rst new file mode 100644 index 000000000..b2c6aa392 --- /dev/null +++ b/doc/source/gmr.rst @@ -0,0 +1,466 @@ +.. _gmr: + +========================= + Guru Meditation Reports +========================= + +A Guru Meditation Reports(GMR) is gerenated by the Designate services when +service processes receiving SIGUSR1 signal. The report is a general-purpose +debug report for developers and system admins which contains the current state +of a running Designate service process. + +Structure of a GMR +================== + +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) + +Processes + Shows information about this process, including pid, ppid, uid and process + state + +Configuration + Lists all the configuration options currently accessible via the CONF object + for the current process + +Generate a GMR +============== + +A GMR can be generated by sending the USR1 signal to any Designate processes. + +For example, suppose ``designate-central`` has pid ``15097``, ``kill -USR1 +15097`` will trigger a GMR. + +If option ``logdir`` has been set in ``designate.conf``, the GMR will be saved +in the folder which ``logdir`` specified. Otherwise, the GMR will be printed to +the stderr. + +Reference +========= + +For more information about GMR, see `GMR wiki`_. + +.. _GMR wiki: https://wiki.openstack.org/wiki/GuruMeditationReport + +GMR Example +=========== + +:: + + ======================================================================== + ==== Guru Meditation ==== + ======================================================================== + |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + + + ======================================================================== + ==== Package ==== + ======================================================================== + product = OpenStack Designate + vendor = OpenStack Foundation + version = 2015.1 + ======================================================================== + ==== Threads ==== + ======================================================================== + ------ Thread #140098874533632 ------ + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:346 in run + `self.wait(sleep_time)` + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/poll.py:85 in wait + `presult = self.do_poll(seconds)` + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/epolls.py:62 in do_poll + `return self.poll.poll(seconds)` + + ======================================================================== + ==== Green Threads ==== + ======================================================================== + ------ Green Thread ------ + + /usr/local/lib/python2.7/dist-packages/eventlet/greenthread.py:214 in main + `result = function(*args, **kwargs)` + + /opt/stack/designate/designate/openstack/common/service.py:492 in run_service + `done.wait()` + + /usr/local/lib/python2.7/dist-packages/eventlet/event.py:121 in wait + `return hubs.get_hub().switch()` + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:294 in switch + `return self.greenlet.switch()` + + ------ Green Thread ------ + + /usr/local/lib/python2.7/dist-packages/eventlet/greenthread.py:214 in main + `result = function(*args, **kwargs)` + + /usr/local/lib/python2.7/dist-packages/oslo_utils/excutils.py:95 in inner_func + `return infunc(*args, **kwargs)` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_executors/impl_eventlet.py:96 in _executor_thread + `incoming = self.listener.poll()` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_drivers/amqpdriver.py:121 in poll + `self.conn.consume(limit=1, timeout=timeout)` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_drivers/impl_rabbit.py:867 in consume + `six.next(it)` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_drivers/impl_rabbit.py:782 in iterconsume + `yield self.ensure(_error_callback, _consume)` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_drivers/impl_rabbit.py:688 in ensure + `ret, channel = autoretry_method()` + + /usr/local/lib/python2.7/dist-packages/kombu/connection.py:436 in _ensured + `return fun(*args, **kwargs)` + + /usr/local/lib/python2.7/dist-packages/kombu/connection.py:508 in __call__ + `return fun(*args, channel=channels[0], **kwargs), channels[0]` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_drivers/impl_rabbit.py:675 in execute_method + `method()` + + /usr/local/lib/python2.7/dist-packages/oslo_messaging/_drivers/impl_rabbit.py:774 in _consume + `return self.connection.drain_events(timeout=poll_timeout)` + + /usr/local/lib/python2.7/dist-packages/kombu/connection.py:275 in drain_events + `return self.transport.drain_events(self.connection, **kwargs)` + + /usr/local/lib/python2.7/dist-packages/kombu/transport/pyamqp.py:91 in drain_events + `return connection.drain_events(**kwargs)` + + /usr/local/lib/python2.7/dist-packages/amqp/connection.py:302 in drain_events + `chanmap, None, timeout=timeout,` + + /usr/local/lib/python2.7/dist-packages/amqp/connection.py:365 in _wait_multiple + `channel, method_sig, args, content = read_timeout(timeout)` + + /usr/local/lib/python2.7/dist-packages/amqp/connection.py:336 in read_timeout + `return self.method_reader.read_method()` + + /usr/local/lib/python2.7/dist-packages/amqp/method_framing.py:186 in read_method + `self._next_method()` + + /usr/local/lib/python2.7/dist-packages/amqp/method_framing.py:107 in _next_method + `frame_type, channel, payload = read_frame()` + + /usr/local/lib/python2.7/dist-packages/amqp/transport.py:154 in read_frame + `frame_header = read(7, True)` + + /usr/local/lib/python2.7/dist-packages/amqp/transport.py:277 in _read + `s = recv(n - len(rbuf))` + + /usr/local/lib/python2.7/dist-packages/eventlet/greenio/base.py:326 in recv + `timeout_exc=socket.timeout("timed out"))` + + /usr/local/lib/python2.7/dist-packages/eventlet/greenio/base.py:201 in _trampoline + `mark_as_closed=self._mark_as_closed)` + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/__init__.py:162 in trampoline + `return hub.switch()` + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:294 in switch + `return self.greenlet.switch()` + + ------ Green Thread ------ + + /usr/local/bin/designate-central:10 in + `sys.exit(main())` + + /opt/stack/designate/designate/cmd/central.py:37 in main + `service.wait()` + + /opt/stack/designate/designate/service.py:356 in wait + `_launcher.wait()` + + /opt/stack/designate/designate/openstack/common/service.py:187 in wait + `status, signo = self._wait_for_exit_or_signal(ready_callback)` + + /opt/stack/designate/designate/openstack/common/service.py:170 in _wait_for_exit_or_signal + `super(ServiceLauncher, self).wait()` + + /opt/stack/designate/designate/openstack/common/service.py:133 in wait + `self.services.wait()` + + /opt/stack/designate/designate/openstack/common/service.py:473 in wait + `self.tg.wait()` + + /opt/stack/designate/designate/openstack/common/threadgroup.py:145 in wait + `x.wait()` + + /opt/stack/designate/designate/openstack/common/threadgroup.py:47 in wait + `return self.thread.wait()` + + /usr/local/lib/python2.7/dist-packages/eventlet/greenthread.py:175 in wait + `return self._exit_event.wait()` + + /usr/local/lib/python2.7/dist-packages/eventlet/event.py:121 in wait + `return hubs.get_hub().switch()` + + /usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:294 in switch + `return self.greenlet.switch()` + + ------ Green Thread ------ + + No Traceback! + + ======================================================================== + ==== Processes ==== + ======================================================================== + Process 15097 (under 7312) [ run by: stanzgy (1000), state: running ] + + ======================================================================== + ==== Configuration ==== + ======================================================================== + + backend:agent:bind9: + query-destination = 127.0.0.1 + rndc-config-file = None + rndc-host = 127.0.0.1 + rndc-key-file = None + rndc-port = 953 + zone-file-path = /opt/stack/data/designate/zones + + backend:bind9: + masters = + 127.0.0.1:5354 + rndc-config-file = None + rndc-host = 127.0.0.1 + rndc-key-file = None + rndc-port = 953 + server_ids = + + backend:fake: + masters = + 127.0.0.1:5354 + server_ids = + + backend:powerdns: + backend = sqlalchemy + connection = *** + connection_debug = 0 + connection_trace = False + db_inc_retry_interval = True + db_max_retries = 20 + db_max_retry_interval = 10 + db_retry_interval = 1 + idle_timeout = 3600 + masters = + 10.180.64.117:5354 + max_overflow = None + max_pool_size = None + max_retries = 10 + min_pool_size = 1 + mysql_sql_mode = TRADITIONAL + pool_timeout = None + retry_interval = 10 + server_ids = + f26e0b32-736f-4f0a-831b-039a415c481e + slave_connection = *** + sqlite_db = oslo.sqlite + sqlite_synchronous = True + use_db_reconnect = False + + backend:powerdns:f26e0b32-736f-4f0a-831b-039a415c481e: + backend = None + connection = *** + connection_debug = None + connection_trace = None + db_inc_retry_interval = None + db_max_retries = None + db_max_retry_interval = None + db_retry_interval = None + host = 10.180.64.117 + idle_timeout = None + masters = None + max_overflow = None + max_pool_size = None + max_retries = None + min_pool_size = None + mysql_sql_mode = None + pool_timeout = None + port = 53 + retry_interval = None + slave_connection = *** + sqlite_db = None + sqlite_synchronous = None + tsig-key = None + use_db_reconnect = None + + default: + allowed_remote_exmods = + backdoor_port = None + backlog = 4096 + central-topic = central + config-dir = None + config-file = + /etc/designate/designate.conf + control_exchange = designate + debug = True + default-soa-expire = 86400 + default-soa-minimum = 3600 + default-soa-refresh = 3600 + default-soa-retry = 600 + default-ttl = 3600 + default_log_levels = + amqp=WARN + amqplib=WARN + boto=WARN + eventlet.wsgi.server=WARN + keystone=INFO + keystonemiddleware.auth_token=INFO + oslo.messaging=WARN + sqlalchemy=WARN + stevedore=WARN + suds=INFO + fatal_deprecations = False + host = cns-dev2 + instance_format = [instance: %(uuid)s] + instance_uuid_format = [instance: %(uuid)s] + log-config-append = None + log-date-format = %Y-%m-%d %H:%M:%S + log-dir = /opt/stack/logs/designate + log-file = None + log-format = None + logging_context_format_string = %(asctime)s.%(msecs)03d %(color)s%(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s%(color)s] %(instance)s%(color)s%(message)s + logging_debug_format_suffix = from (pid=%(process)d) %(funcName)s %(pathname)s:%(lineno)d + logging_default_format_string = %(asctime)s.%(msecs)03d %(color)s%(levelname)s %(name)s [-%(color)s] %(instance)s%(color)s%(message)s + logging_exception_prefix = %(color)s%(asctime)s.%(msecs)03d TRACE %(name)s %(instance)s + mdns-topic = mdns + network_api = neutron + notification_driver = + notification_topics = + notifications + policy_default_rule = default + policy_dirs = + policy.d + policy_file = /etc/designate/policy.json + pool-manager-topic = pool_manager + publish_errors = False + pybasedir = /opt/stack/designate + quota-domain-records = 500 + quota-domain-recordsets = 500 + quota-domains = 10 + quota-driver = storage + quota-recordset-records = 20 + root-helper = sudo designate-rootwrap /etc/designate/rootwrap.conf + rpc_backend = rabbit + rpc_thread_pool_size = 64 + state-path = /opt/stack/data/designate + syslog-log-facility = LOG_USER + tcp_keepidle = 600 + transport_url = None + use-syslog = False + use-syslog-rfc-format = False + use_stderr = True + verbose = True + + network_api:neutron: + admin_password = *** + admin_tenant_name = None + admin_username = None + auth_strategy = keystone + auth_url = None + ca_certificates_file = None + endpoint_type = publicURL + endpoints = None + insecure = False + timeout = 30 + + oslo_concurrency: + disable_process_locking = False + lock_path = None + + oslo_messaging_rabbit: + amqp_auto_delete = False + amqp_durable_queues = False + fake_rabbit = False + kombu_reconnect_delay = 1.0 + kombu_ssl_ca_certs = + kombu_ssl_certfile = + kombu_ssl_keyfile = + kombu_ssl_version = + rabbit_ha_queues = False + rabbit_host = localhost + rabbit_hosts = + 127.0.0.1 + rabbit_login_method = AMQPLAIN + rabbit_max_retries = 0 + rabbit_password = *** + rabbit_port = 5672 + rabbit_retry_backoff = 2 + rabbit_retry_interval = 1 + rabbit_use_ssl = False + rabbit_userid = stackrabbit + rabbit_virtual_host = / + rpc_conn_pool_size = 30 + + proxy: + http_proxy = None + https_proxy = None + no_proxy = + + service:central: + default_pool_id = 794ccc2c-d751-44fe-b57f-8894c9f5c842 + enabled-notification-handlers = + managed_resource_email = hostmaster@example.com + managed_resource_tenant_id = None + max_domain_name_len = 255 + max_recordset_name_len = 255 + min_ttl = None + storage-driver = sqlalchemy + workers = None + + service:pool_manager: + backends = + powerdns + cache-driver = sqlalchemy + enable-recovery-timer = True + enable-sync-timer = True + periodic-recovery-interval = 120 + periodic-sync-interval = 300 + periodic-sync-seconds = None + poll-delay = 1 + poll-max-retries = 3 + poll-retry-interval = 2 + poll-timeout = 30 + pool-id = 794ccc2c-d751-44fe-b57f-8894c9f5c842 + threshold-percentage = 100 + workers = None + + ssl: + ca_file = None + cert_file = None + key_file = None + + storage:sqlalchemy: + backend = sqlalchemy + connection = *** + connection_debug = 0 + connection_trace = False + db_inc_retry_interval = True + db_max_retries = 20 + db_max_retry_interval = 10 + db_retry_interval = 1 + idle_timeout = 3600 + max_overflow = None + max_pool_size = None + max_retries = 10 + min_pool_size = 1 + mysql_sql_mode = TRADITIONAL + pool_timeout = None + retry_interval = 10 + slave_connection = *** + sqlite_db = oslo.sqlite + sqlite_synchronous = True + use_db_reconnect = False diff --git a/doc/source/index.rst b/doc/source/index.rst index fe001c90c..60edfae63 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -50,6 +50,7 @@ Reference Documentation backends integrations tempest + gmr Source Documentation ==================== diff --git a/openstack-common.conf b/openstack-common.conf index 5e31620fe..44f1e94de 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -7,6 +7,13 @@ script=tools/install_venv_common.py module=memorycache module=policy module=service +module=report +module=report.generators +module=report.models +module=report.views +module=report.views.xml +module=report.views.json +module=report.views.text # Modules needed for the deprecated oslo.wsgi we're still using module=sslutils diff --git a/requirements.txt b/requirements.txt index b0c0a9bb9..a97dcf93c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,3 +37,4 @@ oslo.i18n>=1.3.0 # Apache-2.0 oslo.context>=0.2.0 # Apache-2.0 Werkzeug>=0.7 # BSD License python-memcached>=1.48 +psutil>=1.1.1,<2.0.0