Fix peer-controller-gateway-address update

This commit updates the peer group association sync status to
'out-of-sync' after the user updates the
peer-controller-gateway-address attribute of the system-peer object.

This commit also modifies the subcloud update function to update the
subcloud route whenever the systemcontroller_gateway_address is
updated on the primary side and synced to the secondary.

It also adds an informative message to remind the caller to run the
sync command after updating the peer-controller-gateway-address.

Test Plan:
1. PASS: Do the following steps:
         - Create a system peer with an incorrect systemcontroller
         gateway address that's inside the management subcloud, but
         outside the reserved IP range and then create an association.
         Verify that the secondary subcloud and a route was created
         using the incorrect IP.
         - Update the system peer with the correct systemcontroller
         gateway address on the primary site. Verify that the PGA
         sync status is set to 'out-of-sync' on both sites.
         - Sync the PGA and verify that the secondary subcloud
         systemcontroller gateway address was updated and that the
         old route was deleted and a new one using the new address
         was created.
         - Migrate the SPG to the non-primary site and verify that
         it completes successfully and that the subcloud becomes
         online and managed.
2. PASS: Repeat the first step of test case #1, but use an incorrect
         address that's outside the management subnet. Then create
         a PGA and verify that it fails due to the following
         validation:
         "systemcontroller_gateway_address invalid: Address must be in
         subnet <management subnet>"
3. PASS: Repeat the first step of test case #1, but use an incorrect
         address that's inside the reserved IP range. Then create
         a PGA and verify that it fails due to the following
         validation:
         "systemcontroller_gateway_address invalid, is within
         management pool <ip range>"
4. PASS: Create a system peer with a correct systemcontroller gateway
         address for the first time and then create an association.
         Verify that the secondary subcloud and a route was created
         using the correct IP.
5. PASS: Update an attribute of the subcloud (e.g. the subcloud
         description) on the primary site and verify that the sync
         status chages to 'out-of-sync' on both sites, then run
         the PGA sync operation and verify that the attribute was
         synced to the secondary subcloud on the peer site.

Closes-Bug: 2062372

Change-Id: Ibffe6c86656a56a85d10deca54c161bbed7f0d17
Signed-off-by: Gustavo Herzmann <gustavo.herzmann@windriver.com>
This commit is contained in:
Gustavo Herzmann 2024-04-17 15:40:28 -03:00
parent f30002f332
commit d3b7434a5b
5 changed files with 124 additions and 31 deletions

View File

@ -33,6 +33,7 @@ from requests_toolbelt.multipart import decoder
import pecan
from pecan import expose
from pecan import request
import yaml
from fm_api.constants import FM_ALARM_ID_UNSYNCHRONIZED_RESOURCE
@ -876,6 +877,32 @@ class SubcloudsController(object):
)
pecan.abort(400, error_msg)
# If the peer-controller-gateway-address attribute of the
# system_peer object on the peer site is updated, the route needs
# to be updated, so we validate it here.
if bootstrap_values is not None and req_from_another_dc:
try:
bootstrap_values_dict = yaml.load(
bootstrap_values, Loader=yaml.SafeLoader
)
except Exception:
error_msg = 'bootstrap_values is malformed.'
LOG.exception(error_msg)
pecan.abort(400, _(error_msg))
systemcontroller_gateway_address = bootstrap_values_dict.get(
"systemcontroller_gateway_address"
)
if (
systemcontroller_gateway_address is not None and
systemcontroller_gateway_address !=
subcloud.systemcontroller_gateway_ip
):
psd_common.validate_systemcontroller_gateway_address(
systemcontroller_gateway_address
)
management_state = payload.get('management-state')
group_id = payload.get('group_id')
description = payload.get('description')

View File

@ -19,9 +19,11 @@ from pecan import request
from dcmanager.api.controllers import restcomm
from dcmanager.api.policies import system_peers as system_peer_policy
from dcmanager.api import policy
from dcmanager.common import consts
from dcmanager.common.i18n import _
from dcmanager.common import utils
from dcmanager.db import api as db_api
from dcmanager.rpc import client as rpc_client
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@ -60,6 +62,8 @@ class SystemPeersController(restcomm.GenericPathController):
def __init__(self):
super(SystemPeersController, self).__init__()
self.dcmanager_rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template='json')
def index(self):
# Route the request to specific methods with parameters
@ -446,7 +450,34 @@ class SystemPeersController(restcomm.GenericPathController):
heartbeat_failure_threshold,
heartbeat_failure_policy,
heartbeat_maintenance_timeout)
return db_api.system_peer_db_model_to_dict(updated_peer)
updated_peer_dict = db_api.system_peer_db_model_to_dict(updated_peer)
# Set the sync status to out-of-sync if the peer_controller_gateway_ip
# was updated
if (
gateway_ip is not None and
gateway_ip != peer.peer_controller_gateway_ip
):
associations = db_api.peer_group_association_get_by_system_peer_id(
context, peer.id
)
association_ids = set()
for association in associations:
# Update the sync status on both sites
association_ids.update(
self.dcmanager_rpc_client.update_association_sync_status(
context, association.peer_group_id,
consts.ASSOCIATION_SYNC_STATUS_OUT_OF_SYNC
)
)
# Generate an informative message to remind the operator
# that the sync command(s) should be executed.
info_message = utils.generate_sync_info_message(association_ids)
if info_message:
updated_peer_dict["info_message"] = info_message
return updated_peer_dict
except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e:

View File

@ -177,6 +177,39 @@ def validate_secondary_parameter(payload, request):
'not allowed'))
def validate_systemcontroller_gateway_address(gateway_address: str) -> None:
"""Aborts the request if the systemcontroller gateway address is invalid
:param gateway_address: systemcontroller gateway address
"""
# Ensure systemcontroller gateway is in the management subnet
# for the systemcontroller region.
management_address_pool = get_network_address_pool()
systemcontroller_subnet_str = "%s/%d" % (
management_address_pool.network,
management_address_pool.prefix)
systemcontroller_subnet = netaddr.IPNetwork(systemcontroller_subnet_str)
try:
systemcontroller_gw_ip = utils.validate_address_str(
gateway_address,
systemcontroller_subnet
)
except exceptions.ValidateFail as e:
LOG.exception(e)
pecan.abort(400, _("systemcontroller_gateway_address invalid: %s") % e)
# Ensure systemcontroller gateway is not within the actual
# management subnet address pool to prevent address collision.
mgmt_address_start = netaddr.IPAddress(management_address_pool.ranges[0][0])
mgmt_address_end = netaddr.IPAddress(management_address_pool.ranges[0][1])
if ((systemcontroller_gw_ip >= mgmt_address_start) and
(systemcontroller_gw_ip <= mgmt_address_end)):
pecan.abort(400, _("systemcontroller_gateway_address invalid, "
"is within management pool: %(start)s - "
"%(end)s") %
{'start': mgmt_address_start, 'end': mgmt_address_end})
def validate_subcloud_config(context, payload, operation=None,
ignore_conflicts_with=None):
"""Check whether subcloud config is valid."""
@ -299,32 +332,9 @@ def validate_subcloud_config(context, payload, operation=None,
'start': subcloud_mgmt_address_start,
'end': subcloud_mgmt_address_end})
# Ensure systemcontroller gateway is in the management subnet
# for the systemcontroller region.
management_address_pool = get_network_address_pool()
systemcontroller_subnet_str = "%s/%d" % (
management_address_pool.network,
management_address_pool.prefix)
systemcontroller_subnet = netaddr.IPNetwork(systemcontroller_subnet_str)
try:
systemcontroller_gw_ip = utils.validate_address_str(
payload.get('systemcontroller_gateway_address'),
systemcontroller_subnet
)
except exceptions.ValidateFail as e:
LOG.exception(e)
pecan.abort(400, _("systemcontroller_gateway_address invalid: %s") % e)
# Ensure systemcontroller gateway is not within the actual
# management subnet address pool to prevent address collision.
mgmt_address_start = netaddr.IPAddress(management_address_pool.ranges[0][0])
mgmt_address_end = netaddr.IPAddress(management_address_pool.ranges[0][1])
if ((systemcontroller_gw_ip >= mgmt_address_start) and
(systemcontroller_gw_ip <= mgmt_address_end)):
pecan.abort(400, _("systemcontroller_gateway_address invalid, "
"is within management pool: %(start)s - "
"%(end)s") %
{'start': mgmt_address_start, 'end': mgmt_address_end})
validate_systemcontroller_gateway_address(
payload.get('systemcontroller_gateway_address')
)
validate_oam_network_config(
payload.get('external_oam_subnet'),

View File

@ -2836,10 +2836,15 @@ class SubcloudManager(manager.Manager):
'bootstrap-address'] = bootstrap_address
rehome_data = None
systemcontroller_gateway_address = None
if rehome_data_dict:
rehome_data = json.dumps(rehome_data_dict)
systemcontroller_gateway_address = \
rehome_data_dict['saved_payload'].get(
"systemcontroller_gateway_address"
)
return rehome_data
return rehome_data, systemcontroller_gateway_address
def update_subcloud(self,
context,
@ -2889,8 +2894,10 @@ class SubcloudManager(manager.Manager):
subcloud, force)
# Update bootstrap values into rehome_data
rehome_data = self._prepare_rehome_data(subcloud, bootstrap_values,
bootstrap_address)
rehome_data, systemcontroller_gateway_ip = self._prepare_rehome_data(
subcloud, bootstrap_values, bootstrap_address
)
if deploy_status:
msg = None
# Only update deploy_status if subcloud is or will be unmanaged
@ -2908,6 +2915,21 @@ class SubcloudManager(manager.Manager):
LOG.warning(msg)
raise exceptions.BadRequest(resource='subcloud', msg=msg)
# Update route if the systemcontroller_gateway_ip has been updated
if (
systemcontroller_gateway_ip is not None and
systemcontroller_gateway_ip != subcloud.systemcontroller_gateway_ip
):
m_ks_client = OpenStackDriver(
region_name=dccommon_consts.DEFAULT_REGION_NAME,
region_clients=None).keystone_client
self._create_subcloud_route(
{'management_subnet': subcloud.management_subnet},
m_ks_client, systemcontroller_gateway_ip
)
# Deletes old routes (subcloud obj holds old gateway ip)
self._delete_subcloud_routes(m_ks_client, subcloud)
subcloud = db_api.subcloud_update(
context,
subcloud_id,
@ -2918,7 +2940,8 @@ class SubcloudManager(manager.Manager):
data_install=data_install,
deploy_status=deploy_status,
peer_group_id=peer_group_id,
rehome_data=rehome_data
rehome_data=rehome_data,
systemcontroller_gateway_ip=systemcontroller_gateway_ip
)
# Inform orchestrators that subcloud has been updated

View File

@ -288,6 +288,8 @@ class SystemPeerManager(manager.Manager):
rehome_data = json.loads(subcloud.rehome_data)
subcloud_payload = rehome_data['saved_payload']
# Update bootstrap_values with the peer site
# systemcontroller_gateway_address
subcloud_payload['systemcontroller_gateway_address'] = \
peer_controller_gateway_ip