Document PowerVM driver functions in support matrix doc
Add a copy of Nova's support matrix document within the docs for nova-powervm. Within each section a powervm option has been added, denoting whether or not each feature is currently supported. This includes the base support matrix files as well as the library and conf changes required to build it in the nova-powervm docs build. Change-Id: I46511c61ce35164ec89098c6d876edb26ca8d261
This commit is contained in:
parent
c1df6cc974
commit
c10348aeb4
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,3 +10,5 @@ nova_powervm.egg-info/
|
||||
doc/build
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
*.DS_Store
|
||||
.venv
|
||||
|
0
doc/ext/__init__.py
Normal file
0
doc/ext/__init__.py
Normal file
516
doc/ext/support_matrix.py
Normal file
516
doc/ext/support_matrix.py
Normal file
@ -0,0 +1,516 @@
|
||||
# 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 re
|
||||
|
||||
import six
|
||||
from six.moves import configparser
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers import rst
|
||||
|
||||
|
||||
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.features = []
|
||||
|
||||
# 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.targets = {}
|
||||
|
||||
|
||||
class SupportMatrixFeature(object):
|
||||
|
||||
STATUS_MANDATORY = "mandatory"
|
||||
STATUS_CHOICE = "choice"
|
||||
STATUS_CONDITION = "condition"
|
||||
STATUS_OPTIONAL = "optional"
|
||||
|
||||
STATUS_ALL = [STATUS_MANDATORY, STATUS_CHOICE,
|
||||
STATUS_CONDITION, STATUS_OPTIONAL]
|
||||
|
||||
def __init__(self, key, title, status=STATUS_OPTIONAL,
|
||||
group=None, notes=None, cli=[]):
|
||||
# A unique key (eg 'foo.bar.wizz') to identify the feature
|
||||
self.key = key
|
||||
# A human friendly short title for the feature
|
||||
self.title = title
|
||||
# One of the status constants
|
||||
self.status = status
|
||||
# Detail string if status was choice/condition
|
||||
self.group = group
|
||||
# Arbitrarily long string describing the feature in detail
|
||||
self.notes = notes
|
||||
# Dict of (name, SupportMatrixImplementation) detailing
|
||||
# the implementation for each hypervisor driver. The
|
||||
# 'name' dict key is the value from SupportMatrixTarget.key
|
||||
# for the hypervisor in question
|
||||
self.implementations = {}
|
||||
# A list of CLI commands which are related to that feature
|
||||
self.cli = cli
|
||||
|
||||
|
||||
class SupportMatrixImplementation(object):
|
||||
|
||||
STATUS_COMPLETE = "complete"
|
||||
STATUS_PARTIAL = "partial"
|
||||
STATUS_MISSING = "missing"
|
||||
STATUS_UKNOWN = "unknown"
|
||||
|
||||
STATUS_ALL = [STATUS_COMPLETE, STATUS_PARTIAL, STATUS_MISSING,
|
||||
STATUS_UKNOWN]
|
||||
|
||||
def __init__(self, status=STATUS_MISSING, notes=None):
|
||||
# One of the status constants detailing the implementation
|
||||
# level
|
||||
self.status = status
|
||||
# Arbitrary string describing any caveats of the implementation.
|
||||
# Mandatory if status is 'partial', optional otherwise.
|
||||
self.notes = notes
|
||||
|
||||
|
||||
class SupportMatrixTarget(object):
|
||||
|
||||
def __init__(self, key, title, driver, hypervisor=None, architecture=None):
|
||||
""":param key: Unique identifier for the hypervisor driver
|
||||
:param title: Human friendly name of the hypervisor
|
||||
:param driver: Name of the Nova driver
|
||||
:param hypervisor: (optional) Name of the hypervisor, if many
|
||||
:param architecture: (optional) Name of the architecture, if many
|
||||
"""
|
||||
self.key = key
|
||||
self.title = title
|
||||
self.driver = driver
|
||||
self.hypervisor = hypervisor
|
||||
self.architecture = architecture
|
||||
|
||||
|
||||
class SupportMatrixDirective(rst.Directive):
|
||||
|
||||
# The argument is the filename, e.g. support-matrix.ini
|
||||
required_arguments = 1
|
||||
|
||||
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.arguments[0]
|
||||
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()
|
||||
matrix.targets = self._get_targets(cfg)
|
||||
matrix.features = self._get_features(cfg, matrix.targets)
|
||||
|
||||
return matrix
|
||||
|
||||
def _get_targets(self, cfg):
|
||||
# The 'targets' section is special - it lists all the
|
||||
# hypervisors that this file records data for
|
||||
|
||||
targets = {}
|
||||
|
||||
for item in cfg.options("targets"):
|
||||
if not item.startswith("driver-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[12:]
|
||||
title = cfg.get("targets", item)
|
||||
name = key.split("-")
|
||||
if len(name) == 1:
|
||||
target = SupportMatrixTarget(key,
|
||||
title,
|
||||
name[0])
|
||||
elif len(name) == 2:
|
||||
target = SupportMatrixTarget(key,
|
||||
title,
|
||||
name[0],
|
||||
name[1])
|
||||
elif len(name) == 3:
|
||||
target = SupportMatrixTarget(key,
|
||||
title,
|
||||
name[0],
|
||||
name[1],
|
||||
name[2])
|
||||
else:
|
||||
raise Exception("'%s' field is malformed in '[%s]' section" %
|
||||
(item, "DEFAULT"))
|
||||
|
||||
targets[key] = target
|
||||
|
||||
return targets
|
||||
|
||||
def _get_features(self, cfg, targets):
|
||||
# All sections except 'targets' describe some feature of
|
||||
# the Nova hypervisor driver implementation
|
||||
|
||||
features = []
|
||||
|
||||
for section in cfg.sections():
|
||||
if section == "targets":
|
||||
continue
|
||||
if not cfg.has_option(section, "title"):
|
||||
raise Exception(
|
||||
"'title' field missing in '[%s]' section" % section)
|
||||
|
||||
title = cfg.get(section, "title")
|
||||
|
||||
status = SupportMatrixFeature.STATUS_OPTIONAL
|
||||
if cfg.has_option(section, "status"):
|
||||
# The value is a string "status(group)" where
|
||||
# the 'group' part is optional
|
||||
status = cfg.get(section, "status")
|
||||
offset = status.find("(")
|
||||
group = None
|
||||
if offset != -1:
|
||||
group = status[offset + 1:-1]
|
||||
status = status[0:offset]
|
||||
|
||||
if status not in SupportMatrixFeature.STATUS_ALL:
|
||||
raise Exception(
|
||||
"'status' field value '%s' in ['%s']"
|
||||
"section must be %s" %
|
||||
(status, section,
|
||||
",".join(SupportMatrixFeature.STATUS_ALL)))
|
||||
|
||||
notes = None
|
||||
if cfg.has_option(section, "notes"):
|
||||
notes = cfg.get(section, "notes")
|
||||
cli = []
|
||||
if cfg.has_option(section, "cli"):
|
||||
cli = cfg.get(section, "cli")
|
||||
feature = SupportMatrixFeature(section,
|
||||
title,
|
||||
status,
|
||||
group,
|
||||
notes,
|
||||
cli)
|
||||
|
||||
# Now we've got the basic feature details, we must process
|
||||
# the hypervisor driver implementation for each feature
|
||||
for item in cfg.options(section):
|
||||
if not item.startswith("driver-impl-"):
|
||||
continue
|
||||
|
||||
key = item[12:]
|
||||
if key not in targets:
|
||||
raise Exception(
|
||||
"Driver impl '%s' in '[%s]' not declared" %
|
||||
(item, section))
|
||||
|
||||
status = cfg.get(section, item)
|
||||
if status not in SupportMatrixImplementation.STATUS_ALL:
|
||||
raise Exception(
|
||||
"'%s' value '%s' in '[%s]' section must be %s" %
|
||||
(item, status, section,
|
||||
",".join(SupportMatrixImplementation.STATUS_ALL)))
|
||||
|
||||
noteskey = "driver-notes-" + item[12:]
|
||||
notes = None
|
||||
if cfg.has_option(section, noteskey):
|
||||
notes = cfg.get(section, noteskey)
|
||||
|
||||
target = targets[key]
|
||||
impl = SupportMatrixImplementation(status,
|
||||
notes)
|
||||
feature.implementations[target.key] = impl
|
||||
|
||||
for key in targets:
|
||||
if key not in feature.implementations:
|
||||
raise Exception("'%s' missing in '[%s]' section" %
|
||||
(target.key, section))
|
||||
|
||||
features.append(feature)
|
||||
|
||||
return features
|
||||
|
||||
def _build_markup(self, matrix):
|
||||
"""Constructs the docutils content for the support matrix
|
||||
"""
|
||||
content = []
|
||||
self._build_summary(matrix, content)
|
||||
self._build_details(matrix, content)
|
||||
self._build_notes(content)
|
||||
return content
|
||||
|
||||
def _build_summary(self, matrix, content):
|
||||
"""Constructs the docutils content for the summary of
|
||||
the support matrix.
|
||||
|
||||
The summary consists of a giant table, with one row
|
||||
for each feature, and a column for each hypervisor
|
||||
driver. It provides an 'at a glance' summary of the
|
||||
status of each driver
|
||||
"""
|
||||
|
||||
summarytitle = nodes.subtitle(text="Summary")
|
||||
summary = nodes.table()
|
||||
cols = len(matrix.targets.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)
|
||||
|
||||
# This sets up all the column headers - two fixed
|
||||
# columns for feature name & status
|
||||
header = nodes.row()
|
||||
blank = nodes.entry()
|
||||
blank.append(nodes.emphasis(text="Feature"))
|
||||
header.append(blank)
|
||||
blank = nodes.entry()
|
||||
blank.append(nodes.emphasis(text="Status"))
|
||||
header.append(blank)
|
||||
summaryhead.append(header)
|
||||
|
||||
# then one column for each hypervisor driver
|
||||
impls = matrix.targets.keys()
|
||||
impls.sort()
|
||||
for key in impls:
|
||||
target = matrix.targets[key]
|
||||
implcol = nodes.entry()
|
||||
header.append(implcol)
|
||||
implcol.append(nodes.strong(text=target.title))
|
||||
|
||||
# We now produce the body of the table, one row for
|
||||
# each feature to report on
|
||||
for feature in matrix.features:
|
||||
item = nodes.row()
|
||||
|
||||
# the hyperlink target name linking to details
|
||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
||||
feature.key)
|
||||
|
||||
# first the to fixed columns for title/status
|
||||
keycol = nodes.entry()
|
||||
item.append(keycol)
|
||||
keyref = nodes.reference(refid=id)
|
||||
keytxt = nodes.inline()
|
||||
keycol.append(keytxt)
|
||||
keytxt.append(keyref)
|
||||
keyref.append(nodes.strong(text=feature.title))
|
||||
|
||||
statuscol = nodes.entry()
|
||||
item.append(statuscol)
|
||||
statuscol.append(nodes.inline(
|
||||
text=feature.status,
|
||||
classes=["sp_feature_" + feature.status]))
|
||||
|
||||
# and then one column for each hypervisor driver
|
||||
impls = matrix.targets.keys()
|
||||
impls.sort()
|
||||
for key in impls:
|
||||
target = matrix.targets[key]
|
||||
impl = feature.implementations[key]
|
||||
implcol = nodes.entry()
|
||||
item.append(implcol)
|
||||
|
||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
||||
feature.key + "_" + key)
|
||||
|
||||
implref = nodes.reference(refid=id)
|
||||
impltxt = nodes.inline()
|
||||
implcol.append(impltxt)
|
||||
impltxt.append(implref)
|
||||
|
||||
status = ""
|
||||
if impl.status == SupportMatrixImplementation.STATUS_COMPLETE:
|
||||
status = u"\u2714"
|
||||
elif impl.status == SupportMatrixImplementation.STATUS_MISSING:
|
||||
status = u"\u2716"
|
||||
elif impl.status == SupportMatrixImplementation.STATUS_PARTIAL:
|
||||
status = u"\u2714"
|
||||
elif impl.status == SupportMatrixImplementation.STATUS_UKNOWN:
|
||||
status = u"?"
|
||||
|
||||
implref.append(nodes.literal(
|
||||
text=status,
|
||||
classes=["sp_impl_summary", "sp_impl_" + impl.status]))
|
||||
|
||||
summarybody.append(item)
|
||||
|
||||
def _build_details(self, matrix, content):
|
||||
"""Constructs the docutils content for the details of
|
||||
the support matrix.
|
||||
|
||||
This is generated as a bullet list of features.
|
||||
Against each feature we provide the description of
|
||||
the feature and then the details of the hypervisor
|
||||
impls, with any driver specific notes that exist
|
||||
"""
|
||||
|
||||
detailstitle = nodes.subtitle(text="Details")
|
||||
details = nodes.bullet_list()
|
||||
|
||||
content.append(detailstitle)
|
||||
content.append(details)
|
||||
|
||||
# One list entry for each feature we're reporting on
|
||||
for feature in matrix.features:
|
||||
item = nodes.list_item()
|
||||
|
||||
status = feature.status
|
||||
if feature.group is not None:
|
||||
status += "(" + feature.group + ")"
|
||||
|
||||
# The hypervisor target name linked from summary table
|
||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
||||
feature.key)
|
||||
|
||||
# Highlight the feature title name
|
||||
item.append(nodes.strong(text=feature.title,
|
||||
ids=[id]))
|
||||
|
||||
para = nodes.paragraph()
|
||||
para.append(nodes.strong(text="Status: " + status + ". "))
|
||||
if feature.notes is not None:
|
||||
para.append(nodes.inline(text=feature.notes))
|
||||
item.append(para)
|
||||
|
||||
if feature.cli:
|
||||
item.append(self._create_cli_paragraph(feature))
|
||||
|
||||
para_divers = nodes.paragraph()
|
||||
para_divers.append(nodes.strong(text="drivers:"))
|
||||
# A sub-list giving details of each hypervisor target
|
||||
impls = nodes.bullet_list()
|
||||
for key in feature.implementations:
|
||||
target = matrix.targets[key]
|
||||
impl = feature.implementations[key]
|
||||
subitem = nodes.list_item()
|
||||
|
||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
||||
feature.key + "_" + key)
|
||||
subitem += [
|
||||
nodes.strong(text=target.title + ": "),
|
||||
nodes.literal(text=impl.status,
|
||||
classes=["sp_impl_" + impl.status],
|
||||
ids=[id]),
|
||||
]
|
||||
if impl.notes is not None:
|
||||
subitem.append(self._create_notes_paragraph(impl.notes))
|
||||
impls.append(subitem)
|
||||
|
||||
para_divers.append(impls)
|
||||
item.append(para_divers)
|
||||
details.append(item)
|
||||
|
||||
def _build_notes(self, content):
|
||||
"""Constructs a list of notes content for the support matrix.
|
||||
|
||||
This is generated as a bullet list.
|
||||
"""
|
||||
notestitle = nodes.subtitle(text="Notes")
|
||||
notes = nodes.bullet_list()
|
||||
|
||||
content.append(notestitle)
|
||||
content.append(notes)
|
||||
|
||||
NOTES = [
|
||||
"Virtuozzo was formerly named Parallels in this document"
|
||||
]
|
||||
|
||||
for note in NOTES:
|
||||
item = nodes.list_item()
|
||||
item.append(nodes.strong(text=note))
|
||||
notes.append(item)
|
||||
|
||||
def _create_cli_paragraph(self, feature):
|
||||
''' Create a paragraph which represents the CLI commands of the feature
|
||||
|
||||
The paragraph will have a bullet list of CLI commands.
|
||||
'''
|
||||
para = nodes.paragraph()
|
||||
para.append(nodes.strong(text="CLI commands:"))
|
||||
commands = nodes.bullet_list()
|
||||
for c in feature.cli.split(";"):
|
||||
cli_command = nodes.list_item()
|
||||
cli_command += nodes.literal(text=c, classes=["sp_cli"])
|
||||
commands.append(cli_command)
|
||||
para.append(commands)
|
||||
return para
|
||||
|
||||
def _create_notes_paragraph(self, notes):
|
||||
""" Constructs a paragraph which represents the implementation notes
|
||||
|
||||
The paragraph consists of text and clickable URL nodes if links were
|
||||
given in the notes.
|
||||
"""
|
||||
para = nodes.paragraph()
|
||||
# links could start with http:// or https://
|
||||
link_idxs = [m.start() for m in re.finditer('https?://', notes)]
|
||||
start_idx = 0
|
||||
for link_idx in link_idxs:
|
||||
# assume the notes start with text (could be empty)
|
||||
para.append(nodes.inline(text=notes[start_idx:link_idx]))
|
||||
# create a URL node until the next text or the end of the notes
|
||||
link_end_idx = notes.find(" ", link_idx)
|
||||
if link_end_idx == -1:
|
||||
# In case the notes end with a link without a blank
|
||||
link_end_idx = len(notes)
|
||||
uri = notes[link_idx:link_end_idx + 1]
|
||||
para.append(nodes.reference("", uri, refuri=uri))
|
||||
start_idx = link_end_idx + 1
|
||||
|
||||
# get all text after the last link (could be empty) or all of the
|
||||
# text if no link was given
|
||||
para.append(nodes.inline(text=notes[start_idx:]))
|
||||
return para
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive('support_matrix', SupportMatrixDirective)
|
||||
app.add_stylesheet('support-matrix.css')
|
@ -20,6 +20,8 @@ import os
|
||||
# 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 ------------------------------------------------
|
||||
|
||||
@ -31,7 +33,8 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'oslosphinx'
|
||||
'oslosphinx',
|
||||
'ext.support_matrix'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
@ -38,6 +38,7 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
readme
|
||||
support-matrix
|
||||
|
||||
Nova-PowerVM Policies
|
||||
=====================
|
||||
|
597
doc/source/support-matrix.ini
Normal file
597
doc/source/support-matrix.ini
Normal file
@ -0,0 +1,597 @@
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
#
|
||||
# =========================================
|
||||
# Nova Hypervisor Feature Capability Matrix
|
||||
# =========================================
|
||||
#
|
||||
# This obsoletes the information previously at
|
||||
#
|
||||
# https://wiki.openstack.org/wiki/HypervisorSupportMatrix
|
||||
#
|
||||
# This file contains a specification of what feature capabilities each
|
||||
# hypervisor driver in Nova is able to support. Feature capabilities include
|
||||
# what API operations are supported, what storage / networking features can be
|
||||
# used and what aspects of the guest machine can be configured. The capabilities
|
||||
# can be considered to be structured into nested groups, but in this file they
|
||||
# have been flattened for ease of representation. The section names represent
|
||||
# the group structure. At the top level there are the following groups defined
|
||||
#
|
||||
# - operation - public API operations
|
||||
# - storage - host storage configuration options
|
||||
# - networking - host networking configuration options
|
||||
# - guest - guest hardware configuration options
|
||||
#
|
||||
# When considering which capabilities should be marked as mandatory,
|
||||
# consider the general guiding principles listed in the support-matrix.rst
|
||||
# file
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# In the case of the driver being marked as 'partial', then
|
||||
# 'driver-notes-XXX' entry should be used to explain the caveats
|
||||
# around the implementation.
|
||||
#
|
||||
# The 'cli' field takes a list of nova client commands, separated by semicolon.
|
||||
# These CLi commands are related to that feature.
|
||||
# Example:
|
||||
# cli=nova list;nova show <server>
|
||||
#
|
||||
[targets]
|
||||
# List of driver impls we are going to record info for later
|
||||
# This list only covers the PowerVM driver. Please see the equivalent
|
||||
# document in the Nova tree for information on in-tree hypervisors.
|
||||
driver-impl-powervm=PowerVM
|
||||
|
||||
[operation.attach-volume]
|
||||
title=Attach block volume to instance
|
||||
status=optional
|
||||
notes=The attach volume operation provides a means to hotplug
|
||||
additional block storage to a running instance. This allows
|
||||
storage capabilities to be expanded without interruption of
|
||||
service. In a cloud model it would be more typical to just
|
||||
spin up a new instance with large storage, so the ability to
|
||||
hotplug extra storage is for those cases where the instance
|
||||
is considered to be more of a pet than cattle. Therefore
|
||||
this operation is not considered to be mandatory to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.detach-volume]
|
||||
title=Detach block volume from instance
|
||||
status=optional
|
||||
notes=See notes for attach volume operation.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.maintenance-mode]
|
||||
title=Set the host in a maintenance mode
|
||||
status=optional
|
||||
notes=This operation allows a host to be placed into maintenance
|
||||
mode, automatically triggering migration of any running
|
||||
instances to an alternative host and preventing new
|
||||
instances from being launched. This is not considered
|
||||
to be a mandatory operation to support.
|
||||
The CLI command is "nova host-update <host>".
|
||||
The driver methods to implement are "host_maintenance_mode" and
|
||||
"set_host_enabled".
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.evacuate]
|
||||
title=Evacuate instances from a host
|
||||
status=optional
|
||||
notes=A possible failure scenario in a cloud environment is the outage
|
||||
of one of the compute nodes. In such a case the instances of the down
|
||||
host can be evacuated to another host. It is assumed that the old host
|
||||
is unlikely ever to be powered back on, otherwise the evacuation
|
||||
attempt will be rejected. When the instances get moved to the new
|
||||
host, their volumes get re-attached and the locally stored data is
|
||||
dropped. That happens in the same way as a rebuild.
|
||||
This is not considered to be a mandatory operation to support.
|
||||
cli=nova evacuate <server>;nova host-evacuate <host>
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.rebuild]
|
||||
title=Rebuild instance
|
||||
status=optional
|
||||
notes=A possible use case is additional attributes need to be set
|
||||
to the instance, nova will purge all existing data from the system
|
||||
and remakes the VM with given information such as 'metadata' and
|
||||
'personalities'. Though this is not considered to be a mandatory
|
||||
operation to support.
|
||||
cli=nova rebuild <server> <image>;
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.get-guest-info]
|
||||
title=Guest instance status
|
||||
status=mandatory
|
||||
notes=Provides a quick report on information about the guest instance,
|
||||
including the power state, memory allocation, CPU allocation, number
|
||||
of vCPUs and cummulative CPU execution time. As well as being
|
||||
informational, the power state is used by the compute manager for
|
||||
tracking changes in guests. Therefore this operation is considered
|
||||
mandatory to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.get-host-info]
|
||||
title=Guest host status
|
||||
status=optional
|
||||
notes=Unclear what this refers to
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.live-migrate]
|
||||
title=Live migrate instance across hosts
|
||||
status=optional
|
||||
notes=Live migration provides a way to move an instance off one
|
||||
compute host, to another compute host. Administrators may use
|
||||
this to evacuate instances from a host that needs to undergo
|
||||
maintenance tasks, though of course this may not help if the
|
||||
host is already suffering a failure. In general instances are
|
||||
considered cattle rather than pets, so it is expected that an
|
||||
instance is liable to be killed if host maintenance is required.
|
||||
It is technically challenging for some hypervisors to provide
|
||||
support for the live migration operation, particularly those
|
||||
built on the container based virtualization. Therefore this
|
||||
operation is not considered mandatory to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.launch]
|
||||
title=Launch instance
|
||||
status=mandatory
|
||||
notes=Importing pre-existing running virtual machines on a host is
|
||||
considered out of scope of the cloud paradigm. Therefore this
|
||||
operation is mandatory to support in drivers.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.pause]
|
||||
title=Stop instance CPUs (pause)
|
||||
status=optional
|
||||
notes=Stopping an instances CPUs can be thought of as roughly
|
||||
equivalent to suspend-to-RAM. The instance is still present
|
||||
in memory, but execution has stopped. The problem, however,
|
||||
is that there is no mechanism to inform the guest OS that
|
||||
this takes place, so upon unpausing, its clocks will no
|
||||
longer report correct time. For this reason hypervisor vendors
|
||||
generally discourage use of this feature and some do not even
|
||||
implement it. Therefore this operation is considered optional
|
||||
to support in drivers.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.reboot]
|
||||
title=Reboot instance
|
||||
status=optional
|
||||
notes=It is reasonable for a guest OS administrator to trigger a
|
||||
graceful reboot from inside the instance. A host initiated
|
||||
graceful reboot requires guest co-operation and a non-graceful
|
||||
reboot can be achieved by a combination of stop+start. Therefore
|
||||
this operation is considered optional.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.rescue]
|
||||
title=Rescue instance
|
||||
status=optional
|
||||
notes=The rescue operation starts an instance in a special
|
||||
configuration whereby it is booted from an special root
|
||||
disk image. The goal is to allow an administrator to
|
||||
recover the state of a broken virtual machine. In general
|
||||
the cloud model considers instances to be cattle, so if
|
||||
an instance breaks the general expectation is that it be
|
||||
thrown away and a new instance created. Therefore this
|
||||
operation is considered optional to support in drivers.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.resize]
|
||||
title=Resize instance
|
||||
status=optional
|
||||
notes=The resize operation allows the user to change a running
|
||||
instance to match the size of a different flavor from the one
|
||||
it was initially launched with. There are many different
|
||||
flavor attributes that potentially need to be updated. In
|
||||
general it is technically challenging for a hypervisor to
|
||||
support the alteration of all relevant config settings for a
|
||||
running instance. Therefore this operation is considered
|
||||
optional to support in drivers.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.resume]
|
||||
title=Restore instance
|
||||
status=optional
|
||||
notes=See notes for the suspend operation
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.service-control.wtf.com]
|
||||
title=Service control
|
||||
status=optional
|
||||
notes=Something something, dark side, something something.
|
||||
Hard to claim this is mandatory when no one seems to know
|
||||
what "Service control" refers to in the context of virt
|
||||
drivers.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.set-admin-password]
|
||||
title=Set instance admin password
|
||||
status=optional
|
||||
notes=Provides a mechanism to re(set) the password of the administrator
|
||||
account inside the instance operating system. This requires that the
|
||||
hypervisor has a way to communicate with the running guest operating
|
||||
system. Given the wide range of operating systems in existence it is
|
||||
unreasonable to expect this to be practical in the general case. The
|
||||
configdrive and metadata service both provide a mechanism for setting
|
||||
the administrator password at initial boot time. In the case where this
|
||||
operation were not available, the administrator would simply have to
|
||||
login to the guest and change the password in the normal manner, so
|
||||
this is just a convenient optimization. Therefore this operation is
|
||||
not considered mandatory for drivers to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.snapshot]
|
||||
title=Save snapshot of instance disk
|
||||
status=optional
|
||||
notes=The snapshot operation allows the current state of the
|
||||
instance root disk to be saved and uploaded back into the
|
||||
glance image repository. The instance can later be booted
|
||||
again using this saved image. This is in effect making
|
||||
the ephemeral instance root disk into a semi-persistent
|
||||
storage, in so much as it is preserved even though the guest
|
||||
is no longer running. In general though, the expectation is
|
||||
that the root disks are ephemeral so the ability to take a
|
||||
snapshot cannot be assumed. Therefore this operation is not
|
||||
considered mandatory to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.suspend]
|
||||
title=Suspend instance
|
||||
status=optional
|
||||
notes=Suspending an instance can be thought of as roughly
|
||||
equivalent to suspend-to-disk. The instance no longer
|
||||
consumes any RAM or CPUs, with its live running state
|
||||
having been preserved in a file on disk. It can later
|
||||
be restored, at which point it should continue execution
|
||||
where it left off. As with stopping instance CPUs, it suffers from the fact
|
||||
that the guest OS will typically be left with a clock that
|
||||
is no longer telling correct time. For container based
|
||||
virtualization solutions, this operation is particularly
|
||||
technically challenging to implement and is an area of
|
||||
active research. This operation tends to make more sense
|
||||
when thinking of instances as pets, rather than cattle,
|
||||
since with cattle it would be simpler to just terminate
|
||||
the instance instead of suspending. Therefore this operation
|
||||
is considered optional to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.swap-volume]
|
||||
title=Swap block volumes
|
||||
status=optional
|
||||
notes=The swap volume operation is a mechanism for changing a running
|
||||
instance so that its attached volume(s) are backed by different
|
||||
storage in the host. An alternative to this would be to simply
|
||||
terminate the existing instance and spawn a new instance with the
|
||||
new storage. In other words this operation is primarily targeted towards
|
||||
the pet use case rather than cattle, however, it is required for volume
|
||||
migration to work in the volume service. This is considered optional to
|
||||
support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.terminate]
|
||||
title=Shutdown instance
|
||||
status=mandatory
|
||||
notes=The ability to terminate a virtual machine is required in
|
||||
order for a cloud user to stop utilizing resources and thus
|
||||
avoid indefinitely ongoing billing. Therefore this operation
|
||||
is mandatory to support in drivers.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.trigger-crash-dump]
|
||||
title=Trigger crash dump
|
||||
status=optional
|
||||
notes=The trigger crash dump operation is a mechanism for triggering
|
||||
a crash dump in an instance. The feature is typically implemented by
|
||||
injecting an NMI (Non-maskable Interrupt) into the instance. It provides
|
||||
a means to dump the production memory image as a dump file which is useful
|
||||
for users. Therefore this operation is considered optional to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[operation.unpause]
|
||||
title=Resume instance CPUs (unpause)
|
||||
status=optional
|
||||
notes=See notes for the "Stop instance CPUs" operation
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[guest.disk.autoconfigure.wtf.com]
|
||||
title=Auto configure disk
|
||||
status=optional
|
||||
notes=something something, dark side, something something.
|
||||
Unclear just what this is about.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[guest.disk.rate-limit]
|
||||
title=Instance disk I/O limits
|
||||
status=optional
|
||||
notes=The ability to set rate limits on virtual disks allows for
|
||||
greater performance isolation between instances running on the
|
||||
same host storage. It is valid to delegate scheduling of I/O
|
||||
operations to the hypervisor with its default settings, instead
|
||||
of doing fine grained tuning. Therefore this is not considered
|
||||
to be an mandatory configuration to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[guest.setup.configdrive]
|
||||
title=Config drive support
|
||||
status=choice(guest.setup)
|
||||
notes=The config drive provides an information channel into
|
||||
the guest operating system, to enable configuration of the
|
||||
administrator password, file injection, registration of
|
||||
SSH keys, etc. Since cloud images typically ship with all
|
||||
login methods locked, a mechanism to set the administrator
|
||||
password of keys is required to get login access. Alternatives
|
||||
include the metadata service and disk injection. At least one
|
||||
of the guest setup mechanisms is required to be supported by
|
||||
drivers, in order to enable login access.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[guest.setup.inject.file]
|
||||
title=Inject files into disk image
|
||||
status=optional
|
||||
notes=This allows for the end user to provide data for multiple
|
||||
files to be injected into the root filesystem before an instance
|
||||
is booted. This requires that the compute node understand the
|
||||
format of the filesystem and any partitioning scheme it might
|
||||
use on the block device. This is a non-trivial problem considering
|
||||
the vast number of filesystems in existence. The problem of injecting
|
||||
files to a guest OS is better solved by obtaining via the metadata
|
||||
service or config drive. Therefore this operation is considered
|
||||
optional to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[guest.setup.inject.networking]
|
||||
title=Inject guest networking config
|
||||
status=optional
|
||||
notes=This allows for static networking configuration (IP
|
||||
address, netmask, gateway and routes) to be injected directly
|
||||
into the root filesystem before an instance is booted. This
|
||||
requires that the compute node understand how networking is
|
||||
configured in the guest OS which is a non-trivial problem
|
||||
considering the vast number of operating system types. The
|
||||
problem of configuring networking is better solved by DHCP
|
||||
or by obtaining static config via the metadata service or
|
||||
config drive. Therefore this operation is considered optional
|
||||
to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[console.rdp]
|
||||
title=Remote desktop over RDP
|
||||
status=choice(console)
|
||||
notes=This allows the administrator to interact with the graphical
|
||||
console of the guest OS via RDP. This provides a way to see boot
|
||||
up messages and login to the instance when networking configuration
|
||||
has failed, thus preventing a network based login. Some operating
|
||||
systems may prefer to emit messages via the serial console for
|
||||
easier consumption. Therefore support for this operation is not
|
||||
mandatory, however, a driver is required to support at least one
|
||||
of the listed console access operations.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[console.serial.log]
|
||||
title=View serial console logs
|
||||
status=choice(console)
|
||||
notes=This allows the administrator to query the logs of data
|
||||
emitted by the guest OS on its virtualized serial port. For
|
||||
UNIX guests this typically includes all boot up messages and
|
||||
so is useful for diagnosing problems when an instance fails
|
||||
to successfully boot. Not all guest operating systems will be
|
||||
able to emit boot information on a serial console, others may
|
||||
only support graphical consoles. Therefore support for this
|
||||
operation is not mandatory, however, a driver is required to
|
||||
support at least one of the listed console access operations.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[console.serial.interactive]
|
||||
title=Remote interactive serial console
|
||||
status=choice(console)
|
||||
notes=This allows the administrator to interact with the serial
|
||||
console of the guest OS. This provides a way to see boot
|
||||
up messages and login to the instance when networking configuration
|
||||
has failed, thus preventing a network based login. Not all guest
|
||||
operating systems will be able to emit boot information on a serial
|
||||
console, others may only support graphical consoles. Therefore support
|
||||
for this operation is not mandatory, however, a driver is required to
|
||||
support at least one of the listed console access operations.
|
||||
This feature was introduced in the Juno release with blueprint
|
||||
https://blueprints.launchpad.net/nova/+spec/serial-ports
|
||||
cli=nova get-serial-console <server>
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[console.spice]
|
||||
title=Remote desktop over SPICE
|
||||
status=choice(console)
|
||||
notes=This allows the administrator to interact with the graphical
|
||||
console of the guest OS via SPICE. This provides a way to see boot
|
||||
up messages and login to the instance when networking configuration
|
||||
has failed, thus preventing a network based login. Some operating
|
||||
systems may prefer to emit messages via the serial console for
|
||||
easier consumption. Therefore support for this operation is not
|
||||
mandatory, however, a driver is required to support at least one
|
||||
of the listed console access operations.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[console.vnc]
|
||||
title=Remote desktop over VNC
|
||||
status=choice(console)
|
||||
notes=This allows the administrator to interact with the graphical
|
||||
console of the guest OS via VNC. This provides a way to see boot
|
||||
up messages and login to the instance when networking configuration
|
||||
has failed, thus preventing a network based login. Some operating
|
||||
systems may prefer to emit messages via the serial console for
|
||||
easier consumption. Therefore support for this operation is not
|
||||
mandatory, however, a driver is required to support at least one
|
||||
of the listed console access operations.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[storage.block]
|
||||
title=Block storage support
|
||||
status=optional
|
||||
notes=Block storage provides instances with direct attached
|
||||
virtual disks that can be used for persistent storage of data.
|
||||
As an alternative to direct attached disks, an instance may
|
||||
choose to use network based persistent storage. OpenStack provides
|
||||
object storage via the Swift service, or a traditional filesystem
|
||||
such as NFS/GlusterFS may be used. Some types of instances may
|
||||
not require persistent storage at all, being simple transaction
|
||||
processing systems reading requests & sending results to and from
|
||||
the network. Therefore support for this configuration is not
|
||||
considered mandatory for drivers to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[storage.block.backend.fibrechannel]
|
||||
title=Block storage over fibre channel
|
||||
status=optional
|
||||
notes=To maximise performance of the block storage, it may be desirable
|
||||
to directly access fibre channel LUNs from the underlying storage
|
||||
technology on the compute hosts. Since this is just a performance
|
||||
optimization of the I/O path it is not considered mandatory to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[storage.block.backend.iscsi]
|
||||
title=Block storage over iSCSI
|
||||
status=condition(storage.block==complete)
|
||||
notes=If the driver wishes to support block storage, it is common to
|
||||
provide an iSCSI based backend to access the storage from cinder.
|
||||
This isolates the compute layer for knowledge of the specific storage
|
||||
technology used by Cinder, albeit at a potential performance cost due
|
||||
to the longer I/O path involved. If the driver chooses to support
|
||||
block storage, then this is considered mandatory to support, otherwise
|
||||
it is considered optional.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[storage.block.backend.iscsi.auth.chap]
|
||||
title=CHAP authentication for iSCSI
|
||||
status=optional
|
||||
notes=If accessing the cinder iSCSI service over an untrusted LAN it
|
||||
is desirable to be able to enable authentication for the iSCSI
|
||||
protocol. CHAP is the commonly used authentication protocol for
|
||||
iSCSI. This is not considered mandatory to support. (?)
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[storage.image]
|
||||
title=Image storage support
|
||||
status=mandatory
|
||||
notes=This refers to the ability to boot an instance from an image
|
||||
stored in the glance image repository. Without this feature it
|
||||
would not be possible to bootstrap from a clean environment, since
|
||||
there would be no way to get block volumes populated and reliance
|
||||
on external PXE servers is out of scope. Therefore this is considered
|
||||
a mandatory storage feature to support.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.firewallrules]
|
||||
title=Network firewall rules
|
||||
status=optional
|
||||
notes=Unclear how this is different from security groups
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[networking.routing]
|
||||
title=Network routing
|
||||
status=optional
|
||||
notes=Unclear what this refers to
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.securitygroups]
|
||||
title=Network security groups
|
||||
status=optional
|
||||
notes=The security groups feature provides a way to define rules
|
||||
to isolate the network traffic of different instances running
|
||||
on a compute host. This would prevent actions such as MAC and
|
||||
IP address spoofing, or the ability to setup rogue DHCP servers.
|
||||
In a private cloud environment this may be considered to be a
|
||||
superfluous requirement. Therefore this is considered to be an
|
||||
optional configuration to support.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
||||
|
||||
[networking.topology.flat]
|
||||
title=Flat networking
|
||||
status=choice(networking.topology)
|
||||
notes=Provide network connectivity to guests using a
|
||||
flat topology across all compute nodes. At least one
|
||||
of the networking configurations is mandatory to
|
||||
support in the drivers.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.topology.vlan]
|
||||
title=VLAN networking
|
||||
status=choice(networking.topology)
|
||||
notes=Provide network connectivity to guests using VLANs
|
||||
to define the topology. At least one of the networking
|
||||
configurations is mandatory to support in the drivers.
|
||||
cli=
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.uefi-boot]
|
||||
title=uefi boot
|
||||
status=optional
|
||||
notes=This allows users to boot a guest with uefi firmware.
|
||||
cli=
|
||||
driver-impl-powervm=missing
|
41
doc/source/support-matrix.rst
Normal file
41
doc/source/support-matrix.rst
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
Feature Support Matrix
|
||||
======================
|
||||
|
||||
.. warning::
|
||||
Please note, while this document is still being maintained, this is slowly
|
||||
being updated to re-group and classify features
|
||||
|
||||
When considering which capabilities should be marked as mandatory the
|
||||
following general guiding principles were applied
|
||||
|
||||
* **Inclusivity** - people have shown ability to make effective
|
||||
use of a wide range of virtualization technologies with broadly
|
||||
varying featuresets. Aiming to keep the requirements as inclusive
|
||||
as possible, avoids second-guessing what a user may wish to use
|
||||
the cloud compute service for.
|
||||
|
||||
* **Bootstrapping** - a practical use case test is to consider that
|
||||
starting point for the compute deploy is an empty data center
|
||||
with new machines and network connectivity. The look at what
|
||||
are the minimum features required of a compute service, in order
|
||||
to get user instances running and processing work over the
|
||||
network.
|
||||
|
||||
* **Competition** - an early leader in the cloud compute service space
|
||||
was Amazon EC2. A sanity check for whether a feature should be
|
||||
mandatory is to consider whether it was available in the first
|
||||
public release of EC2. This had quite a narrow featureset, but
|
||||
none the less found very high usage in many use cases. So it
|
||||
serves to illustrate that many features need not be considered
|
||||
mandatory in order to get useful work done.
|
||||
|
||||
* **Reality** - there are many virt drivers currently shipped with
|
||||
Nova, each with their own supported feature set. Any feature which is
|
||||
missing in at least one virt driver that is already in-tree, must
|
||||
by inference be considered optional until all in-tree drivers
|
||||
support it. This does not rule out the possibility of a currently
|
||||
optional feature becoming mandatory at a later date, based on other
|
||||
principles above.
|
||||
|
||||
.. support_matrix:: support-matrix.ini
|
Loading…
Reference in New Issue
Block a user