Skip to content

Commit f419488

Browse files
Add Member sync logic
This patch adds the logic to sync a Member entity from the Octavia database, correcting any discrepancies in fields or creating it if it does not exist in the OVN LB related on OVN Northbound (NB) database. Future patches will incrementally add support for syncing the remaining entities. Related-Bug: #2045415 Co-authored-by: Fernando Royo <[email protected]> Co-authored-by: Rico Lin <[email protected]> Change-Id: I0c0b92b89e9ff6b3623d50637fe4bb82d1cae69d
1 parent e7068ae commit f419488

File tree

5 files changed

+662
-16
lines changed

5 files changed

+662
-16
lines changed

ovn_octavia_provider/driver.py

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,42 @@ def _get_pool_request_info(self, pool):
139139

140140
return request_info
141141

142+
def _get_member_request_info(self, member, create=True):
143+
# Validate monitoring options if present
144+
admin_state_up = None
145+
if create:
146+
self._check_member_monitor_options(member)
147+
if self._ip_version_differs(member):
148+
raise ovn_exc.IPVersionsMixingNotSupportedError()
149+
admin_state_up = member.admin_state_up
150+
subnet_id = member.subnet_id
151+
if (isinstance(subnet_id, o_datamodels.UnsetType) or not subnet_id):
152+
subnet_id, subnet_cidr = self._ovn_helper._get_subnet_from_pool(
153+
member.pool_id)
154+
if not (subnet_id and
155+
self._ovn_helper._check_ip_in_subnet(member.address,
156+
subnet_cidr)):
157+
msg = _('Subnet is required, or Loadbalancer associated with '
158+
'Pool must have a subnet, for Member creation '
159+
'with OVN Provider Driver if it is not the same as '
160+
'LB VIP subnet')
161+
raise driver_exceptions.UnsupportedOptionError(
162+
user_fault_string=msg,
163+
operator_fault_string=msg)
164+
165+
if isinstance(admin_state_up, o_datamodels.UnsetType):
166+
admin_state_up = True
167+
request_info = {'id': member.member_id,
168+
'address': member.address,
169+
'protocol_port': member.protocol_port,
170+
'pool_id': member.pool_id,
171+
'subnet_id': subnet_id}
172+
173+
if admin_state_up and create:
174+
request_info['admin_state_up'] = admin_state_up
175+
176+
return request_info
177+
142178
def loadbalancer_create(self, loadbalancer):
143179
request = {'type': ovn_const.REQ_TYPE_LB_CREATE,
144180
'info': self._get_loadbalancer_request_info(
@@ -648,7 +684,16 @@ def _ensure_loadbalancer(self, loadbalancer):
648684
status_pool = self._ovn_helper.pool_create(
649685
self._get_pool_request_info(pool))
650686
status[constants.POOLS].append(status_pool)
687+
for member in pool.members:
688+
status[constants.MEMBERS] = []
689+
if not member.subnet_id:
690+
member.subnet_id = loadbalancer.vip_subnet_id
691+
status_member = self._ovn_helper.member_create(
692+
self._get_member_request_info(member))
693+
status[constants.MEMBERS].append(status_member)
694+
651695
self._ovn_helper._update_status_to_octavia(status)
696+
652697
else:
653698
# Load Balancer found, check LB and listener/pool/member/hms
654699
# related
@@ -667,8 +712,53 @@ def _ensure_loadbalancer(self, loadbalancer):
667712
# Pool
668713
if not isinstance(loadbalancer.pools, o_datamodels.UnsetType):
669714
for pool in loadbalancer.pools:
670-
self._ovn_helper.pool_sync(
671-
self._get_pool_request_info(pool), ovn_lb)
715+
pool_info = self._get_pool_request_info(pool)
716+
self._ovn_helper.pool_sync(pool_info, ovn_lb)
717+
ovn_pool_key = self._ovn_helper._get_pool_key(
718+
pool_info[constants.ID],
719+
is_enabled=pool_info[constants.ADMIN_STATE_UP])
720+
member_ids = []
721+
if not isinstance(pool.members,
722+
o_datamodels.UnsetType):
723+
for member in pool.members:
724+
if not member.subnet_id:
725+
member.subnet_id = (
726+
loadbalancer.vip_subnet_id
727+
)
728+
self._ovn_helper.member_sync(
729+
self._get_member_request_info(member),
730+
ovn_lb,
731+
ovn_pool_key)
732+
member_ids.append(member.member_id)
733+
734+
for ovn_mb_info in \
735+
self._ovn_helper._get_members_in_ovn_lb(
736+
ovn_lb, ovn_pool_key):
737+
# If member ID not in pool member list,
738+
# delete it.
739+
if ovn_mb_info[3] not in member_ids:
740+
LOG.debug(
741+
"Start deleting extra member "
742+
f"{ovn_mb_info[3]} from pool "
743+
"{pool_info[constants.ID]} in OVN."
744+
)
745+
mb_delete_info = {
746+
'id': ovn_mb_info[3],
747+
'subnet_id': ovn_mb_info[2],
748+
}
749+
self._ovn_helper.member_delete(
750+
mb_delete_info)
751+
752+
mb_delete_dvr_info = {
753+
'id': ovn_mb_info[3],
754+
'address': ovn_mb_info[0],
755+
'pool_id': pool_info[constants.ID],
756+
'subnet_id': ovn_mb_info[2],
757+
'action':
758+
ovn_const.REQ_INFO_MEMBER_DELETED
759+
}
760+
self._ovn_helper.handle_member_dvr(
761+
mb_delete_dvr_info)
672762
status = self._ovn_helper._get_current_operating_statuses(
673763
ovn_lb)
674764
self._ovn_helper._update_status_to_octavia(status)
@@ -691,9 +781,20 @@ def do_sync(self, **lb_filters):
691781
] if listeners else o_datamodels.Unset
692782

693783
pools = provider_lb.pools or []
694-
provider_lb.pools = [
695-
o_datamodels.Pool.from_dict(pool)
696-
for pool in pools
697-
] if pools else o_datamodels.Unset
698-
784+
provider_pools = []
785+
for pool in pools:
786+
provider_pool = o_datamodels.Pool.from_dict(pool)
787+
# format member provider
788+
members = provider_pool.members
789+
if not isinstance(members, o_datamodels.UnsetType) and members:
790+
provider_pool.members = [
791+
o_datamodels.Member.from_dict(m)
792+
for m in members]
793+
else:
794+
provider_pool.members = o_datamodels.Unset
795+
provider_pools.append(provider_pool)
796+
797+
provider_lb.pools = (
798+
provider_pools if provider_pools else o_datamodels.Unset
799+
)
699800
self._ensure_loadbalancer(provider_lb)

ovn_octavia_provider/helper.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,42 @@ def _add_persistence_timeout_command(self, commands, ovn_lb,
722722
('options', options))
723723
)
724724

725+
def _update_pool_data(self, member, pool_key, external_ids):
726+
"""Update pool data with member information."""
727+
pool_data = None
728+
existing_members = external_ids.get(pool_key, "")
729+
member_info = self._get_member_info(member)
730+
731+
if existing_members:
732+
members = existing_members.split(",")
733+
if member_info not in members:
734+
members.append(member_info)
735+
pool_data = {pool_key: ",".join(members)}
736+
else:
737+
pool_data = {pool_key: member_info}
738+
739+
return pool_data
740+
741+
def _add_pool_data_command(self, commands, ovn_lb, pool_data):
742+
"""Add command to update pool data in LoadBalancer."""
743+
if pool_data:
744+
commands.append(
745+
self.ovn_nbdb_api.db_set('Load_Balancer', ovn_lb.uuid,
746+
('external_ids', pool_data))
747+
)
748+
749+
def _get_related_lr(self, member):
750+
"""Retrieve the logical router related to the member's subnet."""
751+
neutron_client = clients.get_neutron_client()
752+
try:
753+
subnet = neutron_client.get_subnet(member[constants.SUBNET_ID])
754+
ls_name = utils.ovn_name(subnet.network_id)
755+
ovn_ls = self.ovn_nbdb_api.ls_get(ls_name).execute(
756+
check_error=True)
757+
return self._find_lr_of_ls(ovn_ls, subnet.gateway_ip)
758+
except (idlutils.RowNotFound, openstack.exceptions.ResourceNotFound):
759+
return None
760+
725761
def _lb_status(self, loadbalancer, provisioning_status, operating_status):
726762
"""Return status for the LoadBalancer."""
727763
return {
@@ -2507,6 +2543,73 @@ def _update_external_ids_member_status(self, ovn_lb, member, status=None,
25072543
" %s delete: %s status: %s", str(member),
25082544
str(delete), str(status))
25092545

2546+
def _get_members_in_ovn_lb(self, ovn_lb, pool_key):
2547+
existing_members = ovn_lb.external_ids.get(pool_key, None)
2548+
if existing_members:
2549+
existing_members = existing_members.split(",")
2550+
return [
2551+
self._extract_member_info(
2552+
member)[0] for member in existing_members
2553+
]
2554+
else:
2555+
return []
2556+
2557+
def member_sync(self, member, ovn_lb, pool_key):
2558+
"""Sync Member object with an OVN LoadBalancer
2559+
2560+
The method performs the following steps:
2561+
1. Update pool key with member info on OVN Loadbalancer external_ids
2562+
if needed
2563+
2. Update OVN LoadBalancer vips
2564+
3. Update references on LS or LR from the member if needed
2565+
4. Update OVN Loadbalancer member_status info on external_ids
2566+
2567+
:param member: The source member object from Octavia DB
2568+
:param ovn_lb: The OVN LoadBalancer object that needs to be sync
2569+
:param pool_key: The pool_key where member is associated
2570+
"""
2571+
external_ids = copy.deepcopy(ovn_lb.external_ids)
2572+
pool_data = self._update_pool_data(member, pool_key, external_ids)
2573+
2574+
commands = []
2575+
if pool_data:
2576+
self._add_pool_data_command(commands, ovn_lb, pool_data)
2577+
external_ids[pool_key] = pool_data[pool_key]
2578+
2579+
try:
2580+
if member.get(constants.ADMIN_STATE_UP, False):
2581+
commands.extend(self._refresh_lb_vips(
2582+
ovn_lb, external_ids, is_sync=True))
2583+
except Exception as e:
2584+
LOG.exception(f"Failed to refresh LB VIPs: {e}")
2585+
return
2586+
2587+
try:
2588+
self._execute_commands(commands)
2589+
except Exception as e:
2590+
LOG.exception(f"Failed to execute commands for listener sync: {e}")
2591+
return
2592+
2593+
self._update_lb_to_ls_association(
2594+
ovn_lb, subnet_id=member[constants.SUBNET_ID], associate=True,
2595+
update_ls_ref=True, is_sync=True)
2596+
2597+
# Make sure that all logical switches related to logical router
2598+
# are associated with the load balancer. This is needed to handle
2599+
# potential race that happens when lrp and lb are created at the
2600+
# same time.
2601+
ovn_lr = self._get_related_lr(member)
2602+
2603+
if ovn_lr:
2604+
self._sync_lb_to_lr_association(ovn_lb, ovn_lr)
2605+
2606+
# TODO(froyo): Check if originally status in Octavia is ERROR if
2607+
# we receive that info from the object
2608+
self._update_external_ids_member_status(
2609+
ovn_lb,
2610+
member[constants.ID],
2611+
constants.NO_MONITOR)
2612+
25102613
def _add_member(self, member, ovn_lb, pool_key):
25112614
external_ids = copy.deepcopy(ovn_lb.external_ids)
25122615
existing_members = external_ids[pool_key]

0 commit comments

Comments
 (0)