Skip to content

Commit 65a4e82

Browse files
Member batch actions to increase performance
When a create or delete cascade request is made for a medium or large-sized Load Balancer (including several pools and members), the response time from the OVN provider grows linearly with the number of members added. This patch optimizes the creation process by handling multiple members in batches. Closes-Bug: #2080630 Change-Id: I94622420f631d8d3fb06b9c29a2066ff73d1cb0b Signed-off-by: Fernando Royo <[email protected]>
1 parent be8fd5c commit 65a4e82

File tree

5 files changed

+531
-409
lines changed

5 files changed

+531
-409
lines changed

ovn_octavia_provider/driver.py

Lines changed: 138 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,13 @@ def loadbalancer_create(self, loadbalancer):
203203
if not isinstance(loadbalancer.pools, o_datamodels.UnsetType):
204204
for pool in loadbalancer.pools:
205205
self.pool_create(pool)
206-
for member in pool.members:
207-
if not member.subnet_id:
208-
member.subnet_id = loadbalancer.vip_subnet_id
209-
self.member_create(member)
206+
members = [
207+
member if member.subnet_id else setattr(
208+
member, "subnet_id", loadbalancer.vip_subnet_id) or
209+
member
210+
for member in pool.members
211+
]
212+
self._members_create(members)
210213

211214
def loadbalancer_delete(self, loadbalancer, cascade=False):
212215
request_info = {'id': loadbalancer.loadbalancer_id,
@@ -259,8 +262,8 @@ def pool_delete(self, pool):
259262
if pool.healthmonitor:
260263
self.health_monitor_delete(pool.healthmonitor)
261264

262-
for member in pool.members:
263-
self.member_delete(member)
265+
if pool.members:
266+
self._members_delete(pool.members)
264267

265268
request_info = {'id': pool.pool_id,
266269
'protocol': pool.protocol,
@@ -373,88 +376,108 @@ def _ip_version_differs(self, member):
373376
return vip_version != (netaddr.IPNetwork(member.address).version)
374377

375378
def member_create(self, member):
376-
# Validate monitoring options if present
377-
self._check_member_monitor_options(member)
378-
if self._ip_version_differs(member):
379-
raise ovn_exc.IPVersionsMixingNotSupportedError()
380-
admin_state_up = member.admin_state_up
381-
subnet_id = member.subnet_id
382-
if (isinstance(subnet_id, o_datamodels.UnsetType) or not subnet_id):
383-
subnet_id, subnet_cidr = self._ovn_helper._get_subnet_from_pool(
384-
member.pool_id)
385-
if not (subnet_id and
386-
self._ovn_helper._check_ip_in_subnet(member.address,
387-
subnet_cidr)):
388-
msg = _('Subnet is required, or Loadbalancer associated with '
389-
'Pool must have a subnet, for Member creation '
390-
'with OVN Provider Driver if it is not the same as '
391-
'LB VIP subnet')
392-
raise driver_exceptions.UnsupportedOptionError(
393-
user_fault_string=msg,
394-
operator_fault_string=msg)
379+
self._members_create([member])
380+
381+
def _members_create(self, members):
382+
request_info = []
383+
request_info_dvr = []
384+
for member in members:
385+
# Validate monitoring options if present
386+
self._check_member_monitor_options(member)
387+
if self._ip_version_differs(member):
388+
raise ovn_exc.IPVersionsMixingNotSupportedError()
389+
admin_state_up = member.admin_state_up
390+
subnt_id = member.subnet_id
391+
if (isinstance(subnt_id, o_datamodels.UnsetType) or
392+
not subnt_id):
393+
subnt_id, subnt_cidr = self._ovn_helper._get_subnet_from_pool(
394+
member.pool_id)
395+
396+
if not (subnt_id and
397+
self._ovn_helper._check_ip_in_subnet(member.address,
398+
subnt_cidr)):
399+
msg = _('Subnet is required, or Loadbalancer associated '
400+
'with Pool must have a subnet, for Member '
401+
'creation with OVN Provider Driver if it is not '
402+
'the same as LB VIP subnet')
403+
raise driver_exceptions.UnsupportedOptionError(
404+
user_fault_string=msg,
405+
operator_fault_string=msg)
406+
407+
if isinstance(admin_state_up, o_datamodels.UnsetType):
408+
admin_state_up = True
409+
request_info.append({'id': member.member_id,
410+
'address': member.address,
411+
'protocol_port': member.protocol_port,
412+
'pool_id': member.pool_id,
413+
'subnet_id': subnt_id,
414+
'admin_state_up': admin_state_up})
415+
416+
# NOTE(mjozefcz): If LB has FIP on VIP
417+
# and member has FIP we need to centralize
418+
# traffic for member.
419+
request_info_dvr.append({'id': member.member_id,
420+
'address': member.address,
421+
'pool_id': member.pool_id,
422+
'subnet_id': subnt_id,
423+
'action': ovn_const.REQ_INFO_MEMBER_ADDED}
424+
)
395425

396-
if isinstance(admin_state_up, o_datamodels.UnsetType):
397-
admin_state_up = True
398-
request_info = {'id': member.member_id,
399-
'address': member.address,
400-
'protocol_port': member.protocol_port,
401-
'pool_id': member.pool_id,
402-
'subnet_id': subnet_id,
403-
'admin_state_up': admin_state_up}
404426
request = {'type': ovn_const.REQ_TYPE_MEMBER_CREATE,
405427
'info': request_info}
406428
self._ovn_helper.add_request(request)
407429

408-
# NOTE(mjozefcz): If LB has FIP on VIP
409-
# and member has FIP we need to centralize
410-
# traffic for member.
411-
request_info = {'id': member.member_id,
412-
'address': member.address,
413-
'pool_id': member.pool_id,
414-
'subnet_id': subnet_id,
415-
'action': ovn_const.REQ_INFO_MEMBER_ADDED}
416430
request = {'type': ovn_const.REQ_TYPE_HANDLE_MEMBER_DVR,
417-
'info': request_info}
431+
'info': request_info_dvr}
418432
self._ovn_helper.add_request(request)
419433

420434
def member_delete(self, member):
435+
self._members_delete([member])
436+
437+
def _members_delete(self, members):
421438
# NOTE(froyo): OVN provider allow to create member without param
422439
# subnet_id, in that case the driver search it according to the
423440
# pool_id, but it is not propagated to Octavia. In this case, if
424441
# the member is deleted, Octavia send the object without subnet_id.
425-
subnet_id = member.subnet_id
426-
if (isinstance(subnet_id, o_datamodels.UnsetType) or not subnet_id):
427-
subnet_id, subnet_cidr = self._ovn_helper._get_subnet_from_pool(
428-
member.pool_id)
429-
if not (subnet_id and
430-
self._ovn_helper._check_ip_in_subnet(member.address,
431-
subnet_cidr)):
432-
msg = _('Subnet is required, or Loadbalancer associated with '
433-
'Pool must have a subnet, for Member deletion if it is'
434-
'with OVN Provider Driver if it is not the same as '
435-
'LB VIP subnet')
436-
raise driver_exceptions.UnsupportedOptionError(
437-
user_fault_string=msg,
438-
operator_fault_string=msg)
442+
request_info = []
443+
request_info_dvr = []
444+
for member in members:
445+
subnt_id = member.subnet_id
446+
if (isinstance(subnt_id, o_datamodels.UnsetType) or not subnt_id):
447+
subnt_id, subnt_cidr = self._ovn_helper._get_subnet_from_pool(
448+
member.pool_id)
449+
if not (subnt_id and
450+
self._ovn_helper._check_ip_in_subnet(member.address,
451+
subnt_cidr)):
452+
msg = _('Subnet is required, or Loadbalancer associated '
453+
'with Pool must have a subnet, for Member '
454+
'deletion if it is with OVN Provider Driver if it '
455+
'is not the same as LB VIP subnet')
456+
raise driver_exceptions.UnsupportedOptionError(
457+
user_fault_string=msg,
458+
operator_fault_string=msg)
459+
460+
request_info.append({'id': member.member_id,
461+
'address': member.address,
462+
'protocol_port': member.protocol_port,
463+
'pool_id': member.pool_id,
464+
'subnet_id': subnt_id})
465+
# NOTE(mjozefcz): If LB has FIP on VIP
466+
# and member had FIP we can decentralize
467+
# the traffic now.
468+
request_info_dvr.append(
469+
{'id': member.member_id,
470+
'address': member.address,
471+
'pool_id': member.pool_id,
472+
'subnet_id': subnt_id,
473+
'action': ovn_const.REQ_INFO_MEMBER_DELETED})
439474

440-
request_info = {'id': member.member_id,
441-
'address': member.address,
442-
'protocol_port': member.protocol_port,
443-
'pool_id': member.pool_id,
444-
'subnet_id': subnet_id}
445475
request = {'type': ovn_const.REQ_TYPE_MEMBER_DELETE,
446476
'info': request_info}
447477
self._ovn_helper.add_request(request)
448-
# NOTE(mjozefcz): If LB has FIP on VIP
449-
# and member had FIP we can decentralize
450-
# the traffic now.
451-
request_info = {'id': member.member_id,
452-
'address': member.address,
453-
'pool_id': member.pool_id,
454-
'subnet_id': subnet_id,
455-
'action': ovn_const.REQ_INFO_MEMBER_DELETED}
478+
456479
request = {'type': ovn_const.REQ_TYPE_HANDLE_MEMBER_DVR,
457-
'info': request_info}
480+
'info': request_info_dvr}
458481
self._ovn_helper.add_request(request)
459482

460483
def member_update(self, old_member, new_member):
@@ -470,7 +493,7 @@ def member_update(self, old_member, new_member):
470493
if not isinstance(new_member.admin_state_up, o_datamodels.UnsetType):
471494
request_info['admin_state_up'] = new_member.admin_state_up
472495
request = {'type': ovn_const.REQ_TYPE_MEMBER_UPDATE,
473-
'info': request_info}
496+
'info': [request_info]}
474497
self._ovn_helper.add_request(request)
475498

476499
def member_batch_update(self, pool_id, members):
@@ -482,6 +505,10 @@ def member_batch_update(self, pool_id, members):
482505
members_to_delete = copy.copy(existing_members)
483506
pool_subnet_id = None
484507
pool_subnet_cidr = None
508+
request_info_create = []
509+
request_info_update = []
510+
request_info_delete = []
511+
request_info_dvr_delete = []
485512
for member in members:
486513
# NOTE(froyo): in order to keep sync with Octavia DB, we raise
487514
# not supporting exceptions as soon as posible, considering the
@@ -520,49 +547,65 @@ def member_batch_update(self, pool_id, members):
520547
admin_state_up = True
521548

522549
member_info = self._ovn_helper._get_member_info(member)
550+
request_info = {
551+
'id': member.member_id,
552+
'address': member.address,
553+
'protocol_port': member.protocol_port,
554+
'pool_id': member.pool_id,
555+
'subnet_id': member.subnet_id,
556+
'admin_state_up': admin_state_up}
557+
523558
if member_info not in existing_members:
524-
req_type = ovn_const.REQ_TYPE_MEMBER_CREATE
559+
request_info_create.append(request_info)
525560
else:
526561
# If member exists in pool, then Update
527-
req_type = ovn_const.REQ_TYPE_MEMBER_UPDATE
562+
request_info_update.append(request_info)
528563
# Remove all updating members so only deleted ones are left
529564
members_to_delete.remove(member_info)
530565

531-
request_info = {'id': member.member_id,
532-
'address': member.address,
533-
'protocol_port': member.protocol_port,
534-
'pool_id': member.pool_id,
535-
'subnet_id': member.subnet_id,
536-
'admin_state_up': admin_state_up}
537-
request = {'type': req_type,
538-
'info': request_info}
539-
request_list.append(request)
540-
541566
for member in members_to_delete:
542567
member_info = member.split('_')
543568
member_ip, member_port, subnet_id, member_id = (
544569
self._ovn_helper._extract_member_info(member)[0])
545-
request_info = {'id': member_info[1],
546-
'address': member_ip,
547-
'protocol_port': member_port,
548-
'pool_id': pool_id}
570+
request_info = {
571+
'id': member_info[1],
572+
'address': member_ip,
573+
'protocol_port': member_port,
574+
'pool_id': pool_id}
549575
if len(member_info) == 4:
550576
request_info['subnet_id'] = subnet_id
551-
request = {'type': ovn_const.REQ_TYPE_MEMBER_DELETE,
552-
'info': request_info}
553-
request_list.append(request)
577+
request_info_delete.append(request_info)
554578

555579
# NOTE(mjozefcz): If LB has FIP on VIP
556580
# and member had FIP we can decentralize
557581
# the traffic now.
558-
request_info = {'id': member_id,
559-
'address': member_ip,
560-
'pool_id': pool_id,
561-
'action': ovn_const.REQ_INFO_MEMBER_DELETED}
582+
request_info = {
583+
'id': member_id,
584+
'address': member_ip,
585+
'pool_id': pool_id,
586+
'action': ovn_const.REQ_INFO_MEMBER_DELETED}
562587
if len(member_info) == 4:
563588
request_info['subnet_id'] = subnet_id
589+
request_info_dvr_delete.append(request_info)
590+
591+
if request_info_create:
592+
request = {'type': ovn_const.REQ_TYPE_MEMBER_CREATE,
593+
'info': request_info_create}
594+
request_list.append(request)
595+
596+
if request_info_update:
597+
request = {'type': ovn_const.REQ_TYPE_MEMBER_UPDATE,
598+
'info': request_info_update}
599+
request_list.append(request)
600+
601+
if request_info_delete:
602+
request = {'type': ovn_const.REQ_TYPE_MEMBER_DELETE,
603+
'info': request_info_delete}
604+
request_list.append(request)
605+
606+
if request_info_dvr_delete:
564607
request = {'type': ovn_const.REQ_TYPE_HANDLE_MEMBER_DVR,
565-
'info': request_info}
608+
'info': request_info_dvr_delete}
566609
request_list.append(request)
567610

568611
for request in request_list:

0 commit comments

Comments
 (0)