Add docs generation (+ clean up)
Moved public access to exceptions from metalsmith itself to metalsmith.exceptions to avoid clutterning the former. Updated tox.ini to use upper-constraints. Change-Id: I136e036749171dc6d36d644e79c6fcfeef6242af
This commit is contained in:
parent
86ef953d49
commit
23fbeea112
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@ doc/source/api/
|
||||
# Packages/installer info
|
||||
*.egg
|
||||
*.egg-info
|
||||
.eggs/
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
|
@ -17,8 +17,8 @@ Installation
|
||||
|
||||
pip install --user metalsmith
|
||||
|
||||
Usage
|
||||
-----
|
||||
CLI Usage
|
||||
---------
|
||||
|
||||
Generic usage is as follows::
|
||||
|
||||
|
5
doc/requirements.txt
Normal file
5
doc/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
sphinxcontrib-apidoc>=0.2.0 # BSD
|
88
doc/source/conf.py
Normal file
88
doc/source/conf.py
Normal file
@ -0,0 +1,88 @@
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinxcontrib.apidoc',
|
||||
]
|
||||
|
||||
autoclass_content = 'both'
|
||||
apidoc_module_dir = '../../metalsmith'
|
||||
apidoc_output_dir = 'api'
|
||||
apidoc_excluded_paths = ['test']
|
||||
apidoc_separate_modules = True
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'metalsmith'
|
||||
copyright = u'2018, MetalSmith Developers '
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
from metalsmith import version as ms_version # noqa
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = ms_version.version_info.release_string()
|
||||
# The short X.Y version.
|
||||
version = ms_version.version_info.version_string()
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# 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_static_path = ['static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'MetalSmith Developers', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
# intersphinx_mapping = {'http://docs.python.org/': None}
|
23
doc/source/index.rst
Normal file
23
doc/source/index.rst
Normal file
@ -0,0 +1,23 @@
|
||||
.. include:: ../../README.rst
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
The main entry point to the API is :py:class:`metalsmith.Provisioner`.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
api/metalsmith
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
api/modules
|
||||
|
||||
Indexes
|
||||
-------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
@ -13,5 +13,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from ._exceptions import * # noqa
|
||||
from ._provisioner import Provisioner # noqa
|
||||
from metalsmith._provisioner import Provisioner
|
||||
|
||||
__all__ = ['Provisioner']
|
||||
|
@ -20,10 +20,10 @@ import sys
|
||||
|
||||
import six
|
||||
|
||||
from metalsmith import _exceptions
|
||||
from metalsmith import _os_api
|
||||
from metalsmith import _scheduler
|
||||
from metalsmith import _utils
|
||||
from metalsmith import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -33,7 +33,15 @@ _ATTACHED_PORTS = 'metalsmith_attached_ports'
|
||||
|
||||
|
||||
class Provisioner(object):
|
||||
"""API to deploy/undeploy nodes with OpenStack."""
|
||||
"""API to deploy/undeploy nodes with OpenStack.
|
||||
|
||||
:param session: `Session` object (from ``keystoneauth``) to use when
|
||||
making API requests. Mutually exclusive with **cloud_region**.
|
||||
:param cloud_region: cloud configuration object (from ``openstacksdk``)
|
||||
to use when making API requests. Mutually exclusive with **session**.
|
||||
:param dry_run: boolean value, set to ``True`` to prevent any API calls
|
||||
from being actually made.
|
||||
"""
|
||||
|
||||
def __init__(self, session=None, cloud_region=None, dry_run=False):
|
||||
self._api = _os_api.API(session=session, cloud_region=cloud_region)
|
||||
@ -42,16 +50,21 @@ class Provisioner(object):
|
||||
def reserve_node(self, resource_class, capabilities=None):
|
||||
"""Find and reserve a suitable node.
|
||||
|
||||
Example::
|
||||
|
||||
node = provisioner.reserve_node("compute",
|
||||
capabilities={"boot_mode": "uefi"})
|
||||
|
||||
:param resource_class: Requested resource class.
|
||||
:param capabilities: Requested capabilities as a dict.
|
||||
:return: reserved Node object
|
||||
:raises: ReservationFailed
|
||||
:return: reserved `Node` object.
|
||||
:raises: :py:class:`metalsmith.exceptions.ReservationFailed`
|
||||
"""
|
||||
capabilities = capabilities or {}
|
||||
|
||||
nodes = self._api.list_nodes(resource_class=resource_class)
|
||||
if not nodes:
|
||||
raise _exceptions.ResourceClassNotFound(resource_class,
|
||||
raise exceptions.ResourceClassNotFound(resource_class,
|
||||
capabilities)
|
||||
|
||||
# Make sure parallel executions don't try nodes in the same sequence
|
||||
@ -70,6 +83,14 @@ class Provisioner(object):
|
||||
ssh_keys=None, netboot=False, wait=None):
|
||||
"""Provision the node with the given image.
|
||||
|
||||
Example::
|
||||
|
||||
provisioner.provision_node("compute-1", "centos",
|
||||
nics=[{"network": "private"},
|
||||
{"network": "external"}],
|
||||
root_disk_size=50,
|
||||
wait=3600)
|
||||
|
||||
:param node: Node object, UUID or name.
|
||||
:param image_ref: Image name or UUID to provision.
|
||||
:param nics: List of virtual NICs to attach to physical ports.
|
||||
@ -83,7 +104,8 @@ class Provisioner(object):
|
||||
:param netboot: Whether to use networking boot for final instances.
|
||||
:param wait: How many seconds to wait for the deployment to finish,
|
||||
None to return immediately.
|
||||
:return: Reservation
|
||||
:return: provisioned `Node` object.
|
||||
:raises: :py:class:`metalsmith.exceptions.Error`
|
||||
"""
|
||||
created_ports = []
|
||||
attached_ports = []
|
||||
@ -96,7 +118,7 @@ class Provisioner(object):
|
||||
try:
|
||||
image = self._api.get_image_info(image_ref)
|
||||
except Exception as exc:
|
||||
raise _exceptions.InvalidImage(
|
||||
raise exceptions.InvalidImage(
|
||||
'Cannot find image %(image)s: %(error)s' %
|
||||
{'image': image_ref, 'error': exc})
|
||||
|
||||
@ -188,7 +210,7 @@ class Provisioner(object):
|
||||
try:
|
||||
network = self._api.get_network(nic_id)
|
||||
except Exception as exc:
|
||||
raise _exceptions.InvalidNIC(
|
||||
raise exceptions.InvalidNIC(
|
||||
'Cannot find network %(net)s: %(error)s' %
|
||||
{'net': nic_id, 'error': exc})
|
||||
else:
|
||||
@ -197,7 +219,7 @@ class Provisioner(object):
|
||||
try:
|
||||
port = self._api.get_port(nic_id)
|
||||
except Exception as exc:
|
||||
raise _exceptions.InvalidNIC(
|
||||
raise exceptions.InvalidNIC(
|
||||
'Cannot find port %(port)s: %(error)s' %
|
||||
{'port': nic_id, 'error': exc})
|
||||
else:
|
||||
@ -263,6 +285,7 @@ class Provisioner(object):
|
||||
:param node: node object, UUID or name.
|
||||
:param wait: How many seconds to wait for the process to finish,
|
||||
None to return immediately.
|
||||
:return: nothing.
|
||||
"""
|
||||
node = self._api.get_node(node)
|
||||
if self._dry_run:
|
||||
|
@ -19,8 +19,8 @@ import logging
|
||||
|
||||
import six
|
||||
|
||||
from metalsmith import _exceptions
|
||||
from metalsmith import _utils
|
||||
from metalsmith import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -159,7 +159,7 @@ class CapabilitiesFilter(Filter):
|
||||
message = ("No available nodes found with capabilities %(req)s, "
|
||||
"existing capabilities: %(exist)s" %
|
||||
{'req': requested, 'exist': existing or 'none'})
|
||||
raise _exceptions.CapabilitiesNotFound(message,
|
||||
raise exceptions.CapabilitiesNotFound(message,
|
||||
self._resource_class,
|
||||
self._capabilities)
|
||||
|
||||
@ -189,7 +189,7 @@ class ValidationFilter(Filter):
|
||||
def fail(self):
|
||||
errors = ", ".join(self._failed_validation)
|
||||
message = "All available nodes have failed validation: %s" % errors
|
||||
raise _exceptions.ValidationFailed(message,
|
||||
raise exceptions.ValidationFailed(message,
|
||||
self._resource_class,
|
||||
self._capabilities)
|
||||
|
||||
@ -221,5 +221,5 @@ class IronicReserver(Reserver):
|
||||
return result
|
||||
|
||||
def fail(self):
|
||||
raise _exceptions.AllNodesReserved(self._resource_class,
|
||||
raise exceptions.AllNodesReserved(self._resource_class,
|
||||
self._capabilities)
|
||||
|
@ -19,7 +19,7 @@ import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from metalsmith import _exceptions
|
||||
from metalsmith import exceptions
|
||||
|
||||
|
||||
def log_node(node):
|
||||
@ -74,11 +74,11 @@ def get_root_disk(root_disk_size, node):
|
||||
try:
|
||||
assert int(node.properties['local_gb']) > 0
|
||||
except KeyError:
|
||||
raise _exceptions.UnknownRootDiskSize(
|
||||
raise exceptions.UnknownRootDiskSize(
|
||||
'No local_gb for node %s and no root disk size requested' %
|
||||
log_node(node))
|
||||
except (TypeError, ValueError, AssertionError):
|
||||
raise _exceptions.UnknownRootDiskSize(
|
||||
raise exceptions.UnknownRootDiskSize(
|
||||
'The local_gb for node %(node)s is invalid: '
|
||||
'expected positive integer, got %(value)s' %
|
||||
{'node': log_node(node),
|
||||
|
@ -16,9 +16,9 @@
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from metalsmith import _exceptions
|
||||
from metalsmith import _os_api
|
||||
from metalsmith import _provisioner
|
||||
from metalsmith import exceptions
|
||||
|
||||
|
||||
class Base(testtools.TestCase):
|
||||
@ -44,7 +44,7 @@ class TestReserveNode(Base):
|
||||
def test_no_nodes(self):
|
||||
self.api.list_nodes.return_value = []
|
||||
|
||||
self.assertRaises(_exceptions.ResourceClassNotFound,
|
||||
self.assertRaises(exceptions.ResourceClassNotFound,
|
||||
self.pr.reserve_node, 'control')
|
||||
self.assertFalse(self.api.reserve_node.called)
|
||||
|
||||
@ -315,7 +315,7 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_missing_image(self):
|
||||
self.api.get_image_info.side_effect = RuntimeError('Not found')
|
||||
self.assertRaisesRegex(_exceptions.InvalidImage, 'Not found',
|
||||
self.assertRaisesRegex(exceptions.InvalidImage, 'Not found',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
self.api.update_node.assert_called_once_with(self.node, CLEAN_UP)
|
||||
@ -324,7 +324,7 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_invalid_network(self):
|
||||
self.api.get_network.side_effect = RuntimeError('Not found')
|
||||
self.assertRaisesRegex(_exceptions.InvalidNIC, 'Not found',
|
||||
self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
self.api.update_node.assert_called_once_with(self.node, CLEAN_UP)
|
||||
@ -334,7 +334,7 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_invalid_port(self):
|
||||
self.api.get_port.side_effect = RuntimeError('Not found')
|
||||
self.assertRaisesRegex(_exceptions.InvalidNIC, 'Not found',
|
||||
self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'port': 'port1'}])
|
||||
self.api.update_node.assert_called_once_with(self.node, CLEAN_UP)
|
||||
@ -344,7 +344,7 @@ class TestProvisionNode(Base):
|
||||
|
||||
def test_no_local_gb(self):
|
||||
self.node.properties = {}
|
||||
self.assertRaises(_exceptions.UnknownRootDiskSize,
|
||||
self.assertRaises(exceptions.UnknownRootDiskSize,
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
self.assertFalse(self.api.create_port.called)
|
||||
@ -354,7 +354,7 @@ class TestProvisionNode(Base):
|
||||
def test_invalid_local_gb(self):
|
||||
for value in (None, 'meow', -42, []):
|
||||
self.node.properties = {'local_gb': value}
|
||||
self.assertRaises(_exceptions.UnknownRootDiskSize,
|
||||
self.assertRaises(exceptions.UnknownRootDiskSize,
|
||||
self.pr.provision_node,
|
||||
self.node, 'image', [{'network': 'network'}])
|
||||
self.assertFalse(self.api.create_port.called)
|
||||
|
@ -16,8 +16,8 @@
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from metalsmith import _exceptions
|
||||
from metalsmith import _scheduler
|
||||
from metalsmith import exceptions
|
||||
|
||||
|
||||
class TestScheduleNode(testtools.TestCase):
|
||||
@ -114,7 +114,7 @@ class TestCapabilitiesFilter(testtools.TestCase):
|
||||
|
||||
def test_fail_no_capabilities(self):
|
||||
fltr = _scheduler.CapabilitiesFilter('rsc', {'profile': 'compute'})
|
||||
self.assertRaisesRegex(_exceptions.CapabilitiesNotFound,
|
||||
self.assertRaisesRegex(exceptions.CapabilitiesNotFound,
|
||||
'No available nodes found with capabilities '
|
||||
'profile=compute, existing capabilities: none',
|
||||
fltr.fail)
|
||||
@ -146,7 +146,7 @@ class TestCapabilitiesFilter(testtools.TestCase):
|
||||
properties={'capabilities': 'profile:control'},
|
||||
spec=['properties', 'name', 'uuid'])
|
||||
self.assertFalse(fltr(node))
|
||||
self.assertRaisesRegex(_exceptions.CapabilitiesNotFound,
|
||||
self.assertRaisesRegex(exceptions.CapabilitiesNotFound,
|
||||
'No available nodes found with capabilities '
|
||||
'profile=compute, existing capabilities: '
|
||||
r'profile=control \(1 node\(s\)\)',
|
||||
@ -158,7 +158,7 @@ class TestCapabilitiesFilter(testtools.TestCase):
|
||||
node = mock.Mock(properties={'capabilities': cap},
|
||||
spec=['properties', 'name', 'uuid'])
|
||||
self.assertFalse(fltr(node))
|
||||
self.assertRaisesRegex(_exceptions.CapabilitiesNotFound,
|
||||
self.assertRaisesRegex(exceptions.CapabilitiesNotFound,
|
||||
'No available nodes found with capabilities '
|
||||
'profile=compute, existing capabilities: none',
|
||||
fltr.fail)
|
||||
@ -181,7 +181,7 @@ class TestValidationFilter(testtools.TestCase):
|
||||
self.api.validate_node.side_effect = RuntimeError('boom')
|
||||
self.assertFalse(self.fltr(node))
|
||||
|
||||
self.assertRaisesRegex(_exceptions.ValidationFailed,
|
||||
self.assertRaisesRegex(exceptions.ValidationFailed,
|
||||
'All available nodes have failed validation: '
|
||||
'Node .* failed validation: boom',
|
||||
self.fltr.fail)
|
||||
@ -198,7 +198,7 @@ class TestIronicReserver(testtools.TestCase):
|
||||
self.reserver = _scheduler.IronicReserver(self.api, 'rsc', {})
|
||||
|
||||
def test_fail(self, mock_validation):
|
||||
self.assertRaisesRegex(_exceptions.AllNodesReserved,
|
||||
self.assertRaisesRegex(exceptions.AllNodesReserved,
|
||||
'All the candidate nodes are already reserved',
|
||||
self.reserver.fail)
|
||||
|
||||
|
19
metalsmith/version.py
Normal file
19
metalsmith/version.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pbr.version
|
||||
|
||||
version_info = pbr.version.VersionInfo('metalsmith')
|
||||
"""Package version reported by pbr."""
|
17
tox.ini
17
tox.ini
@ -4,6 +4,8 @@ envlist = py3,py27,pep8
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
coverage run --branch --include "metalsmith*" -m unittest discover metalsmith.test
|
||||
@ -14,12 +16,25 @@ passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY \
|
||||
OS_USER_DOMAIN_NAME OS_PROJECT_DOMAIN_NAME
|
||||
|
||||
[testenv:venv]
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-r{toxinidir}/doc/requirements.txt
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
commands =
|
||||
flake8 metalsmith
|
||||
doc8 README.rst
|
||||
doc8 README.rst doc/source
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/doc/requirements.txt
|
||||
commands =
|
||||
sphinx-build -a -E -W -b html doc/source doc/build/html
|
||||
|
||||
[flake8]
|
||||
max-complexity=15
|
||||
|
Loading…
Reference in New Issue
Block a user