Merge "Implement provider drivers - Driver Library"
This commit is contained in:
commit
d3092c0fac
@ -1703,33 +1703,31 @@ The dictionary takes this form:
|
||||
Update Statistics API
|
||||
---------------------
|
||||
|
||||
Provider drivers can update statistics for load balancers and listeners using
|
||||
the following API. Similar to the status function above, a single dictionary
|
||||
with multiple load balancer and/or listener statistics is used to update
|
||||
statistics in a single call. If an existing load balancer or listener is not
|
||||
included, the statistics those objects remain unchanged.
|
||||
Provider drivers can update statistics for listeners using the following API.
|
||||
Similar to the status function above, a single dictionary
|
||||
with multiple listener statistics is used to update statistics in a single
|
||||
call. If an existing listener is not included, the statistics that object
|
||||
remain unchanged.
|
||||
|
||||
The general form of the input dictionary is a list of load balancer and
|
||||
listener statistics:
|
||||
The general form of the input dictionary is a list of listener statistics:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{ "loadbalancers": [{"id": "123",
|
||||
{ "listeners": [{"id": "123",
|
||||
"active_connections": 12,
|
||||
"bytes_in": 238908,
|
||||
"bytes_out": 290234},
|
||||
"bytes_out": 290234,
|
||||
"request_errors": 0,
|
||||
"total_connections": 3530},...]
|
||||
"listeners": []
|
||||
}
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def update_loadbalancer_statistics(statistics):
|
||||
"""Update load balancer statistics.
|
||||
def update_listener_statistics(statistics):
|
||||
"""Update listener statistics.
|
||||
|
||||
:param statistics (dict): Statistics for loadbalancers and listeners:
|
||||
id (string): ID for load balancer or listener.
|
||||
:param statistics (dict): Statistics for listeners:
|
||||
id (string): ID of the listener.
|
||||
active_connections (int): Number of currently active connections.
|
||||
bytes_in (int): Total bytes received.
|
||||
bytes_out (int): Total bytes sent.
|
||||
@ -1775,7 +1773,7 @@ references to the failed record if available.
|
||||
self.status_object_id = kwargs.pop('status_object_id', None)
|
||||
self.status_record = kwargs.pop('status_record', None)
|
||||
|
||||
super(UnsupportedOptionError, self).__init__(*args, **kwargs)
|
||||
super(UpdateStatusError, self).__init__(*args, **kwargs)
|
||||
|
||||
class UpdateStatisticsError(Exception):
|
||||
fault_string = _("The statistics update had an unknown error.")
|
||||
@ -1790,4 +1788,4 @@ references to the failed record if available.
|
||||
self.stats_object_id = kwargs.pop('stats_object_id', None)
|
||||
self.stats_record = kwargs.pop('stats_record', None)
|
||||
|
||||
super(UnsupportedOptionError, self).__init__(*args, **kwargs)
|
||||
super(UpdateStatisticsError, self).__init__(*args, **kwargs)
|
||||
|
142
octavia/api/drivers/driver_lib.py
Normal file
142
octavia/api/drivers/driver_lib.py
Normal file
@ -0,0 +1,142 @@
|
||||
# Copyright 2018 Rackspace, US Inc.
|
||||
#
|
||||
# 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.api.drivers import exceptions as driver_exceptions
|
||||
from octavia.common import constants as consts
|
||||
from octavia.db import api as db_apis
|
||||
from octavia.db import repositories as repo
|
||||
|
||||
|
||||
class DriverLibrary(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.loadbalancer_repo = repo.LoadBalancerRepository()
|
||||
self.listener_repo = repo.ListenerRepository()
|
||||
self.pool_repo = repo.PoolRepository()
|
||||
self.health_mon_repo = repo.HealthMonitorRepository()
|
||||
self.member_repo = repo.MemberRepository()
|
||||
self.l7policy_repo = repo.L7PolicyRepository()
|
||||
self.l7rule_repo = repo.L7RuleRepository()
|
||||
self.listener_stats_repo = repo.ListenerStatisticsRepository()
|
||||
|
||||
self.db_session = db_apis.get_session()
|
||||
super(DriverLibrary, self).__init__(**kwargs)
|
||||
|
||||
def _process_status_update(self, repo, object_name, record,
|
||||
delete_record=False):
|
||||
# Zero it out so that if the ID is missing from a record we do not
|
||||
# report the last LB as the failed record in the exception
|
||||
record_id = None
|
||||
try:
|
||||
record_id = record['id']
|
||||
record_kwargs = {}
|
||||
prov_status = record.get(consts.PROVISIONING_STATUS, None)
|
||||
if prov_status:
|
||||
if prov_status == consts.DELETED and delete_record:
|
||||
repo.delete(self.db_session, id=record_id)
|
||||
return
|
||||
else:
|
||||
record_kwargs[consts.PROVISIONING_STATUS] = prov_status
|
||||
op_status = record.get(consts.OPERATING_STATUS, None)
|
||||
if op_status:
|
||||
record_kwargs[consts.OPERATING_STATUS] = op_status
|
||||
if prov_status or op_status:
|
||||
repo.update(self.db_session, record_id, **record_kwargs)
|
||||
except Exception as e:
|
||||
# We need to raise a failure here to notify the driver it is
|
||||
# sending bad status data.
|
||||
raise driver_exceptions.UpdateStatusError(
|
||||
fault_string=str(e), status_object_id=record_id,
|
||||
status_object=object_name)
|
||||
|
||||
def update_loadbalancer_status(self, status):
|
||||
"""Update load balancer status.
|
||||
|
||||
:param status: dictionary defining the provisioning status and
|
||||
operating status for load balancer objects, including pools,
|
||||
members, listeners, L7 policies, and L7 rules.
|
||||
iod (string): ID for the object.
|
||||
provisioning_status (string): Provisioning status for the object.
|
||||
operating_status (string): Operating status for the object.
|
||||
:type status: dict
|
||||
:raises: UpdateStatusError
|
||||
:returns: None
|
||||
"""
|
||||
members = status.pop(consts.MEMBERS, [])
|
||||
for member in members:
|
||||
self._process_status_update(self.member_repo, consts.MEMBERS,
|
||||
member, delete_record=True)
|
||||
|
||||
health_mons = status.pop(consts.HEALTHMONITORS, [])
|
||||
for health_mon in health_mons:
|
||||
self._process_status_update(
|
||||
self.health_mon_repo, consts.HEALTHMONITORS, health_mon,
|
||||
delete_record=True)
|
||||
|
||||
pools = status.pop(consts.POOLS, [])
|
||||
for pool in pools:
|
||||
self._process_status_update(self.pool_repo, consts.POOLS,
|
||||
pool, delete_record=True)
|
||||
|
||||
l7rules = status.pop(consts.L7RULES, [])
|
||||
for l7rule in l7rules:
|
||||
self._process_status_update(self.l7rule_repo, consts.L7RULES,
|
||||
l7rule, delete_record=True)
|
||||
|
||||
l7policies = status.pop(consts.L7POLICIES, [])
|
||||
for l7policy in l7policies:
|
||||
self._process_status_update(self.l7policy_repo, consts.L7POLICIES,
|
||||
l7policy, delete_record=True)
|
||||
|
||||
listeners = status.pop(consts.LISTENERS, [])
|
||||
for listener in listeners:
|
||||
self._process_status_update(self.listener_repo, consts.LISTENERS,
|
||||
listener, delete_record=True)
|
||||
|
||||
lbs = status.pop(consts.LOADBALANCERS, [])
|
||||
for lb in lbs:
|
||||
self._process_status_update(self.loadbalancer_repo,
|
||||
consts.LOADBALANCERS, lb)
|
||||
|
||||
def update_listener_statistics(self, statistics):
|
||||
"""Update listener statistics.
|
||||
|
||||
:param statistics: Statistics for listeners:
|
||||
id (string): ID for listener.
|
||||
active_connections (int): Number of currently active connections.
|
||||
bytes_in (int): Total bytes received.
|
||||
bytes_out (int): Total bytes sent.
|
||||
request_errors (int): Total requests not fulfilled.
|
||||
total_connections (int): The total connections handled.
|
||||
:type statistics: dict
|
||||
:raises: UpdateStatisticsError
|
||||
:returns: None
|
||||
"""
|
||||
listener_stats = statistics.get('listeners', [])
|
||||
for stat in listener_stats:
|
||||
try:
|
||||
listener_id = stat.pop('id')
|
||||
except Exception as e:
|
||||
raise driver_exceptions.UpdateStatisticsError(
|
||||
fault_string=str(e), stats_object='listeners')
|
||||
# Provider drivers other than the amphora driver do not have
|
||||
# an amphora ID, use the listener ID again here to meet the
|
||||
# constraint requirement.
|
||||
try:
|
||||
self.listener_stats_repo.replace(self.db_session, listener_id,
|
||||
listener_id, **stat)
|
||||
except Exception as e:
|
||||
raise driver_exceptions.UpdateStatisticsError(
|
||||
fault_string=str(e), stats_object='listeners',
|
||||
stats_object_id=listener_id)
|
@ -89,3 +89,60 @@ class UnsupportedOptionError(Exception):
|
||||
self.operator_fault_string = kwargs.pop('operator_fault_string',
|
||||
self.operator_fault_string)
|
||||
super(UnsupportedOptionError, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class UpdateStatusError(Exception):
|
||||
"""Exception raised when a status update fails.
|
||||
|
||||
Each exception will include a message field that describes the
|
||||
error and references to the failed record if available.
|
||||
:param fault_string: String describing the fault.
|
||||
:type fault_string: string
|
||||
:param status_object: The object the fault occurred on.
|
||||
:type status_object: string
|
||||
:param status_object_id: The ID of the object that failed status update.
|
||||
:type status_object_id: string
|
||||
:param status_record: The status update record that caused the fault.
|
||||
:type status_record: string
|
||||
"""
|
||||
fault_string = _("The status update had an unknown error.")
|
||||
status_object = None
|
||||
status_object_id = None
|
||||
status_record = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.fault_string = kwargs.pop('fault_string', self.fault_string)
|
||||
self.status_object = kwargs.pop('status_object', None)
|
||||
self.status_object_id = kwargs.pop('status_object_id', None)
|
||||
self.status_record = kwargs.pop('status_record', None)
|
||||
|
||||
super(UpdateStatusError, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class UpdateStatisticsError(Exception):
|
||||
"""Exception raised when a statistics update fails.
|
||||
|
||||
Each exception will include a message field that describes the
|
||||
error and references to the failed record if available.
|
||||
:param fault_string: String describing the fault.
|
||||
:type fault_string: string
|
||||
:param status_object: The object the fault occurred on.
|
||||
:type status_object: string
|
||||
:param status_object_id: The ID of the object that failed stats update.
|
||||
:type status_object_id: string
|
||||
:param status_record: The stats update record that caused the fault.
|
||||
:type status_record: string
|
||||
"""
|
||||
fault_string = _("The statistics update had an unknown error.")
|
||||
stats_object = None
|
||||
stats_object_id = None
|
||||
stats_record = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.fault_string = kwargs.pop('fault_string',
|
||||
self.fault_string)
|
||||
self.stats_object = kwargs.pop('stats_object', None)
|
||||
self.stats_object_id = kwargs.pop('stats_object_id', None)
|
||||
self.stats_record = kwargs.pop('stats_record', None)
|
||||
|
||||
super(UpdateStatisticsError, self).__init__(*args, **kwargs)
|
||||
|
@ -204,12 +204,14 @@ LISTENER = 'listener'
|
||||
LISTENERS = 'listeners'
|
||||
LISTENER_ID = 'listener_id'
|
||||
LOADBALANCER = 'loadbalancer'
|
||||
LOADBALANCERS = 'loadbalancers'
|
||||
LOADBALANCER_ID = 'loadbalancer_id'
|
||||
LOAD_BALANCER_ID = 'load_balancer_id'
|
||||
SERVER_GROUP_ID = 'server_group_id'
|
||||
ANTI_AFFINITY = 'anti-affinity'
|
||||
SOFT_ANTI_AFFINITY = 'soft-anti-affinity'
|
||||
MEMBER = 'member'
|
||||
MEMBERS = 'members'
|
||||
MEMBER_ID = 'member_id'
|
||||
COMPUTE_ID = 'compute_id'
|
||||
COMPUTE_OBJ = 'compute_obj'
|
||||
@ -218,6 +220,7 @@ AMPS_DATA = 'amps_data'
|
||||
NICS = 'nics'
|
||||
VIP = 'vip'
|
||||
POOL = 'pool'
|
||||
POOLS = 'pools'
|
||||
POOL_CHILD_COUNT = 'pool_child_count'
|
||||
POOL_ID = 'pool_id'
|
||||
L7POLICY = 'l7policy'
|
||||
@ -231,8 +234,11 @@ ADDED_PORTS = 'added_ports'
|
||||
PORTS = 'ports'
|
||||
MEMBER_PORTS = 'member_ports'
|
||||
LOADBALANCER_TOPOLOGY = 'topology'
|
||||
HEALTHMONITORS = 'healthmonitors'
|
||||
HEALTH_MONITOR_ID = 'health_monitor_id'
|
||||
L7POLICIES = 'l7policies'
|
||||
L7POLICY_ID = 'l7policy_id'
|
||||
L7RULES = 'l7rules'
|
||||
L7RULE_ID = 'l7rule_id'
|
||||
LOAD_BALANCER_UPDATES = 'load_balancer_updates'
|
||||
LISTENER_UPDATES = 'listener_updates'
|
||||
|
@ -35,7 +35,7 @@ CONF = cfg.CONF
|
||||
|
||||
class BaseRepositoryTest(base.OctaviaDBTestBase):
|
||||
|
||||
FAKE_IP = "10.0.0.1"
|
||||
FAKE_IP = "192.0.2.1"
|
||||
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||
FAKE_UUID_2 = uuidutils.generate_uuid()
|
||||
FAKE_UUID_3 = uuidutils.generate_uuid()
|
||||
@ -131,7 +131,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
'server_group_id': uuidutils.generate_uuid(),
|
||||
'project_id': uuidutils.generate_uuid(),
|
||||
'id': uuidutils.generate_uuid()}
|
||||
vip = {'ip_address': '10.0.0.1',
|
||||
vip = {'ip_address': '192.0.2.1',
|
||||
'port_id': uuidutils.generate_uuid(),
|
||||
'subnet_id': uuidutils.generate_uuid(),
|
||||
'network_id': uuidutils.generate_uuid(),
|
||||
@ -394,7 +394,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
'operating_status': constants.ONLINE,
|
||||
'id': uuidutils.generate_uuid()}
|
||||
l7policy['listener_id'] = listener.get('id')
|
||||
vip = {'ip_address': '10.0.0.1', 'port_id': uuidutils.generate_uuid(),
|
||||
vip = {'ip_address': '192.0.2.1', 'port_id': uuidutils.generate_uuid(),
|
||||
'subnet_id': uuidutils.generate_uuid()}
|
||||
lb = {'name': 'lb1', 'description': 'desc1', 'enabled': True,
|
||||
'topology': constants.TOPOLOGY_ACTIVE_STANDBY,
|
||||
@ -419,7 +419,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
test below: `test_create_load_balancer_tree_quotas`.
|
||||
"""
|
||||
project_id = uuidutils.generate_uuid()
|
||||
vip = {'ip_address': '10.0.0.1', 'port_id': uuidutils.generate_uuid(),
|
||||
vip = {'ip_address': '192.0.2.1', 'port_id': uuidutils.generate_uuid(),
|
||||
'subnet_id': uuidutils.generate_uuid()}
|
||||
lb = {'name': 'lb1', 'description': 'desc1', 'enabled': True,
|
||||
'topology': constants.TOPOLOGY_ACTIVE_STANDBY,
|
||||
@ -539,7 +539,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
'operating_status': constants.ONLINE,
|
||||
'id': uuidutils.generate_uuid()}
|
||||
l7policy['listener_id'] = listener.get('id')
|
||||
vip = {'ip_address': '10.0.0.1', 'port_id': uuidutils.generate_uuid(),
|
||||
vip = {'ip_address': '192.0.2.1', 'port_id': uuidutils.generate_uuid(),
|
||||
'subnet_id': uuidutils.generate_uuid()}
|
||||
lb = {'name': 'lb1', 'description': 'desc1', 'enabled': True,
|
||||
'topology': constants.TOPOLOGY_ACTIVE_STANDBY,
|
||||
@ -1885,7 +1885,7 @@ class PoolRepositoryTest(BaseRepositoryTest):
|
||||
member = self.member_repo.create(self.session, id=self.FAKE_UUID_3,
|
||||
project_id=self.FAKE_UUID_2,
|
||||
pool_id=pool.id,
|
||||
ip_address="10.0.0.1",
|
||||
ip_address="192.0.2.1",
|
||||
protocol_port=80, enabled=True,
|
||||
provisioning_status=constants.ACTIVE,
|
||||
operating_status=constants.ONLINE,
|
||||
@ -1939,7 +1939,7 @@ class PoolRepositoryTest(BaseRepositoryTest):
|
||||
member = self.member_repo.create(self.session, id=self.FAKE_UUID_3,
|
||||
project_id=self.FAKE_UUID_2,
|
||||
pool_id=pool.id,
|
||||
ip_address="10.0.0.1",
|
||||
ip_address="192.0.2.1",
|
||||
protocol_port=80,
|
||||
provisioning_status=constants.ACTIVE,
|
||||
operating_status=constants.ONLINE,
|
||||
@ -1989,16 +1989,16 @@ class MemberRepositoryTest(BaseRepositoryTest):
|
||||
|
||||
def test_get(self):
|
||||
member = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.1")
|
||||
self.pool.id, "192.0.2.1")
|
||||
new_member = self.member_repo.get(self.session, id=member.id)
|
||||
self.assertIsInstance(new_member, models.Member)
|
||||
self.assertEqual(member, new_member)
|
||||
|
||||
def test_get_all(self):
|
||||
member_one = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.1")
|
||||
self.pool.id, "192.0.2.1")
|
||||
member_two = self.create_member(self.FAKE_UUID_3, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.2")
|
||||
self.pool.id, "192.0.2.2")
|
||||
member_list, _ = self.member_repo.get_all(self.session,
|
||||
project_id=self.FAKE_UUID_2)
|
||||
self.assertIsInstance(member_list, list)
|
||||
@ -2008,20 +2008,20 @@ class MemberRepositoryTest(BaseRepositoryTest):
|
||||
|
||||
def test_create(self):
|
||||
member = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
|
||||
self.pool.id, ip_address="10.0.0.1")
|
||||
self.pool.id, ip_address="192.0.2.1")
|
||||
new_member = self.member_repo.get(self.session, id=member.id)
|
||||
self.assertEqual(self.FAKE_UUID_1, new_member.id)
|
||||
self.assertEqual(self.FAKE_UUID_2, new_member.project_id)
|
||||
self.assertEqual(self.pool.id, new_member.pool_id)
|
||||
self.assertEqual("10.0.0.1", new_member.ip_address)
|
||||
self.assertEqual("192.0.2.1", new_member.ip_address)
|
||||
self.assertEqual(80, new_member.protocol_port)
|
||||
self.assertEqual(constants.ONLINE, new_member.operating_status)
|
||||
self.assertTrue(new_member.enabled)
|
||||
|
||||
def test_update(self):
|
||||
ip_address_change = "10.0.0.2"
|
||||
ip_address_change = "192.0.2.2"
|
||||
member = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.1")
|
||||
self.pool.id, "192.0.2.1")
|
||||
self.member_repo.update(self.session, id=member.id,
|
||||
ip_address=ip_address_change)
|
||||
new_member = self.member_repo.get(self.session, id=member.id)
|
||||
@ -2029,7 +2029,7 @@ class MemberRepositoryTest(BaseRepositoryTest):
|
||||
|
||||
def test_delete(self):
|
||||
member = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.1")
|
||||
self.pool.id, "192.0.2.1")
|
||||
self.member_repo.delete(self.session, id=member.id)
|
||||
self.assertIsNone(self.member_repo.get(self.session, id=member.id))
|
||||
new_pool = self.pool_repo.get(self.session, id=self.pool.id)
|
||||
@ -2038,9 +2038,9 @@ class MemberRepositoryTest(BaseRepositoryTest):
|
||||
|
||||
def test_update_pool_members(self):
|
||||
member1 = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.1")
|
||||
self.pool.id, "192.0.2.1")
|
||||
member2 = self.create_member(self.FAKE_UUID_3, self.FAKE_UUID_2,
|
||||
self.pool.id, "10.0.0.2")
|
||||
self.pool.id, "192.0.2.2")
|
||||
self.member_repo.update_pool_members(
|
||||
self.session,
|
||||
pool_id=self.pool.id,
|
||||
@ -2481,6 +2481,13 @@ class HealthMonitorRepositoryTest(BaseRepositoryTest):
|
||||
lb_algorithm=constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
provisioning_status=constants.ACTIVE,
|
||||
operating_status=constants.ONLINE, enabled=True)
|
||||
self.pool2 = self.pool_repo.create(
|
||||
self.session, id=self.FAKE_UUID_2, project_id=self.FAKE_UUID_2,
|
||||
name="pool2_test", description="pool2_description",
|
||||
protocol=constants.PROTOCOL_HTTP,
|
||||
lb_algorithm=constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
provisioning_status=constants.ACTIVE,
|
||||
operating_status=constants.ONLINE, enabled=True)
|
||||
|
||||
def create_health_monitor(self, hm_id, pool_id):
|
||||
health_monitor = self.hm_repo.create(
|
||||
@ -2626,7 +2633,7 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
|
||||
def test_delete_with_vip(self):
|
||||
lb = self.create_loadbalancer(self.FAKE_UUID_1)
|
||||
vip = self.vip_repo.create(self.session, load_balancer_id=lb.id,
|
||||
ip_address="10.0.0.1")
|
||||
ip_address="192.0.2.1")
|
||||
new_lb = self.lb_repo.get(self.session, id=lb.id)
|
||||
self.assertIsNotNone(new_lb)
|
||||
self.assertIsNotNone(new_lb.vip)
|
||||
@ -2689,7 +2696,7 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
|
||||
lb_network_ip=self.FAKE_IP,
|
||||
status=constants.ACTIVE)
|
||||
vip = self.vip_repo.create(self.session, load_balancer_id=lb.id,
|
||||
ip_address="10.0.0.1")
|
||||
ip_address="192.0.2.1")
|
||||
listener = self.listener_repo.create(
|
||||
self.session, id=self.FAKE_UUID_1, project_id=self.FAKE_UUID_2,
|
||||
name="listener_name", description="listener_description",
|
||||
@ -2851,7 +2858,7 @@ class VipRepositoryTest(BaseRepositoryTest):
|
||||
|
||||
def create_vip(self, lb_id):
|
||||
vip = self.vip_repo.create(self.session, load_balancer_id=lb_id,
|
||||
ip_address="10.0.0.1")
|
||||
ip_address="192.0.2.1")
|
||||
return vip
|
||||
|
||||
def test_get(self):
|
||||
@ -2864,10 +2871,10 @@ class VipRepositoryTest(BaseRepositoryTest):
|
||||
def test_create(self):
|
||||
vip = self.create_vip(self.lb.id)
|
||||
self.assertEqual(self.lb.id, vip.load_balancer_id)
|
||||
self.assertEqual("10.0.0.1", vip.ip_address)
|
||||
self.assertEqual("192.0.2.1", vip.ip_address)
|
||||
|
||||
def test_update(self):
|
||||
address_change = "10.0.0.2"
|
||||
address_change = "192.0.2.2"
|
||||
vip = self.create_vip(self.lb.id)
|
||||
self.vip_repo.update(self.session, vip.load_balancer_id,
|
||||
ip_address=address_change)
|
||||
|
223
octavia/tests/unit/api/drivers/test_driver_lib.py
Normal file
223
octavia/tests/unit/api/drivers/test_driver_lib.py
Normal file
@ -0,0 +1,223 @@
|
||||
# Copyright 2018 Rackspace, US Inc.
|
||||
#
|
||||
# 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 mock
|
||||
from mock import call
|
||||
|
||||
from octavia.api.drivers import driver_lib
|
||||
from octavia.api.drivers import exceptions as driver_exceptions
|
||||
from octavia.common import constants
|
||||
from octavia.tests.unit import base
|
||||
|
||||
|
||||
class TestDriverLib(base.TestCase):
|
||||
@mock.patch('octavia.db.repositories.L7RuleRepository')
|
||||
@mock.patch('octavia.db.repositories.L7PolicyRepository')
|
||||
@mock.patch('octavia.db.repositories.HealthMonitorRepository')
|
||||
@mock.patch('octavia.db.repositories.MemberRepository')
|
||||
@mock.patch('octavia.db.repositories.PoolRepository')
|
||||
@mock.patch('octavia.db.repositories.ListenerRepository')
|
||||
@mock.patch('octavia.db.repositories.LoadBalancerRepository')
|
||||
@mock.patch('octavia.db.api.get_session')
|
||||
def setUp(self, mock_get_session, mock_lb_repo, mock_list_repo,
|
||||
mock_pool_repo, mock_member_repo, mock_health_repo,
|
||||
mock_l7p_repo, mock_l7r_repo):
|
||||
super(TestDriverLib, self).setUp()
|
||||
self.mock_session = "FAKE_DB_SESSION"
|
||||
mock_get_session.return_value = self.mock_session
|
||||
lb_mock = mock.MagicMock()
|
||||
mock_lb_repo.return_value = lb_mock
|
||||
self.mock_lb_repo = lb_mock
|
||||
list_mock = mock.MagicMock()
|
||||
mock_list_repo.return_value = list_mock
|
||||
self.mock_list_repo = list_mock
|
||||
pool_mock = mock.MagicMock()
|
||||
mock_pool_repo.return_value = pool_mock
|
||||
self.mock_pool_repo = pool_mock
|
||||
member_mock = mock.MagicMock()
|
||||
mock_member_repo.return_value = member_mock
|
||||
self.mock_member_repo = member_mock
|
||||
health_mock = mock.MagicMock()
|
||||
mock_health_repo.return_value = health_mock
|
||||
self.mock_health_repo = health_mock
|
||||
l7p_mock = mock.MagicMock()
|
||||
mock_l7p_repo.return_value = l7p_mock
|
||||
self.mock_l7p_repo = l7p_mock
|
||||
l7r_mock = mock.MagicMock()
|
||||
mock_l7r_repo.return_value = l7r_mock
|
||||
self.mock_l7r_repo = l7r_mock
|
||||
self.driver_lib = driver_lib.DriverLibrary()
|
||||
listener_stats_list = [{"id": 1, "active_connections": 10,
|
||||
"bytes_in": 20,
|
||||
"bytes_out": 30,
|
||||
"request_errors": 40,
|
||||
"total_connections": 50},
|
||||
{"id": 2, "active_connections": 60,
|
||||
"bytes_in": 70,
|
||||
"bytes_out": 80,
|
||||
"request_errors": 90,
|
||||
"total_connections": 100}]
|
||||
self.listener_stats_dict = {"listeners": listener_stats_list}
|
||||
|
||||
def test_process_status_update(self):
|
||||
mock_repo = mock.MagicMock()
|
||||
list_dict = {"id": 2, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
list_prov_dict = {"id": 2,
|
||||
constants.PROVISIONING_STATUS: constants.ACTIVE}
|
||||
list_oper_dict = {"id": 2,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
list_deleted_dict = {
|
||||
"id": 2, constants.PROVISIONING_STATUS: constants.DELETED,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
|
||||
# Test with full record
|
||||
self.driver_lib._process_status_update(mock_repo, 'FakeName',
|
||||
list_dict)
|
||||
mock_repo.update.assert_called_once_with(
|
||||
self.mock_session, 2, provisioning_status=constants.ACTIVE,
|
||||
operating_status=constants.ONLINE)
|
||||
mock_repo.delete.assert_not_called()
|
||||
|
||||
# Test with only provisioning status record
|
||||
mock_repo.reset_mock()
|
||||
self.driver_lib._process_status_update(mock_repo, 'FakeName',
|
||||
list_prov_dict)
|
||||
mock_repo.update.assert_called_once_with(
|
||||
self.mock_session, 2, provisioning_status=constants.ACTIVE)
|
||||
mock_repo.delete.assert_not_called()
|
||||
|
||||
# Test with only operating status record
|
||||
mock_repo.reset_mock()
|
||||
self.driver_lib._process_status_update(mock_repo, 'FakeName',
|
||||
list_oper_dict)
|
||||
mock_repo.update.assert_called_once_with(
|
||||
self.mock_session, 2, operating_status=constants.ONLINE)
|
||||
mock_repo.delete.assert_not_called()
|
||||
|
||||
# Test with deleted but delete_record False
|
||||
mock_repo.reset_mock()
|
||||
self.driver_lib._process_status_update(mock_repo, 'FakeName',
|
||||
list_deleted_dict)
|
||||
mock_repo.update.assert_called_once_with(
|
||||
self.mock_session, 2, provisioning_status=constants.DELETED,
|
||||
operating_status=constants.ONLINE)
|
||||
mock_repo.delete.assert_not_called()
|
||||
|
||||
# Test with an empty update
|
||||
mock_repo.reset_mock()
|
||||
self.driver_lib._process_status_update(mock_repo, 'FakeName',
|
||||
{"id": 2})
|
||||
mock_repo.update.assert_not_called()
|
||||
mock_repo.delete.assert_not_called()
|
||||
|
||||
# Test with deleted and delete_record True
|
||||
mock_repo.reset_mock()
|
||||
self.driver_lib._process_status_update(
|
||||
mock_repo, 'FakeName', list_deleted_dict, delete_record=True)
|
||||
mock_repo.delete.assert_called_once_with(self.mock_session, id=2)
|
||||
mock_repo.update.assert_not_called()
|
||||
|
||||
# Test with an exception
|
||||
mock_repo.reset_mock()
|
||||
mock_repo.update.side_effect = Exception('boom')
|
||||
self.assertRaises(driver_exceptions.UpdateStatusError,
|
||||
self.driver_lib._process_status_update,
|
||||
mock_repo, 'FakeName', list_dict)
|
||||
|
||||
# Test with no ID record
|
||||
mock_repo.reset_mock()
|
||||
self.assertRaises(driver_exceptions.UpdateStatusError,
|
||||
self.driver_lib._process_status_update,
|
||||
mock_repo, 'FakeName', {"fake": "data"})
|
||||
|
||||
@mock.patch(
|
||||
'octavia.api.drivers.driver_lib.DriverLibrary._process_status_update')
|
||||
def test_update_loadbalancer_status(self, mock_status_update):
|
||||
lb_dict = {"id": 1, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
list_dict = {"id": 2, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
pool_dict = {"id": 3, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
member_dict = {"id": 4,
|
||||
constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
hm_dict = {"id": 5, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
l7p_dict = {"id": 6, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
l7r_dict = {"id": 7, constants.PROVISIONING_STATUS: constants.ACTIVE,
|
||||
constants.OPERATING_STATUS: constants.ONLINE}
|
||||
status_dict = {constants.LOADBALANCERS: [lb_dict],
|
||||
constants.LISTENERS: [list_dict],
|
||||
constants.POOLS: [pool_dict],
|
||||
constants.MEMBERS: [member_dict],
|
||||
constants.HEALTHMONITORS: [hm_dict],
|
||||
constants.L7POLICIES: [l7p_dict],
|
||||
constants.L7RULES: [l7r_dict]}
|
||||
|
||||
self.driver_lib.update_loadbalancer_status(status_dict)
|
||||
|
||||
calls = [call(self.mock_member_repo, constants.MEMBERS, member_dict,
|
||||
delete_record=True),
|
||||
call(self.mock_health_repo, constants.HEALTHMONITORS,
|
||||
hm_dict, delete_record=True),
|
||||
call(self.mock_pool_repo, constants.POOLS, pool_dict,
|
||||
delete_record=True),
|
||||
call(self.mock_l7r_repo, constants.L7RULES, l7r_dict,
|
||||
delete_record=True),
|
||||
call(self.mock_l7p_repo, constants.L7POLICIES, l7p_dict,
|
||||
delete_record=True),
|
||||
call(self.mock_list_repo, constants.LISTENERS, list_dict,
|
||||
delete_record=True),
|
||||
call(self.mock_lb_repo, constants.LOADBALANCERS,
|
||||
lb_dict)]
|
||||
mock_status_update.assert_has_calls(calls)
|
||||
|
||||
mock_status_update.reset_mock()
|
||||
self.driver_lib.update_loadbalancer_status({})
|
||||
mock_status_update.assert_not_called()
|
||||
|
||||
@mock.patch('octavia.db.repositories.ListenerStatisticsRepository.replace')
|
||||
def test_update_listener_statistics(self, mock_replace):
|
||||
self.driver_lib.update_listener_statistics(self.listener_stats_dict)
|
||||
calls = [call(self.mock_session, 1, 1, active_connections=10,
|
||||
bytes_in=20, bytes_out=30, request_errors=40,
|
||||
total_connections=50),
|
||||
call(self.mock_session, 2, 2, active_connections=60,
|
||||
bytes_in=70, bytes_out=80, request_errors=90,
|
||||
total_connections=100)]
|
||||
mock_replace.assert_has_calls(calls)
|
||||
|
||||
mock_replace.reset_mock()
|
||||
self.driver_lib.update_listener_statistics({})
|
||||
mock_replace.assert_not_called()
|
||||
|
||||
# Test missing ID
|
||||
bad_id_dict = {"listeners": [{"notID": "one"}]}
|
||||
self.assertRaises(driver_exceptions.UpdateStatisticsError,
|
||||
self.driver_lib.update_listener_statistics,
|
||||
bad_id_dict)
|
||||
|
||||
# Coverage doesn't like this test as part of the above test
|
||||
# So, broke it out in it's own test
|
||||
@mock.patch('octavia.db.repositories.ListenerStatisticsRepository.replace')
|
||||
def test_update_listener_statistics_exception(self, mock_replace):
|
||||
|
||||
# Test stats exception
|
||||
mock_replace.side_effect = Exception('boom')
|
||||
self.assertRaises(driver_exceptions.UpdateStatisticsError,
|
||||
self.driver_lib.update_listener_statistics,
|
||||
self.listener_stats_dict)
|
@ -22,6 +22,9 @@ class TestProviderExceptions(base.TestCase):
|
||||
super(TestProviderExceptions, self).setUp()
|
||||
self.user_fault_string = 'Bad driver'
|
||||
self.operator_fault_string = 'Fix bad driver.'
|
||||
self.fault_object = 'MCP'
|
||||
self.fault_object_id = '-1'
|
||||
self.fault_record = 'skip'
|
||||
|
||||
def test_DriverError(self):
|
||||
driver_error = exceptions.DriverError(
|
||||
@ -55,3 +58,31 @@ class TestProviderExceptions(base.TestCase):
|
||||
self.assertEqual(self.operator_fault_string,
|
||||
unsupported_option_error.operator_fault_string)
|
||||
self.assertIsInstance(unsupported_option_error, Exception)
|
||||
|
||||
def test_UpdateStatusError(self):
|
||||
update_status_error = exceptions.UpdateStatusError(
|
||||
fault_string=self.user_fault_string,
|
||||
status_object=self.fault_object,
|
||||
status_object_id=self.fault_object_id,
|
||||
status_record=self.fault_record)
|
||||
|
||||
self.assertEqual(self.user_fault_string,
|
||||
update_status_error.fault_string)
|
||||
self.assertEqual(self.fault_object, update_status_error.status_object)
|
||||
self.assertEqual(self.fault_object_id,
|
||||
update_status_error.status_object_id)
|
||||
self.assertEqual(self.fault_record, update_status_error.status_record)
|
||||
|
||||
def test_UpdateStatisticsError(self):
|
||||
update_stats_error = exceptions.UpdateStatisticsError(
|
||||
fault_string=self.user_fault_string,
|
||||
stats_object=self.fault_object,
|
||||
stats_object_id=self.fault_object_id,
|
||||
stats_record=self.fault_record)
|
||||
|
||||
self.assertEqual(self.user_fault_string,
|
||||
update_stats_error.fault_string)
|
||||
self.assertEqual(self.fault_object, update_stats_error.stats_object)
|
||||
self.assertEqual(self.fault_object_id,
|
||||
update_stats_error.stats_object_id)
|
||||
self.assertEqual(self.fault_record, update_stats_error.stats_record)
|
||||
|
@ -1634,38 +1634,35 @@ The dictionary takes this form:
|
||||
:raises: UpdateStatusError
|
||||
:returns: None
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
Update statistics API
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Provider drivers can update statistics for load balancers and listeners using
|
||||
the following API. Similar to the status function above, a single dictionary
|
||||
with multiple load balancer and/or listener statistics is used to update
|
||||
statistics in a single call. If an existing load balancer or listener is not
|
||||
included, the statistics those objects remain unchanged.
|
||||
Provider drivers can update statistics for listeners using the following API.
|
||||
Similar to the status function above, a single dictionary
|
||||
with multiple listener statistics is used to update statistics in a single
|
||||
call. If an existing listener is not included, the statistics for that object
|
||||
will remain unchanged.
|
||||
|
||||
The general form of the input dictionary is a list of load balancer and
|
||||
listener statistics:
|
||||
The general form of the input dictionary is a list of listener statistics:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{ "loadbalancers": [{"id": "123",
|
||||
{ "listeners": [{"id": "123",
|
||||
"active_connections": 12,
|
||||
"bytes_in": 238908,
|
||||
"bytes_out": 290234},
|
||||
"bytes_out": 290234,
|
||||
"request_errors": 0,
|
||||
"total_connections": 3530},...]
|
||||
"listeners": []
|
||||
}
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def update_loadbalancer_statistics(statistics):
|
||||
"""Update load balancer statistics.
|
||||
def update_listener_statistics(statistics):
|
||||
"""Update listener statistics.
|
||||
|
||||
:param statistics (dict): Statistics for loadbalancers and listeners:
|
||||
id (string): ID for load balancer or listener.
|
||||
:param statistics (dict): Statistics for listeners:
|
||||
id (string): ID of the listener.
|
||||
active_connections (int): Number of currently active connections.
|
||||
bytes_in (int): Total bytes received.
|
||||
bytes_out (int): Total bytes sent.
|
||||
@ -1674,7 +1671,6 @@ listener statistics:
|
||||
:raises: UpdateStatisticsError
|
||||
:returns: None
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
Get Resource Support
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
Loading…
Reference in New Issue
Block a user