Adding extension framework.

Change-Id: If882f7a822ef6b1e58666b3af6f7166ab0a230fe
This commit is contained in:
Rick Harris 2011-12-15 19:39:33 +00:00
parent a905e5fe07
commit bb879dd10b
10 changed files with 117 additions and 32 deletions

View File

@ -25,7 +25,7 @@ pull requests.
.. _Gerrit: http://wiki.openstack.org/GerritWorkflow
This code a fork of `Jacobian's python-cloudservers`__ If you need API support
for the Rackspace API soley or the BSD license, you should use that repository.
for the Rackspace API solely or the BSD license, you should use that repository.
python-client is licensed under the Apache License like the rest of OpenStack.
__ http://github.com/jacobian/python-cloudservers

View File

@ -74,7 +74,10 @@ class Manager(object):
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
if type(data) is dict:
data = data['values']
try:
data = data['values']
except KeyError:
pass
with self.uuid_cache(obj_class, mode="w"):
return [obj_class(self, res, loaded=True) for res in data if res]

View File

@ -20,10 +20,13 @@ Command-line interface to the OpenStack Nova API.
"""
import argparse
import glob
import httplib2
import imp
import os
import sys
from novaclient import base
from novaclient import exceptions as exc
from novaclient import utils
from novaclient.v1_0 import shell as shell_v1_0
@ -96,7 +99,7 @@ class OpenStackComputeShell(object):
return parser
def get_subcommand_parser(self, version):
def get_subcommand_parser(self, version, extensions):
parser = self.get_base_parser()
self.subcommands = {}
@ -115,8 +118,49 @@ class OpenStackComputeShell(object):
self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self)
for _, _, ext_module in extensions:
self._find_actions(subparsers, ext_module)
self._add_bash_completion_subparser(subparsers)
return parser
def _discover_extensions(self, version):
module_path = os.path.dirname(os.path.abspath(__file__))
version_str = "v%s" % version.replace('.', '_')
ext_path = os.path.join(module_path, version_str, 'contrib')
ext_glob = os.path.join(ext_path, "*.py")
extensions = []
for ext_path in glob.iglob(ext_glob):
name = os.path.basename(ext_path)[:-3]
ext_module = imp.load_source(name, ext_path)
# Extract Manager class
ext_manager_class = None
for attr_value in ext_module.__dict__.values():
try:
if issubclass(attr_value, base.Manager):
ext_manager_class = attr_value
break
except TypeError:
continue # in case attr_value isn't a class...
if not ext_manager_class:
raise Exception("Could not find Manager class in extension.")
extensions.append((name, ext_manager_class, ext_module))
return extensions
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser('bash_completion',
add_help=False,
formatter_class=OpenStackHelpFormatter
)
self.subcommands['bash_completion'] = subparser
subparser.set_defaults(func=self.do_bash_completion)
def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hypen-separated instead of underscores.
@ -147,7 +191,9 @@ class OpenStackComputeShell(object):
(options, args) = parser.parse_known_args(argv)
# build available subcommands based on version
subcommand_parser = self.get_subcommand_parser(options.version)
extensions = self._discover_extensions(options.version)
subcommand_parser = self.get_subcommand_parser(
options.version, extensions)
self.parser = subcommand_parser
# Parse args again and call whatever callback was selected
@ -161,6 +207,9 @@ class OpenStackComputeShell(object):
if args.func == self.do_help:
self.do_help(args)
return 0
elif args.func == self.do_bash_completion:
self.do_bash_completion(args)
return 0
(user, apikey, password, projectid, url, region_name,
endpoint_name, insecure) = (args.username, args.apikey,
@ -199,7 +248,8 @@ class OpenStackComputeShell(object):
self.cs = self.get_api_class(options.version)(user, password,
projectid, url, insecure,
region_name=region_name,
endpoint_name=endpoint_name)
endpoint_name=endpoint_name,
extensions=extensions)
try:
self.cs.authenticate()
@ -221,6 +271,22 @@ class OpenStackComputeShell(object):
except KeyError:
return shell_v1_0.CLIENT_CLASS
def do_bash_completion(self, args):
"""
Prints all of the commands and options to stdout so that the
nova.bash_completion script doesn't have to hard code them.
"""
commands = set()
options = set()
for sc_str, sc in self.subcommands.items():
commands.add(sc_str)
for option in sc._optionals._option_string_actions.keys():
options.add(option)
commands.remove('bash-completion')
commands.remove('bash_completion')
print ' '.join(commands | options)
@utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>')
def do_help(self, args):

View File

@ -71,3 +71,15 @@ def find_resource(manager, name_or_id):
msg = "No %s with a name or ID of '%s' exists." % \
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg)
def _format_servers_list_networks(server):
output = []
for (network, addresses) in server.networks.items():
if len(addresses) == 0:
continue
addresses_csv = ', '.join(addresses)
group = "%s=%s" % (network, addresses_csv)
output.append(group)
return '; '.join(output)

View File

@ -27,7 +27,7 @@ class Client(object):
def __init__(self, username, api_key, project_id, auth_url=None,
insecure=False, timeout=None, token=None, region_name=None,
endpoint_name='publicURL'):
endpoint_name='publicURL', extensions=None):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
@ -40,6 +40,11 @@ class Client(object):
self.servers = servers.ServerManager(self)
self.zones = zones.ZoneManager(self)
# Add in any extensions...
if extensions:
for (ext_name, ext_manager_class, ext_module) in extensions:
setattr(self, ext_name, ext_manager_class(self))
_auth_url = auth_url or 'https://auth.api.rackspacecloud.com/v1.0'
self.client = client.HTTPClient(username,

View File

@ -33,7 +33,7 @@ class Client(object):
# FIXME(jesse): project_id isn't required to authenticate
def __init__(self, username, api_key, project_id, auth_url,
insecure=False, timeout=None, token=None, region_name=None,
endpoint_name='publicURL'):
endpoint_name='publicURL', extensions=None):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
password = api_key
@ -53,6 +53,11 @@ class Client(object):
self.security_group_rules = \
security_group_rules.SecurityGroupRuleManager(self)
# Add in any extensions...
if extensions:
for (ext_name, ext_manager_class, ext_module) in extensions:
setattr(self, ext_name, ext_manager_class(self))
self.client = client.HTTPClient(username,
password,
project_id,

View File

@ -481,23 +481,11 @@ def do_list(cs, args):
id_col = 'ID'
columns = [id_col, 'Name', 'Status', 'Networks']
formatters = {'Networks': _format_servers_list_networks}
formatters = {'Networks': utils._format_servers_list_networks}
utils.print_list(cs.servers.list(search_opts=search_opts), columns,
formatters)
def _format_servers_list_networks(server):
output = []
for (network, addresses) in server.networks.items():
if len(addresses) == 0:
continue
addresses_csv = ', '.join(addresses)
group = "%s=%s" % (network, addresses_csv)
output.append(group)
return '; '.join(output)
@utils.arg('--hard',
dest='reboot_type',
action='store_const',

View File

@ -92,9 +92,8 @@ function run_pep8 {
# other than what the PEP8 tool claims. It is deprecated in Python 3, so,
# perhaps the mistake was thinking that the deprecation applied to Python 2
# as well.
${wrapper} pep8 --repeat --show-pep8 --show-source \
--ignore=E202,W602 \
${srcfiles}
pep8_opts="--ignore=E202,W602 --repeat"
${wrapper} pep8 ${pep8_opts} ${srcfiles}
}
NOSETESTS="nosetests $noseopts $noseargs"

View File

@ -1,3 +1,17 @@
# Copyright 2011 OpenStack, LLC
#
# 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 httplib2
from novaclient import client as base_client

View File

@ -4,15 +4,8 @@ _nova()
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="add-fixed-ip backup backup-schedule backup-schedule-delete boot
boot-for-account delete delete diagnostics flavor-list image-create
image-delete image-list ip-share ip-unshare ipgroup-create
ipgroup-delete ipgroup-list ipgroup-show list migrate pause reboot
rebuild remove-fixed-ip rename rescue resize resize-confirm
resize-revert resume root-password show suspend unpause unrescue
zone zone-add zone-boot zone-delete zone-info zone-list help
--debug --endpoint_name --password --projectid --region_name --url
--username --version"
opts="$(nova bash_completion)"
UUID_CACHE=~/.novaclient_cached_*_uuids
opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')