diff --git a/designate/backend/agent.py b/designate/backend/agent.py index 47c0acdc1..66edc56e5 100644 --- a/designate/backend/agent.py +++ b/designate/backend/agent.py @@ -42,6 +42,8 @@ CONF = cfg.CONF class AgentPoolBackend(base.Backend): __plugin_name__ = 'agent' + __backend_status__ = 'untested' + def __init__(self, target): super(AgentPoolBackend, self).__init__(target) self.host = self.options.get('host') diff --git a/designate/backend/agent_backend/impl_bind9.py b/designate/backend/agent_backend/impl_bind9.py index d9e756d40..1b5082805 100644 --- a/designate/backend/agent_backend/impl_bind9.py +++ b/designate/backend/agent_backend/impl_bind9.py @@ -33,6 +33,8 @@ CFG_GROUP = 'backend:agent:bind9' class Bind9Backend(base.AgentBackend): __plugin_name__ = 'bind9' + __backend_status__ = 'untested' + @classmethod def get_cfg_opts(cls): group = cfg.OptGroup( diff --git a/designate/backend/agent_backend/impl_denominator.py b/designate/backend/agent_backend/impl_denominator.py index 48e867a27..64e7d1172 100644 --- a/designate/backend/agent_backend/impl_denominator.py +++ b/designate/backend/agent_backend/impl_denominator.py @@ -90,6 +90,8 @@ class Denominator(object): class DenominatorBackend(base.AgentBackend): __plugin_name__ = 'denominator' + __backend_status__ = 'untested' + def __init__(self, agent_service): super(DenominatorBackend, self).__init__(agent_service) self.denominator = Denominator(cfg.CONF[CFG_GROUP]) diff --git a/designate/backend/impl_akamai.py b/designate/backend/impl_akamai.py index 155b15f4c..2d0f62f20 100644 --- a/designate/backend/impl_akamai.py +++ b/designate/backend/impl_akamai.py @@ -209,6 +209,8 @@ def build_zone(client, target, domain): class AkamaiBackend(base.Backend): __plugin_name__ = 'akamai' + __backend_status__ = 'release-compatible' + @classmethod def get_cfg_opts(cls): group = cfg.OptGroup( diff --git a/designate/backend/impl_bind9.py b/designate/backend/impl_bind9.py index ea256180c..d2502ce8a 100644 --- a/designate/backend/impl_bind9.py +++ b/designate/backend/impl_bind9.py @@ -29,6 +29,8 @@ DEFAULT_MASTER_PORT = 5354 class Bind9Backend(base.Backend): __plugin_name__ = 'bind9' + __backend_status__ = 'integrated' + def __init__(self, target): super(Bind9Backend, self).__init__(target) diff --git a/designate/backend/impl_dynect.py b/designate/backend/impl_dynect.py index 119af167a..686ddf872 100644 --- a/designate/backend/impl_dynect.py +++ b/designate/backend/impl_dynect.py @@ -303,6 +303,8 @@ class DynECTBackend(base.Backend): """ __plugin_name__ = 'dynect' + __backend_status__ = 'release-compatible' + @classmethod def get_cfg_opts(cls): group = cfg.OptGroup( diff --git a/designate/backend/impl_infoblox/__init__.py b/designate/backend/impl_infoblox/__init__.py index 63f09034e..94a9ea871 100644 --- a/designate/backend/impl_infoblox/__init__.py +++ b/designate/backend/impl_infoblox/__init__.py @@ -27,6 +27,8 @@ LOG = logging.getLogger(__name__) class InfobloxBackend(base.Backend): """Provides a Designate Backend for Infoblox""" + __backend_status__ = 'untested' + __plugin_name__ = 'infoblox' def __init__(self, *args, **kwargs): diff --git a/designate/backend/impl_nsd4.py b/designate/backend/impl_nsd4.py index cb72d8a3a..66f5cf473 100644 --- a/designate/backend/impl_nsd4.py +++ b/designate/backend/impl_nsd4.py @@ -33,6 +33,9 @@ LOG = logging.getLogger(__name__) class NSD4Backend(base.Backend): + + __backend_status__ = 'untested' + __plugin_name__ = 'nsd4' NSDCT_VERSION = 'NSDCT1' diff --git a/designate/backend/impl_powerdns/__init__.py b/designate/backend/impl_powerdns/__init__.py index 6544e535e..138ff2261 100644 --- a/designate/backend/impl_powerdns/__init__.py +++ b/designate/backend/impl_powerdns/__init__.py @@ -38,6 +38,8 @@ def _map_col(keys, col): class PowerDNSBackend(base.Backend): __plugin_name__ = 'powerdns' + __backend_status__ = 'integrated' + @classmethod def get_cfg_opts(cls): group = cfg.OptGroup('backend:powerdns') diff --git a/doc/ext/__init__.py b/doc/ext/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/doc/ext/support_matrix.py b/doc/ext/support_matrix.py new file mode 100644 index 000000000..5ce2c4967 --- /dev/null +++ b/doc/ext/support_matrix.py @@ -0,0 +1,394 @@ +# Copyright (C) 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. + +""" +This provides a sphinx extension able to render the source/support-matrix.ini +file into the developer documentation. + +It is used via a single directive in the .rst file + + .. support_matrix:: + +""" + +import ConfigParser + +from docutils import nodes +from docutils.parsers import rst +from designate.backend.base import Backend +from designate.backend.agent_backend.base import AgentBackend + + +class SupportMatrix(object): + """Represents the entire support matrix for Nova virt drivers + """ + + def __init__(self): + # List of SupportMatrixFeature instances, describing + # all the features present in Nova virt drivers + self.grades = [] + + self.grade_names = {} + + # Dict of (name, SupportMatrixTarget) enumerating + # all the hypervisor drivers that have data recorded + # for them in self.features. The 'name' dict key is + # the value from the SupportMatrixTarget.key attribute + self.backends = {} + + +class SupportMatrixGrade(object): + + def __init__(self, key, title, notes, in_tree): + self.key = key + self.title = title + self.notes = notes + self.in_tree = in_tree + + +class SupportMatrixBackend(object): + + def __init__(self, key, title, status, + maintainers=None, variations=None, + repository=None, type=None): + self.key = key + self.title = title + self.type = type + self.status = status + self.maintainers = maintainers + self.variations = variations + self.repository = repository + + +class SupportMatrixDirective(rst.Directive): + + option_spec = { + 'support-matrix': unicode, + } + + def run(self): + matrix = self._load_support_matrix() + return self._build_markup(matrix) + + def _load_support_matrix(self): + """Reads the support-matrix.ini file and populates an instance + of the SupportMatrix class with all the data. + + :returns: SupportMatrix instance + """ + + cfg = ConfigParser.SafeConfigParser() + env = self.state.document.settings.env + fname = self.options.get("support-matrix", + "support-matrix.ini") + rel_fpath, fpath = env.relfn2path(fname) + with open(fpath) as fp: + cfg.readfp(fp) + + # This ensures that the docs are rebuilt whenever the + # .ini file changes + env.note_dependency(rel_fpath) + + matrix = SupportMatrix() + + # The 'targets' section is special - it lists all the + # hypervisors that this file records data for + for item in cfg.options("backends"): + if not item.startswith("backend-impl-"): + continue + + # The driver string will optionally contain + # a hypervisor and architecture qualifier + # so we expect between 1 and 3 components + # in the name + key = item[13:] + title = cfg.get("backends", item) + name = key.split("-") + + try: + status = cfg.get("backends.%s" % item, "status") + except ConfigParser.NoOptionError: + if cfg.get("backends.%s" % item, "type") == "xfr": + backend = Backend.get_driver(name[0]) + elif cfg.get("backends.%s" % item, "type") == "agent": + backend = AgentBackend.get_driver(name[0]) + status = backend.__backend_status__ + + if len(name) == 1: + backend = SupportMatrixBackend( + key, title, status, name[0]) + elif len(name) == 2: + backend = SupportMatrixBackend( + key, title, status, name[0], variations=name[1]) + else: + raise Exception("'%s' field is malformed in '[%s]' section" % + (item, "DEFAULT")) + + backend.in_tree = cfg.getboolean( + "backends.%s" % item, "in-tree") + backend.type = cfg.get( + "backends.%s" % item, "type") + backend.notes = cfg.get( + "backends.%s" % item, "notes") + backend.repository = cfg.get( + "backends.%s" % item, "repository") + backend.maintainers = cfg.get( + "backends.%s" % item, "maintainers") + + matrix.backends[key] = backend + + grades = cfg.get("grades", "valid-grades") + + grades = grades.split(",") + + for grade in grades: + title = cfg.get("grades.%s" % grade, "title") + notes = cfg.get("grades.%s" % grade, "notes") + in_tree = cfg.get("grades.%s" % grade, "in-tree") + + matrix.grade_names[grade] = title + + grade = SupportMatrixGrade( + grade, title, notes, in_tree) + + matrix.grades.append(grade) + + return matrix + + def _build_markup(self, matrix): + """Constructs the docutils content for the support matrix + """ + content = [] + self._build_grade_listing(matrix, content) + self._build_grade_table(matrix, content) + self._build_backend_detail(matrix, content) + return content + + def _build_backend_detail_table(self, backend, matrix): + + table = nodes.table() + tgroup = nodes.tgroup(cols=2) + thead = nodes.thead() + tbody = nodes.tbody() + + for i in range(2): + tgroup.append(nodes.colspec(colwidth=1)) + + tgroup.append(thead) + tgroup.append(tbody) + table.append(tgroup) + + header = nodes.row() + blank = nodes.entry() + blank.append(nodes.emphasis(text=backend.title)) + header.append(blank) + blank = nodes.entry() + header.append(blank) + thead.append(header) + + graderow = nodes.row() + gradetitle = nodes.entry() + gradetitle.append(nodes.strong(text="Grade")) + gradetext = nodes.entry() + gradetext.append(nodes.paragraph( + text=matrix.grade_names[backend.status])) + graderow.append(gradetitle) + graderow.append(gradetext) + tbody.append(graderow) + + treerow = nodes.row() + treetitle = nodes.entry() + treetitle.append(nodes.strong(text="In Tree")) + if bool(backend.in_tree): + status = u"\u2714" + else: + status = u"\u2716" + treetext = nodes.entry() + treetext.append(nodes.paragraph(text=status)) + treerow.append(treetitle) + treerow.append(treetext) + tbody.append(treerow) + + maintrow = nodes.row() + mainttitle = nodes.entry() + mainttitle.append(nodes.strong(text="Maintainers")) + mainttext = nodes.entry() + mainttext.append(nodes.paragraph(text=backend.maintainers)) + maintrow.append(mainttitle) + maintrow.append(mainttext) + tbody.append(maintrow) + + reporow = nodes.row() + repotitle = nodes.entry() + repotitle.append(nodes.strong(text="Repository")) + repotext = nodes.entry() + repotext.append(nodes.paragraph(text=backend.repository)) + reporow.append(repotitle) + reporow.append(repotext) + tbody.append(reporow) + + noterow = nodes.row() + notetitle = nodes.entry() + notetitle.append(nodes.strong(text="Notes")) + notetext = nodes.entry() + notetext.append(nodes.paragraph(text=backend.notes)) + noterow.append(notetitle) + noterow.append(notetext) + tbody.append(noterow) + + return table + + def _build_backend_detail(self, matrix, content): + + detailstitle = nodes.subtitle(text="Backend Details") + + content.append(detailstitle) + + for key in matrix.backends.keys(): + + content.append( + self._build_backend_detail_table( + matrix.backends[key], + matrix)) + + content.append(nodes.line()) + + return content + + def _build_grade_listing(self, matrix, content): + + summarytitle = nodes.title(text="Grades") + content.append(summarytitle) + table = nodes.table() + grades = matrix.grades + + tablegroup = nodes.tgroup(cols=2) + summarybody = nodes.tbody() + summaryhead = nodes.thead() + + for i in range(2): + tablegroup.append(nodes.colspec(colwidth=1)) + tablegroup.append(summaryhead) + tablegroup.append(summarybody) + table.append(tablegroup) + content.append(table) + + header = nodes.row() + blank = nodes.entry() + blank.append(nodes.strong(text="Grade")) + header.append(blank) + + blank = nodes.entry() + blank.append(nodes.strong(text="Description")) + header.append(blank) + + summaryhead.append(header) + + for grade in grades: + item = nodes.row() + namecol = nodes.entry() + namecol.append(nodes.paragraph(text=grade.title)) + item.append(namecol) + + notescol = nodes.entry() + notescol.append(nodes.paragraph(text=grade.notes)) + item.append(notescol) + + summarybody.append(item) + + return content + + def _build_grade_table(self, matrix, content): + + summarytitle = nodes.title(text="Backends - Summary") + summary = nodes.table() + cols = len(matrix.backends.keys()) + cols += 2 + summarygroup = nodes.tgroup(cols=cols) + summarybody = nodes.tbody() + summaryhead = nodes.thead() + + for i in range(cols): + summarygroup.append(nodes.colspec(colwidth=1)) + summarygroup.append(summaryhead) + summarygroup.append(summarybody) + summary.append(summarygroup) + content.append(summarytitle) + content.append(summary) + + header = nodes.row() + blank = nodes.entry() + blank.append(nodes.strong(text="Backend")) + header.append(blank) + + blank = nodes.entry() + blank.append(nodes.strong(text="Status")) + header.append(blank) + + blank = nodes.entry() + blank.append(nodes.strong(text="Type")) + header.append(blank) + + blank = nodes.entry() + blank.append(nodes.strong(text="In Tree")) + header.append(blank) + + blank = nodes.entry() + blank.append(nodes.strong(text="Notes")) + header.append(blank) + summaryhead.append(header) + + grades = matrix.grades + impls = matrix.backends.keys() + impls.sort() + for grade in grades: + for backend in impls: + if matrix.backends[backend].status == grade.key: + item = nodes.row() + namecol = nodes.entry() + namecol.append( + nodes.paragraph(text=matrix.backends[backend].title)) + item.append(namecol) + + statuscol = nodes.entry() + statuscol.append(nodes.paragraph(text=grade.title)) + item.append(statuscol) + + typecol = nodes.entry() + typecol.append(nodes.paragraph( + text=matrix.backends[backend].type)) + item.append(typecol) + + if bool(matrix.backends[backend].in_tree): + status = u"\u2714" + else: + status = u"\u2716" + + intreecol = nodes.entry() + intreecol.append(nodes.paragraph(text=status)) + item.append(intreecol) + + notescol = nodes.entry() + notescol.append(nodes.paragraph( + text=matrix.backends[backend].notes)) + item.append(notescol) + + summarybody.append(item) + + return content + + +def setup(app): + app.add_directive('support_matrix', SupportMatrixDirective) + app.add_stylesheet('support-matrix.css') diff --git a/doc/source/conf.py b/doc/source/conf.py index b86f92797..709c2ac14 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -16,7 +16,8 @@ import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('../')) +sys.path.insert(0, os.path.abspath('./')) # -- General configuration ----------------------------------------------------- @@ -25,7 +26,11 @@ import sys, os # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinxcontrib.httpdomain', 'oslosphinx'] +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinxcontrib.httpdomain', + 'oslosphinx', + 'ext.support_matrix'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/source/index.rst b/doc/source/index.rst index 60edfae63..28a868dec 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -51,6 +51,7 @@ Reference Documentation integrations tempest gmr + support-matrix Source Documentation ==================== diff --git a/doc/source/support-matrix.ini b/doc/source/support-matrix.ini new file mode 100644 index 000000000..da7de5be6 --- /dev/null +++ b/doc/source/support-matrix.ini @@ -0,0 +1,120 @@ +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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. +# +# +# +# ============================================== +# Designate DNS Driver Feature Capability Matrix +# ============================================== +# +# The 'status' field takes possible values +# +# - mandatory - unconditionally required to be implemented +# - optional - optional to support, nice to have +# - choice(group) - at least one of the options within the named group +# must be implemented +# - conditional(cond) - required, if the referenced condition is met. +# +# The value against each 'driver-impl-XXXX' entry refers to the level +# of the implementation of the feature in that driver +# +# - complete - fully implemented, expected to work at all times +# - partial - implemented, but with caveats about when it will work +# eg some configurations or hardware or guest OS may not +# support it +# - missing - not implemented at all +[DEFAULT] +repository=Designate Repository +maintainers=Designate Team +notes=None +type=xfr +in-tree=True + + +[backends] +backend-impl-bind9=Bind9 +backend-impl-powerdns-mysql=Power DNS (MySQL) +backend-impl-powerdns-pgsql=Power DNS (pgSQL) +backend-impl-dynect=DynECT +backend-impl-akamai=Akamai eDNS +backend-impl-msdns=Microsoft DNS Server +backend-impl-infoblox-xfr=Infoblox (XFR) +backend-impl-nsd4=NSD4 +backend-impl-agent=Agent +backend-impl-bind9-agent=Bind9 (Agent) +backend-impl-denominator=Denominator + +[backends.backend-impl-bind9] + +[backends.backend-impl-powerdns-mysql] + +[backends.backend-impl-powerdns-pgsql] +status=untested + +[backends.backend-impl-dynect] +maintainers=HP DNSaaS Team + +[backends.backend-impl-akamai] +maintainers=HP DNSaaS Team + +[backends.backend-impl-agent] + +[backends.backend-impl-bind9-agent] +type=agent + +[backends.backend-impl-infoblox-xfr] + +[backends.backend-impl-nsd4] + +[backends.backend-impl-denominator] +type=agent + +[backends.backend-impl-msdns] +in-tree=False +status=untested +repository=https://github.com/stackforge/designate-msdnsagent +maintainers=Graham Hayes + +[grades] +valid-grades=integrated,master-compatible,release-compatible,untested,failing,known-broken + +[grades.integrated] +title=Integrated +notes=Tested on every commit by the OpenStack CI Infrastructure, and maintained by designate developers as a reference backend +in-tree=True + +[grades.master-compatible] +title=Master Compatible +notes=Tested on every commit by 3rd party testing, and has a person or group dedicated to maintaining compatibility on a regular basis +in-tree=optional + +[grades.release-compatible] +title=Release Compatible +notes=Not necessarily tested on every commit, but has a maintainer committed to ensuring compatibility for each release +in-tree=optional + +[grades.untested] +title=Untested +notes=All other backends in the designate repository +in-tree=optional + +[grades.failing] +title=Failing +notes=Backends that were previously "Compatible", but tests are now failing on a regular basis. +in-tree=optional + +[grades.known-broken] +title=Known Broken +notes=Backends that do not work, and have been broken with no sign of any fixes +in-tree=optional diff --git a/doc/source/support-matrix.rst b/doc/source/support-matrix.rst new file mode 100644 index 000000000..850d255f0 --- /dev/null +++ b/doc/source/support-matrix.rst @@ -0,0 +1,10 @@ + +DNS Server Driver Support Matrix +================================ + +This info should be maintained along with the list of current driver maintainers responsible for the “Non Integrated” backends. +The upkeep of this list will fall on the PTL or his/her delegate + +Should a backend’s grade be in dispute, it falls on the current project PTL to make the final decision after listening to all sides concerns + +.. support_matrix:: diff --git a/tox.ini b/tox.ini index bd081abc0..53182cfdc 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ commands = sh tools/pretty_tox.sh '{posargs}' [testenv:docs] -commands = python setup.py build_sphinx +commands = python setup.py build_sphinx -E [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}'