public void WhenTryingToFindExistingResourceThenThisResourceWasFound() { // Create hierarchical resources structure: { subscription } -> { resourceGroup } -> { appInsights, virtualMachine } ResourceIdentifier resourceGroupIdentifier = ResourceIdentifier.CreateFromResourceId("/subscriptions/7904b7bd-5e6b-4415-99a8-355657b7da19/resourceGroups/MyResourceGroupName"); List <HierarchicalResource> groupContainedResources = new List <HierarchicalResource>() { this.appInsightsHierarchicalResource, this.virtualMachineHierarchicalResource }; var resourceGroupHierarchicalResource = new HierarchicalResource(resourceGroupIdentifier, groupContainedResources, resourceGroupIdentifier.ResourceGroupName); ResourceIdentifier subscriptionIdentifier = ResourceIdentifier.CreateFromResourceId("/subscriptions/7904b7bd-5e6b-4415-99a8-355657b7da19"); List <HierarchicalResource> subscriptionContainedResources = new List <HierarchicalResource>() { resourceGroupHierarchicalResource }; var subscriptionHierarchicalResource = new HierarchicalResource(subscriptionIdentifier, subscriptionContainedResources, subscriptionIdentifier.SubscriptionId); // Verify hierarchical structure before filtering CollectionAssert.AreEqual(subscriptionContainedResources, subscriptionHierarchicalResource.ContainedResources.OriginalCollection); CollectionAssert.AreEqual(subscriptionContainedResources, subscriptionHierarchicalResource.ContainedResources.FilteredCollection); CollectionAssert.AreEqual(groupContainedResources, resourceGroupHierarchicalResource.ContainedResources.OriginalCollection); CollectionAssert.AreEqual(groupContainedResources, resourceGroupHierarchicalResource.ContainedResources.FilteredCollection); Assert.IsNotNull(subscriptionHierarchicalResource.TryFind("someApp")); Assert.IsNotNull(subscriptionHierarchicalResource.TryFind("MyVirtualMachineName")); // Filter all resources but virtualMachine subscriptionHierarchicalResource.ContainedResources.Filter = this.FilterAllResourcesButVirtualMachine; Assert.IsNotNull(subscriptionHierarchicalResource.TryFind("someApp")); Assert.IsNull(subscriptionHierarchicalResource.TryFind("MyVirtualMachineName")); }
public void Setup() { ResourceIdentifier appInsightsResourceIdentifier = ResourceIdentifier.CreateFromResourceId("/subscriptions/7904b7bd-5e6b-4415-99a8-355657b7da19/resourceGroups/MyResourceGroupName/providers/microsoft.insights/components/someApp"); this.appInsightsHierarchicalResource = new HierarchicalResource(appInsightsResourceIdentifier, new List <HierarchicalResource>(), appInsightsResourceIdentifier.ResourceName); ResourceIdentifier virtualMachineResourceIdentifier = ResourceIdentifier.CreateFromResourceId("/subscriptions/7904b7bd-5e6b-4415-99a8-355657b7da19/resourceGroups/MyResourceGroupName/providers/Microsoft.Compute/virtualMachines/MyVirtualMachineName"); this.virtualMachineHierarchicalResource = new HierarchicalResource(virtualMachineResourceIdentifier, new List <HierarchicalResource>(), virtualMachineResourceIdentifier.ResourceName); }
/// <summary> /// Enumerates all the resource groups in the specified subscription. /// </summary> /// <param name="subscriptionId">The subscription ID.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A <see cref="Task{TResult}"/>, returning the resource groups.</returns> public async Task <IList <ResourceIdentifier> > GetAllResourceGroupsInSubscriptionAsync(string subscriptionId, CancellationToken cancellationToken) { ResourceManagementClient resourceManagementClient = this.GetResourceManagementClient(subscriptionId); Task <IPage <ResourceGroupInner> > FirstPage() => resourceManagementClient.ResourceGroups.ListAsync(cancellationToken: cancellationToken); Task <IPage <ResourceGroupInner> > NextPage(string nextPageLink) => resourceManagementClient.ResourceGroups.ListNextAsync(nextPageLink, cancellationToken); return((await this.RunAndTrack(() => this.ReadAllPages(FirstPage, NextPage))) .Select(resourceGroup => ResourceIdentifier.CreateFromResourceId(resourceGroup.Id)) .ToList()); }
/// <summary> /// Enumerates all the resources in the specified resource group. /// </summary> /// <param name="subscriptionId">The subscription ID.</param> /// <param name="resourceGroupName">The resource group name.</param> /// <param name="resourceTypes">The types of resource to enumerate.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A <see cref="Task{TResult}"/>, returning the resource identifiers.</returns> public async Task <IList <ResourceIdentifier> > GetAllResourcesInResourceGroupAsync(string subscriptionId, string resourceGroupName, IEnumerable <ResourceType> resourceTypes, CancellationToken cancellationToken) { ResourceManagementClient resourceManagementClient = this.GetResourceManagementClient(subscriptionId); ODataQuery <GenericResourceFilterInner> query = GetResourcesByTypeQuery(resourceTypes); Task <IPage <GenericResourceInner> > FirstPage() => resourceManagementClient.Resources.ListByResourceGroupAsync(resourceGroupName, query, cancellationToken); Task <IPage <GenericResourceInner> > NextPage(string nextPageLink) => resourceManagementClient.Resources.ListByResourceGroupNextAsync(nextPageLink, cancellationToken); return((await this.RunAndTrack(() => this.ReadAllPages(FirstPage, NextPage))) .Select(resource => ResourceIdentifier.CreateFromResourceId(resource.Id)) .ToList()); }
/// <summary> /// Runs the smart signal. /// </summary> /// <param name="resources">The resources which the signal should run on.</param> /// <param name="analysisCadence">The analysis cadence.</param> /// <returns>A task that runs the smart signal.</returns> public async Task RunAsync(List <ResourceIdentifier> resources, TimeSpan analysisCadence) { this.cancellationTokenSource = new CancellationTokenSource(); this.Results.Clear(); var analysisRequest = new AnalysisRequest(resources, null, analysisCadence, this.analysisServicesFactory); try { // Run Signal this.IsSignalRunning = true; SmartSignalResult signalResults = await this.smartSignal.AnalyzeResourcesAsync( analysisRequest, this.Tracer, this.cancellationTokenSource.Token); // Create signal result items List <SignalResultItem> signalResultItems = new List <SignalResultItem>(); foreach (var resultItem in signalResults.ResultItems) { // Create result item presentation var resourceIds = resources.Select(resource => resource.ResourceName).ToList(); var smartSignalsSettings = new SmartSignalSettings(); var smartSignalRequest = new SmartSignalRequest(resourceIds, this.smartSignalManifes.Id, null, analysisCadence, smartSignalsSettings); SmartSignalResultItemQueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { resultItem.ResourceIdentifier }, this.cancellationTokenSource.Token); SmartSignalResultItemPresentation resultItemPresentation = SmartSignalResultItemPresentation.CreateFromResultItem( smartSignalRequest, this.smartSignalManifes.Name, resultItem, queryRunInfo); // Create Azure resource identifier ResourceIdentifier resourceIdentifier = ResourceIdentifier.CreateFromResourceId(resultItemPresentation.ResourceId); signalResultItems.Add(new SignalResultItem(resultItemPresentation, resourceIdentifier)); } this.Results = new ObservableCollection <SignalResultItem>(signalResultItems); this.tracer.TraceInformation($"Found {this.Results.Count} results"); } catch (OperationCanceledException) { this.Tracer.TraceError("Signal run was canceled."); } catch (Exception e) { this.Tracer.ReportException(e); } finally { this.IsSignalRunning = false; this.cancellationTokenSource?.Dispose(); } }
private void VerifyConversion(string testResourceId, ResourceIdentifier testResourceIdentifier) { var resourceIdentifier = ResourceIdentifier.CreateFromResourceId(testResourceId); var resourceId = resourceIdentifier.ToResourceId(); Assert.AreEqual(testResourceId, resourceId, "Resource IDs are different"); Assert.AreEqual(testResourceIdentifier, resourceIdentifier, "Resource identifiers are are different"); resourceId = testResourceIdentifier.ToResourceId(); resourceIdentifier = ResourceIdentifier.CreateFromResourceId(resourceId); Assert.AreEqual(testResourceId, resourceId, "Resource IDs are different"); Assert.AreEqual(testResourceIdentifier, resourceIdentifier, "Resource identifiers are are different"); }
/// <summary> /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in Application Insights. /// </summary> /// <param name="resources">The list of resources to analyze.</param> /// <param name="cancellationToken">The cancellation token</param> /// <exception cref="TelemetryDataClientCreationException">An Application Insights telemetry data client could not be created for the specified resources.</exception> /// <returns>The telemetry data client, that can be used to run queries on Application Insights.</returns> public async Task <ITelemetryDataClient> CreateApplicationInsightsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken) { // Get the query run info, and verify it QueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken); this.VerifyRunInfo(runInfo, TelemetryDbType.ApplicationInsights); // Get application Id (for the 1st application) ResourceIdentifier firstApplication = ResourceIdentifier.CreateFromResourceId(runInfo.ResourceIds[0]); string firstApplicationId = await this.azureResourceManagerClient.GetApplicationInsightsAppIdAsync(firstApplication, cancellationToken); // Create the client return(new ApplicationInsightsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, firstApplicationId, resources.Select(resource => resource.ToResourceId()), this.queryTimeout)); }
/// <summary> /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in log analytics workspaces. /// </summary> /// <param name="resources">The list of resources to analyze.</param> /// <param name="cancellationToken">The cancellation token</param> /// <exception cref="TelemetryDataClientCreationException">A log analytics telemetry data client could not be created for the specified resources.</exception> /// <returns>The telemetry data client, that can be used to run queries on log analytics workspaces.</returns> public async Task <ITelemetryDataClient> CreateLogAnalyticsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken) { // Get the query run info, and verify it QueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken); this.VerifyRunInfo(runInfo, TelemetryDbType.LogAnalytics); // Get workspace Id (for the 1st workspace) ResourceIdentifier firstWorkspace = ResourceIdentifier.CreateFromResourceId(runInfo.ResourceIds[0]); string firstWorkspaceId = await this.azureResourceManagerClient.GetLogAnalyticsWorkspaceIdAsync(firstWorkspace, cancellationToken); // Create the client return(new LogAnalyticsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, firstWorkspaceId, runInfo.ResourceIds, this.queryTimeout)); }
public void WhenCreatingNewHierarchicalResourceThenItWasInitializedCorrectly() { ResourceIdentifier resourceGroupIdentifier = ResourceIdentifier.CreateFromResourceId("/subscriptions/7904b7bd-5e6b-4415-99a8-355657b7da19/resourceGroups/MyResourceGroupName"); List <HierarchicalResource> groupContainedResources = new List <HierarchicalResource>() { this.appInsightsHierarchicalResource, this.virtualMachineHierarchicalResource }; var resourceGroupHierarchicalResource = new HierarchicalResource(resourceGroupIdentifier, groupContainedResources, resourceGroupIdentifier.ResourceGroupName); Assert.AreEqual(resourceGroupIdentifier, resourceGroupHierarchicalResource.ResourceIdentifier, "Unexpected resource identifier"); Assert.AreEqual(resourceGroupIdentifier.ResourceGroupName, resourceGroupHierarchicalResource.Name, "Unexpected resource name"); CollectionAssert.AreEqual(groupContainedResources, resourceGroupHierarchicalResource.ContainedResources.OriginalCollection, "Unexpected resource's original contained resources"); CollectionAssert.AreEqual(groupContainedResources, resourceGroupHierarchicalResource.ContainedResources.FilteredCollection, "Unexpected resource's filtered contained resources"); }
/// <summary> /// Verify that the request resource type is supported by the Smart Detector, and enumerate /// the resources that the Smart Detector should run on. /// </summary> /// <param name="requestResourceIds">The request resource Ids</param> /// <param name="smartDetectorManifest">The Smart Detector manifest</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>A <see cref="Task{TResult}"/>, returning the resource identifiers that the Smart Detector should run on</returns> private async Task <List <ResourceIdentifier> > GetResourcesForSmartDetector(IList <string> requestResourceIds, SmartDetectorManifest smartDetectorManifest, CancellationToken cancellationToken) { HashSet <ResourceIdentifier> resourcesForSmartDetector = new HashSet <ResourceIdentifier>(); foreach (string requestResourceId in requestResourceIds) { ResourceIdentifier requestResource = ResourceIdentifier.CreateFromResourceId(requestResourceId); if (smartDetectorManifest.SupportedResourceTypes.Contains(requestResource.ResourceType)) { // If the Smart Detector directly supports the requested resource type, then that's it resourcesForSmartDetector.Add(requestResource); } else if (requestResource.ResourceType == ResourceType.Subscription && smartDetectorManifest.SupportedResourceTypes.Contains(ResourceType.ResourceGroup)) { // If the request is for a subscription, and the Smart Detector supports a resource group type, enumerate all resource groups in the requested subscription IList <ResourceIdentifier> resourceGroups = await this.azureResourceManagerClient.GetAllResourceGroupsInSubscriptionAsync(requestResource.SubscriptionId, cancellationToken); resourcesForSmartDetector.UnionWith(resourceGroups); this.tracer.TraceInformation($"Added {resourceGroups.Count} resource groups found in subscription {requestResource.SubscriptionId}"); } else if (requestResource.ResourceType == ResourceType.Subscription) { // If the request is for a subscription, enumerate all the resources in the requested subscription that the Smart Detector supports IList <ResourceIdentifier> resources = await this.azureResourceManagerClient.GetAllResourcesInSubscriptionAsync(requestResource.SubscriptionId, smartDetectorManifest.SupportedResourceTypes, cancellationToken); resourcesForSmartDetector.UnionWith(resources); this.tracer.TraceInformation($"Added {resources.Count} resources found in subscription {requestResource.SubscriptionId}"); } else if (requestResource.ResourceType == ResourceType.ResourceGroup && smartDetectorManifest.SupportedResourceTypes.Any(type => type != ResourceType.Subscription)) { // If the request is for a resource group, and the Smart Detector supports resource types (other than subscription), // enumerate all the resources in the requested resource group that the Smart Detector supports IList <ResourceIdentifier> resources = await this.azureResourceManagerClient.GetAllResourcesInResourceGroupAsync(requestResource.SubscriptionId, requestResource.ResourceGroupName, smartDetectorManifest.SupportedResourceTypes, cancellationToken); resourcesForSmartDetector.UnionWith(resources); this.tracer.TraceInformation($"Added {resources.Count} resources found in the specified resource group in subscription {requestResource.SubscriptionId}"); } else { // The Smart Detector does not support the requested resource type throw new IncompatibleResourceTypesException(requestResource.ResourceType, smartDetectorManifest); } } return(resourcesForSmartDetector.ToList()); }
/// <summary> /// Returns telemetry id of the first resource in the supplied telemetry resources list, /// and removes that first resource from the list. /// If the first resource doesn't exist, the method will remove it from the list and continue /// until an existing resource is found (in which case the method will return that resource's /// telemetry id, and will remove that resource from the list). /// </summary> /// <param name="telemetryResourceIds">Telemetry resources to filter</param> /// <param name="cancellationToken">The cancellation token to use.</param> /// <returns>A <see cref="Task{TResult}"/>, returning telemetry id or null if none of supplied telemetry resources were found</returns> private async Task <string> GetSingleTelemetryIdAsync(List <string> telemetryResourceIds, CancellationToken cancellationToken) { string telemetryId = null; while (telemetryId == null && telemetryResourceIds.Count > 0) { string candidateTelemetryResourceId = null; try { candidateTelemetryResourceId = telemetryResourceIds.First(); telemetryId = await this.GetTelemetryResourceIdAsync(ResourceIdentifier.CreateFromResourceId(candidateTelemetryResourceId), cancellationToken); } catch (AzureResourceManagerClientException ex) when(ex.StatusCode == HttpStatusCode.NotFound) { this.tracer.TraceWarning($"Telemetry resource '{candidateTelemetryResourceId}' is not found{(telemetryResourceIds.Count > 1 ? ", will attempt to get telemetry id from another resource" : string.Empty)}"); } telemetryResourceIds.Remove(candidateTelemetryResourceId); } return(telemetryId); }
/// <summary> /// Returns a filtered subset of supplied list of resources, containing only currently existing resources /// </summary> /// <param name="telemetryResourceIds">Telemetry resources to filter</param> /// <param name="cancellationToken">The cancellation token to use.</param> /// <returns>A <see cref="Task{TResult}"/>, returning filtered list of existing resources.</returns> private async Task <List <string> > FilterDeletedTelemetryResourcesAsync(IReadOnlyList <string> telemetryResourceIds, CancellationToken cancellationToken) { var filteredTelemetryResourceIds = new List <string>(); foreach (var telemetryResourceId in telemetryResourceIds) { try { await this.AzureResourceManagerClient.GetResourcePropertiesAsync(ResourceIdentifier.CreateFromResourceId(telemetryResourceId), cancellationToken); } catch (AzureResourceManagerClientException ex) when(ex.StatusCode == HttpStatusCode.NotFound) { this.tracer.TraceWarning($"Telemetry resource '{telemetryResourceId}' is not found, skipping this resource"); continue; } filteredTelemetryResourceIds.Add(telemetryResourceId); } this.tracer.TraceInformation($"{filteredTelemetryResourceIds.Count} out of {telemetryResourceIds.Count} telemetry resources left after filtration"); return(filteredTelemetryResourceIds); }
/// <summary> /// Initializes a new instance of the <see cref="EmulationAlert"/> class /// </summary> /// <param name="contractsAlert">The alert presentation object</param> /// <param name="emulationIterationDate">The timestamp of the emulation iteration</param> public EmulationAlert(ContractsAlert contractsAlert, DateTime emulationIterationDate) { this.ContractsAlert = contractsAlert; this.ResourceIdentifier = ResourceIdentifier.CreateFromResourceId(contractsAlert.ResourceId); this.EmulationIterationDate = emulationIterationDate; }
/// <summary> /// Runs the Smart Detector. /// </summary> /// <param name="resources">The resources which the Smart Detector should run on</param> /// <param name="analysisCadence">The analysis cadence</param> /// <param name="startTimeRange">The start time</param> /// <param name="endTimeRange">The end time</param> /// <returns>A task that runs the Smart Detector</returns> public async Task RunAsync(List <ResourceIdentifier> resources, TimeSpan analysisCadence, DateTime startTimeRange, DateTime endTimeRange) { this.cancellationTokenSource = new CancellationTokenSource(); IStateRepository stateRepository = this.stateRepositoryFactory.Create(this.smartDetectorId); List <string> resourceIds = resources.Select(resource => resource.ResourceName).ToList(); this.Alerts.Clear(); try { // Run Smart Detector this.IsSmartDetectorRunning = true; int totalRunsAmount = (int)((endTimeRange.Subtract(startTimeRange).Ticks / analysisCadence.Ticks) + 1); int currentRunNumber = 1; for (var currentTime = startTimeRange; currentTime <= endTimeRange; currentTime = currentTime.Add(analysisCadence)) { this.tracer.TraceInformation($"Start analysis, end of time range: {currentTime}"); var analysisRequest = new AnalysisRequest(resources, currentTime, analysisCadence, null, this.analysisServicesFactory, stateRepository); var newAlerts = await this.smartDetector.AnalyzeResourcesAsync( analysisRequest, this.Tracer, this.cancellationTokenSource.Token); var smartDetectorExecutionRequest = new SmartDetectorExecutionRequest { ResourceIds = resourceIds, SmartDetectorId = this.smartDetectorManifes.Id, Cadence = analysisCadence, DataEndTime = currentTime }; // Show the alerts that were found in this iteration newAlerts.ForEach(async newAlert => { QueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { newAlert.ResourceIdentifier }, this.cancellationTokenSource.Token); ContractsAlert contractsAlert = newAlert.CreateContractsAlert(smartDetectorExecutionRequest, this.smartDetectorManifes.Name, queryRunInfo); // Create Azure resource identifier ResourceIdentifier resourceIdentifier = ResourceIdentifier.CreateFromResourceId(contractsAlert.ResourceId); this.Alerts.Add(new EmulationAlert(contractsAlert, resourceIdentifier, currentTime)); }); this.tracer.TraceInformation($"completed {currentRunNumber} of {totalRunsAmount} runs"); currentRunNumber++; } string separator = "====================================================================================================="; this.tracer.TraceInformation($"Total alerts found: {this.Alerts.Count} {Environment.NewLine} {separator}"); } catch (OperationCanceledException) { this.Tracer.TraceError("Smart Detector run was canceled."); } catch (Exception e) { this.Tracer.ReportException(e); } finally { this.IsSmartDetectorRunning = false; this.cancellationTokenSource?.Dispose(); } }
public async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, TimeSpan?timeout, CancellationToken cancellationToken) { this.CallsToSendAsync++; JObject content = (JObject)JsonConvert.DeserializeObject(await request.Content.ReadAsStringAsync()); string extractedTelemetryId = request.RequestUri.ToString().Split('/')[5]; List <string> telemetryResources = new List <string>(); if (content.ContainsKey("applications")) { telemetryResources.AddRange((content["applications"] as JArray).Values <string>()); } else if (content.ContainsKey("workspaces")) { telemetryResources.AddRange((content["workspaces"] as JArray).Values <string>()); } // Prepare error response JObject errorObject = new JObject() { ["error"] = new JObject() { ["message"] = "test error message", ["code"] = "test error code", ["innererror"] = new JObject() { ["code"] = "test inner error code", ["message"] = "test inner error message" } } }; var errorResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(errorObject.ToString()) }; // Return error if one of resources is missing, or if the telemetry Id in the URL exists in the list if (telemetryResources.Any(resource => this.telemetryResourceIdToTelemetryIdMapping.ContainsKey(ResourceIdentifier.CreateFromResourceId(resource)) == false) || telemetryResources.Any(resource => resource == extractedTelemetryId)) { return(errorResponse); } telemetryResources = telemetryResources.Select(resource => this.telemetryResourceIdToTelemetryIdMapping[ResourceIdentifier.CreateFromResourceId(resource)]).ToList(); if (telemetryResources.Contains(extractedTelemetryId)) { return(errorResponse); } telemetryResources.Add(extractedTelemetryId); return(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(this.MergeJObjectTables(telemetryResources.Select(GetExpectedResultForWorkspaceAsJObject)).ToString()) }); }
/// <summary> /// Sends the Smart Signal result Email. /// </summary> /// <param name="signalExecution">The signals execution information.</param> /// <param name="smartSignalResultItems">The Smart Signal result items.</param> /// <returns>The task object representing the asynchronous operation.</returns> public async Task SendSignalResultEmailAsync(SignalExecutionInfo signalExecution, IList <SmartSignalResultItemPresentation> smartSignalResultItems) { AlertRule alertRule = signalExecution.AlertRule; if (this.sendGridClient == null) { this.tracer.TraceWarning("SendGrid API key was not found, not sending email"); return; } if (alertRule.EmailRecipients == null || !alertRule.EmailRecipients.Any()) { this.tracer.TraceWarning("Email recipients were not provided, not sending email"); return; } if (smartSignalResultItems == null || !smartSignalResultItems.Any()) { this.tracer.TraceInformation($"no result items to publish for signal {alertRule.SignalId}"); return; } this.tracer.TraceInformation($"Sending signal result email for signal {alertRule.SignalId}"); var exceptions = new List <Exception>(); foreach (SmartSignalResultItemPresentation resultItem in smartSignalResultItems) { ResourceIdentifier resource = ResourceIdentifier.CreateFromResourceId(alertRule.ResourceId); // TODO: Fix links string emailBody = Resources.SmartSignalEmailTemplate .Replace(SignalNamePlaceHolder, resultItem.SignalName) .Replace(ResourceNamePlaceHolder, resultItem.ResourceId) .Replace(LinkToPortalPlaceHolder, "LinkToPortal") .Replace(RuleNamePlaceHolder, alertRule.Name) .Replace(RuleDescriptionPlaceHolder, alertRule.Description) .Replace(ServiceNamePlaceHolder, $@"{resource.ResourceType}: {resource.ResourceName} ({resource.ResourceGroupName})") .Replace(AlertActivatedTimePlaceHolder, signalExecution.LastExecutionTime.ToString()) .Replace(SubscriptionNamePlaceHolder, resource.SubscriptionId) .Replace(LinkToFeedbackPlaceHolder, "https://ms.portal.azure.com/"); var msg = new SendGridMessage { From = new EmailAddress("*****@*****.**", "Smart Signals"), Subject = $"Azure Smart Alerts (preview) - {resultItem.SignalName} detected", PlainTextContent = $@"{resultItem.SignalName} was detected for {resultItem.ResourceId}. You can view more details for this alert here: {"LinkToPortal"}", HtmlContent = emailBody }; var emailAddresses = alertRule.EmailRecipients.Select(email => new EmailAddress(email)).ToList(); msg.AddTos(emailAddresses); try { var response = await this.sendGridClient.SendEmailAsync(msg); if (!IsSuccessStatusCode(response.StatusCode)) { string content = response.Body != null ? await response.Body.ReadAsStringAsync() : string.Empty; var message = $"Failed to send signal results Email for signal {alertRule.SignalId}. Fail StatusCode: {response.StatusCode}. Content: {content}."; this.tracer.TraceError(message); exceptions.Add(new EmailSendingException(message)); } } catch (Exception e) { this.tracer.TraceError($"Failed to send email. Exception: {e}"); exceptions.Add(new EmailSendingException($"Exception was thrown fo sending signal results Email for signal {alertRule.SignalId}. Exception: {e}")); } } if (exceptions.Count > 0) { this.tracer.TraceError( $"Failed to send one or more signal result emails." + $"Number of exceptions thrown: {exceptions.Count()}."); throw new AggregateException("Failed to send one or more signal result emails", exceptions); } this.tracer.TraceInformation($"Sent signal result emails successfully for signal {alertRule.SignalId}"); }
/// <summary> /// Runs the Smart Detector's resolution check flow. /// </summary> /// <param name="request">The alert resolution check request.</param> /// <param name="smartDetector">The Smart Detector to run</param> /// <param name="smartDetectorManifest">The Smart Detector's manifest</param> /// <param name="detectorTracer">The tracer to provider for the Smart Detector</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>A <see cref="Task{TResult}"/>, returning the resolution check response generated by the Smart Detector.</returns> private async Task <ContractsAlertResolutionCheckResponse> CheckAlertResolutionAsync( ContractsAlertResolutionCheckRequest request, ISmartDetector smartDetector, SmartDetectorManifest smartDetectorManifest, ITracer detectorTracer, CancellationToken cancellationToken) { // Check that the detector supports resolution if (!(smartDetector is IResolvableAlertSmartDetector resolvableAlertSmartDetector)) { throw new ResolutionCheckNotSupportedException($"Smart Detector {smartDetectorManifest.Name} does not support alert resolution of alerts"); } // Create state repository IStateRepository stateRepository = this.stateRepositoryFactory.Create(request.OriginalAnalysisRequest.SmartDetectorId, request.OriginalAnalysisRequest.AlertRuleResourceId); // Load the resolution state from the repository ResolutionState resolutionState = await stateRepository.GetStateAsync <ResolutionState>(GetResolutionStateKey(request.AlertCorrelationHash), cancellationToken); if (resolutionState == null) { throw new ResolutionStateNotFoundException($"Resolution state for Alert with correlation {request.AlertCorrelationHash} was not found"); } // Create the input for the Smart Detector AnalysisRequestParameters analysisRequestParameters = await this.CreateAnalysisRequestParametersAsync( resolutionState.AnalysisRequestTime, request.OriginalAnalysisRequest, smartDetectorManifest, false, cancellationToken); var alertResolutionCheckRequest = new AlertResolutionCheckRequest( analysisRequestParameters, new AlertResolutionCheckRequestParameters(ResourceIdentifier.CreateFromResourceId(request.TargetResource), request.AlertFireTime, resolutionState.AlertPredicates), this.analysisServicesFactory, stateRepository); // Run the Smart Detector this.tracer.TraceInformation($"Started running Smart Detector ID {smartDetectorManifest.Id}, Name {smartDetectorManifest.Name} for resolution check"); try { AlertResolutionCheckResponse alertResolutionCheckResponse = await resolvableAlertSmartDetector.CheckForResolutionAsync(alertResolutionCheckRequest, detectorTracer, cancellationToken); this.tracer.TraceInformation($"Completed running Smart Detector ID {smartDetectorManifest.Id}, Name {smartDetectorManifest.Name} for resolution check"); // If the alert is resolved - delete the state if (alertResolutionCheckResponse.ShouldBeResolved) { await stateRepository.DeleteStateAsync(GetResolutionStateKey(request.AlertCorrelationHash), cancellationToken); } // Convert the result return(new ContractsAlertResolutionCheckResponse { ShouldBeResolved = alertResolutionCheckResponse.ShouldBeResolved, ResolutionParameters = alertResolutionCheckResponse.AlertResolutionParameters?.CreateContractsResolutionParameters() }); } catch (Exception e) { this.tracer.TraceError($"Failed running Smart Detector ID {smartDetectorManifest.Id}, Name {smartDetectorManifest.Name} for resolution check: {e}"); throw new FailedToRunSmartDetectorException($"Calling Smart Detector '{smartDetectorManifest.Name}' for resolution check failed with exception of type {e.GetType()} and message: {e.Message}", e); } }
/// <summary> /// Read the metric values /// </summary> /// <returns>A <see cref="Task"/>, running the current operation, returning the metric values as a <see cref="LineSeries"/></returns> private async Task <ChartValues <DateTimePoint> > ReadChartValuesAsync() { CancellationToken cancellationToken = CancellationToken.None; // Verify start/end times DateTime startTime = this.metricChartAlertProperty.StartTimeUtc ?? throw new ApplicationException("Start time cannot be null"); DateTime endTime = this.metricChartAlertProperty.EndTimeUtc ?? throw new ApplicationException("End time cannot be null"); if (endTime > DateTime.UtcNow) { endTime = DateTime.UtcNow; } // Convert from aggregation type to aggregation Aggregation aggregation; switch (this.metricChartAlertProperty.AggregationType) { case AggregationType.Average: aggregation = Aggregation.Average; break; case AggregationType.Count: aggregation = Aggregation.Count; break; case AggregationType.Sum: aggregation = Aggregation.Total; break; case AggregationType.Maximum: aggregation = Aggregation.Maximum; break; case AggregationType.Minimum: aggregation = Aggregation.Minimum; break; default: throw new ApplicationException($"Invalid aggregation type {this.metricChartAlertProperty.AggregationType}"); } // Create the metrics client ResourceIdentifier resource = ResourceIdentifier.CreateFromResourceId(this.metricChartAlertProperty.ResourceId); IMetricClient metricClient = await this.analysisServicesFactory.CreateMetricClientAsync(resource.SubscriptionId, cancellationToken) .ConfigureAwait(false); // Send a metric query using the metric client IEnumerable <MetricQueryResult> metricQueryResults = await metricClient.GetResourceMetricsAsync( this.metricChartAlertProperty.ResourceId, new QueryParameters() { MetricNamespace = this.metricChartAlertProperty.MetricNamespace, MetricNames = new List <string>() { this.metricChartAlertProperty.MetricName }, StartTime = startTime, EndTime = endTime, Interval = this.metricChartAlertProperty.TimeGrain, Aggregations = new List <Aggregation>() { aggregation }, }, cancellationToken) .ConfigureAwait(false); // Get chart points ChartValues <DateTimePoint> values = new ChartValues <DateTimePoint>( metricQueryResults .Single() .Timeseries .Single() .Data .Select(p => new DateTimePoint(p.TimeStamp, p.GetValue(aggregation)))); return(values); }