From d3b7434a5b6279ecdc924f625e19579033868321 Mon Sep 17 00:00:00 2001 From: Gustavo Herzmann Date: Wed, 17 Apr 2024 15:40:28 -0300 Subject: [PATCH] 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 " 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 " 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 --- .../dcmanager/api/controllers/v1/subclouds.py | 27 ++++++++ .../api/controllers/v1/system_peers.py | 33 +++++++++- .../common/phased_subcloud_deploy.py | 62 +++++++++++-------- .../dcmanager/manager/subcloud_manager.py | 31 ++++++++-- .../dcmanager/manager/system_peer_manager.py | 2 + 5 files changed, 124 insertions(+), 31 deletions(-) diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index 68b528856..c5d0398d3 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -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') diff --git a/distributedcloud/dcmanager/api/controllers/v1/system_peers.py b/distributedcloud/dcmanager/api/controllers/v1/system_peers.py index c365152b0..032649a57 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/system_peers.py +++ b/distributedcloud/dcmanager/api/controllers/v1/system_peers.py @@ -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: diff --git a/distributedcloud/dcmanager/common/phased_subcloud_deploy.py b/distributedcloud/dcmanager/common/phased_subcloud_deploy.py index 22c663d27..9fe84f8fd 100644 --- a/distributedcloud/dcmanager/common/phased_subcloud_deploy.py +++ b/distributedcloud/dcmanager/common/phased_subcloud_deploy.py @@ -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'), diff --git a/distributedcloud/dcmanager/manager/subcloud_manager.py b/distributedcloud/dcmanager/manager/subcloud_manager.py index 6e1e8240a..b6c5b6c6a 100644 --- a/distributedcloud/dcmanager/manager/subcloud_manager.py +++ b/distributedcloud/dcmanager/manager/subcloud_manager.py @@ -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 diff --git a/distributedcloud/dcmanager/manager/system_peer_manager.py b/distributedcloud/dcmanager/manager/system_peer_manager.py index 1712eea1b..11cfc79c8 100644 --- a/distributedcloud/dcmanager/manager/system_peer_manager.py +++ b/distributedcloud/dcmanager/manager/system_peer_manager.py @@ -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