Discard batch-update-members not valid request

An out of sync has been identified between the changes applied
over the OVN NB DB and Octavia DB when a batch-update-members
includes some unsupported option for any of the member to be
modified.

To prevent such inconsistencies, this patch rejects the entire
request if any of the proposed changes are identified as
unsupported. The user will be notified of the reason for the
rejection.

Closes-Bug: 2017216
Change-Id: I6e132ab5c23c9c53176612f74bb500e46c89024f
changes/98/881198/4
Fernando Royo 2023-04-21 10:14:50 +02:00
parent 0285967056
commit 5f27384805
3 changed files with 56 additions and 11 deletions

View File

@ -346,7 +346,6 @@ class OvnProviderDriver(driver_base.ProviderDriver):
def member_batch_update(self, pool_id, members):
request_list = []
skipped_members = []
pool_key, ovn_lb = self._ovn_helper._find_ovn_lb_by_pool_id(pool_id)
external_ids = copy.deepcopy(ovn_lb.external_ids)
pool = external_ids[pool_key]
@ -354,10 +353,16 @@ class OvnProviderDriver(driver_base.ProviderDriver):
members_to_delete = copy.copy(existing_members)
pool_subnet_id = None
for member in members:
if (self._check_monitor_options(member) or
member.address and self._ip_version_differs(member)):
skipped_members.append(member.member_id)
continue
# NOTE(froyo): in order to keep sync with Octavia DB, we raise
# not supporting exceptions as soon as posible, considering the
# full request as not valid
if (self._check_monitor_options(member)):
msg = 'OVN provider does not support monitor options'
raise driver_exceptions.UnsupportedOptionError(
user_fault_string=msg,
operator_fault_string=msg)
if (member.address and self._ip_version_differs(member)):
raise ovn_exc.IPVersionsMixingNotSupportedError()
# NOTE(froyo): if subnet_id not provided, lets try to get it
# from the member pool_id
subnet_id = member.subnet_id
@ -419,12 +424,6 @@ class OvnProviderDriver(driver_base.ProviderDriver):
for request in request_list:
self._ovn_helper.add_request(request)
if skipped_members:
msg = (_('OVN provider does not support monitor options, '
'so following members skipped: %s') % skipped_members)
raise driver_exceptions.UnsupportedOptionError(
user_fault_string=msg,
operator_fault_string=msg)
def create_vip_port(self, lb_id, project_id, vip_dict,
additional_vip_dicts=None):

View File

@ -464,4 +464,14 @@ class TestOvnOctaviaProviderDriver(ovn_base.TestOvnOctaviaBase):
# Deleting one member, while keeping the other member available
self._update_members_in_batch_and_validate(lb_data, pool_id,
[m_member])
# Create Member-3 with monitor options
m_member = self._create_member_model(pool_id,
lb_data['vip_net_info'][1],
'10.0.0.12')
m_member.monitor_port = 8080
members = [m_member]
self.assertRaises(o_exceptions.UnsupportedOptionError,
self.ovn_driver.member_batch_update,
pool_id,
members)
self._delete_load_balancer_and_validate(lb_data)

View File

@ -415,6 +415,22 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
[self.ref_member, self.update_member])
self.assertEqual(self.mock_add_request.call_count, 3)
def test_member_batch_update_member_delete(self):
info_md = {
'id': self.ref_member.member_id,
'address': mock.ANY,
'protocol_port': mock.ANY,
'pool_id': self.ref_member.pool_id,
'subnet_id': self.ref_member.subnet_id}
expected_dict_md = {
'type': ovn_const.REQ_TYPE_MEMBER_DELETE,
'info': info_md}
expected = [
mock.call(expected_dict_md)]
self.driver.member_batch_update(self.pool_id, [])
self.assertEqual(self.mock_add_request.call_count, 1)
self.mock_add_request.assert_has_calls(expected)
def test_member_batch_update_no_members(self):
pool_key = 'pool_%s' % self.pool_id
ovn_lb = copy.copy(self.ovn_lb)
@ -443,6 +459,26 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
self.driver.member_batch_update(self.pool_id, [self.ref_member])
self.assertEqual(self.mock_add_request.call_count, 2)
def test_member_batch_update_toggle_admin_state_up(self):
info_mu = {
'id': self.ref_member.member_id,
'address': self.member_address,
'protocol_port': self.member_port,
'pool_id': self.ref_member.pool_id,
'subnet_id': self.ref_member.subnet_id,
'admin_state_up': False}
expected_dict_mu = {
'type': ovn_const.REQ_TYPE_MEMBER_UPDATE,
'info': info_mu}
expected = [
mock.call(expected_dict_mu)]
self.ref_member.admin_state_up = False
self.ref_member.address = self.member_address
self.ref_member.protocol_port = self.member_port
self.driver.member_batch_update(self.pool_id, [self.ref_member])
self.assertEqual(self.mock_add_request.call_count, 1)
self.mock_add_request.assert_has_calls(expected)
def test_member_batch_update_missing_subnet_id(self):
self.ref_member.subnet_id = None
self.assertRaises(exceptions.UnsupportedOptionError,