
This is a large and invasive change to the underlying guts. Most casual use should not notice a difference, but advanced users, especially those using the Profile or Authenticator interfaces or making use of pluggable providers will be broken. The overall intent is to align directly on top of the mechanisms that came from os-client-config for config and to use keystoneauth1's Adapter interface to make use of the canonical implementations of such things as service and version discovery. The end goal is that openstacksdk provides the REST interaction layer for python-openstackclient, shade, Ansible and nodepool. Replace profile with openstack.config os-client-config is used by shade and python-openstackclient to read and process configuration. openstacksdk also can use the os-client-config interface, but translates it internally into the Profile object. As os-client-config has been injested into openstack.config, remove Profile and just use the config classes. Make proxy subclass of adapter This gives every service a generic passthrough for REST calls, which means we can map unknown service-type values to a generic proxy. Strip endpoint_filter We're passing Adapters around, not sessions. Doing so means that self.service and endpoint_filter have become unnecessary. Rename _Request.uri to _Request.url This is a stepping-stone to replacing _Request with requests.Request and using requests.Session.prepare_request inside of _prepare_request. Rename service proxy instances to match their official service-type. Aliases are kept for the old versions, but make the canonical versions match the official name. Rename bare_metal to baremetal Rename cluster to clustering Rename block_store to block_storage Rename telemetry to meter Create generic proxies for all services in STA Every service listed in service types authority is an OpenStack service. Even if we don't know about it in SDK, we should at the very least have a low-level Adapter for it so that people can use REST calls while waiting on the SDK to add higher-level constructs. The pypy jobs are happily green. Run them as voting rather than non-voting. Add syntatic sugar alias for making connections Typing: import openstack.connection conn = openstack.connection.Connection(cloud='example') is annoying. This allows: import openstack conn = openstack.connect(cloud='example') Use task_manager and Adapter from shade As a stepping-stone towards shade and sdk codepaths being rationalized, we need to get SDK using the Adapter from shade that submits requests into the TaskManager. For normal operation this is a passthrough/no-op sort of thing, but it's essential for high-volume consumers such as nodepool. This exposes a bunch of places in tests where we're mocking a bit too deeply. We should go back through and fix all of those via requests_mock, but that's WAY too much for today. This was a 'for later' task, but it turns out that the move to Adapter was causing exceptions to be thrown that were not the exceptions that were intended to be caught in the SDK layer, which was causing functional tests of things like GET operations to fail. So it became a today task. Change-Id: I7b46e263a76d84573bdfbbece57b1048764ed939
142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
import importlib
|
|
import itertools
|
|
import os
|
|
|
|
from bs4 import BeautifulSoup
|
|
from sphinx import errors
|
|
|
|
# NOTE: We do this because I can't find any way to pass "-v"
|
|
# into sphinx-build through pbr...
|
|
DEBUG = True if os.getenv("ENFORCER_DEBUG") else False
|
|
|
|
WRITTEN_METHODS = set()
|
|
|
|
# NOTE: This is temporary! These methods currently exist on the base
|
|
# Proxy class as public methods, but they're deprecated in favor of
|
|
# subclasses actually exposing them if necessary. However, as they're
|
|
# public and purposely undocumented, they cause spurious warnings.
|
|
# Ignore these methods until they're actually removed from the API,
|
|
# and then we can take this special case out.
|
|
IGNORED_METHODS = ("wait_for_delete", "wait_for_status")
|
|
|
|
class EnforcementError(errors.SphinxError):
|
|
"""A mismatch between what exists and what's documented"""
|
|
category = "Enforcer"
|
|
|
|
|
|
def get_proxy_methods():
|
|
"""Return a set of public names on all proxies"""
|
|
names = ["openstack.baremetal.v1._proxy",
|
|
"openstack.clustering.v1._proxy",
|
|
"openstack.block_storage.v2._proxy",
|
|
"openstack.compute.v2._proxy",
|
|
"openstack.database.v1._proxy",
|
|
"openstack.identity.v2._proxy",
|
|
"openstack.identity.v3._proxy",
|
|
"openstack.image.v1._proxy",
|
|
"openstack.image.v2._proxy",
|
|
"openstack.key_manager.v1._proxy",
|
|
"openstack.load_balancer.v2._proxy",
|
|
"openstack.message.v1._proxy",
|
|
"openstack.message.v2._proxy",
|
|
"openstack.metric.v1._proxy",
|
|
"openstack.network.v2._proxy",
|
|
"openstack.object_store.v1._proxy",
|
|
"openstack.orchestration.v1._proxy",
|
|
"openstack.meter.v2._proxy",
|
|
"openstack.meter.alarm.v2._proxy",
|
|
"openstack.workflow.v2._proxy"]
|
|
|
|
modules = (importlib.import_module(name) for name in names)
|
|
|
|
methods = set()
|
|
for module in modules:
|
|
# We're not going to use the Proxy for anything other than a `dir`
|
|
# so just pass a dummy value so we can create the instance.
|
|
instance = module.Proxy("")
|
|
# We only document public names
|
|
names = [name for name in dir(instance) if not name.startswith("_")]
|
|
|
|
# Remove the wait_for_* names temporarily.
|
|
for name in IGNORED_METHODS:
|
|
names.remove(name)
|
|
|
|
good_names = [module.__name__ + ".Proxy." + name for name in names]
|
|
methods.update(good_names)
|
|
|
|
return methods
|
|
|
|
|
|
def page_context(app, pagename, templatename, context, doctree):
|
|
"""Handle html-page-context-event
|
|
|
|
This event is emitted once the builder has the contents to create
|
|
an HTML page, but before the template is rendered. This is the point
|
|
where we'll know what documentation is going to be written, so
|
|
gather all of the method names that are about to be included
|
|
so we can check which ones were or were not processed earlier
|
|
by autodoc.
|
|
"""
|
|
if "users/proxies" in pagename:
|
|
soup = BeautifulSoup(context["body"], "html.parser")
|
|
dts = soup.find_all("dt")
|
|
ids = [dt.get("id") for dt in dts]
|
|
|
|
written = 0
|
|
for id in ids:
|
|
if id is not None and "_proxy.Proxy" in id:
|
|
WRITTEN_METHODS.add(id)
|
|
written += 1
|
|
|
|
if DEBUG:
|
|
app.info("ENFORCER: Wrote %d proxy methods for %s" % (
|
|
written, pagename))
|
|
|
|
|
|
def build_finished(app, exception):
|
|
"""Handle build-finished event
|
|
|
|
This event is emitted once the builder has written all of the output.
|
|
At this point we just compare what we know was written to what we know
|
|
exists within the modules and share the results.
|
|
|
|
When enforcer_warnings_as_errors=True in conf.py, this method
|
|
will raise EnforcementError on any failures in order to signal failure.
|
|
"""
|
|
all_methods = get_proxy_methods()
|
|
|
|
app.info("ENFORCER: %d proxy methods exist" % len(all_methods))
|
|
app.info("ENFORCER: %d proxy methods written" % len(WRITTEN_METHODS))
|
|
missing = all_methods - WRITTEN_METHODS
|
|
|
|
def is_ignored(name):
|
|
for ignored_name in IGNORED_METHODS:
|
|
if ignored_name in name:
|
|
return True
|
|
return False
|
|
|
|
# TEMPORARY: Ignore the wait_for names when determining what is missing.
|
|
app.info("ENFORCER: Ignoring wait_for_* names...")
|
|
missing = set(itertools.ifilterfalse(is_ignored, missing))
|
|
|
|
missing_count = len(missing)
|
|
app.info("ENFORCER: Found %d missing proxy methods "
|
|
"in the output" % missing_count)
|
|
|
|
# TODO(shade) Remove the if DEBUG once the build-openstack-sphinx-docs
|
|
# has been updated to use sphinx-build.
|
|
if DEBUG:
|
|
for name in sorted(missing):
|
|
app.info("ENFORCER: %s was not included in the output" % name)
|
|
|
|
if app.config.enforcer_warnings_as_errors and missing_count > 0:
|
|
raise EnforcementError(
|
|
"There are %d undocumented proxy methods" % missing_count)
|
|
|
|
|
|
def setup(app):
|
|
app.add_config_value("enforcer_warnings_as_errors", False, "env")
|
|
|
|
app.connect("html-page-context", page_context)
|
|
app.connect("build-finished", build_finished)
|