diff --git a/api-ref/source/samples/subclouds/subcloud-get-detail-response.json b/api-ref/source/samples/subclouds/subcloud-get-detail-response.json index fb51134a7..3b8e0ff8d 100644 --- a/api-ref/source/samples/subclouds/subcloud-get-detail-response.json +++ b/api-ref/source/samples/subclouds/subcloud-get-detail-response.json @@ -21,6 +21,7 @@ "data_install": null, "data_upgrade": null, "oam_floating_ip": "192.168.101.2", + "config_sync_status": "Deployment: configurations up-to-date" "endpoint_sync_status": [ { "sync_status": "in-sync", diff --git a/distributedcloud/dccommon/consts.py b/distributedcloud/dccommon/consts.py index cd8c4ef45..541f72e66 100644 --- a/distributedcloud/dccommon/consts.py +++ b/distributedcloud/dccommon/consts.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2022 Wind River Systems, Inc. +# Copyright (c) 2020-2023 Wind River Systems, Inc. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -124,6 +124,11 @@ SYNC_STATUS_UNKNOWN = "unknown" SYNC_STATUS_IN_SYNC = "in-sync" SYNC_STATUS_OUT_OF_SYNC = "out-of-sync" +# Subcloud deploy configuration status +DEPLOY_CONFIG_UP_TO_DATE = 'Deployment: configurations up-to-date' +DEPLOY_CONFIG_OUT_OF_DATE = 'Deployment: configurations out-of-date' +MONITORED_ALARM_ENTITIES = ['host.starlingx.windriver.com', ] + # OS type OS_RELEASE_FILE = '/etc/os-release' OS_CENTOS = 'centos' diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index 9040926c2..eb637c2ca 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -39,6 +39,7 @@ from pecan import expose from pecan import request from dccommon import consts as dccommon_consts +from dccommon.drivers.openstack.fm import FmClient from dccommon.drivers.openstack import patching_v1 from dccommon.drivers.openstack.patching_v1 import PatchingClient from dccommon.drivers.openstack.sdk_platform import OpenStackDriver @@ -61,6 +62,7 @@ from dcmanager.common import utils from dcmanager.db import api as db_api from dcmanager.rpc import client as rpc_client +from fm_api.constants import FM_ALARM_ID_UNSYNCHRONIZED_RESOURCE from six.moves import range CONF = cfg.CONF @@ -1006,12 +1008,11 @@ class SubcloudsController(object): return sysinv_client.get_management_address_pool() # TODO(gsilvatr): refactor to use implementation from common/utils and test - def _get_oam_addresses(self, context, subcloud_name): + def _get_oam_addresses(self, context, subcloud_name, sc_ks_client): """Get the subclouds oam addresses""" # First need to retrieve the Subcloud's Keystone session try: - sc_ks_client = self.get_ks_client(subcloud_name) endpoint = sc_ks_client.endpoint_cache.get_endpoint('sysinv') sysinv_client = SysinvClient(subcloud_name, sc_ks_client.session, @@ -1027,6 +1028,31 @@ class SubcloudsController(object): LOG.error(message) return None + def _get_deploy_config_sync_status(self, context, subcloud_name, keystone_client): + """Get the deploy configuration insync status of the subcloud """ + detected_alarms = None + try: + fm_client = FmClient(subcloud_name, keystone_client.session) + detected_alarms = fm_client.get_alarms_by_id( + FM_ALARM_ID_UNSYNCHRONIZED_RESOURCE) + except Exception as ex: + LOG.error(str(ex)) + return None + + out_of_date = False + if detected_alarms: + # Check if any alarm.entity_instance_id contains any of the values + # in MONITORED_ALARM_ENTITIES. + # We want to scope 260.002 alarms to the host entity only. + out_of_date = any( + any(entity_id in alarm.entity_instance_id + for entity_id in dccommon_consts.MONITORED_ALARM_ENTITIES) + for alarm in detected_alarms + ) + sync_status = dccommon_consts.DEPLOY_CONFIG_OUT_OF_DATE if out_of_date \ + else dccommon_consts.DEPLOY_CONFIG_UP_TO_DATE + return sync_status + def _add_subcloud_to_database(self, context, payload): try: db_api.subcloud_get_by_name(context, payload['name']) @@ -1190,16 +1216,26 @@ class SubcloudsController(object): if detail is not None: oam_floating_ip = "unavailable" + config_sync_status = "unknown" if subcloud.availability_status == dccommon_consts.AVAILABILITY_ONLINE: + + # Get the keystone client that will be used + # for _get_deploy_config_sync_status and _get_oam_addresses + sc_ks_client = self.get_ks_client(subcloud.name) oam_addresses = self._get_oam_addresses(context, - subcloud.name) + subcloud.name, sc_ks_client) if oam_addresses is not None: oam_floating_ip = oam_addresses.oam_floating_ip - floating_ip_dict = {"oam_floating_ip": - oam_floating_ip} - subcloud_dict.update(floating_ip_dict) + deploy_config_state = self._get_deploy_config_sync_status( + context, subcloud.name, sc_ks_client) + if deploy_config_state is not None: + config_sync_status = deploy_config_state + extra_details = {"oam_floating_ip": oam_floating_ip, + "config_sync_status": config_sync_status} + + subcloud_dict.update(extra_details) return subcloud_dict @utils.synchronized(LOCK_NAME) diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py index f16499e8f..884c23d57 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py @@ -1051,12 +1051,18 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest): self.assertEqual(response.json.get('oam_floating_ip', None), None) self.assertEqual(response.json['name'], subcloud.name) + @mock.patch.object(subclouds.SubcloudsController, + '_get_deploy_config_sync_status') @mock.patch.object(subclouds.SubcloudsController, '_get_oam_addresses') + @mock.patch.object(subclouds.SubcloudsController, + 'get_ks_client') @mock.patch.object(rpc_client, 'ManagerClient') def test_get_online_subcloud_with_additional_detail(self, mock_rpc_client, - mock_get_oam_addresses): + mock_get_ks_client, + mock_get_oam_addresses, + mock_get_deploy_config_sync_status): subcloud = fake_subcloud.create_fake_subcloud(self.ctx) updated_subcloud = db_api.subcloud_update( self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE) @@ -1069,11 +1075,14 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest): '10.10.10.3', '10.10.10.1', '10.10.10.2') + mock_get_ks_client.return_value = 'ks_client' mock_get_oam_addresses.return_value = oam_addresses + mock_get_deploy_config_sync_status.return_value = dccommon_consts.DEPLOY_CONFIG_UP_TO_DATE response = self.app.get(get_url, headers=FAKE_HEADERS) self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.status_code, http_client.OK) self.assertEqual('10.10.10.2', response.json['oam_floating_ip']) + self.assertEqual('Deployment: configurations up-to-date', response.json['config_sync_status']) @mock.patch.object(rpc_client, 'ManagerClient') def test_get_offline_subcloud_with_additional_detail(self, @@ -1084,18 +1093,47 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest): self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.status_code, http_client.OK) self.assertEqual('unavailable', response.json['oam_floating_ip']) + self.assertEqual('unknown', response.json['config_sync_status']) + + @mock.patch.object(subclouds.SubcloudsController, + '_get_deploy_config_sync_status') + @mock.patch.object(subclouds.SubcloudsController, + '_get_oam_addresses') + @mock.patch.object(subclouds.SubcloudsController, + 'get_ks_client') + @mock.patch.object(rpc_client, 'ManagerClient') + def test_get_subcloud_deploy_config_status_unknown(self, + mock_rpc_client, + mock_get_ks_client, + mock_get_oam_addresses, + mock_get_deploy_config_sync_status): + subcloud = fake_subcloud.create_fake_subcloud(self.ctx) + updated_subcloud = db_api.subcloud_update( + self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE) + get_url = FAKE_URL + '/' + str(updated_subcloud.id) + '/detail' + mock_get_ks_client.return_value = 'ks_client' + mock_get_oam_addresses.return_value = None + mock_get_deploy_config_sync_status.return_value = None + response = self.app.get(get_url, headers=FAKE_HEADERS) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.status_code, http_client.OK) + self.assertEqual('unknown', response.json['config_sync_status']) @mock.patch.object(subclouds.SubcloudsController, '_get_oam_addresses') + @mock.patch.object(subclouds.SubcloudsController, + 'get_ks_client') @mock.patch.object(rpc_client, 'ManagerClient') def test_get_subcloud_oam_ip_unavailable(self, mock_rpc_client, + mock_get_ks_client, mock_get_oam_addresses): subcloud = fake_subcloud.create_fake_subcloud(self.ctx) updated_subcloud = db_api.subcloud_update( self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE) get_url = FAKE_URL + '/' + str(updated_subcloud.id) + '/detail' + mock_get_ks_client.return_value = 'ks_client' mock_get_oam_addresses.return_value = None response = self.app.get(get_url, headers=FAKE_HEADERS) self.assertEqual(response.content_type, 'application/json')