Skip to content

Commit b814516

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add Member sync logic"
2 parents 80cffad + f419488 commit b814516

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
@@ -721,6 +721,42 @@ def _add_persistence_timeout_command(self, commands, ovn_lb,
721721
('options', options))
722722
)
723723

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

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

0 commit comments

Comments
 (0)