Implement provider drivers - Driver Library

This patch implements the provider driver support library.
This library contains the callback methods that allow provider drivers
to update status and statistics.

This patch also clears some tech debt by correcting the IP addresses
used in some test cases.

Change-Id: I4e91e1b4f7ce611e603ea7aeb17f5c649cdb3c3d
Story: 1655768
Task: 5165
This commit is contained in:
Michael Johnson 2018-05-30 19:14:15 -07:00
parent 8f3eeb5b2e
commit 4a9f83d039
8 changed files with 514 additions and 54 deletions

View File

@ -1697,33 +1697,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.
@ -1769,7 +1767,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.")
@ -1784,4 +1782,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)

View 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)

View File

@ -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)

View File

@ -203,12 +203,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'
@ -217,6 +219,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'
@ -230,8 +233,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'

View File

@ -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)

View 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)

View File

@ -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)

View File

@ -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
^^^^^^^^^^^^^^^^^^^^