Add an API example base and functional test base
Add examples/common.py, which is a basic common setup that mimics OSC's configuration options and logging without the rest of the CLI. Also add the functional test tooling for examples to prevent bit rot. Co-Authored-By: Dean Troyer <dtroyer@gmail.com> Change-Id: Ie92b675eafd93482ddc9a8ce0b0588e23ed50c35
This commit is contained in:
parent
c55fdb6f6d
commit
126b2c5436
264
examples/common.py
Executable file
264
examples/common.py
Executable file
@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python
|
||||
# common.py - Common bits for API examples
|
||||
|
||||
# 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.
|
||||
|
||||
"""
|
||||
API Examples
|
||||
|
||||
This is a collection of common functions used by the example scripts.
|
||||
It may also be run directly as a script to do basic testing of itself.
|
||||
|
||||
common.object_parser() provides the common set of command-line arguments
|
||||
used in the library CLIs for setting up authentication. This should make
|
||||
playing with the example scripts against a running OpenStack simpler.
|
||||
|
||||
common.configure_logging() provides the same basic logging control as
|
||||
the OSC shell.
|
||||
|
||||
common.make_session() does the minimal loading of a Keystone authentication
|
||||
plugin and creates a Keystone client Session.
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from keystoneclient import session as ksc_session
|
||||
|
||||
from openstackclient.api import auth
|
||||
|
||||
|
||||
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
|
||||
DEFAULT_VERBOSE_LEVEL = 1
|
||||
USER_AGENT = 'osc-examples'
|
||||
|
||||
PARSER_DESCRIPTION = 'A demonstration framework'
|
||||
|
||||
DEFAULT_IDENTITY_API_VERSION = '2.0'
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# --debug sets this True
|
||||
dump_stack_trace = False
|
||||
|
||||
|
||||
# Generally useful stuff often found in a utils module
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
"""Search for the first defined of possibly many env vars
|
||||
|
||||
Returns the first environment variable defined in vars, or
|
||||
returns the default defined in kwargs.
|
||||
|
||||
"""
|
||||
for v in vars:
|
||||
value = os.environ.get(v, None)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
# Common Example functions
|
||||
|
||||
def base_parser(parser):
|
||||
"""Set up some of the common CLI options
|
||||
|
||||
These are the basic options that match the library CLIs so
|
||||
command-line/environment setups for those also work with these
|
||||
demonstration programs.
|
||||
|
||||
"""
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument(
|
||||
'--os-url',
|
||||
metavar='<url>',
|
||||
default=env('OS_URL'),
|
||||
help='Defaults to env[OS_URL]',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--os-region-name',
|
||||
metavar='<auth-region-name>',
|
||||
default=env('OS_REGION_NAME'),
|
||||
help='Authentication region name (Env: OS_REGION_NAME)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--os-cacert',
|
||||
metavar='<ca-bundle-file>',
|
||||
default=env('OS_CACERT'),
|
||||
help='CA certificate bundle file (Env: OS_CACERT)',
|
||||
)
|
||||
verify_group = parser.add_mutually_exclusive_group()
|
||||
verify_group.add_argument(
|
||||
'--verify',
|
||||
action='store_true',
|
||||
help='Verify server certificate (default)',
|
||||
)
|
||||
verify_group.add_argument(
|
||||
'--insecure',
|
||||
action='store_true',
|
||||
help='Disable server certificate verification',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--timing',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Print API call timing info",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='count',
|
||||
dest='verbose_level',
|
||||
default=1,
|
||||
help='Increase verbosity of output. Can be repeated.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--debug',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='show tracebacks on errors',
|
||||
)
|
||||
parser.add_argument(
|
||||
'rest',
|
||||
nargs='*',
|
||||
help='the rest of the args',
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def configure_logging(opts):
|
||||
"""Typical app logging setup
|
||||
|
||||
Based on OSC/cliff
|
||||
|
||||
"""
|
||||
|
||||
global dump_stack_trace
|
||||
|
||||
root_logger = logging.getLogger('')
|
||||
|
||||
# Requests logs some stuff at INFO that we don't want
|
||||
# unless we have DEBUG
|
||||
requests_log = logging.getLogger("requests")
|
||||
requests_log.setLevel(logging.ERROR)
|
||||
|
||||
# Other modules we don't want DEBUG output for so
|
||||
# don't reset them below
|
||||
iso8601_log = logging.getLogger("iso8601")
|
||||
iso8601_log.setLevel(logging.ERROR)
|
||||
|
||||
# Always send higher-level messages to the console via stderr
|
||||
console = logging.StreamHandler(sys.stderr)
|
||||
formatter = logging.Formatter(CONSOLE_MESSAGE_FORMAT)
|
||||
console.setFormatter(formatter)
|
||||
root_logger.addHandler(console)
|
||||
|
||||
# Set logging to the requested level
|
||||
dump_stack_trace = False
|
||||
if opts.verbose_level == 0:
|
||||
# --quiet
|
||||
root_logger.setLevel(logging.ERROR)
|
||||
elif opts.verbose_level == 1:
|
||||
# This is the default case, no --debug, --verbose or --quiet
|
||||
root_logger.setLevel(logging.WARNING)
|
||||
elif opts.verbose_level == 2:
|
||||
# One --verbose
|
||||
root_logger.setLevel(logging.INFO)
|
||||
elif opts.verbose_level >= 3:
|
||||
# Two or more --verbose
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
requests_log.setLevel(logging.DEBUG)
|
||||
|
||||
if opts.debug:
|
||||
# --debug forces traceback
|
||||
dump_stack_trace = True
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
requests_log.setLevel(logging.DEBUG)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def make_session(opts, **kwargs):
|
||||
"""Create our base session using simple auth from ksc plugins
|
||||
|
||||
The arguments required in opts varies depending on the auth plugin
|
||||
that is used. This example assumes Identity v2 will be used
|
||||
and selects token auth if both os_url and os_token have been
|
||||
provided, otherwise it uses password.
|
||||
|
||||
:param Namespace opts:
|
||||
A parser options Namespace containing the authentication
|
||||
options to be used
|
||||
:param dict kwargs:
|
||||
Additional options passed directly to Session constructor
|
||||
"""
|
||||
|
||||
# If no auth type is named by the user, select one based on
|
||||
# the supplied options
|
||||
auth_plugin_name = auth.select_auth_plugin(opts)
|
||||
|
||||
(auth_plugin, auth_params) = auth.build_auth_params(
|
||||
auth_plugin_name,
|
||||
opts,
|
||||
)
|
||||
auth_p = auth_plugin.load_from_options(**auth_params)
|
||||
|
||||
session = ksc_session.Session(
|
||||
auth=auth_p,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return session
|
||||
|
||||
|
||||
# Top-level functions
|
||||
|
||||
def run(opts):
|
||||
"""Default run command"""
|
||||
|
||||
# Do some basic testing here
|
||||
sys.stdout.write("Default run command\n")
|
||||
sys.stdout.write("Verbose level: %s\n" % opts.verbose_level)
|
||||
sys.stdout.write("Debug: %s\n" % opts.debug)
|
||||
sys.stdout.write("dump_stack_trace: %s\n" % dump_stack_trace)
|
||||
|
||||
|
||||
def setup():
|
||||
"""Parse command line and configure logging"""
|
||||
opts = base_parser(
|
||||
auth.build_auth_plugins_option_parser(
|
||||
argparse.ArgumentParser(description='Object API Example')
|
||||
)
|
||||
).parse_args()
|
||||
configure_logging(opts)
|
||||
return opts
|
||||
|
||||
|
||||
def main(opts, run):
|
||||
try:
|
||||
return run(opts)
|
||||
except Exception as e:
|
||||
if dump_stack_trace:
|
||||
_logger.error(traceback.format_exc(e))
|
||||
else:
|
||||
_logger.error('Exception raised: ' + str(e))
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
opts = setup()
|
||||
sys.exit(main(opts, run))
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
@ -19,6 +20,11 @@ import six
|
||||
|
||||
from functional.common import exceptions
|
||||
|
||||
COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..'))
|
||||
ROOT_DIR = os.path.normpath(os.path.join(FUNCTIONAL_DIR, '..'))
|
||||
EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples')
|
||||
|
||||
|
||||
def execute(cmd, action, flags='', params='', fail_ok=False,
|
||||
merge_stderr=False):
|
||||
|
22
functional/tests/test_examples.py
Normal file
22
functional/tests/test_examples.py
Normal file
@ -0,0 +1,22 @@
|
||||
# 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.
|
||||
|
||||
from functional.common import test
|
||||
|
||||
|
||||
class ExampleTests(test.TestCase):
|
||||
"""Functional tests for running examples."""
|
||||
|
||||
def test_common(self):
|
||||
# NOTE(stevemar): If an examples has a non-zero return
|
||||
# code, then execute will raise an error by default.
|
||||
test.execute('python', test.EXAMPLE_DIR + '/common.py --debug')
|
Loading…
Reference in New Issue
Block a user