Migrate client code from libra codebase
Change-Id: Icd9d758e45c7167c5b8db7aa2c5e15b4bb93c766
This commit is contained in:
parent
775f861346
commit
a861024bed
@ -1,6 +1,5 @@
|
|||||||
|
[gerrit]
|
||||||
[gerrit]
|
host=review.openstack.org
|
||||||
host=review.openstack.org
|
port=29418
|
||||||
port=29418
|
project=stackforge/python-libraclient.git
|
||||||
project=stackforge/python-libraclient.git
|
|
||||||
|
|
4
.testr.conf
Normal file
4
.testr.conf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
|
||||||
|
test_id_option=--load-list $IDFILE
|
||||||
|
test_list_option=--list
|
9
MANIFEST.in
Normal file
9
MANIFEST.in
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
include README
|
||||||
|
|
||||||
|
exclude .gitignore
|
||||||
|
exclude .gitreview
|
||||||
|
|
||||||
|
global-exclude *.pyc
|
||||||
|
|
||||||
|
graft doc
|
||||||
|
graft tools
|
6
build_pdf.sh
Executable file
6
build_pdf.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
python setup.py build_sphinx_latex
|
||||||
|
# Fix option double dashes in latex output
|
||||||
|
perl -i -pe 's/\\bfcode\{--(.*)\}/\\bfcode\{-\{\}-\1\}/g' build/sphinx/latex/*.tex
|
||||||
|
perl -i -pe 's/\\index\{(.*?)--(.*?)\}/\\index\{\1-\{\}-\2\}/g' build/sphinx/latex/*.tex
|
||||||
|
make -C build/sphinx/latex all-pdf
|
15
client/__init__.py
Normal file
15
client/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
__version__ = "1.0"
|
36
client/client.py
Normal file
36
client/client.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 libraapi import LibraAPI
|
||||||
|
from clientoptions import ClientOptions
|
||||||
|
from novaclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
options = ClientOptions()
|
||||||
|
args = options.run()
|
||||||
|
|
||||||
|
api = LibraAPI(args.os_username, args.os_password, args.os_tenant_name,
|
||||||
|
args.os_auth_url, args.os_region_name, args.insecure,
|
||||||
|
args.debug, args.bypass_url)
|
||||||
|
|
||||||
|
cmd = args.command.replace('-', '_')
|
||||||
|
method = getattr(api, '{cmd}_lb'.format(cmd=cmd))
|
||||||
|
|
||||||
|
try:
|
||||||
|
method(args)
|
||||||
|
except exceptions.ClientException as exc:
|
||||||
|
print exc
|
||||||
|
|
||||||
|
return 0
|
154
client/clientoptions.py
Normal file
154
client/clientoptions.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 argparse
|
||||||
|
|
||||||
|
|
||||||
|
class ClientOptions(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.options = argparse.ArgumentParser('Libra command line client')
|
||||||
|
|
||||||
|
def _generate(self):
|
||||||
|
self.options.add_argument(
|
||||||
|
'--os_auth_url',
|
||||||
|
metavar='<auth-url>',
|
||||||
|
required=True,
|
||||||
|
help='Authentication URL'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--os_username',
|
||||||
|
metavar='<auth-user-name>',
|
||||||
|
required=True,
|
||||||
|
help='Authentication username'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--os_password',
|
||||||
|
metavar='<auth-password>',
|
||||||
|
required=True,
|
||||||
|
help='Authentication password'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--os_tenant_name',
|
||||||
|
metavar='<auth-tenant-name>',
|
||||||
|
required=True,
|
||||||
|
help='Authentication tenant'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--os_region_name',
|
||||||
|
metavar='<region-name>',
|
||||||
|
required=True,
|
||||||
|
help='Authentication region'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--debug',
|
||||||
|
action='store_true',
|
||||||
|
help='Debug network messages'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--insecure',
|
||||||
|
action='store_true',
|
||||||
|
help='Don\'t verify SSL cert'
|
||||||
|
)
|
||||||
|
self.options.add_argument(
|
||||||
|
'--bypass_url',
|
||||||
|
help='Use this API endpoint instead of the Service Catalog'
|
||||||
|
)
|
||||||
|
subparsers = self.options.add_subparsers(
|
||||||
|
metavar='<subcommand>', dest='command'
|
||||||
|
)
|
||||||
|
subparsers.add_parser(
|
||||||
|
'limits', help='get account API usage limits'
|
||||||
|
)
|
||||||
|
subparsers.add_parser(
|
||||||
|
'algorithms', help='get a list of supported algorithms'
|
||||||
|
)
|
||||||
|
subparsers.add_parser(
|
||||||
|
'protocols', help='get a list of supported protocols and ports'
|
||||||
|
)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'list', help='list load balancers'
|
||||||
|
)
|
||||||
|
sp.add_argument(
|
||||||
|
'--deleted', help='list deleted load balancers',
|
||||||
|
action='store_true'
|
||||||
|
)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'delete', help='delete a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'create', help='create a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--name', help='name for the load balancer',
|
||||||
|
required=True)
|
||||||
|
sp.add_argument('--port',
|
||||||
|
help='port for the load balancer, 80 is default')
|
||||||
|
sp.add_argument('--protocol',
|
||||||
|
help='protocol for the load balancer, HTTP is default',
|
||||||
|
choices=['HTTP', 'TCP'])
|
||||||
|
sp.add_argument('--algorithm',
|
||||||
|
help='algorithm for the load balancer,'
|
||||||
|
' ROUND_ROBIN is default',
|
||||||
|
choices=['LEAST_CONNECTIONS', 'ROUND_ROBIN'])
|
||||||
|
sp.add_argument('--node',
|
||||||
|
help='a node for the load balancer in ip:port format',
|
||||||
|
action='append', required=True)
|
||||||
|
sp.add_argument('--vip',
|
||||||
|
help='the virtual IP to attach the load balancer to')
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'modify', help='modify a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp.add_argument('--name', help='new name for the load balancer')
|
||||||
|
sp.add_argument('--algorithm',
|
||||||
|
help='new algorithm for the load balancer',
|
||||||
|
choices=['LEAST_CONNECTIONS', 'ROUND_ROBIN'])
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'status', help='get status of a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'node-list', help='list nodes in a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'node-delete', help='delete node from a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp.add_argument('--nodeid',
|
||||||
|
help='node ID to remove from load balancer',
|
||||||
|
required=True)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'node-add', help='add node to a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp.add_argument('--node', help='node to add in ip:port form',
|
||||||
|
required=True, action='append')
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'node-modify', help='modify node in a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp.add_argument('--nodeid', help='node ID to modify', required=True)
|
||||||
|
sp.add_argument('--condition', help='the new state for the node',
|
||||||
|
choices=['ENABLED', 'DISABLED'], required=True)
|
||||||
|
sp = subparsers.add_parser(
|
||||||
|
'node-status', help='get status of a node in a load balancer'
|
||||||
|
)
|
||||||
|
sp.add_argument('--id', help='load balancer ID', required=True)
|
||||||
|
sp.add_argument('--nodeid', help='node ID to get status from',
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self._generate()
|
||||||
|
return self.options.parse_args()
|
217
client/libraapi.py
Normal file
217
client/libraapi.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 prettytable
|
||||||
|
import novaclient
|
||||||
|
|
||||||
|
from novaclient import client
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(LinuxJedi): Override novaclient's error handler as we send messages in
|
||||||
|
# a slightly different format which causes novaclient's to throw an exception
|
||||||
|
|
||||||
|
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 = novaclient.exceptions._code_map.get(
|
||||||
|
response.status, novaclient.exceptions.ClientException
|
||||||
|
)
|
||||||
|
request_id = response.get('x-compute-request-id')
|
||||||
|
if body:
|
||||||
|
message = "n/a"
|
||||||
|
details = "n/a"
|
||||||
|
if hasattr(body, 'keys'):
|
||||||
|
message = body.get('message', None)
|
||||||
|
details = body.get('details', None)
|
||||||
|
return cls(code=response.status, message=message, details=details,
|
||||||
|
request_id=request_id)
|
||||||
|
else:
|
||||||
|
return cls(code=response.status, request_id=request_id)
|
||||||
|
|
||||||
|
novaclient.exceptions.from_response = from_response
|
||||||
|
|
||||||
|
|
||||||
|
class LibraAPI(object):
|
||||||
|
def __init__(self, username, password, tenant, auth_url, region,
|
||||||
|
insecure, debug, bypass_url):
|
||||||
|
self.nova = client.HTTPClient(
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
tenant,
|
||||||
|
auth_url,
|
||||||
|
region_name=region,
|
||||||
|
service_type='compute',
|
||||||
|
http_log_debug=debug,
|
||||||
|
insecure=insecure,
|
||||||
|
bypass_url=bypass_url
|
||||||
|
)
|
||||||
|
|
||||||
|
def limits_lb(self, args):
|
||||||
|
resp, body = self._get('/limits')
|
||||||
|
column_names = ['Verb', 'Value', 'Remaining', 'Unit', 'Next Available']
|
||||||
|
columns = ['verb', 'value', 'remaining', 'unit', 'next-available']
|
||||||
|
self._render_list(column_names, columns,
|
||||||
|
body['limits']['rate']['values']['limit'])
|
||||||
|
column_names = ['Values']
|
||||||
|
columns = ['values']
|
||||||
|
self._render_dict(column_names, columns, body['limits']['absolute'])
|
||||||
|
|
||||||
|
def protocols_lb(self, args):
|
||||||
|
resp, body = self._get('/protocols')
|
||||||
|
column_names = ['Name', 'Port']
|
||||||
|
columns = ['name', 'port']
|
||||||
|
self._render_list(column_names, columns, body['protocols'])
|
||||||
|
|
||||||
|
def algorithms_lb(self, args):
|
||||||
|
resp, body = self._get('/algorithms')
|
||||||
|
column_names = ['Name']
|
||||||
|
columns = ['name']
|
||||||
|
self._render_list(column_names, columns, body['algorithms'])
|
||||||
|
|
||||||
|
def list_lb(self, args):
|
||||||
|
if args.deleted:
|
||||||
|
resp, body = self._get('/loadbalancers?status=DELETED')
|
||||||
|
else:
|
||||||
|
resp, body = self._get('/loadbalancers')
|
||||||
|
column_names = ['Name', 'ID', 'Protocol', 'Port', 'Algorithm',
|
||||||
|
'Status', 'Created', 'Updated']
|
||||||
|
columns = ['name', 'id', 'protocol', 'port', 'algorithm', 'status',
|
||||||
|
'created', 'updated']
|
||||||
|
self._render_list(column_names, columns, body['loadBalancers'])
|
||||||
|
|
||||||
|
def status_lb(self, args):
|
||||||
|
resp, body = self._get('/loadbalancers/{0}'.format(args.id))
|
||||||
|
column_names = ['ID', 'Name', 'Protocol', 'Port', 'Algorithm',
|
||||||
|
'Status', 'Created', 'Updated', 'IPs', 'Nodes',
|
||||||
|
'Persistence Type', 'Connection Throttle']
|
||||||
|
columns = ['id', 'name', 'protocol', 'port', 'algorithm', 'status',
|
||||||
|
'created', 'updated', 'virtualIps', 'nodes',
|
||||||
|
'sessionPersistence', 'connectionThrottle']
|
||||||
|
if 'sessionPersistence' not in body:
|
||||||
|
body['sessionPersistence'] = 'None'
|
||||||
|
if 'connectionThrottle' not in body:
|
||||||
|
body['connectionThrottle'] = 'None'
|
||||||
|
self._render_dict(column_names, columns, body)
|
||||||
|
|
||||||
|
def delete_lb(self, args):
|
||||||
|
self._delete('/loadbalancers/{0}'.format(args.id))
|
||||||
|
|
||||||
|
def create_lb(self, args):
|
||||||
|
data = {}
|
||||||
|
nodes = []
|
||||||
|
data['name'] = args.name
|
||||||
|
if args.port is not None:
|
||||||
|
data['port'] = args.port
|
||||||
|
if args.protocol is not None:
|
||||||
|
data['protocol'] = args.protocol
|
||||||
|
if args.algorithm is not None:
|
||||||
|
data['algorithm'] = args.algorithm
|
||||||
|
for node in args.node:
|
||||||
|
addr = node.split(':')
|
||||||
|
nodes.append({'address': addr[0], 'port': addr[1],
|
||||||
|
'condition': 'ENABLED'})
|
||||||
|
data['nodes'] = nodes
|
||||||
|
if args.vip is not None:
|
||||||
|
data['virtualIps'] = [{'id': args.vip}]
|
||||||
|
|
||||||
|
resp, body = self._post('/loadbalancers', body=data)
|
||||||
|
column_names = ['ID', 'Name', 'Protocol', 'Port', 'Algorithm',
|
||||||
|
'Status', 'Created', 'Updated', 'IPs', 'Nodes']
|
||||||
|
columns = ['id', 'name', 'protocol', 'port', 'algorithm', 'status',
|
||||||
|
'created', 'updated', 'virtualIps', 'nodes']
|
||||||
|
self._render_dict(column_names, columns, body)
|
||||||
|
|
||||||
|
def modify_lb(self, args):
|
||||||
|
data = {}
|
||||||
|
if args.name is not None:
|
||||||
|
data['name'] = args.name
|
||||||
|
if args.algorithm is not None:
|
||||||
|
data['algorithm'] = args.algorithm
|
||||||
|
self._put('/loadbalancers/{0}'.format(args.id), body=data)
|
||||||
|
|
||||||
|
def node_list_lb(self, args):
|
||||||
|
resp, body = self._get('/loadbalancers/{0}/nodes'.format(args.id))
|
||||||
|
column_names = ['ID', 'Address', 'Port', 'Condition', 'Status']
|
||||||
|
columns = ['id', 'address', 'port', 'condition', 'status']
|
||||||
|
self._render_list(column_names, columns, body['nodes'])
|
||||||
|
|
||||||
|
def node_delete_lb(self, args):
|
||||||
|
self._delete('/loadbalancers/{0}/nodes/{1}'
|
||||||
|
.format(args.id, args.nodeid))
|
||||||
|
|
||||||
|
def node_add_lb(self, args):
|
||||||
|
data = {}
|
||||||
|
nodes = []
|
||||||
|
|
||||||
|
for node in args.node:
|
||||||
|
addr = node.split(':')
|
||||||
|
nodes.append({'address': addr[0], 'port': addr[1],
|
||||||
|
'condition': 'ENABLED'})
|
||||||
|
data['nodes'] = nodes
|
||||||
|
resp, body = self._post('/loadbalancers/{0}/nodes'
|
||||||
|
.format(args.id), body=data)
|
||||||
|
column_names = ['ID', 'Address', 'Port', 'Condition', 'Status']
|
||||||
|
columns = ['id', 'address', 'port', 'condition', 'status']
|
||||||
|
self._render_list(column_names, columns, body['nodes'])
|
||||||
|
|
||||||
|
def node_modify_lb(self, args):
|
||||||
|
data = {'condition': args.condition}
|
||||||
|
self._put('/loadbalancers/{0}/nodes/{1}'
|
||||||
|
.format(args.id, args.nodeid), body=data)
|
||||||
|
|
||||||
|
def node_status_lb(self, args):
|
||||||
|
resp, body = self._get('/loadbalancers/{0}/nodes/{1}'
|
||||||
|
.format(args.id, args.nodeid))
|
||||||
|
column_names = ['ID', 'Address', 'Port', 'Condition', 'Status']
|
||||||
|
columns = ['id', 'address', 'port', 'condition', 'status']
|
||||||
|
self._render_dict(column_names, columns, body)
|
||||||
|
|
||||||
|
def _render_list(self, column_names, columns, data):
|
||||||
|
table = prettytable.PrettyTable(column_names)
|
||||||
|
for item in data:
|
||||||
|
row = []
|
||||||
|
for column in columns:
|
||||||
|
rdata = item[column]
|
||||||
|
row.append(rdata)
|
||||||
|
table.add_row(row)
|
||||||
|
print table
|
||||||
|
|
||||||
|
def _render_dict(self, column_names, columns, data):
|
||||||
|
table = prettytable.PrettyTable(column_names)
|
||||||
|
row = []
|
||||||
|
for column in columns:
|
||||||
|
rdata = data[column]
|
||||||
|
row.append(rdata)
|
||||||
|
table.add_row(row)
|
||||||
|
print table
|
||||||
|
|
||||||
|
def _get(self, url, **kwargs):
|
||||||
|
return self.nova.get(url, **kwargs)
|
||||||
|
|
||||||
|
def _post(self, url, **kwargs):
|
||||||
|
return self.nova.post(url, **kwargs)
|
||||||
|
|
||||||
|
def _put(self, url, **kwargs):
|
||||||
|
return self.nova.put(url, **kwargs)
|
||||||
|
|
||||||
|
def _delete(self, url, **kwargs):
|
||||||
|
return self.nova.delete(url, **kwargs)
|
237
doc/command.rst
Normal file
237
doc/command.rst
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
Libra Client
|
||||||
|
============
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
:program:`libra_client` [:ref:`GENERAL OPTIONS <libra_client-options>`] [:ref:`COMMAND <libra_client-commands>`] [*COMMAND_OPTIONS*]
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
:program:`libra_client` is a utility designed to communicate with Atlas API
|
||||||
|
based Load Balancer as a Service systems.
|
||||||
|
|
||||||
|
.. _libra_client-options:
|
||||||
|
|
||||||
|
Global Options
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. program:: libra_client
|
||||||
|
|
||||||
|
.. option:: --help, -h
|
||||||
|
|
||||||
|
Show help message and exit
|
||||||
|
|
||||||
|
.. option:: --debug
|
||||||
|
|
||||||
|
Turn on HTTP debugging for requests
|
||||||
|
|
||||||
|
.. option:: --insecure
|
||||||
|
|
||||||
|
Don't validate SSL certs
|
||||||
|
|
||||||
|
.. option:: --bypass_url <bypass-url>
|
||||||
|
|
||||||
|
URL to use as an endpoint instead of the one specified by the Service
|
||||||
|
Catalog
|
||||||
|
|
||||||
|
.. option:: --os_auth_url <auth-url>
|
||||||
|
|
||||||
|
The OpenStack authentication URL
|
||||||
|
|
||||||
|
.. option:: --os_username <auth-user-name>
|
||||||
|
|
||||||
|
The user name to use for authentication
|
||||||
|
|
||||||
|
.. option:: --os_password <auth-password>
|
||||||
|
|
||||||
|
The password to use for authentication
|
||||||
|
|
||||||
|
.. option:: --os_tenant_name <auth-tenant-name>
|
||||||
|
|
||||||
|
The tenant to authenticate to
|
||||||
|
|
||||||
|
.. option:: --os_region_name <region-name>
|
||||||
|
|
||||||
|
The region the load balancer is located
|
||||||
|
|
||||||
|
.. _libra_client-commands:
|
||||||
|
|
||||||
|
Client Commands
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. program:: libra_client create
|
||||||
|
|
||||||
|
create
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Create a load balancer
|
||||||
|
|
||||||
|
.. option:: --name <name>
|
||||||
|
|
||||||
|
The name of the node to be created
|
||||||
|
|
||||||
|
.. option:: --port <port>
|
||||||
|
|
||||||
|
The port the load balancer will listen on
|
||||||
|
|
||||||
|
.. option:: --protocol <protocol>
|
||||||
|
|
||||||
|
The protocol type for the load balancer (HTTP or TCP)
|
||||||
|
|
||||||
|
.. option:: --node <ip:port>
|
||||||
|
|
||||||
|
The IP and port for a load balancer node (can be used multiple times to add multiple nodes)
|
||||||
|
|
||||||
|
.. option:: --vip <vip>
|
||||||
|
|
||||||
|
The virtual IP ID of an existing load balancer to attach to
|
||||||
|
|
||||||
|
.. program:: libra_client modify
|
||||||
|
|
||||||
|
modify
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Update a load balancer's configuration
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. option:: --name <name>
|
||||||
|
|
||||||
|
A new name for the load balancer
|
||||||
|
|
||||||
|
.. option:: --algorithm <algorithm>
|
||||||
|
|
||||||
|
A new algorithm for the load balancer
|
||||||
|
|
||||||
|
.. program:: libra_client list
|
||||||
|
|
||||||
|
list
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
List all load balancers
|
||||||
|
|
||||||
|
.. option:: --deleted
|
||||||
|
|
||||||
|
Show deleted load balancers
|
||||||
|
|
||||||
|
.. program:: libra_client limits
|
||||||
|
|
||||||
|
limits
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Show the API limits for the user
|
||||||
|
|
||||||
|
.. program:: libra_client algorithms
|
||||||
|
|
||||||
|
algorithms
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
Gets a list of supported algorithms
|
||||||
|
|
||||||
|
.. program:: libra_client protocols
|
||||||
|
|
||||||
|
protocols
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Gets a list of supported protocols
|
||||||
|
|
||||||
|
.. program:: libra_client status
|
||||||
|
|
||||||
|
status
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Get the status of a single load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. program:: libra_client delete
|
||||||
|
|
||||||
|
delete
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Delete a load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. program:: libra_client node-list
|
||||||
|
|
||||||
|
node-list
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
List the nodes in a load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. program:: libra_client node-delete
|
||||||
|
|
||||||
|
node-delete
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Delete a node from the load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. option:: --nodeid <nodeid>
|
||||||
|
|
||||||
|
The ID of the node to be removed
|
||||||
|
|
||||||
|
.. program:: libra_client node-add
|
||||||
|
|
||||||
|
node-add
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
Add a node to a load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. option:: --node <ip:port>
|
||||||
|
|
||||||
|
The node address in ip:port format (can be used multiple times to add multiple nodes)
|
||||||
|
|
||||||
|
.. program:: libra_client node-modify
|
||||||
|
|
||||||
|
node-modify
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Modify a node's state in a load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. option:: --nodeid <nodeid>
|
||||||
|
|
||||||
|
The ID of the node to be modified
|
||||||
|
|
||||||
|
.. option:: --condition <condition>
|
||||||
|
|
||||||
|
The new state of the node (either ENABLED or DISABLED)
|
||||||
|
|
||||||
|
.. program:: libra_client node-status
|
||||||
|
|
||||||
|
node-status
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Get the status of a node in a load balancer
|
||||||
|
|
||||||
|
.. option:: --id <id>
|
||||||
|
|
||||||
|
The ID of the load balancer
|
||||||
|
|
||||||
|
.. option:: --nodeid <nodeid>
|
||||||
|
|
||||||
|
The ID of the node in the load balancer
|
234
doc/conf.py
Normal file
234
doc/conf.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# OpenStack CI documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Mon Jul 18 13:42:23 2011.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing
|
||||||
|
# dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
#extensions = ['rst2pdf.pdfbuilder']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'LBaaS Command Line Client'
|
||||||
|
copyright = u'2012, Andrew Hutchings'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = "%d-%02d-%02d-alpha1" % (
|
||||||
|
datetime.datetime.now().year,
|
||||||
|
datetime.datetime.now().month,
|
||||||
|
datetime.datetime.now().day
|
||||||
|
)
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = "%d-%02d-%02d-alpha1" % (
|
||||||
|
datetime.datetime.now().year,
|
||||||
|
datetime.datetime.now().month,
|
||||||
|
datetime.datetime.now().day
|
||||||
|
)
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
|
# documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = 'default'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'LibraClent'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
||||||
|
# The paper size ('letter' or 'a4').
|
||||||
|
#latex_paper_size = 'letter'
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#latex_font_size = '10pt'
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# documentclass [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'pthon-libraclient-{0}.tex'.format(version), u'Libra Client Documentation',
|
||||||
|
u'Andrew Hutchings', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
#pdf_documents = [('index', 'Libra-{0}'.format(version), u'Libra Client, Worker and Pool Manager Documentation', u'Andrew Hutchings and David Shrewsbury')]
|
||||||
|
|
||||||
|
#pdf_break_level = 1
|
||||||
|
|
||||||
|
#pdf_stylesheets = ['sphinx', 'libra']
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#latex_preamble = ''
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output -------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'lbaas', u'LBaaS Client',
|
||||||
|
[u'Andrew Hutchings'], 1)
|
||||||
|
]
|
7
doc/index.rst
Normal file
7
doc/index.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Libra Command Line Client
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
command
|
7
openstack-common.conf
Normal file
7
openstack-common.conf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
|
||||||
|
# The list of modules to copy from openstack-common
|
||||||
|
modules=importutils,setup
|
||||||
|
|
||||||
|
# The base module to hold the copy of openstack.common
|
||||||
|
base=
|
0
openstack/__init__.py
Normal file
0
openstack/__init__.py
Normal file
0
openstack/common/__init__.py
Normal file
0
openstack/common/__init__.py
Normal file
59
openstack/common/importutils.py
Normal file
59
openstack/common/importutils.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Import related utilities and helper functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(import_str):
|
||||||
|
"""Returns a class from a string including module and class"""
|
||||||
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
try:
|
||||||
|
__import__(mod_str)
|
||||||
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
except (ValueError, AttributeError), exc:
|
||||||
|
raise ImportError('Class %s cannot be found (%s)' %
|
||||||
|
(class_str,
|
||||||
|
traceback.format_exception(*sys.exc_info())))
|
||||||
|
|
||||||
|
|
||||||
|
def import_object(import_str, *args, **kwargs):
|
||||||
|
"""Import a class and return an instance of it."""
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Import a class and return an instance of it, first by trying
|
||||||
|
to find the class in a default namespace, then failing back to
|
||||||
|
a full path if not found in the default namespace.
|
||||||
|
"""
|
||||||
|
import_value = "%s.%s" % (name_space, import_str)
|
||||||
|
try:
|
||||||
|
return import_class(import_value)(*args, **kwargs)
|
||||||
|
except ImportError:
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_module(import_str):
|
||||||
|
"""Import a module."""
|
||||||
|
__import__(import_str)
|
||||||
|
return sys.modules[import_str]
|
360
openstack/common/setup.py
Normal file
360
openstack/common/setup.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Utilities with minimum-depends for use in setup.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from setuptools.command import sdist
|
||||||
|
|
||||||
|
|
||||||
|
def parse_mailmap(mailmap='.mailmap'):
|
||||||
|
mapping = {}
|
||||||
|
if os.path.exists(mailmap):
|
||||||
|
with open(mailmap, 'r') as fp:
|
||||||
|
for l in fp:
|
||||||
|
l = l.strip()
|
||||||
|
if not l.startswith('#') and ' ' in l:
|
||||||
|
canonical_email, alias = [x for x in l.split(' ')
|
||||||
|
if x.startswith('<')]
|
||||||
|
mapping[alias] = canonical_email
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def canonicalize_emails(changelog, mapping):
|
||||||
|
"""Takes in a string and an email alias mapping and replaces all
|
||||||
|
instances of the aliases in the string with their real email.
|
||||||
|
"""
|
||||||
|
for alias, email in mapping.iteritems():
|
||||||
|
changelog = changelog.replace(alias, email)
|
||||||
|
return changelog
|
||||||
|
|
||||||
|
|
||||||
|
# Get requirements from the first file that exists
|
||||||
|
def get_reqs_from_files(requirements_files):
|
||||||
|
for requirements_file in requirements_files:
|
||||||
|
if os.path.exists(requirements_file):
|
||||||
|
with open(requirements_file, 'r') as fil:
|
||||||
|
return fil.read().split('\n')
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def parse_requirements(requirements_files=['requirements.txt',
|
||||||
|
'tools/pip-requires']):
|
||||||
|
requirements = []
|
||||||
|
for line in get_reqs_from_files(requirements_files):
|
||||||
|
# For the requirements list, we need to inject only the portion
|
||||||
|
# after egg= so that distutils knows the package it's looking for
|
||||||
|
# such as:
|
||||||
|
# -e git://github.com/openstack/nova/master#egg=nova
|
||||||
|
if re.match(r'\s*-e\s+', line):
|
||||||
|
requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1',
|
||||||
|
line))
|
||||||
|
# such as:
|
||||||
|
# http://github.com/openstack/nova/zipball/master#egg=nova
|
||||||
|
elif re.match(r'\s*https?:', line):
|
||||||
|
requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1',
|
||||||
|
line))
|
||||||
|
# -f lines are for index locations, and don't get used here
|
||||||
|
elif re.match(r'\s*-f\s+', line):
|
||||||
|
pass
|
||||||
|
# argparse is part of the standard library starting with 2.7
|
||||||
|
# adding it to the requirements list screws distro installs
|
||||||
|
elif line == 'argparse' and sys.version_info >= (2, 7):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
requirements.append(line)
|
||||||
|
|
||||||
|
return requirements
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dependency_links(requirements_files=['requirements.txt',
|
||||||
|
'tools/pip-requires']):
|
||||||
|
dependency_links = []
|
||||||
|
# dependency_links inject alternate locations to find packages listed
|
||||||
|
# in requirements
|
||||||
|
for line in get_reqs_from_files(requirements_files):
|
||||||
|
# skip comments and blank lines
|
||||||
|
if re.match(r'(\s*#)|(\s*$)', line):
|
||||||
|
continue
|
||||||
|
# lines with -e or -f need the whole line, minus the flag
|
||||||
|
if re.match(r'\s*-[ef]\s+', line):
|
||||||
|
dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
|
||||||
|
# lines that are only urls can go in unmolested
|
||||||
|
elif re.match(r'\s*https?:', line):
|
||||||
|
dependency_links.append(line)
|
||||||
|
return dependency_links
|
||||||
|
|
||||||
|
|
||||||
|
def write_requirements():
|
||||||
|
venv = os.environ.get('VIRTUAL_ENV', None)
|
||||||
|
if venv is not None:
|
||||||
|
with open("requirements.txt", "w") as req_file:
|
||||||
|
output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
requirements = output.communicate()[0].strip()
|
||||||
|
req_file.write(requirements)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_shell_command(cmd):
|
||||||
|
output = subprocess.Popen(["/bin/sh", "-c", cmd],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
out = output.communicate()
|
||||||
|
if len(out) == 0:
|
||||||
|
return None
|
||||||
|
if len(out[0].strip()) == 0:
|
||||||
|
return None
|
||||||
|
return out[0].strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_git_next_version_suffix(branch_name):
|
||||||
|
datestamp = datetime.datetime.now().strftime('%Y%m%d')
|
||||||
|
if branch_name == 'milestone-proposed':
|
||||||
|
revno_prefix = "r"
|
||||||
|
else:
|
||||||
|
revno_prefix = ""
|
||||||
|
_run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*")
|
||||||
|
milestone_cmd = "git show meta/openstack/release:%s" % branch_name
|
||||||
|
milestonever = _run_shell_command(milestone_cmd)
|
||||||
|
if not milestonever:
|
||||||
|
milestonever = ""
|
||||||
|
post_version = _get_git_post_version()
|
||||||
|
# post version should look like:
|
||||||
|
# 0.1.1.4.gcc9e28a
|
||||||
|
# where the bit after the last . is the short sha, and the bit between
|
||||||
|
# the last and second to last is the revno count
|
||||||
|
(revno, sha) = post_version.split(".")[-2:]
|
||||||
|
first_half = "%s~%s" % (milestonever, datestamp)
|
||||||
|
second_half = "%s%s.%s" % (revno_prefix, revno, sha)
|
||||||
|
return ".".join((first_half, second_half))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_git_current_tag():
|
||||||
|
return _run_shell_command("git tag --contains HEAD")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_git_tag_info():
|
||||||
|
return _run_shell_command("git describe --tags")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_git_post_version():
|
||||||
|
current_tag = _get_git_current_tag()
|
||||||
|
if current_tag is not None:
|
||||||
|
return current_tag
|
||||||
|
else:
|
||||||
|
tag_info = _get_git_tag_info()
|
||||||
|
if tag_info is None:
|
||||||
|
base_version = "0.0"
|
||||||
|
cmd = "git --no-pager log --oneline"
|
||||||
|
out = _run_shell_command(cmd)
|
||||||
|
revno = len(out.split("\n"))
|
||||||
|
sha = _run_shell_command("git describe --always")
|
||||||
|
else:
|
||||||
|
tag_infos = tag_info.split("-")
|
||||||
|
base_version = "-".join(tag_infos[:-2])
|
||||||
|
(revno, sha) = tag_infos[-2:]
|
||||||
|
return "%s.%s.%s" % (base_version, revno, sha)
|
||||||
|
|
||||||
|
|
||||||
|
def write_git_changelog():
|
||||||
|
"""Write a changelog based on the git changelog."""
|
||||||
|
new_changelog = 'ChangeLog'
|
||||||
|
if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'):
|
||||||
|
if os.path.isdir('.git'):
|
||||||
|
git_log_cmd = 'git log --stat'
|
||||||
|
changelog = _run_shell_command(git_log_cmd)
|
||||||
|
mailmap = parse_mailmap()
|
||||||
|
with open(new_changelog, "w") as changelog_file:
|
||||||
|
changelog_file.write(canonicalize_emails(changelog, mailmap))
|
||||||
|
else:
|
||||||
|
open(new_changelog, 'w').close()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_authors():
|
||||||
|
"""Create AUTHORS file using git commits."""
|
||||||
|
jenkins_email = 'jenkins@review.(openstack|stackforge).org'
|
||||||
|
old_authors = 'AUTHORS.in'
|
||||||
|
new_authors = 'AUTHORS'
|
||||||
|
if not os.getenv('SKIP_GENERATE_AUTHORS'):
|
||||||
|
if os.path.isdir('.git'):
|
||||||
|
# don't include jenkins email address in AUTHORS file
|
||||||
|
git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | "
|
||||||
|
"egrep -v '" + jenkins_email + "'")
|
||||||
|
changelog = _run_shell_command(git_log_cmd)
|
||||||
|
mailmap = parse_mailmap()
|
||||||
|
with open(new_authors, 'w') as new_authors_fh:
|
||||||
|
new_authors_fh.write(canonicalize_emails(changelog, mailmap))
|
||||||
|
if os.path.exists(old_authors):
|
||||||
|
with open(old_authors, "r") as old_authors_fh:
|
||||||
|
new_authors_fh.write('\n' + old_authors_fh.read())
|
||||||
|
else:
|
||||||
|
open(new_authors, 'w').close()
|
||||||
|
|
||||||
|
|
||||||
|
_rst_template = """%(heading)s
|
||||||
|
%(underline)s
|
||||||
|
|
||||||
|
.. automodule:: %(module)s
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def read_versioninfo(project):
|
||||||
|
"""Read the versioninfo file. If it doesn't exist, we're in a github
|
||||||
|
zipball, and there's really no way to know what version we really
|
||||||
|
are, but that should be ok, because the utility of that should be
|
||||||
|
just about nil if this code path is in use in the first place."""
|
||||||
|
versioninfo_path = os.path.join(project, 'versioninfo')
|
||||||
|
if os.path.exists(versioninfo_path):
|
||||||
|
with open(versioninfo_path, 'r') as vinfo:
|
||||||
|
version = vinfo.read().strip()
|
||||||
|
else:
|
||||||
|
version = "0.0.0"
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def write_versioninfo(project, version):
|
||||||
|
"""Write a simple file containing the version of the package."""
|
||||||
|
with open(os.path.join(project, 'versioninfo'), 'w') as fil:
|
||||||
|
fil.write("%s\n" % version)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cmdclass():
|
||||||
|
"""Return dict of commands to run from setup.py."""
|
||||||
|
|
||||||
|
cmdclass = dict()
|
||||||
|
|
||||||
|
def _find_modules(arg, dirname, files):
|
||||||
|
for filename in files:
|
||||||
|
if filename.endswith('.py') and filename != '__init__.py':
|
||||||
|
arg["%s.%s" % (dirname.replace('/', '.'),
|
||||||
|
filename[:-3])] = True
|
||||||
|
|
||||||
|
class LocalSDist(sdist.sdist):
|
||||||
|
"""Builds the ChangeLog and Authors files from VC first."""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
write_git_changelog()
|
||||||
|
generate_authors()
|
||||||
|
# sdist.sdist is an old style class, can't use super()
|
||||||
|
sdist.sdist.run(self)
|
||||||
|
|
||||||
|
cmdclass['sdist'] = LocalSDist
|
||||||
|
|
||||||
|
# If Sphinx is installed on the box running setup.py,
|
||||||
|
# enable setup.py to build the documentation, otherwise,
|
||||||
|
# just ignore it
|
||||||
|
try:
|
||||||
|
from sphinx.setup_command import BuildDoc
|
||||||
|
|
||||||
|
class LocalBuildDoc(BuildDoc):
|
||||||
|
def generate_autoindex(self):
|
||||||
|
print "**Autodocumenting from %s" % os.path.abspath(os.curdir)
|
||||||
|
modules = {}
|
||||||
|
option_dict = self.distribution.get_option_dict('build_sphinx')
|
||||||
|
source_dir = os.path.join(option_dict['source_dir'][1], 'api')
|
||||||
|
if not os.path.exists(source_dir):
|
||||||
|
os.makedirs(source_dir)
|
||||||
|
for pkg in self.distribution.packages:
|
||||||
|
if '.' not in pkg:
|
||||||
|
os.path.walk(pkg, _find_modules, modules)
|
||||||
|
module_list = modules.keys()
|
||||||
|
module_list.sort()
|
||||||
|
autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
|
||||||
|
with open(autoindex_filename, 'w') as autoindex:
|
||||||
|
autoindex.write(""".. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
""")
|
||||||
|
for module in module_list:
|
||||||
|
output_filename = os.path.join(source_dir,
|
||||||
|
"%s.rst" % module)
|
||||||
|
heading = "The :mod:`%s` Module" % module
|
||||||
|
underline = "=" * len(heading)
|
||||||
|
values = dict(module=module, heading=heading,
|
||||||
|
underline=underline)
|
||||||
|
|
||||||
|
print "Generating %s" % output_filename
|
||||||
|
with open(output_filename, 'w') as output_file:
|
||||||
|
output_file.write(_rst_template % values)
|
||||||
|
autoindex.write(" %s.rst\n" % module)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not os.getenv('SPHINX_DEBUG'):
|
||||||
|
self.generate_autoindex()
|
||||||
|
|
||||||
|
for builder in ['html', 'man']:
|
||||||
|
self.builder = builder
|
||||||
|
self.finalize_options()
|
||||||
|
self.project = self.distribution.get_name()
|
||||||
|
self.version = self.distribution.get_version()
|
||||||
|
self.release = self.distribution.get_version()
|
||||||
|
BuildDoc.run(self)
|
||||||
|
cmdclass['build_sphinx'] = LocalBuildDoc
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return cmdclass
|
||||||
|
|
||||||
|
|
||||||
|
def get_git_branchname():
|
||||||
|
for branch in _run_shell_command("git branch --color=never").split("\n"):
|
||||||
|
if branch.startswith('*'):
|
||||||
|
_branch_name = branch.split()[1].strip()
|
||||||
|
if _branch_name == "(no":
|
||||||
|
_branch_name = "no-branch"
|
||||||
|
return _branch_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_pre_version(projectname, base_version):
|
||||||
|
"""Return a version which is leading up to a version that will
|
||||||
|
be released in the future."""
|
||||||
|
if os.path.isdir('.git'):
|
||||||
|
current_tag = _get_git_current_tag()
|
||||||
|
if current_tag is not None:
|
||||||
|
version = current_tag
|
||||||
|
else:
|
||||||
|
branch_name = os.getenv('BRANCHNAME',
|
||||||
|
os.getenv('GERRIT_REFNAME',
|
||||||
|
get_git_branchname()))
|
||||||
|
version_suffix = _get_git_next_version_suffix(branch_name)
|
||||||
|
version = "%s~%s" % (base_version, version_suffix)
|
||||||
|
write_versioninfo(projectname, version)
|
||||||
|
return version
|
||||||
|
else:
|
||||||
|
version = read_versioninfo(projectname)
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def get_post_version(projectname):
|
||||||
|
"""Return a version which is equal to the tag that's on the current
|
||||||
|
revision if there is one, or tag plus number of additional revisions
|
||||||
|
if the current revision has no tag."""
|
||||||
|
|
||||||
|
if os.path.isdir('.git'):
|
||||||
|
version = _get_git_post_version()
|
||||||
|
write_versioninfo(projectname, version)
|
||||||
|
return version
|
||||||
|
return read_versioninfo(projectname)
|
68
setup.py
Normal file
68
setup.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
import setuptools
|
||||||
|
from openstack.common import setup
|
||||||
|
|
||||||
|
requires = setup.parse_requirements()
|
||||||
|
tests_requires = setup.parse_requirements(['tools/test-requires'])
|
||||||
|
|
||||||
|
ci_cmdclass = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sphinx.setup_command import BuildDoc
|
||||||
|
|
||||||
|
class local_BuildDoc(BuildDoc):
|
||||||
|
def run(self):
|
||||||
|
builders = ['html', 'man']
|
||||||
|
for builder in builders:
|
||||||
|
self.builder = builder
|
||||||
|
self.finalize_options()
|
||||||
|
BuildDoc.run(self)
|
||||||
|
|
||||||
|
class local_BuildDoc_latex(BuildDoc):
|
||||||
|
def run(self):
|
||||||
|
builders = ['latex']
|
||||||
|
for builder in builders:
|
||||||
|
self.builder = builder
|
||||||
|
self.finalize_options()
|
||||||
|
BuildDoc.run(self)
|
||||||
|
|
||||||
|
ci_cmdclass['build_sphinx'] = local_BuildDoc
|
||||||
|
ci_cmdclass['build_sphinx_latex'] = local_BuildDoc_latex
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
setup_reqs = ['Sphinx']
|
||||||
|
|
||||||
|
execfile('client/__init__.py')
|
||||||
|
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
name="python-libraclient",
|
||||||
|
version=__version__,
|
||||||
|
description="Python client for libra LBaaS solution",
|
||||||
|
author="Andrew Hutchings <andrew@linuxjedi.co.uk>",
|
||||||
|
packages=setuptools.find_packages(exclude=["*.tests"]),
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'libra_client = client.client:main',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
cmdclass=ci_cmdclass,
|
||||||
|
tests_require=tests_requires,
|
||||||
|
install_requires=requires,
|
||||||
|
setup_requires=setup_reqs
|
||||||
|
)
|
13
tests/__init__.py
Normal file
13
tests/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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.
|
330
tests/test_lbaas_client.py
Normal file
330
tests/test_lbaas_client.py
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
import json
|
||||||
|
import mock
|
||||||
|
import httplib2
|
||||||
|
import sys
|
||||||
|
import novaclient
|
||||||
|
import testtools
|
||||||
|
from StringIO import StringIO
|
||||||
|
from client.libraapi import LibraAPI
|
||||||
|
|
||||||
|
class DummyArgs(object):
|
||||||
|
""" Fake argparse response """
|
||||||
|
def __init__(self):
|
||||||
|
self.id = 2000
|
||||||
|
self.deleted = False
|
||||||
|
|
||||||
|
class DummyCreateArgs(object):
|
||||||
|
""" Fake argparse response for Create function """
|
||||||
|
def __init__(self):
|
||||||
|
self.name = 'a-new-loadbalancer'
|
||||||
|
self.node = ['10.1.1.1:80', '10.1.1.2:81']
|
||||||
|
self.port = None
|
||||||
|
self.protocol = None
|
||||||
|
self.algorithm = None
|
||||||
|
self.vip = None
|
||||||
|
|
||||||
|
class DummyModifyArgs(object):
|
||||||
|
""" Fake argparse response for Modify function """
|
||||||
|
def __init__(self):
|
||||||
|
self.id = 2012
|
||||||
|
self.name = 'a-modified-loadbalancer'
|
||||||
|
self.algorithm = 'LEAST_CONNECTIONS'
|
||||||
|
|
||||||
|
class MockLibraAPI(LibraAPI):
|
||||||
|
""" Used to capture data that would be sent to the API server """
|
||||||
|
def __init__(self, username, password, tenant, auth_url, region):
|
||||||
|
self.postdata = None
|
||||||
|
self.putdata = None
|
||||||
|
return super(MockLibraAPI, self).__init__(username, password, tenant, auth_url, region, False, False, None)
|
||||||
|
def _post(self, url, **kwargs):
|
||||||
|
""" Store the post data and execute as normal """
|
||||||
|
self.postdata = kwargs['body']
|
||||||
|
return super(MockLibraAPI, self)._post(url, **kwargs)
|
||||||
|
def _put(self, url, **kwargs):
|
||||||
|
""" Store the put data, no need to execute the httplib """
|
||||||
|
self.putdata = kwargs['body']
|
||||||
|
|
||||||
|
class TestLBaaSClientLibraAPI(testtools.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
""" Fake a login with token """
|
||||||
|
super(TestLBaaSClientLibraAPI, self).setUp()
|
||||||
|
self.api = MockLibraAPI('username', 'password', 'tenant', 'auth_test', 'region')
|
||||||
|
self.api.nova.management_url = "http://example.com"
|
||||||
|
self.api.nova.auth_token = "token"
|
||||||
|
|
||||||
|
def testListLb(self):
|
||||||
|
""" Test the table generated from the LIST function """
|
||||||
|
fake_response = httplib2.Response({"status": '200'})
|
||||||
|
fake_body = json.dumps({
|
||||||
|
"loadBalancers":[
|
||||||
|
{
|
||||||
|
"name":"lb-site1",
|
||||||
|
"id":"71",
|
||||||
|
"protocol":"HTTP",
|
||||||
|
"port":"80",
|
||||||
|
"algorithm":"LEAST_CONNECTIONS",
|
||||||
|
"status":"ACTIVE",
|
||||||
|
"created":"2010-11-30T03:23:42Z",
|
||||||
|
"updated":"2010-11-30T03:23:44Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"lb-site2",
|
||||||
|
"id":"166",
|
||||||
|
"protocol":"TCP",
|
||||||
|
"port":"9123",
|
||||||
|
"algorithm":"ROUND_ROBIN",
|
||||||
|
"status":"ACTIVE",
|
||||||
|
"created":"2010-11-30T03:23:42Z",
|
||||||
|
"updated":"2010-11-30T03:23:44Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
mock_request = mock.Mock(return_value=(fake_response, fake_body))
|
||||||
|
|
||||||
|
with mock.patch.object(httplib2.Http, "request", mock_request):
|
||||||
|
with mock.patch('time.time', mock.Mock(return_value=1234)):
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
args = DummyArgs()
|
||||||
|
self.api.list_lb(args)
|
||||||
|
output = out.getvalue().strip()
|
||||||
|
self.assertRegexpMatches(output, 'lb-site1')
|
||||||
|
self.assertRegexpMatches(output, '71')
|
||||||
|
self.assertRegexpMatches(output, 'HTTP')
|
||||||
|
self.assertRegexpMatches(output, '80')
|
||||||
|
self.assertRegexpMatches(output, 'LEAST_CONNECTIONS')
|
||||||
|
self.assertRegexpMatches(output, 'ACTIVE')
|
||||||
|
self.assertRegexpMatches(output, '2010-11-30T03:23:42Z')
|
||||||
|
self.assertRegexpMatches(output, '2010-11-30T03:23:44Z')
|
||||||
|
finally:
|
||||||
|
sys.stdout = orig
|
||||||
|
|
||||||
|
def testGetLb(self):
|
||||||
|
""" Test the table generated from the STATUS function """
|
||||||
|
fake_response = httplib2.Response({"status": '200'})
|
||||||
|
fake_body = json.dumps({
|
||||||
|
"id": "2000",
|
||||||
|
"name":"sample-loadbalancer",
|
||||||
|
"protocol":"HTTP",
|
||||||
|
"port": "80",
|
||||||
|
"algorithm":"ROUND_ROBIN",
|
||||||
|
"status":"ACTIVE",
|
||||||
|
"created":"2010-11-30T03:23:42Z",
|
||||||
|
"updated":"2010-11-30T03:23:44Z",
|
||||||
|
"virtualIps":[
|
||||||
|
{
|
||||||
|
"id": "1000",
|
||||||
|
"address":"2001:cdba:0000:0000:0000:0000:3257:9652",
|
||||||
|
"type":"PUBLIC",
|
||||||
|
"ipVersion":"IPV6"
|
||||||
|
}],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "1041",
|
||||||
|
"address":"10.1.1.1",
|
||||||
|
"port": "80",
|
||||||
|
"condition":"ENABLED",
|
||||||
|
"status":"ONLINE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1411",
|
||||||
|
"address":"10.1.1.2",
|
||||||
|
"port": "80",
|
||||||
|
"condition":"ENABLED",
|
||||||
|
"status":"ONLINE"
|
||||||
|
}],
|
||||||
|
"sessionPersistence":{
|
||||||
|
"persistenceType":"HTTP_COOKIE"
|
||||||
|
},
|
||||||
|
"connectionThrottle":{
|
||||||
|
"maxRequestRate": "50",
|
||||||
|
"rateInterval": "60"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock_request = mock.Mock(return_value=(fake_response, fake_body))
|
||||||
|
with mock.patch.object(httplib2.Http, "request", mock_request):
|
||||||
|
with mock.patch('time.time', mock.Mock(return_value=1234)):
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
args = DummyArgs()
|
||||||
|
self.api.status_lb(args)
|
||||||
|
output = out.getvalue().strip()
|
||||||
|
self.assertRegexpMatches(output, 'HTTP_COOKIE')
|
||||||
|
finally:
|
||||||
|
sys.stdout = orig
|
||||||
|
|
||||||
|
def testDeleteFailLb(self):
|
||||||
|
"""
|
||||||
|
Test a failure of a DELETE function. We don't test a succeed yet
|
||||||
|
since that has no response so nothing to assert on
|
||||||
|
"""
|
||||||
|
fake_response = httplib2.Response({"status": '500'})
|
||||||
|
fake_body = ''
|
||||||
|
mock_request = mock.Mock(return_value=(fake_response, fake_body))
|
||||||
|
with mock.patch.object(httplib2.Http, "request", mock_request):
|
||||||
|
with mock.patch('time.time', mock.Mock(return_value=1234)):
|
||||||
|
args = DummyArgs()
|
||||||
|
self.assertRaises(novaclient.exceptions.ClientException,
|
||||||
|
self.api.delete_lb, args)
|
||||||
|
|
||||||
|
def testCreateLb(self):
|
||||||
|
"""
|
||||||
|
Tests the CREATE function, tests that:
|
||||||
|
1. We send the correct POST data
|
||||||
|
2. We create a table from the response correctly
|
||||||
|
"""
|
||||||
|
fake_response = httplib2.Response({"status": '202'})
|
||||||
|
fake_body = json.dumps({
|
||||||
|
'name': 'a-new-loadbalancer',
|
||||||
|
'id': '144',
|
||||||
|
'protocol': 'HTTP',
|
||||||
|
'port': '83',
|
||||||
|
'algorithm': 'ROUND_ROBIN',
|
||||||
|
'status': 'BUILD',
|
||||||
|
'created': '2011-04-13T14:18:07Z',
|
||||||
|
'updated': '2011-04-13T14:18:07Z',
|
||||||
|
'virtualIps': [
|
||||||
|
{
|
||||||
|
'address': '15.0.0.1',
|
||||||
|
'id': '39',
|
||||||
|
'type': 'PUBLIC',
|
||||||
|
'ipVersion': 'IPV4',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'nodes': [
|
||||||
|
{
|
||||||
|
'address': '10.1.1.1',
|
||||||
|
'id': '653',
|
||||||
|
'port': '80',
|
||||||
|
'status': 'ONLINE',
|
||||||
|
'condition': 'ENABLED'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
# This is what the POST data should look like based on the args passed
|
||||||
|
post_compare = {
|
||||||
|
"name": "a-new-loadbalancer",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"address": "10.1.1.1",
|
||||||
|
"condition": "ENABLED",
|
||||||
|
"port": "80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "10.1.1.2",
|
||||||
|
"condition": "ENABLED",
|
||||||
|
"port": "81"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
mock_request = mock.Mock(return_value=(fake_response, fake_body))
|
||||||
|
with mock.patch.object(httplib2.Http, "request", mock_request):
|
||||||
|
with mock.patch('time.time', mock.Mock(return_value=1234)):
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
args = DummyCreateArgs()
|
||||||
|
self.api.create_lb(args)
|
||||||
|
self.assertEquals(post_compare, self.api.postdata)
|
||||||
|
output = out.getvalue().strip()
|
||||||
|
# At some point we should possibly compare the complete
|
||||||
|
# table rendering somehow instead of basic field data
|
||||||
|
self.assertRegexpMatches(output, 'ROUND_ROBIN')
|
||||||
|
self.assertRegexpMatches(output, 'BUILD')
|
||||||
|
self.assertRegexpMatches(output, '144')
|
||||||
|
finally:
|
||||||
|
sys.stdout = orig
|
||||||
|
|
||||||
|
def testCreateAddLb(self):
|
||||||
|
"""
|
||||||
|
Tests the CREATE function as above but adding a load balancer to a
|
||||||
|
virtual IP
|
||||||
|
"""
|
||||||
|
fake_response = httplib2.Response({"status": '202'})
|
||||||
|
fake_body = json.dumps({
|
||||||
|
'name': 'a-new-loadbalancer',
|
||||||
|
'id': '144',
|
||||||
|
'protocol': 'HTTP',
|
||||||
|
'port': '83',
|
||||||
|
'algorithm': 'ROUND_ROBIN',
|
||||||
|
'status': 'BUILD',
|
||||||
|
'created': '2011-04-13T14:18:07Z',
|
||||||
|
'updated': '2011-04-13T14:18:07Z',
|
||||||
|
'virtualIps': [
|
||||||
|
{
|
||||||
|
'address': '15.0.0.1',
|
||||||
|
'id': '39',
|
||||||
|
'type': 'PUBLIC',
|
||||||
|
'ipVersion': 'IPV4',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'nodes': [
|
||||||
|
{
|
||||||
|
'address': '10.1.1.1',
|
||||||
|
'id': '653',
|
||||||
|
'port': '80',
|
||||||
|
'status': 'ONLINE',
|
||||||
|
'condition': 'ENABLED'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
# This is what the POST data should look like based on the args passed
|
||||||
|
post_compare = {
|
||||||
|
"name": "a-new-loadbalancer",
|
||||||
|
"port": "83",
|
||||||
|
"protocol": "HTTP",
|
||||||
|
"virtualIps": [
|
||||||
|
{
|
||||||
|
"id": "39"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"address": "10.1.1.1",
|
||||||
|
"condition": "ENABLED",
|
||||||
|
"port": "80"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
mock_request = mock.Mock(return_value=(fake_response, fake_body))
|
||||||
|
with mock.patch.object(httplib2.Http, "request", mock_request):
|
||||||
|
with mock.patch('time.time', mock.Mock(return_value=1234)):
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
# Add args to add a LB to a VIP
|
||||||
|
args = DummyCreateArgs()
|
||||||
|
args.port = '83'
|
||||||
|
args.protocol = 'HTTP'
|
||||||
|
args.vip = '39'
|
||||||
|
args.node = ['10.1.1.1:80']
|
||||||
|
self.api.create_lb(args)
|
||||||
|
self.assertEquals(post_compare, self.api.postdata)
|
||||||
|
output = out.getvalue().strip()
|
||||||
|
# At some point we should possibly compare the complete
|
||||||
|
# table rendering somehow instead of basic field data
|
||||||
|
self.assertRegexpMatches(output, 'ROUND_ROBIN')
|
||||||
|
self.assertRegexpMatches(output, 'BUILD')
|
||||||
|
self.assertRegexpMatches(output, '144')
|
||||||
|
finally:
|
||||||
|
sys.stdout = orig
|
||||||
|
|
||||||
|
|
||||||
|
def testModifyLb(self):
|
||||||
|
"""
|
||||||
|
Tests the MODIFY function, no repsonse so we only test the PUT data
|
||||||
|
"""
|
||||||
|
# This is what the PUT data should look like based on the args passed
|
||||||
|
put_compare = {
|
||||||
|
"name": "a-modified-loadbalancer",
|
||||||
|
"algorithm": "LEAST_CONNECTIONS"
|
||||||
|
}
|
||||||
|
args = DummyModifyArgs()
|
||||||
|
self.api.modify_lb(args)
|
||||||
|
self.assertEquals(put_compare, self.api.putdata)
|
1
tools/pip-requires
Normal file
1
tools/pip-requires
Normal file
@ -0,0 +1 @@
|
|||||||
|
python_novaclient
|
5
tools/test-requires
Normal file
5
tools/test-requires
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pep8
|
||||||
|
mock
|
||||||
|
httplib2
|
||||||
|
testrepository>=0.0.8
|
||||||
|
testtools>=0.9.22
|
21
tox.ini
Normal file
21
tox.ini
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[tox]
|
||||||
|
envlist = py27,pep8
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
deps = -r{toxinidir}/tools/pip-requires
|
||||||
|
-r{toxinidir}/tools/test-requires
|
||||||
|
commands = bash -c 'if [ ! -d ./.testrepository ] ; then testr init ; fi'
|
||||||
|
bash -c 'testr run --parallel {posargs} ; RET=$? ; echo "Slowest Tests" ; testr slowest && exit $RET'
|
||||||
|
|
||||||
|
[tox:jenkins]
|
||||||
|
downloadcache = ~/cache/pip
|
||||||
|
|
||||||
|
[testenv:py27]
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
deps = pep8
|
||||||
|
commands = pep8 --repeat --show-source --exclude=.venv,.tox,dist,doc,*openstack/common*,*lib/python*,*egg client setup.py
|
||||||
|
|
||||||
|
[testenv:pyflakes]
|
||||||
|
deps = pyflakes
|
||||||
|
commands = pyflakes client
|
Loading…
x
Reference in New Issue
Block a user