/// <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();
            }
        }
Example #2
0
        public static async Task <HttpResponseMessage> RunAsync(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "analyze")] HttpRequestMessage request,
            TraceWriter log,
            ExecutionContext context,
            CancellationToken cancellationToken)
        {
            using (IUnityContainer childContainer = Container.CreateChildContainer().WithTracer(log, true))
            {
                // Create a tracer for this run (that will also log to the specified TraceWriter)
                ITracer tracer = childContainer.Resolve <ITracer>();
                tracer.TraceInformation($"Analyze function request received with invocation Id {context.InvocationId}");
                tracer.AddCustomProperty("FunctionName", context.FunctionName);
                tracer.AddCustomProperty("InvocationId", context.InvocationId.ToString("N"));

                try
                {
                    // Trace app counters (before analysis)
                    tracer.TraceAppCounters();

                    // Read the request
                    SmartSignalRequest smartSignalRequest = await request.Content.ReadAsAsync <SmartSignalRequest>(cancellationToken);

                    tracer.AddCustomProperty("SignalId", smartSignalRequest.SignalId);
                    tracer.TraceInformation($"Analyze request received: {JsonConvert.SerializeObject(smartSignalRequest)}");

                    // Process the request
                    ISmartSignalRunner runner = childContainer.Resolve <ISmartSignalRunner>();
                    List <SmartSignalResultItemPresentation> resultPresentations = await runner.RunAsync(smartSignalRequest, cancellationToken);

                    tracer.TraceInformation($"Analyze completed, returning {resultPresentations.Count} results");

                    // Return the generated presentations
                    return(request.CreateResponse(HttpStatusCode.OK, resultPresentations));
                }
                catch (Exception e)
                {
                    // Handle the exception
                    TopLevelExceptionHandler.TraceUnhandledException(e, tracer, log);

                    // Return error status
                    return(request.CreateResponse(HttpStatusCode.InternalServerError, e.Message));
                }
                finally
                {
                    // Trace app counters (after analysis)
                    tracer.TraceAppCounters();
                }
            }
        }
        /// <summary>
        /// Runs the signal analysis, in a separate process
        /// </summary>
        /// <param name="request">The request</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>A <see cref="Task{TResult}"/>, returning the generated result items presentations</returns>
        public async Task <List <SmartSignalResultItemPresentation> > RunAsync(SmartSignalRequest request, CancellationToken cancellationToken)
        {
            // Find the executable location
            string currentDllPath = new Uri(typeof(SmartSignalRunnerInChildProcess).Assembly.CodeBase).AbsolutePath;
            string exePath        = Path.Combine(Path.GetDirectoryName(currentDllPath) ?? string.Empty, ChildProcessName);

            if (!File.Exists(exePath))
            {
                this.tracer.TraceError($"Verification of executable path {exePath} failed");
                throw new FileNotFoundException("Could not find child process executable", ChildProcessName);
            }

            // Run the child process
            return(await this.childProcessManager.RunChildProcessAsync <List <SmartSignalResultItemPresentation> >(exePath, request, cancellationToken));
        }
        private SmartSignalResultItemPresentation CreatePresentation(SmartSignalResultItem resultItem, bool nullQueryRunInfo = false)
        {
            SmartSignalResultItemQueryRunInfo queryRunInfo = null;

            if (!nullQueryRunInfo)
            {
                queryRunInfo = new SmartSignalResultItemQueryRunInfo(
                    resultItem.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? TelemetryDbType.ApplicationInsights : TelemetryDbType.LogAnalytics,
                    new List <string>()
                {
                    "resourceId1", "resourceId2"
                });
            }

            DateTime lastExecutionTime = DateTime.Now.Date.AddDays(-1);
            string   resourceId        = "resourceId";
            var      request           = new SmartSignalRequest(new List <string>()
            {
                resourceId
            }, "signalId", lastExecutionTime, TimeSpan.FromDays(1), new SmartSignalSettings());

            return(SmartSignalResultItemPresentation.CreateFromResultItem(request, SignalName, resultItem, queryRunInfo));
        }
        /// <summary>
        /// Loads the signal, runs it, and returns the generated result presentations
        /// </summary>
        /// <param name="request">The signal request</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>A <see cref="Task{TResult}"/>, returning the list of Smart Signal result item presentations generated by the signal</returns>
        public async Task <List <SmartSignalResultItemPresentation> > RunAsync(SmartSignalRequest request, CancellationToken cancellationToken)
        {
            // Read the signal's package
            this.tracer.TraceInformation($"Loading signal package for signal ID {request.SignalId}");
            SmartSignalPackage signalPackage = await this.smartSignalRepository.ReadSignalPackageAsync(request.SignalId, cancellationToken);

            SmartSignalManifest signalManifest = signalPackage.Manifest;

            this.tracer.TraceInformation($"Read signal package, ID {signalManifest.Id}, Version {signalManifest.Version}");

            // Load the signal
            ISmartSignal signal = this.smartSignalLoader.LoadSignal(signalPackage);

            this.tracer.TraceInformation($"Signal instance loaded successfully, ID {signalManifest.Id}");

            // Get the resources on which to run the signal
            List <ResourceIdentifier> resources = await this.GetResourcesForSignal(request.ResourceIds, signalManifest, cancellationToken);

            // Run the signal
            this.tracer.TraceInformation($"Started running signal ID {signalManifest.Id}, Name {signalManifest.Name}");
            SmartSignalResult signalResult;

            try
            {
                var analysisRequest = new AnalysisRequest(resources, request.LastExecutionTime, request.Cadence, this.analysisServicesFactory);
                signalResult = await signal.AnalyzeResourcesAsync(analysisRequest, this.tracer, cancellationToken);

                this.tracer.TraceInformation($"Completed running signal ID {signalManifest.Id}, Name {signalManifest.Name}, returning {signalResult.ResultItems.Count} result items");
            }
            catch (Exception e)
            {
                this.tracer.TraceInformation($"Failed running signal ID {signalManifest.Id}, Name {signalManifest.Name}: {e.Message}");
                throw new SmartSignalCustomException(e.GetType().ToString(), e.Message, e.StackTrace);
            }

            // Verify that each result item belongs to one of the types declared in the signal manifest
            foreach (SmartSignalResultItem resultItem in signalResult.ResultItems)
            {
                if (!signalManifest.SupportedResourceTypes.Contains(resultItem.ResourceIdentifier.ResourceType))
                {
                    throw new UnidentifiedResultItemResourceTypeException(resultItem.ResourceIdentifier);
                }
            }

            // Trace the number of result items of each type
            foreach (var resultItemType in signalResult.ResultItems.GroupBy(x => x.GetType().Name))
            {
                this.tracer.TraceInformation($"Got {resultItemType.Count()} Smart Signal result items of type '{resultItemType.Key}'");
                this.tracer.ReportMetric("SignalResultItemType", resultItemType.Count(), new Dictionary <string, string>()
                {
                    { "ResultItemType", resultItemType.Key }
                });
            }

            // Create results
            List <SmartSignalResultItemPresentation> results = new List <SmartSignalResultItemPresentation>();

            foreach (var resultItem in signalResult.ResultItems)
            {
                SmartSignalResultItemQueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { resultItem.ResourceIdentifier }, cancellationToken);

                results.Add(SmartSignalResultItemPresentation.CreateFromResultItem(request, signalManifest.Name, resultItem, queryRunInfo));
            }

            this.tracer.TraceInformation($"Returning {results.Count} results");
            return(results);
        }
        /// <summary>
        /// Sends an HTTP request to the analysis function with the smart signal request
        /// </summary>
        /// <param name="analysisRequest">The request to send to the analysis function</param>
        /// <returns>A list of smart signal result items</returns>
        private async Task <IList <SmartSignalResultItemPresentation> > SendToAnalysisAsync(SmartSignalRequest analysisRequest)
        {
            var    requestMessage = new HttpRequestMessage(HttpMethod.Post, this.analysisUrl);
            string requestBody    = JsonConvert.SerializeObject(analysisRequest);

            requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");

            this.tracer.TraceVerbose($"Sending analysis request {requestBody}");

            // Send the request
            var response = await this.retryPolicy.RunAndTrackDependencyAsync(this.tracer, DependencyName, analysisRequest.SignalId, () => this.httpClientWrapper.SendAsync(requestMessage, default(CancellationToken)));

            if (!response.IsSuccessStatusCode)
            {
                string content = response.Content != null ? await response.Content.ReadAsStringAsync() : string.Empty;

                var message = $"Failed to execute signal {analysisRequest.SignalId}. Fail StatusCode: {response.StatusCode}. Reason: {response.ReasonPhrase}. Content: {content}.";
                throw new AnalysisExecutionException(message);
            }

            var httpAnalysisResult = await response.Content.ReadAsStringAsync();

            return(JsonConvert.DeserializeObject <IList <SmartSignalResultItemPresentation> >(httpAnalysisResult));
        }
        /// <summary>
        /// Executes the signal via the analysis flow
        /// </summary>
        /// <param name="signalExecutionInfo">The signal execution information</param>
        /// <param name="resourceIds">The resource IDs used by the signal</param>
        /// <returns>A list of smart signal result items</returns>
        public async Task <IList <SmartSignalResultItemPresentation> > ExecuteSignalAsync(SignalExecutionInfo signalExecutionInfo, IList <string> resourceIds)
        {
            var analysisRequest = new SmartSignalRequest(resourceIds, signalExecutionInfo.AlertRule.SignalId, signalExecutionInfo.LastExecutionTime, signalExecutionInfo.AlertRule.Cadence, null);

            return(await this.SendToAnalysisAsync(analysisRequest));
        }
Example #8
0
        /// <summary>
        /// Creates a presentation from a result item
        /// </summary>
        /// <param name="request">The smart signal request</param>
        /// <param name="signalName">The signal name</param>
        /// <param name="smartSignalResultItem">The result item</param>
        /// <param name="queryRunInfo">The query run information</param>
        /// <returns>The presentation</returns>
        public static SmartSignalResultItemPresentation CreateFromResultItem(SmartSignalRequest request, string signalName, SmartSignalResultItem smartSignalResultItem, SmartSignalResultItemQueryRunInfo queryRunInfo)
        {
            // A null result item has null presentation
            if (smartSignalResultItem == null)
            {
                return(null);
            }

            // Create presentation elements for each result item property
            Dictionary <string, string> predicates = new Dictionary <string, string>();
            List <SmartSignalResultItemPresentationProperty> presentationProperties = new List <SmartSignalResultItemPresentationProperty>();
            SmartSignalResultItemPresentationProperty        summaryChart           = null;
            string summaryValue   = null;
            string summaryDetails = null;
            Dictionary <string, string> rawProperties = new Dictionary <string, string>();

            foreach (PropertyInfo property in smartSignalResultItem.GetType().GetProperties())
            {
                // Get the property value
                string propertyValue = PropertyValueToString(property.GetValue(smartSignalResultItem));
                rawProperties[property.Name] = propertyValue;

                // Check if this property is a predicate
                if (property.GetCustomAttribute <ResultItemPredicateAttribute>() != null)
                {
                    predicates[property.Name] = propertyValue;
                }

                // Get the presentation attribute
                ResultItemPresentationAttribute attribute = property.GetCustomAttribute <ResultItemPresentationAttribute>();
                if (attribute != null)
                {
                    // Verify that if the entity is a chart or query, then query run information was provided
                    if (queryRunInfo == null && (attribute.Section == ResultItemPresentationSection.Chart || attribute.Section == ResultItemPresentationSection.AdditionalQuery))
                    {
                        throw new InvalidSmartSignalResultItemPresentationException($"The presentation contains an item for the {attribute.Section} section, but no telemetry data client was provided");
                    }

                    // Get the attribute title and information balloon - support interpolated strings
                    string attributeTitle       = Smart.Format(attribute.Title, smartSignalResultItem);
                    string attributeInfoBalloon = Smart.Format(attribute.InfoBalloon, smartSignalResultItem);

                    // Add presentation to the summary component
                    if (attribute.Component.HasFlag(ResultItemPresentationComponent.Summary))
                    {
                        if (attribute.Section == ResultItemPresentationSection.Chart)
                        {
                            // Verify there is at most one summary chart
                            if (summaryChart != null)
                            {
                                throw new InvalidSmartSignalResultItemPresentationException("There can be at most one summary chart for each resultItem");
                            }

                            // Create the summary chart presentation property
                            summaryChart = new SmartSignalResultItemPresentationProperty(attributeTitle, propertyValue, attribute.Section, attributeInfoBalloon);
                        }
                        else if (attribute.Section == ResultItemPresentationSection.Property)
                        {
                            // Verify there is at most one summary presentation property
                            if (summaryValue != null)
                            {
                                throw new InvalidSmartSignalResultItemPresentationException("There must be exactly one summary property for each resultItem");
                            }

                            // Set summary presentation elements
                            summaryValue   = propertyValue;
                            summaryDetails = attributeTitle;
                        }
                        else
                        {
                            throw new InvalidSmartSignalResultItemPresentationException($"Invalid section for summary property {property.Name}: {attribute.Section}");
                        }
                    }

                    // Add presentation to the details component
                    if (attribute.Component.HasFlag(ResultItemPresentationComponent.Details))
                    {
                        presentationProperties.Add(new SmartSignalResultItemPresentationProperty(attributeTitle, propertyValue, attribute.Section, attributeInfoBalloon));
                    }
                }
            }

            // Verify that a summary was provided
            if (summaryValue == null)
            {
                throw new InvalidSmartSignalResultItemPresentationException("There must be exactly one summary property for each result item");
            }

            string id              = string.Join("##", smartSignalResultItem.GetType().FullName, JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(smartSignalResultItem)).Hash();
            string resourceId      = smartSignalResultItem.ResourceIdentifier.ToResourceId();
            string correlationHash = string.Join("##", predicates.OrderBy(x => x.Key).Select(x => x.Key + "|" + x.Value)).Hash();

            // Return the presentation object
            return(new SmartSignalResultItemPresentation(
                       id,
                       smartSignalResultItem.Title,
                       new SmartSignalResultItemPresentationSummary(summaryValue, summaryDetails, summaryChart),
                       resourceId,
                       correlationHash,
                       request.SignalId,
                       signalName,
                       DateTime.UtcNow,
                       (int)request.Cadence.TotalMinutes,
                       presentationProperties,
                       rawProperties,
                       queryRunInfo));
        }
        private void TestInitialize(ResourceType requestResourceType, ResourceType signalResourceType)
        {
            this.tracerMock = new Mock <ITracer>();

            ResourceIdentifier resourceId;

            switch (requestResourceType)
            {
            case ResourceType.Subscription:
                resourceId = new ResourceIdentifier(requestResourceType, "subscriptionId", string.Empty, string.Empty);
                break;

            case ResourceType.ResourceGroup:
                resourceId = new ResourceIdentifier(requestResourceType, "subscriptionId", "resourceGroup", string.Empty);
                break;

            default:
                resourceId = new ResourceIdentifier(requestResourceType, "subscriptionId", "resourceGroup", "resourceName");
                break;
            }

            this.resourceIds = new List <string>()
            {
                resourceId.ToResourceId()
            };
            this.request = new SmartSignalRequest(this.resourceIds, "1", DateTime.UtcNow.AddDays(-1), TimeSpan.FromDays(1), new SmartSignalSettings());

            var smartSignalManifest = new SmartSignalManifest("1", "Test signal", "Test signal description", Version.Parse("1.0"), "assembly", "class", new List <ResourceType>()
            {
                signalResourceType
            }, new List <int> {
                60
            });

            this.smartSignalPackage = new SmartSignalPackage(smartSignalManifest, new Dictionary <string, byte[]> {
                ["TestSignalLibrary"] = new byte[0]
            });

            this.smartSignalsRepositoryMock = new Mock <ISmartSignalRepository>();
            this.smartSignalsRepositoryMock
            .Setup(x => x.ReadSignalPackageAsync(It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(() => this.smartSignalPackage);

            this.analysisServicesFactoryMock = new Mock <IAnalysisServicesFactory>();

            this.signal = new TestSignal {
                ExpectedResourceType = signalResourceType
            };

            this.smartSignalLoaderMock = new Mock <ISmartSignalLoader>();
            this.smartSignalLoaderMock
            .Setup(x => x.LoadSignal(this.smartSignalPackage))
            .Returns(this.signal);

            this.azureResourceManagerClientMock = new Mock <IAzureResourceManagerClient>();
            this.azureResourceManagerClientMock
            .Setup(x => x.GetAllResourceGroupsInSubscriptionAsync(It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((string subscriptionId, CancellationToken cancellationToken) => new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.ResourceGroup, subscriptionId, "resourceGroupName", string.Empty)
            });
            this.azureResourceManagerClientMock
            .Setup(x => x.GetAllResourcesInSubscriptionAsync(It.IsAny <string>(), It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((string subscriptionId, IEnumerable <ResourceType> resourceTypes, CancellationToken cancellationToken) => new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.VirtualMachine, subscriptionId, "resourceGroupName", "resourceName")
            });
            this.azureResourceManagerClientMock
            .Setup(x => x.GetAllResourcesInResourceGroupAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((string subscriptionId, string resourceGroupName, IEnumerable <ResourceType> resourceTypes, CancellationToken cancellationToken) => new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.VirtualMachine, subscriptionId, resourceGroupName, "resourceName")
            });

            this.queryRunInfoProviderMock = new Mock <IQueryRunInfoProvider>();
        }
        /// <summary>
        /// Run the signal, by delegating the call to the registered <see cref="ISmartSignalRunner"/>
        /// </summary>
        /// <param name="request">The signal request</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>A <see cref="Task{TResult}"/>, returning the result items presentations generated by the signal</returns>
        private static async Task <List <SmartSignalResultItemPresentation> > RunSignalAsync(SmartSignalRequest request, CancellationToken cancellationToken)
        {
            ISmartSignalRunner smartSignalRunner = container.Resolve <ISmartSignalRunner>();

            return(await smartSignalRunner.RunAsync(request, cancellationToken));
        }