Add Backend grading, and associated docs page

This adds a parameter to the backend class the defines its grade.
It can be overridden in the support-matrix.ini file it needed.

Change-Id: Icbcb3d88242c6d2de2c361722ddea85d4b585fca
This commit is contained in:
Graham Hayes 2015-05-12 20:02:10 +01:00
parent 5d4eeca3e5
commit 374463d8e8
16 changed files with 552 additions and 3 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -303,6 +303,8 @@ class DynECTBackend(base.Backend):
"""
__plugin_name__ = 'dynect'
__backend_status__ = 'release-compatible'
@classmethod
def get_cfg_opts(cls):
group = cfg.OptGroup(

View File

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

View File

@ -33,6 +33,9 @@ LOG = logging.getLogger(__name__)
class NSD4Backend(base.Backend):
__backend_status__ = 'untested'
__plugin_name__ = 'nsd4'
NSDCT_VERSION = 'NSDCT1'

View File

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

0
doc/ext/__init__.py Normal file
View File

394
doc/ext/support_matrix.py Normal file
View File

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

View File

@ -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']

View File

@ -51,6 +51,7 @@ Reference Documentation
integrations
tempest
gmr
support-matrix
Source Documentation
====================

View File

@ -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 <dnsaas@hp.com>
[backends.backend-impl-akamai]
maintainers=HP DNSaaS Team <dnsaas@hp.com>
[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 <graham.hayes@hp.com>
[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

View File

@ -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 backends grade be in dispute, it falls on the current project PTL to make the final decision after listening to all sides concerns
.. support_matrix::

View File

@ -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}'