# 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 six import six.moves.configparser as config_parser 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 = {} self.grade_classes = {} # 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, css_class): self.key = key self.title = title self.notes = notes self.in_tree = in_tree self.css_class = css_class 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 = config_parser.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 config_parser.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") css_class = cfg.get("grades.%s" % grade, "css-class") matrix.grade_names[grade] = title matrix.grade_classes[grade] = css_class grade = SupportMatrixGrade( grade, title, notes, in_tree, css_class) 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() table.set_class("table") table.set_class("table-condensed") tgroup = nodes.tgroup(cols=2) tbody = nodes.tbody() for i in range(2): tgroup.append(nodes.colspec(colwidth=1)) tgroup.append(tbody) table.append(tgroup) graderow = nodes.row() gradetitle = nodes.entry() gradetitle.append(nodes.strong(text="Grade")) gradetext = nodes.entry() class_name = "label-%s" % matrix.grade_classes[backend.status] status_text = nodes.paragraph( text=matrix.grade_names[backend.status]) status_text.set_class(class_name) status_text.set_class("label") gradetext.append(status_text) 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" intree = nodes.paragraph(text=status) intree.set_class("label") intree.set_class("label-success") else: status = u"\u2716" intree = nodes.paragraph(text=status) intree.set_class("label") intree.set_class("label-danger") status = u"\u2714" treetext = nodes.entry() treetext.append(intree) 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 six.iterkeys(matrix.backends): content.append( nodes.subtitle(text=matrix.backends[key].title)) 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.subtitle(text="Grades") content.append(nodes.raw(text="Grades", attributes={'tagname': 'h2'})) content.append(summarytitle) table = nodes.table() table.set_class("table") table.set_class("table-condensed") 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() class_name = "label-%s" % grade.css_class status_text = nodes.paragraph(text=grade.title) status_text.set_class(class_name) status_text.set_class("label") namecol.append(status_text) 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.subtitle(text="Backends - Summary") summary = nodes.table() summary.set_class("table") summary.set_class("table-condensed") cols = len(list(six.iterkeys(matrix.backends))) 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 = list(six.iterkeys(matrix.backends)) 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() class_name = "label-%s" % grade.css_class status_text = nodes.paragraph(text=grade.title) status_text.set_class(class_name) status_text.set_class("label") statuscol.append(status_text) item.append(statuscol) typecol = nodes.entry() type_text = nodes.paragraph( text=matrix.backends[backend].type) type_text.set_class("label") type_text.set_class("label-info") typecol.append(type_text) item.append(typecol) if bool(matrix.backends[backend].in_tree): status = u"\u2714" intree = nodes.paragraph(text=status) intree.set_class("label") intree.set_class("label-success") else: status = u"\u2716" intree = nodes.paragraph(text=status) intree.set_class("label") intree.set_class("label-danger") intreecol = nodes.entry() intreecol.append(intree) 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_stylesheet('support-matrix.css') app.add_directive('support_matrix', SupportMatrixDirective)