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 .. _Gerrit: http://wiki.openstack.org/GerritWorkflow
This code a fork of `Jacobian's python-cloudservers`__ If you need API support 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. python-client is licensed under the Apache License like the rest of OpenStack.
__ http://github.com/jacobian/python-cloudservers __ http://github.com/jacobian/python-cloudservers

View File

@ -74,7 +74,10 @@ class Manager(object):
# NOTE(ja): keystone returns values as list as {'values': [ ... ]} # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list... # unlike other services which just return the list...
if type(data) is dict: if type(data) is dict:
data = data['values'] try:
data = data['values']
except KeyError:
pass
with self.uuid_cache(obj_class, mode="w"): with self.uuid_cache(obj_class, mode="w"):
return [obj_class(self, res, loaded=True) for res in data if res] 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 argparse
import glob
import httplib2 import httplib2
import imp
import os import os
import sys import sys
from novaclient import base
from novaclient import exceptions as exc from novaclient import exceptions as exc
from novaclient import utils from novaclient import utils
from novaclient.v1_0 import shell as shell_v1_0 from novaclient.v1_0 import shell as shell_v1_0
@ -96,7 +99,7 @@ class OpenStackComputeShell(object):
return parser return parser
def get_subcommand_parser(self, version): def get_subcommand_parser(self, version, extensions):
parser = self.get_base_parser() parser = self.get_base_parser()
self.subcommands = {} self.subcommands = {}
@ -115,8 +118,49 @@ class OpenStackComputeShell(object):
self._find_actions(subparsers, actions_module) self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self) self._find_actions(subparsers, self)
for _, _, ext_module in extensions:
self._find_actions(subparsers, ext_module)
self._add_bash_completion_subparser(subparsers)
return parser 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): def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')): for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hypen-separated instead of underscores. # I prefer to be hypen-separated instead of underscores.
@ -147,7 +191,9 @@ class OpenStackComputeShell(object):
(options, args) = parser.parse_known_args(argv) (options, args) = parser.parse_known_args(argv)
# build available subcommands based on version # 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 self.parser = subcommand_parser
# Parse args again and call whatever callback was selected # Parse args again and call whatever callback was selected
@ -161,6 +207,9 @@ class OpenStackComputeShell(object):
if args.func == self.do_help: if args.func == self.do_help:
self.do_help(args) self.do_help(args)
return 0 return 0
elif args.func == self.do_bash_completion:
self.do_bash_completion(args)
return 0
(user, apikey, password, projectid, url, region_name, (user, apikey, password, projectid, url, region_name,
endpoint_name, insecure) = (args.username, args.apikey, endpoint_name, insecure) = (args.username, args.apikey,
@ -199,7 +248,8 @@ class OpenStackComputeShell(object):
self.cs = self.get_api_class(options.version)(user, password, self.cs = self.get_api_class(options.version)(user, password,
projectid, url, insecure, projectid, url, insecure,
region_name=region_name, region_name=region_name,
endpoint_name=endpoint_name) endpoint_name=endpoint_name,
extensions=extensions)
try: try:
self.cs.authenticate() self.cs.authenticate()
@ -221,6 +271,22 @@ class OpenStackComputeShell(object):
except KeyError: except KeyError:
return shell_v1_0.CLIENT_CLASS 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='?', @utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>') help='Display help for <subcommand>')
def do_help(self, args): 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." % \ msg = "No %s with a name or ID of '%s' exists." % \
(manager.resource_class.__name__.lower(), name_or_id) (manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg) 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, def __init__(self, username, api_key, project_id, auth_url=None,
insecure=False, timeout=None, token=None, region_name=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 # FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument # know it's not being used as keyword argument
@ -40,6 +40,11 @@ class Client(object):
self.servers = servers.ServerManager(self) self.servers = servers.ServerManager(self)
self.zones = zones.ZoneManager(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' _auth_url = auth_url or 'https://auth.api.rackspacecloud.com/v1.0'
self.client = client.HTTPClient(username, self.client = client.HTTPClient(username,

View File

@ -33,7 +33,7 @@ class Client(object):
# FIXME(jesse): project_id isn't required to authenticate # FIXME(jesse): project_id isn't required to authenticate
def __init__(self, username, api_key, project_id, auth_url, def __init__(self, username, api_key, project_id, auth_url,
insecure=False, timeout=None, token=None, region_name=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 # FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument # know it's not being used as keyword argument
password = api_key password = api_key
@ -53,6 +53,11 @@ class Client(object):
self.security_group_rules = \ self.security_group_rules = \
security_group_rules.SecurityGroupRuleManager(self) 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, self.client = client.HTTPClient(username,
password, password,
project_id, project_id,

View File

@ -481,23 +481,11 @@ def do_list(cs, args):
id_col = 'ID' id_col = 'ID'
columns = [id_col, 'Name', 'Status', 'Networks'] 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, utils.print_list(cs.servers.list(search_opts=search_opts), columns,
formatters) 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', @utils.arg('--hard',
dest='reboot_type', dest='reboot_type',
action='store_const', 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, # 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 # perhaps the mistake was thinking that the deprecation applied to Python 2
# as well. # as well.
${wrapper} pep8 --repeat --show-pep8 --show-source \ pep8_opts="--ignore=E202,W602 --repeat"
--ignore=E202,W602 \ ${wrapper} pep8 ${pep8_opts} ${srcfiles}
${srcfiles}
} }
NOSETESTS="nosetests $noseopts $noseargs" 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 import httplib2
from novaclient import client as base_client from novaclient import client as base_client

View File

@ -4,15 +4,8 @@ _nova()
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" 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 opts="$(nova bash_completion)"
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"
UUID_CACHE=~/.novaclient_cached_*_uuids UUID_CACHE=~/.novaclient_cached_*_uuids
opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ') opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')