Browse Source

Add support for Member Batch Update

Currently the member batch update is not in supported in the same
manner as is expected by Octavia.
This patch fixes the same.

Closes-Bug: #1806844
Change-Id: I8678392b953b92f1dd6cd5d44c1d5bcee6cc92bf
tags/6.0.0.0b1
Reedip Banerjee 5 months ago
parent
commit
e89a8a2563

+ 105
- 2
networking_ovn/octavia/ovn_driver.py View File

@@ -1018,7 +1018,7 @@ class OvnProviderHelper(object):
1018 1018
                      'provisioning_status': constants.ACTIVE})
1019 1019
             status['listeners'] = listener_status
1020 1020
         except Exception:
1021
-            LOG.exception('Exception during pool delete')
1021
+            LOG.exception('Exception during pool update')
1022 1022
             status = {
1023 1023
                 'pools': [{"id": pool['id'],
1024 1024
                            'provisioning_status': constants.ERROR}],
@@ -1192,6 +1192,52 @@ class OvnProviderHelper(object):
1192 1192
                                    'provisioning_status': constants.ACTIVE}]}
1193 1193
         return status
1194 1194
 
1195
+    def _get_existing_pool_members(self, pool_id):
1196
+        pool_key = self._get_pool_key(pool_id)
1197
+        ovn_lb = self._find_ovn_lb_with_pool_key(pool_key)
1198
+        if not ovn_lb:
1199
+            pool_key = self._get_pool_key(pool_id, is_enabled=False)
1200
+            ovn_lb = self._find_ovn_lb_with_pool_key(pool_key)
1201
+            if not ovn_lb:
1202
+                msg = _("Loadbalancer with pool %s does not exist") % pool_key
1203
+                raise driver_exceptions.DriverError(msg)
1204
+        external_ids = dict(ovn_lb.external_ids)
1205
+        return external_ids[pool_key]
1206
+
1207
+    def get_pool_member_id(self, pool_id, mem_addr_port=None):
1208
+        '''Gets Member information
1209
+
1210
+        :param pool_id: ID of the Pool whose member information is reqd.
1211
+        :param mem_addr_port: Combination of Member Address+Port. Default=None
1212
+        :returns: UUID -- ID of the Member if member exists in pool.
1213
+        :returns: None -- if no member exists in the pool
1214
+        :raises: Exception if Loadbalancer is not found for a Pool ID
1215
+        '''
1216
+        existing_members = self._get_existing_pool_members(pool_id)
1217
+        # Members are saved in OVN in the form of
1218
+        # member1_UUID_IP:Port, member2_UUID_IP:Port
1219
+        # Match the IP:Port for all members with the mem_addr_port
1220
+        # information and return the UUID.
1221
+        for meminf in existing_members.split(','):
1222
+            if mem_addr_port == meminf.split('_')[2]:
1223
+                return meminf.split('_')[1]
1224
+
1225
+    def get_member_info(self, pool_id):
1226
+        '''Gets Member information
1227
+
1228
+        :param pool_id: ID of the Pool whose member information is reqd.
1229
+        :param mem_addr_port: Combination of Member Address+Port. Default=None
1230
+        :returns: List -- List of Member Address+Pool of all members in pool.
1231
+        :returns:[None] -- if no member exists in the pool.
1232
+        :raises: Exception if Loadbalancer is not found for a Pool ID
1233
+        '''
1234
+        existing_members = self._get_existing_pool_members(pool_id)
1235
+        # Members are saved in OVN in the form of
1236
+        # member1_UUID_IP:Port, member2_UUID_IP:Port
1237
+        # Return the list of (UUID,IP:Port) for all members.
1238
+        return [(meminf.split('_')[1], meminf.split(
1239
+            '_')[2]) for meminf in existing_members.split(',')]
1240
+
1195 1241
 
1196 1242
 class OvnProviderDriver(driver_base.ProviderDriver):
1197 1243
     def __init__(self):
@@ -1375,5 +1421,62 @@ class OvnProviderDriver(driver_base.ProviderDriver):
1375 1421
         self._ovn_helper.add_request(request)
1376 1422
 
1377 1423
     def member_batch_update(self, members):
1424
+        # Note(rbanerje): all members belong to the same pool.
1425
+        request_list = []
1426
+        skipped_members = []
1427
+        pool_id = None
1428
+        try:
1429
+            pool_id = members[0].pool_id
1430
+        except IndexError:
1431
+            msg = (_('No member information has been passed'))
1432
+            raise driver_exceptions.UnsupportedOptionError(
1433
+                user_fault_string=msg,
1434
+                operator_fault_string=msg)
1435
+        except AttributeError:
1436
+            msg = (_('Member does not have proper pool information'))
1437
+            raise driver_exceptions.UnsupportedOptionError(
1438
+                user_fault_string=msg,
1439
+                operator_fault_string=msg)
1440
+        current_members = self._ovn_helper.get_member_info(pool_id)
1441
+        # current_members gets a list of tuples (ID, IP:Port) for pool members
1378 1442
         for member in members:
1379
-            self.member_update(member, member)
1443
+            if member.monitor_address or member.monitor_port:
1444
+                skipped_members.append(member.member_id)
1445
+                continue
1446
+            admin_state_up = member.admin_state_up
1447
+            if isinstance(admin_state_up, o_datamodels.UnsetType):
1448
+                admin_state_up = True
1449
+            mem_addr_port = str(member.address) + ':' + str(
1450
+                member.protocol_port)
1451
+            if (member.member_id, mem_addr_port) not in current_members:
1452
+                req_type = REQ_TYPE_MEMBER_CREATE
1453
+            else:
1454
+                # If member exists in pool, then Update
1455
+                req_type = REQ_TYPE_MEMBER_UPDATE
1456
+                current_members.remove((member.member_id, mem_addr_port))
1457
+                # Remove all updating members so only deleted ones are left
1458
+            request_info = {'id': member.member_id,
1459
+                            'address': member.address,
1460
+                            'protocol_port': member.protocol_port,
1461
+                            'pool_id': member.pool_id,
1462
+                            'subnet_id': member.subnet_id,
1463
+                            'admin_state_up': admin_state_up}
1464
+            request = {'type': req_type,
1465
+                       'info': request_info}
1466
+            request_list.append(request)
1467
+        for cmember in current_members:
1468
+            request_info = {'id': cmember[0],
1469
+                            'address': cmember[1].split(':')[0],
1470
+                            'protocol_port': cmember[1].split(':')[1],
1471
+                            'pool_id': pool_id}
1472
+            request = {'type': REQ_TYPE_MEMBER_DELETE,
1473
+                       'info': request_info}
1474
+            request_list.append(request)
1475
+        for request in request_list:
1476
+            self._ovn_helper.add_request(request)
1477
+        if skipped_members:
1478
+            msg = (_('OVN provider does not support monitor options, '
1479
+                     'so following members skipped: %s') % skipped_members)
1480
+            raise driver_exceptions.UnsupportedOptionError(
1481
+                user_fault_string=msg,
1482
+                operator_fault_string=msg)

+ 56
- 20
networking_ovn/tests/functional/octavia/test_ovn_driver.py View File

@@ -195,7 +195,8 @@ class TestOctaviaOvnProviderDriver(base.TestOVNFunctionalBase):
195 195
             else:
196 196
                 del lb_data[ovn_driver.LB_EXT_IDS_LS_REFS_KEY][net_id]
197 197
 
198
-    def _wait_for_status_and_validate(self, lb_data, expected_status):
198
+    def _wait_for_status_and_validate(self, lb_data, expected_status,
199
+                                      check_call=True):
199 200
         call_count = len(expected_status)
200 201
         expected_calls = [mock.call(status) for status in expected_status]
201 202
         update_loadbalancer_status = (
@@ -203,12 +204,13 @@ class TestOctaviaOvnProviderDriver(base.TestOVNFunctionalBase):
203 204
         n_utils.wait_until_true(
204 205
             lambda: update_loadbalancer_status.call_count == call_count,
205 206
             timeout=10)
206
-        try:
207
-            self._o_driver_lib.update_loadbalancer_status.assert_has_calls(
208
-                expected_calls, any_order=True)
209
-        except Exception:
210
-            raise Exception(
211
-                self._o_driver_lib.update_loadbalancer_status.mock_calls)
207
+        if check_call:
208
+            try:
209
+                self._o_driver_lib.update_loadbalancer_status.assert_has_calls(
210
+                    expected_calls, any_order=True)
211
+            except Exception:
212
+                raise Exception(
213
+                    self._o_driver_lib.update_loadbalancer_status.mock_calls)
212 214
         expected_lbs = self._make_expected_lbs(lb_data)
213 215
         self._validate_loadbalancers(expected_lbs)
214 216
 
@@ -545,14 +547,12 @@ class TestOctaviaOvnProviderDriver(base.TestOVNFunctionalBase):
545 547
         self._wait_for_status_and_validate(lb_data, [expected_status])
546 548
 
547 549
     def _update_members_in_batch_and_validate(self, lb_data, pool_id,
548
-                                              member_addresses):
550
+                                              members):
549 551
         pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
550
-
551
-        members = []
552 552
         expected_status = []
553
-        for addr in member_addresses:
554
-            member = self._get_pool_member(pool, addr)
555
-            members.append(member)
553
+        self._o_driver_lib.update_loadbalancer_status.reset_mock()
554
+        self.ovn_driver.member_batch_update(members)
555
+        for member in members:
556 556
             expected_status.append(
557 557
                 {'pools': [{'id': pool.pool_id,
558 558
                            'provisioning_status': 'ACTIVE'}],
@@ -562,9 +562,23 @@ class TestOctaviaOvnProviderDriver(base.TestOVNFunctionalBase):
562 562
                  'loadbalancers': [{'id': pool.loadbalancer_id,
563 563
                                    'provisioning_status': 'ACTIVE'}],
564 564
                  'listeners': []})
565
-        self._o_driver_lib.update_loadbalancer_status.reset_mock()
566
-        self.ovn_driver.member_batch_update(members)
567
-        self._wait_for_status_and_validate(lb_data, expected_status)
565
+        for m in pool.members:
566
+            found = False
567
+            for member in members:
568
+                if member.member_id == m.member_id:
569
+                    found = True
570
+                    break
571
+            if not found:
572
+                expected_status.append(
573
+                    {'pools': [{'id': pool.pool_id,
574
+                                'provisioning_status': 'ACTIVE'}],
575
+                     'members': [{'id': m.member_id,
576
+                                  'provisioning_status': 'DELETED'}],
577
+                     'loadbalancers': [{'id': pool.loadbalancer_id,
578
+                                        'provisioning_status': 'ACTIVE'}],
579
+                     'listeners': []})
580
+        self._wait_for_status_and_validate(lb_data, expected_status,
581
+                                           check_call=False)
568 582
 
569 583
     def _delete_member_and_validate(self, lb_data, pool_id, network_id,
570 584
                                     member_address):
@@ -707,10 +721,6 @@ class TestOctaviaOvnProviderDriver(base.TestOVNFunctionalBase):
707 721
         # Enable loadbalancer back
708 722
         self._update_load_balancer_and_validate(lb_data,
709 723
                                                 admin_state_up=True)
710
-        # Test member_batch_update
711
-        self._update_members_in_batch_and_validate(lb_data, pool_id,
712
-                                                   ['10.0.0.10', '10.0.0.11'])
713
-
714 724
         self._delete_member_and_validate(lb_data, pool_id,
715 725
                                          lb_data['vip_net_info'][0],
716 726
                                          '10.0.0.10')
@@ -829,3 +839,29 @@ class TestOctaviaOvnProviderDriver(base.TestOVNFunctionalBase):
829 839
                                        lb_data['listeners'][0].listener_id)
830 840
         self._delete_listener_and_validate(lb_data)
831 841
         self._delete_load_balancer_and_validate(lb_data)
842
+
843
+    def test_lb_member_batch_update(self):
844
+        # Create a LoadBalancer
845
+        lb_data = self._create_load_balancer_and_validate(
846
+            {'vip_network': 'vip_network',
847
+             'cidr': '10.0.0.0/24'})
848
+        # Create a pool
849
+        self._create_pool_and_validate(lb_data, "p1")
850
+        pool_id = lb_data['pools'][0].pool_id
851
+        # Create Member-1 and associate it with lb_data
852
+        self._create_member_and_validate(
853
+            lb_data, pool_id, lb_data['vip_net_info'][1],
854
+            lb_data['vip_net_info'][0], '10.0.0.10')
855
+        # Create Member-2
856
+        m_member = self._create_member_model(pool_id,
857
+                                             lb_data['vip_net_info'][1],
858
+                                             '10.0.0.12')
859
+        # Update ovn's Logical switch reference
860
+        self._update_ls_refs(lb_data, lb_data['vip_net_info'][0])
861
+        lb_data['pools'][0].members.append(m_member)
862
+        # Add a new member to the LB
863
+        members = [m_member] + [lb_data['pools'][0].members[0]]
864
+        self._update_members_in_batch_and_validate(lb_data, pool_id, members)
865
+        # Deleting one member, while keeping the other member available
866
+        self._update_members_in_batch_and_validate(lb_data, pool_id,
867
+                                                   [m_member])

Loading…
Cancel
Save