Added windc API client, sync repo with dev box.

This commit is contained in:
Timur Nurlygayanov 2013-02-18 00:32:23 -08:00
parent 1571d0775a
commit 6c51f7d983
13 changed files with 1207 additions and 2 deletions

View File

@ -6,7 +6,9 @@
This file is described how to install new tab on horizon dashboard.
We should do the following:
1. Copy directory 'windc' to directory '/opt/stack/horizon/openstack_dashboard/dashboards/project'
2. Edit file '/opt/stack/horizon/openstack_dashboard/dashboards/project/dashboard.py'
2. Copy api/windc.py to directory '/opt/stack/horizon/openstack_dashboard/api'
3. Copy directory 'windcclient' to directory '/opt/stack/horizon/'
4. Edit file '/opt/stack/horizon/openstack_dashboard/dashboards/project/dashboard.py'
Add line with windc project:
...
@ -24,6 +26,6 @@ class BasePanels(horizon.PanelGroup):
...
3. Run the test Django server:
5. Run the test Django server:
cd /opt/stack/horizon
python manage.py runserver 67.207.197.36:8080

64
dashboard/api/windc.py Normal file
View File

@ -0,0 +1,64 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# 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 logging
import urlparse
from django.utils.decorators import available_attrs
from windcclient.v1 import client as windc_client
#from horizon.api import base
__all__ = ('datacenter_get','datacenter_list',
'datacenter_create','datacenter_delete')
LOG = logging.getLogger(__name__)
def windcclient(request):
o = urlparse.urlparse("http://127.0.0.1:8082")
url = "http://127.0.0.1:8082/foo"
LOG.debug('windcclient connection created using token "%s" and url "%s"'
% (request.user.token, url))
return windc_client.Client(endpoint=url, token=None)
def datacenter_create(request, parameters):
name = parameters.get('name')
_type = parameters.get('type')
version = parameters.get('version')
ip = parameters.get('ip')
port = parameters.get('port')
user = parameters.get('user')
password = parameters.get('password')
return windcclient(request).datacenters.create(name, _type,
version, ip,
port, user, password)
def datacenter_delete(request, datacenter):
return windcclient(request).datacenters.delete(datacenter)
def datacenter_get(request, lb_id):
return windcclient(request).datacenters.get(lb_id)
def datacenter_list(request):
return windcclient(request).datacenters.list()

View File

View File

View File

@ -0,0 +1,137 @@
# Copyright 2012 OpenStack LLC.
# 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.
"""
Base utilities to build API operation managers and objects on top of.
"""
def getid(obj):
"""
Abstracts the common pattern of allowing both an object or an object's ID
(UUID) as a parameter when dealing with relationships.
"""
try:
return obj.id
except AttributeError:
return obj
class Manager(object):
"""
Managers interact with a particular type of API and provide CRUD
operations for them.
"""
resource_class = None
def __init__(self, api):
self.api = api
def _list(self, url, response_key, obj_class=None, body=None):
resp, body = self.api.client.json_request('GET', url, body=body)
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
return [obj_class(self, res, loaded=True) for res in data if res]
def _delete(self, url):
self.api.client.raw_request('DELETE', url)
def _update(self, url, body, response_key=None):
resp, body = self.api.client.json_request('PUT', url, body=body)
# PUT requests may not return a body
if body:
return self.resource_class(self, body[response_key])
def _create(self, url, body, response_key, return_raw=False):
resp, body = self.api.client.json_request('POST', url, body=body)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
def _get(self, url, response_key, return_raw=False):
resp, body = self.api.client.json_request('GET', url)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
class Resource(object):
"""
A resource represents a particular instance of an object (tenant, user,
etc). This is pretty much just a bag for attributes.
:param manager: Manager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
"""
def __init__(self, manager, info, loaded=False):
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
def _add_details(self, info):
for (k, v) in info.iteritems():
setattr(self, k, v)
def __getattr__(self, k):
if k not in self.__dict__:
#NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
return self.__dict__[k]
def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
def get_info(self):
if not self.is_loaded():
self.get()
if self._info:
return self._info.copy()
return {}
def get(self):
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._info = new._info
self._add_details(new._info)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
if hasattr(self, 'id') and hasattr(other, 'id'):
return self.id == other.id
return self._info == other._info
def is_loaded(self):
return self._loaded
def set_loaded(self, val):
self._loaded = val

View File

@ -0,0 +1,148 @@
# Copyright 2012 OpenStack LLC.
# 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
import httplib2
import copy
import logging
import json
from . import exceptions
from . import utils
from .service_catalog import ServiceCatalog
logger = logging.getLogger(__name__)
class HTTPClient(httplib2.Http):
USER_AGENT = 'python-balancerclient'
def __init__(self, endpoint=None, token=None, username=None,
password=None, tenant_name=None, tenant_id=None,
region_name=None, auth_url=None, auth_tenant_id=None,
timeout=600, insecure=False):
super(HTTPClient, self).__init__(timeout=timeout)
self.endpoint = endpoint
self.auth_token = token
self.auth_url = auth_url
self.auth_tenant_id = auth_tenant_id
self.username = username
self.password = password
self.tenant_name = tenant_name
self.tenant_id = tenant_id
self.region_name = region_name
self.force_exception_to_status_code = True
self.disable_ssl_certificate_validation = insecure
if self.endpoint is None:
self.authenticate()
def _http_request(self, url, method, **kwargs):
""" Send an http request with the specified characteristics.
"""
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', self.USER_AGENT)
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
resp, body = super(HTTPClient, self).request(url, method, **kwargs)
if logger.isEnabledFor(logging.DEBUG):
utils.http_log(logger, (url, method,), kwargs, resp, body)
if resp.status in (301, 302, 305):
return self._http_request(resp['location'], method, **kwargs)
return resp, body
def _json_request(self, method, url, **kwargs):
""" Wrapper around _http_request to handle setting headers,
JSON enconding/decoding and error handling.
"""
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
if 'body' in kwargs and kwargs['body'] is not None:
kwargs['body'] = json.dumps(kwargs['body'])
resp, body = self._http_request(url, method, **kwargs)
if body:
try:
body = json.loads(body)
except ValueError:
logger.debug("Could not decode JSON from body: %s" % body)
else:
logger.debug("No body was returned.")
body = None
if 400 <= resp.status < 600:
raise exceptions.from_response(resp, body)
return resp, body
def raw_request(self, method, url, **kwargs):
url = self.endpoint + url
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
resp, body = self._http_request(url, method, **kwargs)
if 400 <= resp.status < 600:
raise exceptions.from_response(resp, body)
return resp, body
def json_request(self, method, url, **kwargs):
url = self.endpoint + url
resp, body = self._json_request(method, url, **kwargs)
return resp, body
def authenticate(self):
token_url = self.auth_url + "/tokens"
body = {'auth': {'passwordCredentials': {'username': self.username,
'password': self.password}}}
if self.tenant_id:
body['auth']['tenantId'] = self.tenant_id
elif self.tenant_name:
body['auth']['tenantName'] = self.tenant_name
tmp_follow_all_redirects = self.follow_all_redirects
self.follow_all_redirects = True
try:
resp, body = self._json_request('POST', token_url, body=body)
finally:
self.follow_all_redirects = tmp_follow_all_redirects
try:
self.service_catalog = ServiceCatalog(body['access'])
token = self.service_catalog.get_token()
self.auth_token = token['id']
self.auth_tenant_id = token['tenant_id']
except KeyError:
logger.exception("Parse service catalog failed.")
raise exceptions.AuthorizationFailure()
self.endpoint = self.service_catalog.url_for(attr='region',
filter_value=self.region_name)

View File

@ -0,0 +1,140 @@
# Copyright 2010 Jacob Kaplan-Moss
"""
Exception definitions.
"""
class UnsupportedVersion(Exception):
"""Indicates that the user is trying to use an unsupported
version of the API"""
pass
class CommandError(Exception):
pass
class AuthorizationFailure(Exception):
pass
class NoUniqueMatch(Exception):
pass
class NoTokenLookupException(Exception):
"""This form of authentication does not support looking up
endpoints from an existing token."""
pass
class EndpointNotFound(Exception):
"""Could not find Service or Region in Service Catalog."""
pass
class AmbiguousEndpoints(Exception):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
self.endpoints = endpoints
def __str__(self):
return "AmbiguousEndpoints: %s" % repr(self.endpoints)
class ClientException(Exception):
"""
The base exception class for all exceptions this library raises.
"""
def __init__(self, code, message=None, details=None):
self.code = code
self.message = message or self.__class__.message
self.details = details
def __str__(self):
return "%s (HTTP %s)" % (self.message, self.code)
class BadRequest(ClientException):
"""
HTTP 400 - Bad request: you sent some malformed data.
"""
http_status = 400
message = "Bad request"
class Unauthorized(ClientException):
"""
HTTP 401 - Unauthorized: bad credentials.
"""
http_status = 401
message = "Unauthorized"
class Forbidden(ClientException):
"""
HTTP 403 - Forbidden: your credentials don't give you access to this
resource.
"""
http_status = 403
message = "Forbidden"
class NotFound(ClientException):
"""
HTTP 404 - Not found
"""
http_status = 404
message = "Not found"
class OverLimit(ClientException):
"""
HTTP 413 - Over limit: you're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
# NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException):
"""
HTTP 501 - Not Implemented: the server does not support this operation.
"""
http_status = 501
message = "Not Implemented"
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
# so we can do this:
# _code_map = dict((c.http_status, c)
# for c in ClientException.__subclasses__())
#
# Instead, we have to hardcode it:
_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
Forbidden, NotFound, OverLimit, HTTPNotImplemented])
def from_response(response, body):
"""
Return an instance of an ClientException or subclass
based on an httplib2 response.
Usage::
resp, body = http.request(...)
if resp.status != 200:
raise exception_from_response(resp, body)
"""
cls = _code_map.get(response.status, ClientException)
if body:
if hasattr(body, 'keys'):
error = body[body.keys()[0]]
message = error.get('message', None)
details = error.get('details', None)
else:
message = 'n/a'
details = body
return cls(code=response.status, message=message, details=details)
else:
return cls(code=response.status)

View File

@ -0,0 +1,62 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011, Piston Cloud Computing, Inc.
# Copyright 2011 Nebula, 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.
from . import exceptions
class ServiceCatalog(object):
"""Helper methods for dealing with a Keystone Service Catalog."""
def __init__(self, resource_dict):
self.catalog = resource_dict
def get_token(self):
"""Fetch token details fron service catalog"""
token = {'id': self.catalog['token']['id'],
'expires': self.catalog['token']['expires']}
try:
token['user_id'] = self.catalog['user']['id']
token['tenant_id'] = self.catalog['token']['tenant']['id']
except:
# just leave the tenant and user out if it doesn't exist
pass
return token
def url_for(self, attr=None, filter_value=None,
service_type='loadbalancer', endpoint_type='publicURL'):
"""Fetch an endpoint from the service catalog.
Fetch the specified endpoint from the service catalog for
a particular endpoint attribute. If no attribute is given, return
the first endpoint of the specified type.
See tests for a sample service catalog.
"""
catalog = self.catalog.get('serviceCatalog', [])
for service in catalog:
if service['type'] != service_type:
continue
endpoints = service['endpoints']
for endpoint in endpoints:
if not filter_value or endpoint.get(attr) == filter_value:
return endpoint[endpoint_type]
raise exceptions.EndpointNotFound('Endpoint not found.')

View File

@ -0,0 +1,291 @@
import os
import re
import sys
import uuid
import logging
import prettytable
from . import exceptions
def arg(*args, **kwargs):
"""Decorator for CLI args."""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*vars, **kwargs):
"""
returns the first environment variable set
if none are non-empty, defaults to '' or keyword arg default
"""
for v in vars:
value = os.environ.get(v, None)
if value:
return value
return kwargs.get('default', '')
def add_arg(f, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(f, 'arguments'):
f.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in f.arguments:
# Because of the sematics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
f.arguments.insert(0, (args, kwargs))
def add_resource_manager_extra_kwargs_hook(f, hook):
"""Adds hook to bind CLI arguments to ResourceManager calls.
The `do_foo` calls in shell.py will receive CLI args and then in turn pass
them through to the ResourceManager. Before passing through the args, the
hooks registered here will be called, giving us a chance to add extra
kwargs (taken from the command-line) to what's passed to the
ResourceManager.
"""
if not hasattr(f, 'resource_manager_kwargs_hooks'):
f.resource_manager_kwargs_hooks = []
names = [h.__name__ for h in f.resource_manager_kwargs_hooks]
if hook.__name__ not in names:
f.resource_manager_kwargs_hooks.append(hook)
def get_resource_manager_extra_kwargs(f, args, allow_conflicts=False):
"""Return extra_kwargs by calling resource manager kwargs hooks."""
hooks = getattr(f, "resource_manager_kwargs_hooks", [])
extra_kwargs = {}
for hook in hooks:
hook_name = hook.__name__
hook_kwargs = hook(args)
conflicting_keys = set(hook_kwargs.keys()) & set(extra_kwargs.keys())
if conflicting_keys and not allow_conflicts:
raise Exception("Hook '%(hook_name)s' is attempting to redefine"
" attributes '%(conflicting_keys)s'" % locals())
extra_kwargs.update(hook_kwargs)
return extra_kwargs
def unauthenticated(f):
"""
Adds 'unauthenticated' attribute to decorated function.
Usage:
@unauthenticated
def mymethod(f):
...
"""
f.unauthenticated = True
return f
def isunauthenticated(f):
"""
Checks to see if the function is marked as not requiring authentication
with the @unauthenticated decorator. Returns True if decorator is
set to True, False otherwise.
"""
return getattr(f, 'unauthenticated', False)
def service_type(stype):
"""
Adds 'service_type' attribute to decorated function.
Usage:
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype
return f
return inner
def get_service_type(f):
"""
Retrieves service type from function
"""
return getattr(f, 'service_type', None)
def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
def print_list(objs, fields, formatters={}, sortby_index=0):
if sortby_index == None:
sortby = None
else:
sortby = fields[sortby_index]
pt = prettytable.PrettyTable([f for f in fields], caching=False)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](o))
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
pt.add_row(row)
print pt.get_string(sortby=sortby)
def print_flat_list(lst, field):
pt = prettytable.PrettyTable(field)
for el in lst:
pt.add_row([el])
print pt.get_string()
def print_dict(d, property="Property"):
pt = prettytable.PrettyTable([property, 'Value'], caching=False)
pt.align = 'l'
[pt.add_row(list(r)) for r in d.iteritems()]
print pt.get_string(sortby=property)
def find_resource(manager, name_or_id):
"""Helper for the _find_* methods."""
# first try to get entity as integer id
try:
if isinstance(name_or_id, int) or name_or_id.isdigit():
return manager.get(int(name_or_id))
except exceptions.NotFound:
pass
# now try to get entity as uuid
try:
uuid.UUID(str(name_or_id))
return manager.get(name_or_id)
except (ValueError, exceptions.NotFound):
pass
try:
try:
return manager.find(human_id=name_or_id)
except exceptions.NotFound:
pass
# finally try to find entity by name
try:
return manager.find(name=name_or_id)
except exceptions.NotFound:
try:
# Volumes does not have name, but display_name
return manager.find(display_name=name_or_id)
except exceptions.NotFound:
msg = "No %s with a name or ID of '%s' exists." % \
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg)
except exceptions.NoUniqueMatch:
msg = ("Multiple %s matches found for '%s', use an ID to be more"
" specific." % (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)
class HookableMixin(object):
"""Mixin so classes can register and run hooks."""
_hooks_map = {}
@classmethod
def add_hook(cls, hook_type, hook_func):
if hook_type not in cls._hooks_map:
cls._hooks_map[hook_type] = []
cls._hooks_map[hook_type].append(hook_func)
@classmethod
def run_hooks(cls, hook_type, *args, **kwargs):
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
hook_func(*args, **kwargs)
def safe_issubclass(*args):
"""Like issubclass, but will just return False if not a class."""
try:
if issubclass(*args):
return True
except TypeError:
pass
return False
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
_slugify_strip_re = re.compile(r'[^\w\s-]')
_slugify_hyphenate_re = re.compile(r'[-\s]+')
# http://code.activestate.com/recipes/
# 577257-slugify-make-a-string-usable-in-a-url-or-filename/
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
From Django's "django/template/defaultfilters.py".
"""
import unicodedata
if not isinstance(value, unicode):
value = unicode(value)
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(_slugify_strip_re.sub('', value).strip().lower())
return _slugify_hyphenate_re.sub('-', value)
def http_log(logger, args, kwargs, resp, body):
# if not logger.isEnabledFor(logging.DEBUG):
# return
string_parts = ['curl -i']
for element in args:
if element in ('GET', 'POST'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
string_parts.append(header)
logger.debug("REQ: %s\n" % "".join(string_parts))
if 'body' in kwargs and kwargs['body']:
logger.debug("REQ BODY: %s\n" % (kwargs['body']))
logger.debug("RESP:%s\n", resp)
logger.debug("RESP BODY:%s\n", body)

View File

@ -0,0 +1,285 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack LLC.
# 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.
"""
Command-line interface to the OpenStack LBaaS API.
"""
import argparse
import httplib2
import os
import sys
import logging
from balancerclient.common import exceptions as exc
from balancerclient.common import utils
from balancerclient.v1 import shell as shell_v1
LOG = logging.getLogger(__name__)
class OpenStackBalancerShell(object):
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='balancer',
description=__doc__.strip(),
epilog='See "balancer help COMMAND" '
'for help on a specific command.',
add_help=False,
formatter_class=OpenStackHelpFormatter,
)
# Global arguments
parser.add_argument('-h',
'--help',
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--debug',
default=False,
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--os_username',
metavar='<auth-user-name>',
default=utils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME]')
parser.add_argument('--os_password',
metavar='<auth-password>',
default=utils.env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD]')
parser.add_argument('--os_tenant_name',
metavar='<auth-tenant-name>',
default=utils.env('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME]')
parser.add_argument('--os_tenant_id',
metavar='<tenant-id>',
default=utils.env('OS_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID]')
parser.add_argument('--os_auth_url',
metavar='<auth-url>',
default=utils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL]')
parser.add_argument('--os_region_name',
metavar='<region-name>',
default=utils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME]')
parser.add_argument('--os_balancer_api_version',
metavar='<balancer-api-version>',
default=utils.env('OS_BALANCER_API_VERSION',
'KEYSTONE_VERSION'),
help='Defaults to env[OS_BALANCER_API_VERSION]'
' or 2.0')
parser.add_argument('--token',
metavar='<service-token>',
default=utils.env('SERVICE_TOKEN'),
help='Defaults to env[SERVICE_TOKEN]')
parser.add_argument('--endpoint',
metavar='<service-endpoint>',
default=utils.env('SERVICE_ENDPOINT'),
help='Defaults to env[SERVICE_ENDPOINT]')
return parser
def get_subcommand_parser(self, version):
parser = self.get_base_parser()
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
try:
actions_module = {
'1': shell_v1,
}[version]
except KeyError:
actions_module = shell_v1
self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self)
return parser
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.
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
help = desc.strip().split('\n')[0]
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(
command,
help=help,
description=desc,
add_help=False,
formatter_class=OpenStackHelpFormatter)
subparser.add_argument('-h', '--help', action='help',
help=argparse.SUPPRESS)
self.subcommands[command] = subparser
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback)
def main(self, argv):
# Parse args once to find version
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
# build available subcommands based on version
api_version = options.os_balancer_api_version
subcommand_parser = self.get_subcommand_parser(api_version)
self.parser = subcommand_parser
# Handle top-level --help/-h before attempting to parse
# a command off the command line
if not argv or options.help:
self.do_help(options)
return 0
# Parse args again and call whatever callback was selected
args = subcommand_parser.parse_args(argv)
# Deal with global arguments
if args.debug:
httplib2.debuglevel = 1
# Short-circuit and deal with help command right away.
if args.func == self.do_help:
self.do_help(args)
return 0
#FIXME(usrleon): Here should be restrict for project id same as
# for username or apikey but for compatibility it is not.
if not utils.isunauthenticated(args.func):
# if the user hasn't provided any auth data
if not (args.token or args.endpoint or args.os_username or
args.os_password or args.os_auth_url):
raise exc.CommandError('Expecting authentication method via \n'
' either a service token, '
'--token or env[SERVICE_TOKEN], \n'
' or credentials, '
'--os_username or env[OS_USERNAME].')
# if it looks like the user wants to provide a service token
# but is missing something
if args.token or args.endpoint and not (
args.token and args.endpoint):
if not args.token:
raise exc.CommandError(
'Expecting a token provided via either --token or '
'env[SERVICE_TOKEN]')
if not args.endpoint:
raise exc.CommandError(
'Expecting an endpoint provided via either --endpoint '
'or env[SERVICE_ENDPOINT]')
# if it looks like the user wants to provide a credentials
# but is missing something
if ((args.os_username or args.os_password or args.os_auth_url)
and not (args.os_username and args.os_password and
args.os_auth_url)):
if not args.os_username:
raise exc.CommandError(
'Expecting a username provided via either '
'--os_username or env[OS_USERNAME]')
if not args.os_password:
raise exc.CommandError(
'Expecting a password provided via either '
'--os_password or env[OS_PASSWORD]')
if not args.os_auth_url:
raise exc.CommandError(
'Expecting an auth URL via either --os_auth_url or '
'env[OS_AUTH_URL]')
if utils.isunauthenticated(args.func):
self.cs = shell_generic.CLIENT_CLASS(endpoint=args.os_auth_url)
else:
token = None
endpoint = None
if args.token and args.endpoint:
token = args.token
endpoint = args.endpoint
api_version = options.os_balancer_api_version
self.cs = self.get_api_class(api_version)(
username=args.os_username,
tenant_name=args.os_tenant_name,
tenant_id=args.os_tenant_id,
token=token,
endpoint=endpoint,
password=args.os_password,
auth_url=args.os_auth_url,
region_name=args.os_region_name)
try:
args.func(self.cs, args)
except exc.Unauthorized:
raise exc.CommandError("Invalid OpenStack LBaaS credentials.")
except exc.AuthorizationFailure:
raise exc.CommandError("Unable to authorize user")
def get_api_class(self, version):
try:
return {
"1": shell_v1.CLIENT_CLASS,
}[version]
except KeyError:
return shell_v1.CLIENT_CLASS
@utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>')
def do_help(self, args):
"""
Display help about this program or one of its subcommands.
"""
if getattr(args, 'command', None):
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError("'%s' is not a valid subcommand" %
args.command)
else:
self.parser.print_help()
# I'm picky about my shell help.
class OpenStackHelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(OpenStackHelpFormatter, self).start_section(heading)
def main():
try:
return OpenStackBalancerShell().main(sys.argv[1:])
except Exception, err:
LOG.exception("The operation executed with an error %r." % err)
raise

View File

View File

@ -0,0 +1,27 @@
# Copyright 2012 OpenStack LLC.
# 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from windcclient.common import client
from . import datacenters
class Client(object):
"""Client for the WinDC v1 API."""
def __init__(self, **kwargs):
self.client = client.HTTPClient(**kwargs)
self.datacenters = datacenters.DCManager(self)

View File

@ -0,0 +1,49 @@
# Copyright 2012 OpenStack LLC.
# 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from windcclient.common import base
class DC(base.Resource):
"""Represent load balancer device instance."""
def __repr__(self):
return "<DC(%s)>" % self._info
class DCManager(base.Manager):
resource_class = DC
def list(self):
return self._list('/datacenters', 'datacenters')
def create(self, name, type, version, ip, port, user, password, **extra):
body = {'name': name,
'type': type,
'version': version,
'ip': ip,
'port': port,
'user': user,
'password': password}
body.update(extra)
return self._create('/devices', body, 'device')
def delete(self, datacenter):
self._delete("/datacenters/%s" % base.getid(datacenter))
def get(self, datacenter):
return self._get("/datacenters/%s" % base.getid(datacenter), 'datacenter')