neutron-lbaas/neutron_lbaas/drivers/octavia/driver.py
Carlos D. Garza 0bf1965057 Implementing EventStreamer reciever for octavia
Listens to oslo_messaging for database updates from octavia.

When neutron-lbaas is being used with octavia this queing system will be
used to so that health check and status updates from octavia are sent to
neutron-lbaas so that neutron updates its database in sync with octavia.
See CR https://review.openstack.org/218735 for details on the octavia
side.

This event handler can actually be used for other drivers as long,
but for now its only used for octavia.

Co-Authored-By: Brandon Logan <brandon.logan@rackspace.com>
Change-Id: I4694d9830d37c6432ff64497396be120dda31501
2016-01-19 00:46:35 -06:00

402 lines
13 KiB
Python

# Copyright 2015, A10 Networks
#
# 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 datetime import datetime
from functools import wraps
import threading
import time
from neutron import context as ncontext
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_service import service
from oslo_utils import excutils
import requests
from neutron_lbaas._i18n import _
from neutron_lbaas.common import keystone
from neutron_lbaas.drivers import driver_base
from neutron_lbaas.drivers.octavia import octavia_messaging_consumer
LOG = logging.getLogger(__name__)
VERSION = "1.0.1"
OPTS = [
cfg.StrOpt(
'base_url',
default='http://127.0.0.1:9876',
help=_('URL of Octavia controller root'),
),
cfg.IntOpt(
'request_poll_interval',
default=3,
help=_('Interval in seconds to poll octavia when an entity is created,'
' updated, or deleted.')
),
cfg.IntOpt(
'request_poll_timeout',
default=100,
help=_('Time to stop polling octavia when a status of an entity does '
'not change.')
),
cfg.BoolOpt(
'allocates_vip',
default=False,
help=_('True if Octavia will be responsible for allocating the VIP.'
' False if neutron-lbaas will allocate it and pass to Octavia.')
)
]
cfg.CONF.register_opts(OPTS, 'octavia')
def thread_op(manager, entity, delete=False, lb_create=False):
context = ncontext.get_admin_context()
poll_interval = cfg.CONF.octavia.request_poll_interval
poll_timeout = cfg.CONF.octavia.request_poll_timeout
start_dt = datetime.now()
prov_status = None
while (datetime.now() - start_dt).seconds < poll_timeout:
octavia_lb = manager.driver.load_balancer.get(entity.root_loadbalancer)
prov_status = octavia_lb.get('provisioning_status')
LOG.debug("Octavia reports load balancer {0} has provisioning status "
"of {1}".format(entity.root_loadbalancer.id, prov_status))
if prov_status == 'ACTIVE' or prov_status == 'DELETED':
kwargs = {'delete': delete}
if manager.driver.allocates_vip and lb_create:
kwargs['lb_create'] = lb_create
# TODO(blogan): drop fk constraint on vip_port_id to ports
# table because the port can't be removed unless the load
# balancer has been deleted. Until then we won't populate the
# vip_port_id field.
# entity.vip_port_id = octavia_lb.get('vip').get('port_id')
entity.vip_address = octavia_lb.get('vip').get('ip_address')
manager.successful_completion(context, entity, **kwargs)
return
elif prov_status == 'ERROR':
manager.failed_completion(context, entity)
return
time.sleep(poll_interval)
LOG.debug("Timeout has expired for load balancer {0} to complete an "
"operation. The last reported status was "
"{1}".format(entity.root_loadbalancer.id, prov_status))
manager.failed_completion(context, entity)
# A decorator for wrapping driver operations, which will automatically
# set the neutron object's status based on whether it sees an exception
def async_op(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
d = (func.__name__ == 'delete')
lb_create = ((func.__name__ == 'create') and
isinstance(args[0], LoadBalancerManager))
try:
r = func(*args, **kwargs)
thread = threading.Thread(target=thread_op,
args=(args[0], args[2]),
kwargs={'delete': d,
'lb_create': lb_create})
thread.setDaemon(True)
thread.start()
return r
except Exception:
with excutils.save_and_reraise_exception():
args[0].failed_completion(args[1], args[2])
return func_wrapper
class OctaviaRequest(object):
def __init__(self, base_url, auth_session):
self.base_url = base_url
self.auth_session = auth_session
def request(self, method, url, args=None, headers=None):
if args:
if not headers:
token = self.auth_session.get_token()
headers = {
'Content-type': 'application/json',
'X-Auth-Token': token
}
args = jsonutils.dumps(args)
LOG.debug("url = %s", '%s%s' % (self.base_url, str(url)))
LOG.debug("args = %s", args)
r = requests.request(method,
'%s%s' % (self.base_url, str(url)),
data=args,
headers=headers)
LOG.debug("Octavia Response Code: {0}".format(r.status_code))
LOG.debug("Octavia Response Body: {0}".format(r.content))
LOG.debug("Octavia Response Headers: {0}".format(r.headers))
if method != 'DELETE':
return r.json()
def post(self, url, args):
return self.request('POST', url, args)
def put(self, url, args):
return self.request('PUT', url, args)
def delete(self, url):
self.request('DELETE', url)
def get(self, url):
return self.request('GET', url)
class OctaviaDriver(driver_base.LoadBalancerBaseDriver):
def __init__(self, plugin):
super(OctaviaDriver, self).__init__(plugin)
self.req = OctaviaRequest(cfg.CONF.octavia.base_url,
keystone.get_session())
self.load_balancer = LoadBalancerManager(self)
self.listener = ListenerManager(self)
self.pool = PoolManager(self)
self.member = MemberManager(self)
self.health_monitor = HealthMonitorManager(self)
self.octavia_consumer = octavia_messaging_consumer.OctaviaConsumer(
self)
service.launch(cfg.CONF, self.octavia_consumer)
LOG.debug("OctaviaDriver: initialized, version=%s", VERSION)
@property
def allocates_vip(self):
return self.load_balancer.allocates_vip
class LoadBalancerManager(driver_base.BaseLoadBalancerManager):
@staticmethod
def _url(lb, id=None):
s = '/v1/loadbalancers'
if id:
s += '/%s' % id
return s
@property
def allocates_vip(self):
return cfg.CONF.octavia.allocates_vip
def create_and_allocate_vip(self, context, lb):
self.create(context, lb)
@async_op
def create(self, context, lb):
args = {
'id': lb.id,
'name': lb.name,
'description': lb.description,
'enabled': lb.admin_state_up,
'project_id': lb.tenant_id,
'vip': {
'subnet_id': lb.vip_subnet_id,
'ip_address': lb.vip_address,
'port_id': lb.vip_port_id,
}
}
self.driver.req.post(self._url(lb), args)
@async_op
def update(self, context, old_lb, lb):
args = {
'name': lb.name,
'description': lb.description,
'enabled': lb.admin_state_up,
}
self.driver.req.put(self._url(lb, lb.id), args)
@async_op
def delete(self, context, lb):
self.driver.req.delete(self._url(lb, lb.id))
@async_op
def refresh(self, context, lb):
pass
def stats(self, context, lb):
return {} # todo
def get(self, lb):
return self.driver.req.get(self._url(lb, lb.id))
class ListenerManager(driver_base.BaseListenerManager):
@staticmethod
def _url(listener, id=None):
s = '/v1/loadbalancers/%s/listeners' % listener.loadbalancer.id
if id:
s += '/%s' % id
return s
@classmethod
def _write(cls, write_func, url, listener, create=True):
sni_container_ids = [sni.tls_container_id
for sni in listener.sni_containers]
args = {
'name': listener.name,
'description': listener.description,
'enabled': listener.admin_state_up,
'protocol': listener.protocol,
'protocol_port': listener.protocol_port,
'connection_limit': listener.connection_limit,
'tls_certificate_id': listener.default_tls_container_id,
'sni_containers': sni_container_ids
}
if create:
args['project_id'] = listener.tenant_id
args['id'] = listener.id
write_func(url, args)
@async_op
def create(self, context, listener):
self._write(self.driver.req.post, self._url(listener), listener)
@async_op
def update(self, context, old_listener, listener):
self._write(self.driver.req.put, self._url(listener, id=listener.id),
listener, create=False)
@async_op
def delete(self, context, listener):
self.driver.req.delete(self._url(listener, id=listener.id))
class PoolManager(driver_base.BasePoolManager):
@staticmethod
def _url(pool, id=None):
s = '/v1/loadbalancers/%s/listeners/%s/pools' % (
pool.listener.loadbalancer.id,
pool.listener.id)
if id:
s += '/%s' % id
return s
@classmethod
def _write(cls, write_func, url, pool, create=True):
args = {
'name': pool.name,
'description': pool.description,
'enabled': pool.admin_state_up,
'protocol': pool.protocol,
'lb_algorithm': pool.lb_algorithm
}
if pool.session_persistence:
args['session_persistence'] = {
'type': pool.session_persistence.type,
'cookie_name': pool.session_persistence.cookie_name,
}
if create:
args['project_id'] = pool.tenant_id
args['id'] = pool.id
write_func(url, args)
@async_op
def create(self, context, pool):
self._write(self.driver.req.post, self._url(pool), pool)
@async_op
def update(self, context, old_pool, pool):
self._write(self.driver.req.put, self._url(pool, id=pool.id), pool,
create=False)
@async_op
def delete(self, context, pool):
self.driver.req.delete(self._url(pool, id=pool.id))
class MemberManager(driver_base.BaseMemberManager):
@staticmethod
def _url(member, id=None):
s = '/v1/loadbalancers/%s/listeners/%s/pools/%s/members' % (
member.pool.listener.loadbalancer.id,
member.pool.listener.id,
member.pool.id)
if id:
s += '/%s' % id
return s
@async_op
def create(self, context, member):
args = {
'id': member.id,
'enabled': member.admin_state_up,
'ip_address': member.address,
'protocol_port': member.protocol_port,
'weight': member.weight,
'subnet_id': member.subnet_id,
'project_id': member.tenant_id
}
self.driver.req.post(self._url(member), args)
@async_op
def update(self, context, old_member, member):
args = {
'enabled': member.admin_state_up,
'protocol_port': member.protocol_port,
'weight': member.weight,
}
self.driver.req.put(self._url(member, member.id), args)
@async_op
def delete(self, context, member):
self.driver.req.delete(self._url(member, member.id))
class HealthMonitorManager(driver_base.BaseHealthMonitorManager):
@staticmethod
def _url(hm):
s = '/v1/loadbalancers/%s/listeners/%s/pools/%s/healthmonitor' % (
hm.pool.listener.loadbalancer.id,
hm.pool.listener.id,
hm.pool.id)
return s
@classmethod
def _write(cls, write_func, url, hm, create=True):
args = {
'type': hm.type,
'delay': hm.delay,
'timeout': hm.timeout,
'rise_threshold': hm.max_retries,
'fall_threshold': hm.max_retries,
'http_method': hm.http_method,
'url_path': hm.url_path,
'expected_codes': hm.expected_codes,
'enabled': hm.admin_state_up
}
if create:
args['project_id'] = hm.tenant_id
write_func(cls._url(hm), args)
@async_op
def create(self, context, hm):
self._write(self.driver.req.post, self._url(hm), hm)
@async_op
def update(self, context, old_hm, hm):
self._write(self.driver.req.put, self._url(hm), hm, create=False)
@async_op
def delete(self, context, hm):
self.driver.req.delete(self._url(hm))