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