Merge "Defining interface for amphora base driver"
This commit is contained in:
commit
2a1e144911
|
@ -0,0 +1,119 @@
|
|||
# Copyright 2011-2014 OpenStack Foundation,author: Min Wang,German Eichberger
|
||||
#
|
||||
# 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 octavia.openstack.common import excutils
|
||||
|
||||
|
||||
class AmphoraDriverError(Exception):
|
||||
|
||||
message = _("A super class for all other exceptions and the catch.")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
try:
|
||||
super(AmphoraDriverError, self).__init__(self.message % kwargs)
|
||||
self.msg = self.message % kwargs
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
if not self.use_fatal_exceptions():
|
||||
ctxt.reraise = False
|
||||
# at least get the core message out if something happened
|
||||
super(AmphoraDriverError, self).__init__(self.message)
|
||||
|
||||
def __unicode__(self):
|
||||
|
||||
return unicode(self.msg)
|
||||
|
||||
def use_fatal_exceptions(self):
|
||||
"""Return True if use fatal exceptions by raising them."""
|
||||
return False
|
||||
|
||||
|
||||
class NotFoundError(AmphoraDriverError):
|
||||
|
||||
message = _('this amphora couldn\'t be found')
|
||||
|
||||
|
||||
class InfoException(AmphoraDriverError):
|
||||
|
||||
message = _('gathering information about this amphora failed')
|
||||
|
||||
|
||||
class MetricsException(AmphoraDriverError):
|
||||
|
||||
message = _('gathering metrics failed')
|
||||
|
||||
|
||||
class UnauthorizedException(AmphoraDriverError):
|
||||
|
||||
message = _('the driver can\'t access the amphora')
|
||||
|
||||
|
||||
class StatisticsException(AmphoraDriverError):
|
||||
|
||||
message = _('gathering statistics failed')
|
||||
|
||||
|
||||
class TimeOutException(AmphoraDriverError):
|
||||
|
||||
message = _('contacting the amphora timed out')
|
||||
|
||||
|
||||
class UnavailableException(AmphoraDriverError):
|
||||
|
||||
message = _('the amphora is temporary unavailable')
|
||||
|
||||
|
||||
class DeleteFailed(AmphoraDriverError):
|
||||
|
||||
message = _('this load balancer couldn\'t be deleted')
|
||||
|
||||
|
||||
class SuspendFailed(AmphoraDriverError):
|
||||
|
||||
message = _('this load balancer couldn\'t be suspended')
|
||||
|
||||
|
||||
class EnableFailed(AmphoraDriverError):
|
||||
|
||||
message = _('this load balancer couldn\'t be enabled')
|
||||
|
||||
|
||||
class ArchiveException(AmphoraDriverError):
|
||||
|
||||
message = _('couldn\'t archive the logs')
|
||||
|
||||
|
||||
class ProvisioningErrors(AmphoraDriverError):
|
||||
|
||||
message = _('Super class for provisioning amphora errors')
|
||||
|
||||
|
||||
class ListenerProvisioningError(ProvisioningErrors):
|
||||
|
||||
message = _('couldn\'t provision Listener')
|
||||
|
||||
|
||||
class LoadBalancerProvisoningError(ProvisioningErrors):
|
||||
|
||||
message = _('couldn\'t provision LoadBalancer')
|
||||
|
||||
|
||||
class HealthMonitorProvisioningError(ProvisioningErrors):
|
||||
|
||||
message = _('couldn\'t provision HealthMonitor')
|
||||
|
||||
|
||||
class NodeProvisioningError(ProvisioningErrors):
|
||||
|
||||
message = _('couldn\'t provision Node')
|
|
@ -0,0 +1,210 @@
|
|||
# Copyright 2011-2014 OpenStack Foundation,author: Min Wang,German Eichberger
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AmphoraLoadBalancerDriver(object):
|
||||
@abc.abstractmethod
|
||||
def get_logger(self):
|
||||
"""Return the logger to use.
|
||||
|
||||
This is a way to inject a custom logger for testing,etc.
|
||||
:returns: the logger
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, listener, vip):
|
||||
"""Update the amphora with a new configuration
|
||||
|
||||
:param listener: listener object,
|
||||
need to use its protocol_port property
|
||||
:type listener: object
|
||||
:param vip: vip object, need to use its ip_address property
|
||||
:type vip: object
|
||||
:returns: return a value list (listener, vip, status flag--update)
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def disable(self, listener, vip):
|
||||
"""Suspend the running amphora --optional
|
||||
|
||||
:param listener: listener object,
|
||||
need to use its protocol_port property
|
||||
:type listener: object
|
||||
:param vip: vip object, need to use its ip_address property
|
||||
:type vip: object
|
||||
:returns: return a value list (listener, vip, status flag--suspend)
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def enable(self, listener, vip):
|
||||
"""Start/enable the listener
|
||||
|
||||
:param listener: listener object,
|
||||
need to use its protocol_port property
|
||||
:type listener: object
|
||||
:param vip : vip object, need to use its ip_address property
|
||||
:type vip: object
|
||||
:returns: return a value list (listener, vip, status flag--enable)
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete(self, listener, vip):
|
||||
"""Delete the listener from the amphora
|
||||
|
||||
:param listener: listener object,
|
||||
need to use its protocol_port property
|
||||
:type listener: object
|
||||
:param vip: vip object, need to use its ip_address property
|
||||
:type vip: object
|
||||
:returns: return a value list (listener, vip, status flag--delete)
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def info(self, amphora):
|
||||
"""Returns information about the amphora
|
||||
|
||||
:param amphora: amphora object, need to use its id property
|
||||
:type amphora: object
|
||||
:returns: return a value list (amphora.id, status flag--'info')
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development, eventually, we want it
|
||||
to return information as:
|
||||
{"Rest Interface": "1.0", "Amphorae": "1.0",
|
||||
"packages":{"ha proxy":"1.5"}}
|
||||
some information might come from querying the amphora
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_metrics(self, amphora):
|
||||
"""Return ceilometer ready metrics
|
||||
|
||||
Some amphora might choose to send them straight to ceilometer others
|
||||
might use the mixin support metrics to be compatible with Neutron LBaaS
|
||||
|
||||
:param amphora: amphora object, need to use its id property
|
||||
:type amphora: object
|
||||
:returns: return a value list (amphora.id, status flag--'get_metrics')
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development, eventually, we want it
|
||||
to return information as:
|
||||
{"Rest Interface": "1.0", "Amphorae": "1.0",
|
||||
"packages":{"ha proxy":"1.5"}}
|
||||
some information might come from querying the amphora
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_health(self, amphora):
|
||||
"""Return ceilometer ready health
|
||||
|
||||
:param amphora: amphora object, need to use its id property
|
||||
:type amphora: object
|
||||
:returns: return a value list (amphora.id, status flag--'get_health')
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development, eventually, we want it
|
||||
to return information as:
|
||||
returns map: {"amphora-status":HEALTHY,
|
||||
loadbalancers: {"loadbalancer-id": {"loadbalancer-status": HEALTHY,
|
||||
"listeners":{"listener-id":{"listener-status":HEALTHY,
|
||||
"nodes":{"node-id":HEALTHY, ...}}, ...}, ...}}
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_diagnostics(self, amphora):
|
||||
"""Return ceilometer ready health
|
||||
|
||||
:param amphora: amphora object, need to use its id property
|
||||
:type amphora: object
|
||||
:returns: return a value list (amphora.id, status flag--'ge
|
||||
t_diagnostics')
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development, eventually, we want it
|
||||
run some expensive self tests to determine if the amphora and the lbs
|
||||
are healthy the idea is that those tests are triggered more infrequent
|
||||
than the health gathering
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class HealthMixin(object):
|
||||
@abc.abstractmethod
|
||||
def update_health(self, health):
|
||||
"""Return ceilometer ready health
|
||||
|
||||
:param health: health information emitted from the amphora
|
||||
:type health: bool
|
||||
:returns: return health
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development, eventually, we want it
|
||||
return:
|
||||
map: {"amphora-status":HEALTHY, loadbalancers: {"loadbalancer-id":
|
||||
{"loadbalancer-status": HEALTHY,
|
||||
"listeners":{"listener-id":{"listener-status":HEALTHY,
|
||||
"nodes":{"node-id":HEALTHY, ...}}, ...}, ...}}
|
||||
only items whose health has changed need to be submitted
|
||||
awesome update code
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class StatsMixin(object):
|
||||
@abc.abstractmethod
|
||||
def update_stats(self, stats):
|
||||
"""Return ceilometer ready stats
|
||||
|
||||
:param stats: statistic information emitted from the amphora
|
||||
:type stats: string
|
||||
:returns: return stats
|
||||
|
||||
At this moment, we just build the basic structure for testing, will
|
||||
add more function along with the development, eventually, we want it
|
||||
return:
|
||||
uses map {"loadbalancer-id":{"listener-id":
|
||||
{"bytes-in": 123, "bytes_out":123, "active_connections":123,
|
||||
"total_connections", 123}, ...}
|
||||
elements are named to keep it extsnsible for future versions
|
||||
awesome update code and code to send to ceilometer
|
||||
"""
|
||||
pass
|
|
@ -0,0 +1,128 @@
|
|||
# Copyright 2014, Author: Min Wang,German Eichberger
|
||||
#
|
||||
# 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 octavia.amphorae.drivers import driver_base as driver_base
|
||||
from octavia.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoggingMixIn(driver_base.HealthMixin, driver_base.StatsMixin):
|
||||
def update_stats(self, stats):
|
||||
LOG.debug("Amphora %s no-op, update stats %s",
|
||||
self.__class__.__name__, stats)
|
||||
self.stats = stats
|
||||
|
||||
def update_health(self, health):
|
||||
LOG.debug("Amphora %s no-op, update health %s",
|
||||
self.__class__.__name__, health)
|
||||
self.health = health
|
||||
|
||||
|
||||
class NoopManager(object):
|
||||
|
||||
def __init__(self):
|
||||
super(NoopManager, self).__init__()
|
||||
self.amphoraconfig = {}
|
||||
|
||||
def update(self, listener, vip):
|
||||
LOG.debug("Amphora %s no-op, update listener %s, vip %s",
|
||||
self.__class__.__name__, listener.protocol_port,
|
||||
vip.ip_address)
|
||||
self.amphoraconfig[(listener.protocol_port,
|
||||
vip.ip_address)] = (listener, vip, 'active')
|
||||
|
||||
def disable(self, listener, vip):
|
||||
LOG.debug("Amphora %s no-op, suspend listener %s, vip %s",
|
||||
self.__class__.__name__,
|
||||
listener.protocol_port, vip.ip_address)
|
||||
self.amphoraconfig[(listener.protocol_port,
|
||||
vip.ip_address)] = (listener, vip, 'disable')
|
||||
|
||||
def delete(self, listener, vip):
|
||||
LOG.debug("Amphora %s no-op, delete listener %s, vip %s",
|
||||
self.__class__.__name__,
|
||||
listener.protocol_port, vip.ip_address)
|
||||
self.amphoraconfig[(listener.protocol_port,
|
||||
vip.ip_address)] = (listener, vip, 'delete')
|
||||
|
||||
def enable(self, listener, vip):
|
||||
LOG.debug("Amphora %s no-op, enable listener %s, vip %s",
|
||||
self.__class__.__name__,
|
||||
listener.protocol_port, vip.ip_address)
|
||||
self.amphoraconfig[(listener.protocol_port,
|
||||
vip.ip_address)] = (listener, vip, 'enable')
|
||||
|
||||
def info(self, amphora):
|
||||
LOG.debug("Amphora %s no-op, info amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
self.amphoraconfig[amphora.id] = (amphora.id, 'info')
|
||||
|
||||
def get_metrics(self, amphora):
|
||||
LOG.debug("Amphora %s no-op, get metrics amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
self.amphoraconfig[amphora.id] = (amphora.id, 'get_metrics')
|
||||
|
||||
def get_health(self, amphora):
|
||||
LOG.debug("Amphora %s no-op, get health amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
self.amphoraconfig[amphora.id] = (amphora.id, 'get_health')
|
||||
|
||||
def get_diagnostics(self, amphora):
|
||||
LOG.debug("Amphora %s no-op, get diagnostics amphora %s",
|
||||
self.__class__.__name__, amphora.id)
|
||||
self.amphoraconfig[amphora.id] = (amphora.id, 'get_diagnostics')
|
||||
|
||||
|
||||
class NoopAmphoraLoadBalancerDriver(driver_base.AmphoraLoadBalancerDriver):
|
||||
def __init__(self, log):
|
||||
super(NoopAmphoraLoadBalancerDriver, self).__init__()
|
||||
self.log = log
|
||||
self.driver = NoopManager()
|
||||
|
||||
def get_logger(self):
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, listener, vip):
|
||||
|
||||
self.driver.update(listener, vip)
|
||||
|
||||
def disable(self, listener, vip):
|
||||
|
||||
self.driver.disable(listener, vip)
|
||||
|
||||
def enable(self, listener, vip):
|
||||
|
||||
self.driver.enable(listener, vip)
|
||||
|
||||
def delete(self, listener, vip):
|
||||
|
||||
self.driver.delete(listener, vip)
|
||||
|
||||
def info(self, amphora):
|
||||
|
||||
self.driver.info(amphora)
|
||||
|
||||
def get_metrics(self, amphora):
|
||||
|
||||
self.driver.get_metrics(amphora)
|
||||
|
||||
def get_health(self, amphora):
|
||||
|
||||
self.driver.get_health(amphora)
|
||||
|
||||
def get_diagnostics(self, amphora):
|
||||
|
||||
self.driver.get_diagnostics(amphora)
|
|
@ -0,0 +1,105 @@
|
|||
# Copyright 2014, Author: Min Wang,German Eichberger
|
||||
#
|
||||
# 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 octavia.amphorae.drivers.noop_driver import driver as driver
|
||||
from octavia.db import models as models
|
||||
from octavia.openstack.common import log as logging
|
||||
from octavia.openstack.common import uuidutils
|
||||
from octavia.tests.unit import base as base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoggingMixIn(base.TestCase):
|
||||
def setUp(self):
|
||||
super(LoggingMixIn, self).setUp()
|
||||
self.mixin = driver.LoggingMixIn()
|
||||
|
||||
def test_update_stats(self):
|
||||
self.mixin.update_stats('test update stats')
|
||||
self.assertEqual('test update stats', self.mixin.stats)
|
||||
|
||||
def test_update_health(self):
|
||||
self.mixin.update_health('test update health')
|
||||
self.assertEqual('test update health', self.mixin.health)
|
||||
|
||||
|
||||
class NoopAmphoraLoadBalancerDriver(base.TestCase):
|
||||
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||
|
||||
def setUp(self):
|
||||
super(NoopAmphoraLoadBalancerDriver, self).setUp()
|
||||
self.driver = driver.NoopAmphoraLoadBalancerDriver(LOG)
|
||||
self.listener = models.Listener()
|
||||
self.listener.protocol_port = 80
|
||||
self.vip = models.Vip()
|
||||
self.vip.ip_address = "10.0.0.1"
|
||||
self.amphora = models.Amphora()
|
||||
self.amphora.id = self.FAKE_UUID_1
|
||||
|
||||
def test_get_logger(self):
|
||||
self.assertEqual(LOG, self.driver.log)
|
||||
|
||||
def test_update(self):
|
||||
self.driver.update(self.listener, self.vip)
|
||||
self.assertEqual((self.listener, self.vip, 'active'),
|
||||
self.driver.driver.amphoraconfig[(
|
||||
self.listener.protocol_port,
|
||||
self.vip.ip_address)])
|
||||
|
||||
def test_disable(self):
|
||||
self.driver.disable(self.listener, self.vip)
|
||||
self.assertEqual((self.listener, self.vip, 'disable'),
|
||||
self.driver.driver.amphoraconfig[(
|
||||
self.listener.protocol_port,
|
||||
self.vip.ip_address)])
|
||||
|
||||
def test_delete(self):
|
||||
self.driver.delete(self.listener, self.vip)
|
||||
self.assertEqual((self.listener, self.vip, 'delete'),
|
||||
self.driver.driver.amphoraconfig[(
|
||||
self.listener.protocol_port,
|
||||
self.vip.ip_address)])
|
||||
|
||||
def test_enable(self):
|
||||
self.driver.enable(self.listener, self.vip)
|
||||
self.assertEqual((self.listener, self.vip, 'enable'),
|
||||
self.driver.driver.amphoraconfig[(
|
||||
self.listener.protocol_port,
|
||||
self.vip.ip_address)])
|
||||
|
||||
def test_info(self):
|
||||
self.driver.info(self.amphora)
|
||||
self.assertEqual((self.amphora.id, 'info'),
|
||||
self.driver.driver.amphoraconfig[
|
||||
self.amphora.id])
|
||||
|
||||
def test_get_metrics(self):
|
||||
self.driver.get_metrics(self.amphora)
|
||||
self.assertEqual((self.amphora.id, 'get_metrics'),
|
||||
self.driver.driver.amphoraconfig[
|
||||
self.amphora.id])
|
||||
|
||||
def test_get_health(self):
|
||||
self.driver.get_health(self.amphora)
|
||||
self.assertEqual((self.amphora.id, 'get_health'),
|
||||
self.driver.driver.amphoraconfig[
|
||||
self.amphora.id])
|
||||
|
||||
def test_get_diagnostics(self):
|
||||
self.driver.get_diagnostics(self.amphora)
|
||||
self.assertEqual((self.amphora.id, 'get_diagnostics'),
|
||||
self.driver.driver.amphoraconfig[
|
||||
self.amphora.id])
|
|
@ -16,4 +16,6 @@ import testtools
|
|||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
Loading…
Reference in New Issue