API+ADMIN_API: SSL and DB fixes

* Add SSL to Admin API
* Improve debug flag on both APIs
* Make Admin API use its own model
* Improve DB session handling and rollbacks

Change-Id: I9d268079effbaca70c8c69d591bf48d494c1293f
This commit is contained in:
Andrew Hutchings 2013-06-10 14:10:30 +01:00
parent 971f37d680
commit df060ccd19
14 changed files with 83 additions and 70 deletions

View File

@ -76,6 +76,8 @@ poll_timeout_retry = 30
db_user=root
db_pass=passwd
db_schema=lbaas
ssl_certfile=certfile.crt
ssl_keyfile=keyfile.key
[api]
host=0.0.0.0

View File

@ -46,6 +46,10 @@ def setup_app(pecan_config, args):
'host': args.db_host,
'schema': args.db_schema
}
if args.debug:
config['wsme'] = {'debug': True}
config['app']['debug'] = True
pecan.configuration.set_config(config, overwrite=True)
app = pecan.make_app(
@ -93,11 +97,20 @@ def main():
options.parser.add_argument(
'--db_schema', help='MySQL schema for libra'
)
options.parser.add_argument(
'--ssl_cert',
help='Path to an SSL certificate file'
)
options.parser.add_argument(
'--ssl_keyfile',
help='Path to an SSL key file'
)
args = options.run()
required_args = [
'db_user', 'db_pass', 'db_host', 'db_schema'
'db_user', 'db_pass', 'db_host', 'db_schema', 'ssl_certfile',
'ssl_keyfile'
]
missing_args = 0
@ -131,6 +144,15 @@ def main():
logger.info('Starting on {0}:{1}'.format(args.host, args.port))
api = setup_app(pc, args)
sys.stderr = LogStdout(logger)
wsgi.server(eventlet.listen((args.host, args.port)), api)
# TODO: set ca_certs and cert_reqs=CERT_REQUIRED
wsgi.server(
eventlet.wrap_ssl(
eventlet.listen((args.host, args.port)),
certfile=args.ssl_certfile,
keyfile=args.ssl_keyfile,
server_side=True
),
api
)
return 0

View File

@ -19,31 +19,8 @@ app = {
'modules': ['libra.admin_api'],
'static_root': '%(confdir)s/public',
'template_path': '%(confdir)s/admin_api/templates',
'debug': True,
'errors': {
404: '/notfound',
'__force_dict__': True
}
}
wsme = {
'debug': True
}
#database = {
# 'username': 'root',
# 'password': 'testaburger',
# 'host': 'localhost',
# 'schema': 'lbaas'
#}
#gearman = {
# 'server': ['localhost:4730'],
#}
# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

View File

@ -21,8 +21,8 @@ import wsmeext.pecan as wsme_pecan
from wsme.exc import ClientSideError
from usage import UsageController
from libra.admin_api.model.validators import DeviceResp, DevicePost, DevicePut
from libra.api.model.lbaas import LoadBalancer, Device, session
from libra.api.model.lbaas import loadbalancers_devices
from libra.admin_api.model.lbaas import LoadBalancer, Device, session
from libra.admin_api.model.lbaas import loadbalancers_devices
class DevicesController(RestController):

View File

@ -14,7 +14,7 @@
# under the License.
from pecan import expose, response
from libra.api.model.lbaas import Device, session
from libra.admin_api.model.lbaas import Device, session
from libra.admin_api.model.responses import Responses
from pecan.rest import RestController

View File

@ -59,6 +59,10 @@ def setup_app(pecan_config, args):
config['gearman'] = {
'server': args.gearman
}
if args.debug:
config['wsme'] = {'debug': True}
config['app']['debug'] = True
pecan.configuration.set_config(config, overwrite=True)
app = pecan.make_app(

View File

@ -19,31 +19,8 @@ app = {
'modules': ['libra.api'],
'static_root': '%(confdir)s/public',
'template_path': '%(confdir)s/api/templates',
'debug': True,
'errors': {
404: '/notfound',
'__force_dict__': True
}
}
#wsme = {
# 'debug': True
#}
#database = {
# 'username': 'root',
# 'password': 'testaburger',
# 'host': 'localhost',
# 'schema': 'lbaas'
#}
#gearman = {
# 'server': ['localhost:4730'],
#}
# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

View File

@ -15,16 +15,18 @@
from pecan import expose
from pecan.rest import RestController
from libra.api.model.lbaas import Limits, session
from libra.api.model.lbaas import Limits, get_session
class LimitsController(RestController):
@expose('json')
def get(self):
resp = {}
session = get_session()
limits = session.query(Limits).all()
for limit in limits:
resp[limit.name] = limit.value
resp = {"limits": {"absolute": {"values": resp}}}
session.rollback()
return resp

View File

@ -25,7 +25,7 @@ from nodes import NodesController
from virtualips import VipsController
from logs import LogsController
# models
from libra.api.model.lbaas import LoadBalancer, Device, Node, session
from libra.api.model.lbaas import LoadBalancer, Device, Node, get_session
from libra.api.model.lbaas import loadbalancers_devices, Limits
from libra.api.model.validators import LBPut, LBPost, LBResp, LBVipResp
from libra.api.model.validators import LBRespNode
@ -58,7 +58,7 @@ class LoadBalancersController(RestController):
"""
tenant_id = get_limited_to_project(request.headers)
session = get_session()
# if we don't have an id then we want a list of them own by this tenent
if not load_balancer_id:
lbs = session.query(
@ -177,7 +177,7 @@ class LoadBalancersController(RestController):
raise ClientSideError(
'IP Address {0} not valid'.format(node.address)
)
session = get_session()
lblimit = session.query(Limits.value).\
filter(Limits.name == 'maxLoadBalancers').scalar()
nodelimit = session.query(Limits.value).\
@ -189,16 +189,19 @@ class LoadBalancersController(RestController):
filter(LoadBalancer.status != 'DELETED').count()
if len(body.name) > namelimit:
session.rollback()
raise ClientSideError(
'Length of Load Balancer name too long'
)
# TODO: this should probably be a 413, not sure how to do that yet
if count >= lblimit:
session.rollback()
raise OverLimit(
'Account has hit limit of {0} Load Balancers'.
format(lblimit)
)
if len(body.nodes) > nodelimit:
session.rollback()
raise OverLimit(
'Too many backend nodes supplied (limit is {0}'.
format(nodelimit)
@ -230,6 +233,7 @@ class LoadBalancersController(RestController):
filter(Device.id == virtual_id).\
first()
if old_lb is None:
session.rollback()
raise InvalidInput(
'virtualIps', virtual_id, 'Invalid virtual IP provided'
)
@ -243,6 +247,7 @@ class LoadBalancersController(RestController):
filter(LoadBalancer.protocol == 'HTTP').\
count()
if old_count:
session.rollback()
# Error here, can have only one HTTP
raise ClientSideError(
'Only one HTTP load balancer allowed per device'
@ -256,12 +261,14 @@ class LoadBalancersController(RestController):
filter(LoadBalancer.protocol == 'TCP').\
count()
if old_count:
session.rollback()
# Error here, can have only one TCP
raise ClientSideError(
'Only one TCP load balancer allowed per device'
)
if device is None:
session.rollback()
raise RuntimeError('No devices available')
lb.tenantid = tenant_id
@ -354,7 +361,7 @@ class LoadBalancersController(RestController):
raise ClientSideError('Load Balancer ID is required')
tenant_id = get_limited_to_project(request.headers)
session = get_session()
# grab the lb
lb = session.query(LoadBalancer).\
filter(LoadBalancer.id == self.lbid).\
@ -362,12 +369,14 @@ class LoadBalancersController(RestController):
filter(LoadBalancer.status != 'DELETED').first()
if lb is None:
session.rollback()
raise ClientSideError('Load Balancer ID is not valid')
if body.name != Unset:
namelimit = session.query(Limits.value).\
filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
if len(body.name) > namelimit:
session.rollback()
raise ClientSideError(
'Length of Load Balancer name too long'
)
@ -405,12 +414,14 @@ class LoadBalancersController(RestController):
"""
tenant_id = get_limited_to_project(request.headers)
# grab the lb
session = get_session()
lb = session.query(LoadBalancer).\
filter(LoadBalancer.id == load_balancer_id).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.status != 'DELETED').first()
if lb is None:
session.rollback()
response.status = 400
return dict(
faultcode="Client",
@ -435,6 +446,7 @@ class LoadBalancersController(RestController):
response.status = 202
return ''
except:
session.rollback()
logger = logging.getLogger(__name__)
logger.exception('Error communicating with load balancer pool')
response.status = 500

View File

@ -19,7 +19,7 @@ from pecan import conf
import wsmeext.pecan as wsme_pecan
from wsme.exc import ClientSideError
from wsme import Unset
from libra.api.model.lbaas import LoadBalancer, Device, session
from libra.api.model.lbaas import LoadBalancer, Device, get_session
from libra.api.acl import get_limited_to_project
from libra.api.model.validators import LBLogsPost
from libra.api.library.gearman_client import submit_job
@ -35,13 +35,14 @@ class LogsController(RestController):
raise ClientSideError('Load Balancer ID has not been supplied')
tenant_id = get_limited_to_project(request.headers)
session = get_session()
load_balancer = session.query(LoadBalancer).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.id == self.lbid).\
filter(LoadBalancer.status != 'DELETED').\
first()
if load_balancer is None:
session.rollback()
raise ClientSideError('Load Balancer not found')
load_balancer.status = 'PENDING_UPDATE'

View File

@ -20,7 +20,8 @@ import wsmeext.pecan as wsme_pecan
from wsme.exc import ClientSideError
from wsme import Unset
#default response objects
from libra.api.model.lbaas import LoadBalancer, Node, session, Limits, Device
from libra.api.model.lbaas import LoadBalancer, Node, get_session, Limits
from libra.api.model.lbaas import Device
from libra.api.acl import get_limited_to_project
from libra.api.model.validators import LBNodeResp, LBNodePost, NodeResp
from libra.api.model.validators import LBNodePut
@ -56,7 +57,7 @@ class NodesController(RestController):
faultcode='Client',
faultstring='Load Balancer ID not supplied'
)
session = get_session()
if not node_id:
nodes = session.query(
Node.id, Node.address, Node.port, Node.status, Node.enabled
@ -132,13 +133,14 @@ class NodesController(RestController):
raise ClientSideError(
'IP Address {0} not valid'.format(node.address)
)
session = get_session()
load_balancer = session.query(LoadBalancer).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.id == self.lbid).\
filter(LoadBalancer.status != 'DELETED').\
first()
if load_balancer is None:
session.rollback()
raise ClientSideError('Load Balancer not found')
load_balancer.status = 'PENDING_UPDATE'
@ -149,6 +151,7 @@ class NodesController(RestController):
filter(Node.lbid == self.lbid).count()
if (nodecount + len(body.nodes)) > nodelimit:
session.rollback()
raise OverLimit(
'Command would exceed Load Balancer node limit'
)
@ -195,6 +198,7 @@ class NodesController(RestController):
raise ClientSideError('Node ID has not been supplied')
tenant_id = get_limited_to_project(request.headers)
session = get_session()
# grab the lb
lb = session.query(LoadBalancer).\
filter(LoadBalancer.id == self.lbid).\
@ -202,6 +206,7 @@ class NodesController(RestController):
filter(LoadBalancer.status != 'DELETED').first()
if lb is None:
session.rollback()
raise ClientSideError('Load Balancer ID is not valid')
node = session.query(Node).\
@ -209,6 +214,7 @@ class NodesController(RestController):
filter(Node.id == self.nodeid).first()
if node is None:
session.rollback()
raise ClientSideError('Node ID is not valid')
if body.condition != Unset:
@ -250,12 +256,14 @@ class NodesController(RestController):
)
tenant_id = get_limited_to_project(request.headers)
session = get_session()
load_balancer = session.query(LoadBalancer).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.id == self.lbid).\
filter(LoadBalancer.device != 'DELETED').\
first()
if load_balancer is None:
session.rollback()
response.status = 400
return dict(
faultcode="Client",
@ -266,16 +274,18 @@ class NodesController(RestController):
filter(Node.lbid == self.lbid).count()
# Can't delete the last LB
if nodecount <= 1:
session.rollback()
response.status = 400
return dict(
faultcode="Client",
faultstring="Load Balancer not found"
faultstring="Cannot delete the last node in a load balancer"
)
node = session.query(Node).\
filter(Node.lbid == self.lbid).\
filter(Node.id == node_id).\
first()
if not node:
session.rollback()
response.status = 400
return dict(
faultcode="Client",

View File

@ -15,7 +15,7 @@
from pecan import response, expose, request
from pecan.rest import RestController
from libra.api.model.lbaas import LoadBalancer, Device, session
from libra.api.model.lbaas import LoadBalancer, Device, get_session
from libra.api.acl import get_limited_to_project
@ -41,6 +41,7 @@ class VipsController(RestController):
faultcode="Client",
faultstring="Load Balancer ID not provided"
)
session = get_session()
device = session.query(
Device.id, Device.floatingIpAddr
).join(LoadBalancer.devices).\
@ -48,6 +49,7 @@ class VipsController(RestController):
filter(LoadBalancer.tenantid == tenant_id).first()
if not device:
session.rollback()
response.status = 400
return dict(
faultcode="Client",
@ -61,5 +63,5 @@ class VipsController(RestController):
"ipVersion": "IPV4"
}]
}
session.rollback()
return resp

View File

@ -16,7 +16,7 @@ import eventlet
eventlet.monkey_patch()
import logging
from libra.common.json_gearman import JSONGearmanClient
from libra.api.model.lbaas import LoadBalancer, session, Device
from libra.api.model.lbaas import LoadBalancer, get_session, Device
from pecan import conf
@ -56,6 +56,7 @@ class GearmanClientThread(object):
self.gearman_client = JSONGearmanClient(conf.gearman.server)
def send_delete(self, data):
session = get_session()
count = session.query(
LoadBalancer
).join(LoadBalancer.devices).\
@ -100,7 +101,7 @@ class GearmanClientThread(object):
filter(LoadBalancer.id == self.lbid).\
first()
if not status:
self._set_error(data, response)
self._set_error(data, response, session)
else:
lb.status = 'DELETED'
if count == 0:
@ -111,7 +112,7 @@ class GearmanClientThread(object):
device.status = 'OFFLINE'
session.commit()
def _set_error(self, device_id, errmsg):
def _set_error(self, device_id, errmsg, session):
lbs = session.query(
LoadBalancer
).join(LoadBalancer.nodes).\
@ -128,6 +129,7 @@ class GearmanClientThread(object):
lb.errmsg = errmsg
def send_archive(self, data):
session = get_session()
lb = session.query(LoadBalancer).\
filter(LoadBalancer.id == self.lbid).\
first()
@ -155,6 +157,7 @@ class GearmanClientThread(object):
session.commit()
def send_update(self, data):
session = get_session()
lbs = session.query(
LoadBalancer
).join(LoadBalancer.nodes).\
@ -190,7 +193,7 @@ class GearmanClientThread(object):
filter(LoadBalancer.id == self.lbid).\
first()
if not status:
self._set_error(data, response)
self._set_error(data, response, session)
else:
lb.status = 'ACTIVE'
session.commit()

View File

@ -113,5 +113,6 @@ class Node(DeclarativeBase):
weight = Column(u'weight', INTEGER(), nullable=False)
"""session"""
session = sessionmaker(bind=engine)()
def get_session():
session = sessionmaker(bind=engine)()
return session