class SourceStatus: """Source Status.""" def __init__(self, request, source_id): """Initialize source id.""" self.request = request self.user = request.user self.source_id = source_id self.source = Sources.objects.get(source_id=source_id) self.sources_client = SourcesHTTPClient(self.source.auth_header, source_id=source_id) @property def sources_response(self): return self.sources_client.build_source_status(self.status()) def _set_provider_active_status(self, active_status): """Set provider active status.""" if self.source.koku_uuid: try: provider = Provider.objects.get(uuid=self.source.koku_uuid) provider.active = active_status provider.save() except Provider.DoesNotExist: LOG.info( f"No provider found for Source ID: {self.source.source_id}" ) def determine_status(self, provider_type, source_authentication, source_billing_source): """Check cloud configuration status.""" interface = ProviderAccessor(provider_type) error_obj = None try: interface.cost_usage_source_ready(source_authentication, source_billing_source) self._set_provider_active_status(True) except ValidationError as validation_error: self._set_provider_active_status(False) error_obj = validation_error return error_obj def status(self): """Find the source's availability status.""" source_billing_source = self.source.billing_source.get( "data_source") or {} source_authentication = self.source.authentication.get( "credentials") or {} provider_type = self.source.source_type return self.determine_status(provider_type, source_authentication, source_billing_source) def push_status(self): """Push status_msg to platform sources.""" try: status_obj = self.status() self.sources_client.set_source_status(status_obj) except SourcesHTTPClientError as error: err_msg = "Unable to push source status. Reason: {}".format( str(error)) LOG.warning(err_msg)
def test_set_source_status_patch_fail(self): """Test to set source status where the patch fails.""" test_source_id = 1 application_type_id = 2 application_id = 3 status = 'available' error_msg = 'my error' client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: m.get(( f'http://www.sources.com/api/v1.0/applications?' f'filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}' ), status_code=200, json={'data': [{ 'id': application_id }]}) m.patch( f'http://www.sources.com/api/v1.0/applications/{application_id}', status_code=400, json={ 'availability_status': status, 'availability_status_error': str(error_msg) }) with self.assertRaises(SourcesHTTPClientError): client.set_source_status(error_msg, application_type_id)
def test_set_source_status_patch_fail(self, *args): """Test to set source status where the patch fails.""" test_source_id = 1 client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: application_type_id = 2 application_id = 3 m.get( (f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": [{ "id": application_id }]}, ) status = "unavailable" error_msg = "my error" m.patch( f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}/{application_id}", status_code=400, json={ "availability_status": status, "availability_status_error": str(error_msg) }, ) with self.assertRaises(SourcesHTTPClientError): client.set_source_status(error_msg, application_type_id)
def test_set_source_status_patch_missing_application(self, *args): """Test to set source status where the patch encounters an application 404.""" test_source_id = 1 client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: application_type_id = 2 application_id = 3 m.get( (f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": [{ "id": application_id }]}, ) m.patch( f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}/{application_id}", status_code=404) with self.assertLogs("sources.sources_http_client", "INFO") as captured_logs: error_msg = "my error" client.set_source_status(error_msg, application_type_id) self.assertIn("[set_source_status] error", captured_logs.output[-1])
def test_set_source_status_patch_fail(self): """Test to set source status where the patch fails.""" test_source_id = 1 application_type_id = 2 application_id = 3 status = "unavailable" error_msg = "my error" source = Sources.objects.create(source_id=test_source_id, offset=42, source_type="AWS") source.save() client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: m.get( (f"http://www.sources.com/api/v1.0/applications?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": [{ "id": application_id }]}, ) m.patch( f"http://www.sources.com/api/v1.0/applications/{application_id}", status_code=400, json={ "availability_status": status, "availability_status_error": str(error_msg) }, ) with self.assertRaises(SourcesHTTPClientError): client.set_source_status(error_msg, application_type_id)
def execute_koku_provider_op(msg, cost_management_type_id): """ Execute the 'create' or 'destroy Koku-Provider operations. 'create' operations: Koku POST /providers is executed along with updating the Sources database table with the Koku Provider uuid. 'destroy' operations: Koku DELETE /providers is executed along with removing the Sources database entry. Two types of exceptions are handled for Koku HTTP operations. Recoverable client and Non-Recoverable client errors. If the error is recoverable the calling function (synchronize_sources) will re-queue the operation. Args: msg (Asyncio msg): Dictionary messages containing operation, provider and offset. example: {'operation': 'create', 'provider': SourcesModelObj, 'offset': 3} cost_management_type_id (Integer): Cost Management Type Identifier Returns: None """ provider = msg.get("provider") operation = msg.get("operation") try: if operation == "create": task = create_or_update_provider.delay(provider.source_id) LOG.info( f"Creating Koku Provider for Source ID: {str(provider.source_id)} in task: {task.id}" ) elif operation == "update": task = create_or_update_provider.delay(provider.source_id) LOG.info( f"Updating Koku Provider for Source ID: {str(provider.source_id)} in task: {task.id}" ) elif operation == "destroy": task = delete_source_and_provider.delay(provider.source_id, provider.source_uuid, provider.auth_header) LOG.info( f"Deleting Koku Provider/Source for Source ID: {str(provider.source_id)} in task: {task.id}" ) else: LOG.error(f"unknown operation: {operation}") except RabbitOperationalError: err_msg = f"RabbitmQ unavailable. Unable to {operation} Source ID: {provider.source_id}" LOG.error(err_msg) sources_network = SourcesHTTPClient(provider.auth_header, provider.source_id) sources_network.set_source_status( err_msg, cost_management_type_id=cost_management_type_id) # Re-raise exception so it can be re-queued in synchronize_sources raise RabbitOperationalError(err_msg)
def save_auth_info(auth_header, source_id): """ Store Sources Authentication information given an Source ID. This method is called when a Cost Management application is attached to a given Source as well as when an Authentication is created. We have to handle both cases since an Authentication.create event can occur before a Source is attached to the Cost Management application. Authentication is stored in the Sources database table. Args: source_id (Integer): Platform Sources ID. auth_header (String): Authentication Header. Returns: None """ source_type = storage.get_source_type(source_id) if source_type: sources_network = SourcesHTTPClient(auth_header, source_id) else: LOG.info(f"Source ID not found for ID: {source_id}") return try: if source_type == Provider.PROVIDER_OCP: source_details = sources_network.get_source_details() if source_details.get("source_ref"): authentication = { "resource_name": source_details.get("source_ref") } else: raise SourcesHTTPClientError("Unable to find Cluster ID") elif source_type in (Provider.PROVIDER_AWS, Provider.PROVIDER_AWS_LOCAL): authentication = { "resource_name": sources_network.get_aws_role_arn() } elif source_type in (Provider.PROVIDER_AZURE, Provider.PROVIDER_AZURE_LOCAL): authentication = { "credentials": sources_network.get_azure_credentials() } else: LOG.error(f"Unexpected source type: {source_type}") return storage.add_provider_sources_auth_info(source_id, authentication) storage.clear_update_flag(source_id) LOG.info(f"Authentication attached to Source ID: {source_id}") except SourcesHTTPClientError as error: LOG.info( f"Authentication info not available for Source ID: {source_id}") sources_network.set_source_status(str(error))
def execute_koku_provider_op(msg, cost_management_type_id): """ Execute the 'create' or 'destroy Koku-Provider operations. 'create' operations: Koku POST /providers is executed along with updating the Sources database table with the Koku Provider uuid. 'destroy' operations: Koku DELETE /providers is executed along with removing the Sources database entry. Two types of exceptions are handled for Koku HTTP operations. Recoverable client and Non-Recoverable client errors. If the error is recoverable the calling function (synchronize_sources) will re-queue the operation. Args: msg (Asyncio msg): Dictionary messages containing operation, provider and offset. example: {'operation': 'create', 'provider': SourcesModelObj, 'offset': 3} cost_management_type_id (Integer): Cost Management Type Identifier Returns: None """ provider = msg.get('provider') operation = msg.get('operation') koku_client = KokuHTTPClient(provider.auth_header) sources_client = SourcesHTTPClient(provider.auth_header, provider.source_id) try: if operation == 'create': LOG.info(f'Creating Koku Provider for Source ID: {str(provider.source_id)}') koku_details = koku_client.create_provider(provider.name, provider.source_type, provider.authentication, provider.billing_source, provider.source_uuid) LOG.info(f'Koku Provider UUID {koku_details.get("uuid")} assigned to Source ID {str(provider.source_id)}.') storage.add_provider_koku_uuid(provider.source_id, koku_details.get('uuid')) elif operation == 'destroy': if provider.koku_uuid: try: response = koku_client.destroy_provider(provider.koku_uuid) LOG.info( f'Koku Provider UUID ({provider.koku_uuid}) Removal Status Code: {str(response.status_code)}') except KokuHTTPClientNonRecoverableError: LOG.info(f'Koku Provider already removed. Remove Source ID: {str(provider.source_id)}.') storage.destroy_provider_event(provider.source_id) elif operation == 'update': koku_details = koku_client.update_provider(provider.koku_uuid, provider.name, provider.source_type, provider.authentication, provider.billing_source) storage.clear_update_flag(provider.source_id) LOG.info(f'Koku Provider UUID {koku_details.get("uuid")} with Source ID {str(provider.source_id)} updated.') sources_client.set_source_status(None, cost_management_type_id) except KokuHTTPClientError as koku_error: raise SourcesIntegrationError('Koku provider error: ', str(koku_error)) except KokuHTTPClientNonRecoverableError as koku_error: err_msg = f'Unable to {operation} provider for Source ID: {str(provider.source_id)}. Reason: {str(koku_error)}' LOG.error(err_msg) sources_client.set_source_status(str(koku_error), cost_management_type_id)
class SourceStatus: """Source Status.""" def __init__(self, request, source_id): """Initialize source id.""" self.request = request self.user = request.user self.source_id = source_id self.source = Sources.objects.get(source_id=source_id) self.sources_client = SourcesHTTPClient(self.source.auth_header, source_id=source_id) @property def sources_response(self): return self.sources_client.build_source_status(self.status()) def determine_status(self, provider, source_authentication, source_billing_source): """Check cloud configuration status.""" interface = ProviderAccessor(provider) error_obj = None try: interface.cost_usage_source_ready(source_authentication, source_billing_source) except ValidationError as validation_error: error_obj = validation_error return error_obj def status(self): """Find the source's availability status.""" # Get the source billing_source, whether it's named bucket if self.source.billing_source.get("bucket"): source_billing_source = self.source.billing_source.get("bucket") elif self.source.billing_source.get("data_source"): source_billing_source = self.source.billing_source.get("data_source") else: source_billing_source = {} # Get the source authentication if self.source.authentication.get("resource_name"): source_authentication = self.source.authentication.get("resource_name") elif self.source.authentication.get("credentials"): source_authentication = self.source.authentication.get("credentials") else: source_authentication = {} provider = self.source.source_type status_obj = self.determine_status(provider, source_authentication, source_billing_source) return status_obj def push_status(self): """Push status_msg to platform sources.""" try: status_obj = self.status() self.sources_client.set_source_status(status_obj) except SourcesHTTPClientError as error: err_msg = "Unable to push source status. Reason: {}".format(str(error)) LOG.warning(err_msg)
def set_status_for_source(source_id, error_message): try: instance = Sources.objects.get(source_id=source_id) except Exception as e: LOG.error(f"[set_status_for_source] This Source ID {source_id} should exist. error: {e}") return LOG.info(f"Setting availability status for Source ID: {source_id}") client = SourcesHTTPClient(instance.auth_header, source_id) try: client.set_source_status(error_message) except SourcesHTTPClientError as err: LOG.error(err)
def execute_koku_provider_op(msg): """ Execute the 'create' or 'destroy Koku-Provider operations. 'create' operations: Koku POST /providers is executed along with updating the Sources database table with the Koku Provider uuid. 'destroy' operations: Koku DELETE /providers is executed along with removing the Sources database entry. Two types of exceptions are handled for Koku HTTP operations. Recoverable client and Non-Recoverable client errors. If the error is recoverable the calling function (synchronize_sources) will re-queue the operation. Args: msg (Asyncio msg): Dictionary messages containing operation, provider and offset. example: {'operation': 'create', 'provider': SourcesModelObj, 'offset': 3} Returns: None """ provider = msg.get("provider") operation = msg.get("operation") account_coordinator = SourcesProviderCoordinator(provider.source_id, provider.auth_header) sources_client = SourcesHTTPClient(provider.auth_header, provider.source_id) try: if operation == "create": LOG.info(f"Creating Koku Provider for Source ID: {str(provider.source_id)}") instance = account_coordinator.create_account(provider) LOG.info(f"Creating provider {instance.uuid} for Source ID: {provider.source_id}") elif operation == "update": instance = account_coordinator.update_account(provider) LOG.info(f"Updating provider {instance.uuid} for Source ID: {provider.source_id}") elif operation == "destroy": account_coordinator.destroy_account(provider) LOG.info(f"Destroying provider {provider.koku_uuid} for Source ID: {provider.source_id}") else: LOG.error(f"unknown operation: {operation}") sources_client.set_source_status(None) except SourcesProviderCoordinatorError as account_error: raise SourcesIntegrationError("Koku provider error: ", str(account_error)) except ValidationError as account_error: err_msg = ( f"Unable to {operation} provider for Source ID: {str(provider.source_id)}. Reason: {str(account_error)}" ) LOG.warning(err_msg) sources_client.set_source_status(account_error)
class SourceStatus: """Source Status.""" def __init__(self, request, source_id): """Initialize source id.""" self.request = request self.user = request.user self.source_id = source_id self.source = Sources.objects.get(source_id=source_id) self.auth_header = request.headers.get("X-Rh-Identity") self.sources_client = SourcesHTTPClient(auth_header=self.auth_header, source_id=source_id) def status(self): """Find the source's availability status.""" # Get the source billing_source, whether it's named bucket if self.source.billing_source.get("bucket"): source_billing_source = self.source.billing_source.get("bucket") elif self.source.billing_source.get("data_source"): source_billing_source = self.source.billing_source.get( "data_source") else: source_billing_source = {} # Get the source authentication if self.source.authentication.get("resource_name"): source_authentication = self.source.authentication.get( "resource_name") elif self.source.authentication.get("credentials"): source_authentication = self.source.authentication.get( "credentials") else: source_authentication = {} provider = self.source.source_type interface = ProviderAccessor(provider) availability_status = interface.availability_status( source_authentication, source_billing_source) return availability_status def push_status(self): """Push status_msg to platform sources.""" try: status_obj = self.status() status_msg = status_obj.get("availability_status_error", "Status unavailable") self.sources_client.set_source_status(status_msg) except SourcesHTTPClientError as error: err_msg = "Unable to push source status. Reason: {}".format( str(error)) LOG.warning(err_msg)
def test_set_source_status_unexpected_header(self): """Test to set source status with missing account in header.""" test_source_id = 1 application_type_id = 2 error_msg = "my error" malformed_identity_header = { "not_identity": { "type": "User", "user": {"username": "******", "email": "*****@*****.**", "is_org_admin": True}, } } json_malformed_identity = json_dumps(malformed_identity_header) malformed_internal_header = b64encode(json_malformed_identity.encode("utf-8")) malformed_auth_header = malformed_internal_header.decode("utf-8") missing_account_header = { "identity": { "type": "User", "user": {"username": "******", "email": "*****@*****.**", "is_org_admin": True}, } } missing_account_identity = json_dumps(missing_account_header) missing_account_internal_header = b64encode(missing_account_identity.encode("utf-8")) missing_account_auth_header = missing_account_internal_header.decode("utf-8") test_headers = [malformed_auth_header, missing_account_auth_header] source = Sources.objects.create(source_id=test_source_id, offset=42, source_type="AWS") source.save() for header in test_headers: client = SourcesHTTPClient(auth_header=header, source_id=test_source_id) response = client.set_source_status(error_msg, application_type_id) self.assertFalse(response)
def test_set_source_status_branches(self, *args): """Test to set source status.""" test_source_id = 1 client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: application_type_id = COST_MGMT_APP_TYPE_ID application_id = 3 m.get( (f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": [{ "id": application_id }]}, ) status = "unavailable" error_msg = "my error" m.patch( f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}/{application_id}", status_code=204, json={ "availability_status": status, "availability_status_error": str(error_msg) }, ) response = client.set_source_status(error_msg) self.assertTrue(response)
class SourceStatus: """Source Status.""" def __init__(self, source_id): """Initialize source id.""" self.source = Sources.objects.get(source_id=source_id) self.auth_header = self.source.auth_header self.sources_client = SourcesHTTPClient(auth_header=self.auth_header, source_id=source_id) def status(self): """Find the source's availability status.""" # Get the source billing_source, whether it's named bucket if self.source.billing_source.get('bucket'): source_billing_source = self.source.billing_source.get('bucket') elif self.source.billing_source.get('data_source'): source_billing_source = self.source.billing_source.get( 'data_source') else: source_billing_source = {} # Get the source authentication if self.source.authentication.get('resource_name'): source_authentication = self.source.authentication.get( 'resource_name') elif self.source.authentication.get('credentials'): source_authentication = self.source.authentication.get( 'credentials') else: source_authentication = {} provider = self.source.source_type interface = ProviderAccessor(provider) availability_status = interface.availability_status( source_authentication, source_billing_source) return availability_status def push_status(self, status_msg): """Push status_msg to platform sources.""" try: self.sources_client.set_source_status(status_msg) except SourcesHTTPClientError as error: err_msg = 'Unable to push source status. Reason: {}'.format( str(error)) LOG.error(err_msg)
def save_auth_info(auth_header, source_id): """ Store Sources Authentication information given an Source ID. This method is called when a Cost Management application is attached to a given Source as well as when an Authentication is created. We have to handle both cases since an Authentication.create event can occur before a Source is attached to the Cost Management application. Authentication is stored in the Sources database table. Args: source_id (Integer): Platform Sources ID. auth_header (String): Authentication Header. Returns: None """ source_type = storage.get_source_type(source_id) if not source_type: LOG.info(f"Source ID not found for ID: {source_id}") return sources_network = SourcesHTTPClient(auth_header, source_id) try: authentication = get_authentication(source_type, sources_network) except SourcesHTTPClientError as error: LOG.info( f"Authentication info not available for Source ID: {source_id}") sources_network.set_source_status(error) else: if not authentication: return storage.add_provider_sources_auth_info(source_id, authentication) storage.clear_update_flag(source_id) LOG.info(f"Authentication attached to Source ID: {source_id}")
def test_set_source_status_patch_missing_application(self): """Test to set source status where the patch encounters an application 404.""" test_source_id = 1 application_type_id = 2 application_id = 3 error_msg = "my error" source = Sources.objects.create(source_id=test_source_id, offset=42, source_type="AWS") source.save() client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: m.get( ( f"http://www.sources.com/api/v1.0/applications?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": [{"id": application_id}]}, ) m.patch(f"http://www.sources.com/api/v1.0/applications/{application_id}", status_code=404) with self.assertLogs("sources.sources_http_client", "INFO") as captured_logs: client.set_source_status(error_msg, application_type_id) self.assertIn("Unable to set status for Source", captured_logs.output[-1])
def test_set_source_status_source_deleted(self): """Test to set source status after source has been deleted.""" test_source_id = 1 application_type_id = 2 error_msg = 'my error' client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: m.get(( f'http://www.sources.com/api/v1.0/applications?' f'filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}' ), status_code=200, json={'data': []}) response = client.set_source_status(error_msg, application_type_id) self.assertFalse(response)
def test_set_source_status_source_deleted(self, *args): """Test to set source status after source has been deleted on platform.""" test_source_id = 1 client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: application_type_id = 2 m.get( (f"{MOCK_URL}/api/v1.0/{ENDPOINT_APPLICATIONS}?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": []}, ) error_msg = "my error" response = client.set_source_status(error_msg, application_type_id) self.assertFalse(response)
def test_set_source_status_source_deleted(self): """Test to set source status after source has been deleted.""" test_source_id = 1 application_type_id = 2 error_msg = "my error" source = Sources.objects.create(source_id=test_source_id, offset=42, source_type="AWS") source.save() client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: m.get( ( f"http://www.sources.com/api/v1.0/applications?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": []}, ) response = client.set_source_status(error_msg, application_type_id) self.assertFalse(response)
def test_set_source_status(self): """Test to set source status.""" test_source_id = 1 application_type_id = 2 application_id = 3 status = "available" error_msg = "my error" client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER, source_id=test_source_id) with requests_mock.mock() as m: m.get( ( f"http://www.sources.com/api/v1.0/applications?" f"filter[application_type_id]={application_type_id}&filter[source_id]={test_source_id}" ), status_code=200, json={"data": [{"id": application_id}]}, ) m.patch( f"http://www.sources.com/api/v1.0/applications/{application_id}", status_code=204, json={"availability_status": status, "availability_status_error": str(error_msg)}, ) response = client.set_source_status(error_msg, application_type_id) self.assertTrue(response)
def test_set_source_status_failed_header(self): """Test set_source_status with invalid header.""" client = SourcesHTTPClient(auth_header=Config.SOURCES_FAKE_HEADER) with patch.object(SourcesHTTPClient, "build_status_header", return_value=None): self.assertFalse(client.set_source_status(""))
class SourceStatus: """Source Status.""" def __init__(self, source_id): """Initialize source id.""" self.source_id = source_id self.source = Sources.objects.get(source_id=source_id) if not source_settings_complete( self.source) or self.source.pending_delete: raise ObjectDoesNotExist( f"Source ID: {self.source_id} not ready for status") self.sources_client = SourcesHTTPClient(self.source.auth_header, source_id=source_id) @property def sources_response(self): return self.sources_client.build_source_status(self.status()) def _set_provider_active_status(self, active_status): """Set provider active status.""" if self.source.koku_uuid: try: provider = Provider.objects.get(uuid=self.source.koku_uuid) provider.active = active_status provider.save() except Provider.DoesNotExist: LOG.info( f"No provider found for Source ID: {self.source.source_id}" ) def determine_status(self, provider_type, source_authentication, source_billing_source): """Check cloud configuration status.""" interface = ProviderAccessor(provider_type) error_obj = None try: if self.source.account_id not in settings.DEMO_ACCOUNTS: interface.cost_usage_source_ready(source_authentication, source_billing_source) self._set_provider_active_status(True) except ValidationError as validation_error: self._set_provider_active_status(False) error_obj = validation_error self.source.refresh_from_db() return error_obj def status(self): """Find the source's availability status.""" source_billing_source = self.source.billing_source.get( "data_source") or {} source_authentication = self.source.authentication.get( "credentials") or {} provider_type = self.source.source_type return self.determine_status(provider_type, source_authentication, source_billing_source) @transaction.atomic def update_source_name(self): """Update source name if it is out of sync with platform.""" source_details = self.sources_client.get_source_details() if source_details.get("name") != self.source.name: self.source.name = source_details.get("name") self.source.save() builder = SourcesProviderCoordinator(self.source_id, self.source.auth_header) builder.update_account(self.source) def push_status(self): """Push status_msg to platform sources.""" try: status_obj = self.status() if self.source.source_type in (Provider.PROVIDER_GCP, Provider.PROVIDER_GCP_LOCAL): builder = SourcesProviderCoordinator(self.source.source_id, self.source.auth_header) if not status_obj: if self.source.koku_uuid: builder.update_account(self.source) elif self.source.billing_source.get("data_source", {}).get("table_id"): builder.create_account(self.source) self.sources_client.set_source_status(status_obj) self.update_source_name() LOG.info( f"Source status for Source ID: {str(self.source_id)}: Status: {str(status_obj)}" ) except SkipStatusPush as error: LOG.info( f"Platform sources status push skipped. Reason: {str(error)}") except SourcesHTTPClientError as error: err_msg = "Unable to push source status. Reason: {}".format( str(error)) LOG.warning(err_msg)