Fix document building

Closes-Bug: 1692828
Change-Id: I0090c63da6db325717f717a7e1df95a09e5e4b16
This commit is contained in:
Pengfei Ni 2017-05-23 15:39:02 +08:00
parent 614454ac2a
commit 882164d1af
8 changed files with 565 additions and 38 deletions

View File

@ -0,0 +1,33 @@
.sp_feature_required {
font-weight: bold;
}
.sp_impl_complete {
color: rgb(0, 120, 0);
font-weight: normal;
}
.sp_impl_missing {
color: rgb(120, 0, 0);
font-weight: normal;
}
.sp_impl_partial {
color: rgb(170, 170, 0);
font-weight: normal;
}
.sp_impl_unknown {
color: rgb(170, 170, 170);
font-weight: normal;
}
.sp_impl_summary {
font-size: 2em;
}
.sp_cli {
font-family: monospace;
background-color: #F5F5F5;
}

View File

@ -30,6 +30,7 @@ import os
import subprocess
import sys
import warnings
import oslosphinx
# 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
@ -123,7 +124,8 @@ modindex_common_prefix = ['stackube.']
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
html_theme_path = [os.path.join(os.path.dirname(oslosphinx.__file__), 'theme')]
html_theme = 'openstack'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View File

@ -0,0 +1,482 @@
# 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/general_feature_support_matrix.ini
file into the developer documentation.
It is used via a single directive in the .rst file
.. support_matrix::
"""
import re
from docutils import nodes
from docutils.parsers import rst
from six.moves import configparser
RE_PATTERN = re.compile("[^a-zA-Z0-9_]")
class SupportMatrix(object):
"""Represents the entire support matrix for Neutron drivers"""
def __init__(self):
self.features = []
self.targets = {}
class SupportMatrixFeature(object):
STATUS_IMMATURE = "immature"
STATUS_MATURE = "mature"
STATUS_REQUIRED = "required"
STATUS_DEPRECATED = "deprecated"
STATUS_ALL = [STATUS_IMMATURE, STATUS_MATURE,
STATUS_REQUIRED, STATUS_DEPRECATED]
def __init__(self, key, title, status=STATUS_IMMATURE,
group=None, notes=None, cli=(), api=None):
self.key = key
self.title = title
self.status = status
self.group = group
self.notes = notes
self.cli = cli
self.api = api
self.implementations = {}
class SupportMatrixImplementation(object):
STATUS_COMPLETE = "complete"
STATUS_PARTIAL = "partial"
STATUS_INCOMPLETE = "incomplete"
STATUS_UNKNOWN = "unknown"
STATUS_ALL = [STATUS_COMPLETE, STATUS_INCOMPLETE,
STATUS_PARTIAL, STATUS_UNKNOWN]
def __init__(self, status=STATUS_INCOMPLETE, notes=None):
self.status = status
self.notes = notes
STATUS_DICT = {
SupportMatrixImplementation.STATUS_COMPLETE: u"\u2714",
SupportMatrixImplementation.STATUS_INCOMPLETE: u"\u2716",
SupportMatrixImplementation.STATUS_PARTIAL: u"\u2714",
SupportMatrixImplementation.STATUS_UNKNOWN: u"?"
}
class SupportMatrixTarget(object):
def __init__(self, key, title, driver, plugin=None,
architecture=None, api=None):
""":param key: Unique identifier for plugin
:param title: Human readable name for plugin
:param driver: name of the driver
:param plugin: optional name of plugin
:param architecture: optional name of architecture
"""
self.api = api
self.key = key
self.title = title
self.driver = driver
self.plugin = plugin
self.architecture = architecture
class SupportMatrixDirective(rst.Directive):
# general_feature_support_matrix.ini is the arg
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
# backend drivers that this file records data for
targets = {}
network_target = "networking-"
for item in cfg.options("targets"):
if not item.startswith(network_target):
continue
# The driver string will optionally contain
# 'networking-*' qualifier
# so we expect between 1 and 3 components
# in the name
key = item[len(network_target):]
title = cfg.get("targets", item)
name = key.split("-")
if len(name) > 3:
raise Exception("'%s' field is malformed in '[%s]' section" %
(item, "DEFAULT"))
else:
target = SupportMatrixTarget(key, title, *name)
targets[key] = target
return targets
def _get_features(self, cfg, targets):
# All sections except 'targets' describe some feature of
# the Neutron backend driver.
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_IMMATURE
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)))
cli = []
if cfg.has_option(section, "cli"):
cli = cfg.get(section, "cli")
api = None
if cfg.has_option(section, "api"):
api = cfg.get(section, "api")
notes = None
if cfg.has_option(section, "notes"):
notes = cfg.get(section, "notes")
feature = SupportMatrixFeature(section, title, status, group,
notes, cli, api)
# Now we've got the basic feature details, we must process
# the backend driver implementation for each feature
for item in cfg.options(section):
network_target = "networking-"
network_notes = "networking-notes-"
if not item.startswith(network_target):
continue
key = item[len(network_target):]
if key not in targets:
raise Exception(
"networking-'%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)))
notes_key = network_notes + item[len(network_notes):]
notes = None
if cfg.has_option(section, notes_key):
notes = cfg.get(section, notes_key)
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 backend
driver. It provides an 'at a glance' summary of the
status of each driver
"""
summary_title = nodes.subtitle(text="Summary")
summary = nodes.table()
cols = len(matrix.targets.keys())
cols += 2
summary_group = nodes.tgroup(cols=cols)
summary_body = nodes.tbody()
summary_head = nodes.thead()
for i in range(cols):
summary_group.append(nodes.colspec(colwidth=1))
summary_group.append(summary_head)
summary_group.append(summary_body)
summary.append(summary_group)
content.append(summary_title)
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)
summary_head.append(header)
# then one column for each backend 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
feature_id = re.sub(RE_PATTERN, "_", feature.key)
# first the fixed columns for title/status
key_col = nodes.entry()
item.append(key_col)
key_ref = nodes.reference(refid=feature_id)
key_txt = nodes.inline()
key_col.append(key_txt)
key_txt.append(key_ref)
key_ref.append(nodes.strong(text=feature.title))
status_col = nodes.entry()
item.append(status_col)
status_col.append(nodes.inline(
text=feature.status,
classes=["sp_feature_" + feature.status]))
# and then one column for each backend driver
impls = matrix.targets.keys()
impls.sort()
for key in impls:
target = matrix.targets[key]
impl = feature.implementations[key]
impl_col = nodes.entry()
item.append(impl_col)
key_id = re.sub(RE_PATTERN, "_",
"{}_{}".format(feature.key, key))
impl_ref = nodes.reference(refid=key_id)
impl_txt = nodes.inline()
impl_col.append(impl_txt)
impl_txt.append(impl_ref)
status = STATUS_DICT.get(impl.status, "")
impl_ref.append(nodes.literal(
text=status,
classes=["sp_impl_summary", "sp_impl_" + impl.status]))
summary_body.append(item)
def _build_details(self, matrix, content):
"""Constructs the docutils content for the details of
the support matrix.
"""
details_title = nodes.subtitle(text="Details")
details = nodes.bullet_list()
content.append(details_title)
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 += "({})".format(feature.group)
feature_id = re.sub(RE_PATTERN, "_", feature.key)
# Highlight the feature title name
item.append(nodes.strong(text=feature.title, ids=[feature_id]))
# Add maturity status
para = nodes.paragraph()
para.append(nodes.strong(text="Status: {} ".format(status)))
item.append(para)
# If API Alias exists add it
if feature.api is not None:
para = nodes.paragraph()
para.append(
nodes.strong(text="API Alias: {} ".format(feature.api)))
item.append(para)
if feature.cli:
item.append(self._create_cli_paragraph(feature))
if feature.notes is not None:
item.append(self._create_notes_paragraph(feature.notes))
para_divers = nodes.paragraph()
para_divers.append(nodes.strong(text="Driver Support:"))
# A sub-list giving details of each backend driver target
impls = nodes.bullet_list()
for key in feature.implementations:
target = matrix.targets[key]
impl = feature.implementations[key]
subitem = nodes.list_item()
key_id = re.sub(RE_PATTERN, "_",
"{}_{}".format(feature.key, key))
subitem += [
nodes.strong(text="{}: ".format(target.title)),
nodes.literal(text=impl.status,
classes=["sp_impl_{}".format(impl.status)],
ids=[key_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.
"""
notes_title = nodes.subtitle(text="Notes:")
notes = nodes.bullet_list()
content.append(notes_title)
content.append(notes)
for note in ["This document is a continuous work in progress"]:
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()
para.append(nodes.strong(text="Notes: "))
# 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')

15
doc/source/index.rst Normal file
View File

@ -0,0 +1,15 @@
=============================================
Welcome to Stackube developer documentation!
=============================================
Stackube is a multi-tenant and secure Kubernetes deployment enabled by OpenStack
core components.
Stackube Scope
==============
.. toctree::
:maxdepth: 2
stackube_scope_clarification

View File

@ -10,13 +10,13 @@ Not another “Kubernetes on OpenStack” project
Stackube is a standard upstream Kubernetes deployment with:
1. Mixed container runtime of Docker (Linux container) and HyperContainer (hypervisor-based container)
#. Mixed container runtime of Docker (Linux container) and HyperContainer (hypervisor-based container)
2. Keystone for tenant management
#. Keystone for tenant management
3. Neutron for container network
#. Neutron for container network
4. Cinder for persistent volume
#. Cinder for persistent volume
The main difference between Stackube with existing container service
project in OpenStack foundation (e.g. Magnum) is: **Stackube works
@ -24,51 +24,51 @@ alongside OpenStack, not on OpenStack**.
This means:
1. Only standalone vanilla OpenStack components are required
#. Only standalone vanilla OpenStack components are required
2. Traditional VMs are not required because HyperContainer will provide hypervisor level isolation for containerized workloads.
#. Traditional VMs are not required because HyperContainer will provide hypervisor level isolation for containerized workloads.
3. All the components mentioned above are managed by Kubernetes plugin API.
#. All the components mentioned above are managed by Kubernetes plugin API.
Whats inside Stackube repo?
============================
1. Keystone RBAC plugin
#. Keystone RBAC plugin
2. Neutron CNI plugin
#. Neutron CNI plugin
a. With a k8s Network object controller
* With a k8s Network object controller
3. Standard k8s upstream Cinder plugin with block device mode
#. Standard k8s upstream Cinder plugin with block device mode
4. Deployment scripts and guide
#. Deployment scripts and guide
5. Other documentations
#. Other documentations
Please note:
1. Plugins above will be deployed as system Pod and DaemonSet.
#. Plugins above will be deployed as system Pod and DaemonSet.
2. All other Kubernetes volumes are also supported in Stackube, while k8s Cinder plugin with block device mode can provide better performance in mixed runtime which will be preferred by default.
#. All other Kubernetes volumes are also supported in Stackube, while k8s Cinder plugin with block device mode can provide better performance in mixed runtime which will be preferred by default.
Whats the difference between other plugin projects?
====================================================
1. Kuryr
#. Kuryr
a. This is a Neutron network plugin for Docker network model, which is not directly supported in Kubernetes. Kuryr can provide CNI interface, but Stackube also requires tenant aware network management which is not included in Kuryr.
* This is a Neutron network plugin for Docker network model, which is not directly supported in Kubernetes. Kuryr can provide CNI interface, but Stackube also requires tenant aware network management which is not included in Kuryr.
2. Fuxi
#. Fuxi
a. This is a Cinder volume plugin for Docker volume model, which is not supported in latest CRI based Kubernetes (using k8s volume plugin for now, and soon CSI). Also, Stackube prefers a “block-device to Pod” mode in volume plugin when HyperContainer runtime is enabled, which is not supported in Fuxi.
* This is a Cinder volume plugin for Docker volume model, which is not supported in latest CRI based Kubernetes (using k8s volume plugin for now, and soon CSI). Also, Stackube prefers a “block-device to Pod” mode in volume plugin when HyperContainer runtime is enabled, which is not supported in Fuxi.
3. K8s-cloud-provider
#. K8s-cloud-provider
a. This is a “Kubernetes on OpenStack” integration which requires full functioning OpenStack deployment.
* This is a “Kubernetes on OpenStack” integration which requires full functioning OpenStack deployment.
4. Zun
#. Zun
a. This is a OpenStack API container service, while Stackube exposes well-known Kubernetes API and does not require full OpenStack deployment.
* This is a OpenStack API container service, while Stackube exposes well-known Kubernetes API and does not require full OpenStack deployment.
As summary, one distinguishable difference is that plugins in Stackube
are designed to enable hard multi-tenancy in Kubernetes as a whole
@ -92,23 +92,22 @@ A typical deployment workflow of Stackube
On control nodes:
1. Install standalone Keystone, Neutron, Cinder (ceph rbd)
#. Install standalone Keystone, Neutron, Cinder (ceph rbd)
a. This can be done by any existing tool like devstack, RDO etc
* This can be done by any existing tool like devstack, RDO etc
On other nodes:
1. Install Kubernetes
#. Install Kubernetes
a. Including container runtimes, CRI shims, CNI etc
b. This can be done by any existing tool like kubeadm etc
* Including container runtimes, CRI shims, CNI etc
* This can be done by any existing tool like kubeadm etc
Deploy Stackube:
1. *kubectl apply -f stackube.yaml*
#. *kubectl apply -f stackube.yaml*
a. This will deploy all Stackube plugins as Pods and DaemonSets to the cluster
* This will deploy all Stackube plugins as Pods and DaemonSets to the cluster
(You can also deploy all these components in a single node)

View File

@ -1,6 +0,0 @@
==============
Welcome to Stackube developer documentation!
==============
Stackube is a multi-tenant and secure Kubernetes deployment enabled by OpenStack
core components.

2
test-requirements.txt Normal file
View File

@ -0,0 +1,2 @@
sphinx!=1.6.1,>=1.5.1 # BSD
oslosphinx>=4.7.0 # Apache-2.0