diff --git a/etc/sample_libra.cfg b/etc/sample_libra.cfg index 3cb8db52..771eadf5 100644 --- a/etc/sample_libra.cfg +++ b/etc/sample_libra.cfg @@ -83,6 +83,8 @@ db_user=root db_pass=passwd db_schema=lbaas gearman=127.0.0.1:4730 +swift_basepath=lbaaslogs +swift_endpoint=https://host.com:443/v1/ # Keystone options go here [keystone] diff --git a/libra/api/app.py b/libra/api/app.py index 361b9efd..bf0d20c4 100644 --- a/libra/api/app.py +++ b/libra/api/app.py @@ -47,6 +47,10 @@ def setup_app(pecan_config, args): 'host': args.db_host, 'schema': args.db_schema } + config['swift'] = { + 'swift_basepath': args.swift_basepath, + 'swift_endpoint': args.swift_endpoint + } config['gearman'] = { 'server': args.gearman } @@ -117,11 +121,20 @@ def main(): default='keystoneclient.middleware.auth_token:AuthProtocol', help='A colon separated module and class for keystone middleware' ) + options.parser.add_argument( + '--swift_basepath', + help='Default swift container to use for pushing log files to' + ) + options.parser.add_argument( + '--swift_endpoint', + help='Default endpoint URL (tenant ID will be appended to this' + ) args = options.run() required_args = [ - 'db_user', 'db_pass', 'db_host', 'db_schema' + 'db_user', 'db_pass', 'db_host', 'db_schema', 'swift_basepath', + 'swift_endpoint' ] missing_args = 0 diff --git a/libra/api/controllers/load_balancers.py b/libra/api/controllers/load_balancers.py index d6fe34d5..87c435ab 100644 --- a/libra/api/controllers/load_balancers.py +++ b/libra/api/controllers/load_balancers.py @@ -22,6 +22,7 @@ from wsme import Unset # other controllers 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 loadbalancers_devices, Limits @@ -423,6 +424,9 @@ class LoadBalancersController(RestController): return NodesController(lbid), remainder[1:] if remainder[0] == 'virtualips': return VipsController(lbid), remainder[1:] + if remainder[0] == 'logs': + return LogsController(lbid), remainder[1:] + # Kludgy fix for PUT since WSME doesn't like IDs on the path elif lbid: return LoadBalancersController(lbid), remainder diff --git a/libra/api/controllers/logs.py b/libra/api/controllers/logs.py new file mode 100644 index 00000000..f0a02a96 --- /dev/null +++ b/libra/api/controllers/logs.py @@ -0,0 +1,81 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# 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. + +from pecan import request +from pecan.rest import RestController +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.acl import get_limited_to_project +from libra.api.model.validators import LBLogsPost +from libra.api.library.gearman_client import submit_job + + +class LogsController(RestController): + def __init__(self, load_balancer_id=None): + self.lbid = load_balancer_id + + @wsme_pecan.wsexpose(None, body=LBLogsPost, status_code=202) + def post(self, body=None): + if self.lbid is None: + raise ClientSideError('Load Balancer ID has not been supplied') + + tenant_id = get_limited_to_project(request.headers) + + 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: + raise ClientSideError('Load Balancer not found') + + load_balancer.status = 'PENDING_UPDATE' + device = session.query( + Device.id, Device.name + ).join(LoadBalancer.devices).\ + filter(LoadBalancer.id == self.lbid).\ + first() + session.commit() + data = { + 'deviceid': device.id + } + if body.objectStoreType != Unset: + data['objectStoreType'] = body.objectStoreType.lower() + else: + data['objectStoreType'] = 'swift' + + if body.objectStoreBasePath != Unset: + data['objectStoreBasePath'] = body.objectStoreBasePath + else: + data['objectStoreBasePath'] = conf.swift.swift_basepath + + if body.objectStoreEndpoint != Unset: + data['objectStoreEndpoint'] = body.objectStoreEndpoint + else: + data['objectStoreEndpoint'] = '{0}/{1}'.\ + format(conf.swift.swift_endpoint.rstrip('/'), tenant_id) + + if body.authToken != Unset: + data['authToken'] = body.authToken + else: + data['authToken'] = request.headers.get('X-Auth-Token') + + submit_job( + 'ARCHIVE', device.name, data, self.lbid + ) + return diff --git a/libra/api/controllers/virtualips.py b/libra/api/controllers/virtualips.py new file mode 100644 index 00000000..1baac6c0 --- /dev/null +++ b/libra/api/controllers/virtualips.py @@ -0,0 +1,65 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# 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. + +from pecan import response, expose, request +from pecan.rest import RestController +from libra.api.model.lbaas import LoadBalancer, Device, session +from libra.api.acl import get_limited_to_project + + +class VipsController(RestController): + def __init__(self, load_balancer_id=None): + self.lbid = load_balancer_id + + @expose('json') + def get(self): + """Returns a list of virtual ips attached to a specific Load Balancer. + + :param load_balancer_id: id of lb + + Url: + GET /loadbalancers/{load_balancer_id}/virtualips + + Returns: dict + """ + tenant_id = get_limited_to_project(request.headers) + if not self.lbid: + response.status = 400 + return dict( + faultcode="Client", + faultstring="Load Balancer ID not provided" + ) + device = session.query( + Device.id, Device.floatingIpAddr + ).join(LoadBalancer.devices).\ + filter(LoadBalancer.id == self.lbid).\ + filter(LoadBalancer.tenantid == tenant_id).first() + + if not device: + response.status = 400 + return dict( + faultcode="Client", + faultstring="Load Balancer ID not valid" + ) + resp = { + "virtualIps": [{ + "id": device.id, + "address": device.floatingIpAddr, + "type": "PUBLIC", + "ipVersion": "IPV4" + }] + } + + return resp diff --git a/libra/api/library/gearman_client.py b/libra/api/library/gearman_client.py index 287f3f31..88d608a6 100644 --- a/libra/api/library/gearman_client.py +++ b/libra/api/library/gearman_client.py @@ -44,6 +44,8 @@ def client_job(logger, job_type, host, data, lbid): client.send_update(data) if job_type == 'DELETE': client.send_delete(data) + if job_type == 'ARCHIVE': + client.send_archive(data) class GearmanClientThread(object): @@ -124,6 +126,33 @@ class GearmanClientThread(object): lb.status = 'ERROR' lb.errmsg = errmsg + def send_archive(self, data): + lb = session.query(LoadBalancer).\ + filter(LoadBalancer.id == self.lbid).\ + first() + job_data = { + 'hpcs_action': 'ARCHIVE', + 'hpcs_object_store_basepath': data['objectStoreBasePath'], + 'hpcs_object_store_endpoint': data['objectStoreEndpoint'], + 'hpcs_object_store_token': data['authToken'], + 'hpcs_object_store_type': data['objectStoreType'], + 'loadBalancers': [{ + 'id': str(lb.id), + 'name': lb.name, + 'protocol': lb.protocol + }] + } + status, response = self._send_message(job_data) + device = session.query(Device).\ + filter(Device.id == data['deviceid']).\ + first() + if status: + device.errmsg = 'Log archive successful' + else: + device.errmsg = 'Log archive failed: {0}'.format(response) + lb.status = 'ACTIVE' + session.commit() + def send_update(self, data): lbs = session.query( LoadBalancer diff --git a/libra/api/model/validators.py b/libra/api/model/validators.py index ffb2a2b2..b88c400e 100644 --- a/libra/api/model/validators.py +++ b/libra/api/model/validators.py @@ -69,6 +69,13 @@ class LBVipResp(Base): ipVersion = wtypes.text +class LBLogsPost(Base): + objectStoreType = Enum(wtypes.text, 'Swift') + objectStoreEndpoint = wtypes.text + objectStoreBasePath = wtypes.text + authToken = wtypes.text + + class LBResp(Base): id = int name = wtypes.text