public async Task ReturnsTrueIfAggregationStillActive(bool inputIsActive) { var aggregatedEntity = new TAggregatedEntity { ParentRowKey = AggregationRowKey }; Table.SetupQuery(aggregatedEntity); var incident = new Incident() { Source = new IncidentSourceData() { CreateDate = new DateTime(2018, 9, 13) } }; if (!inputIsActive) { incident.MitigationData = new IncidentStateChangeEventData() { Date = new DateTime(2018, 10, 9) }; } var input = new ParsedIncident(incident, "", ComponentStatus.Up); Updater .Setup(x => x.UpdateAsync(Aggregation, input.StartTime)) .Returns(Task.CompletedTask); var result = await Strategy.CanBeAggregatedByAsync(input, Aggregation); Assert.True(result); }
public async Task CreatesEvent() { var input = new ParsedIncident(Incident, "somePath", ComponentStatus.Up); EventEntity entity = null; Table .Setup(x => x.InsertOrReplaceAsync(It.IsAny <ITableEntity>())) .Returns(Task.CompletedTask) .Callback <ITableEntity>(e => { Assert.IsType <EventEntity>(e); entity = e as EventEntity; }); var aggregationPath = "thePath"; Provider .Setup(x => x.Get(input)) .Returns(aggregationPath); var result = await Factory.CreateAsync(input); Assert.Equal(entity, result); Assert.Equal(aggregationPath, entity.AffectedComponentPath); Assert.Equal((int)ComponentStatus.Up, entity.AffectedComponentStatus); Assert.Equal(input.StartTime, entity.StartTime); Table .Verify( x => x.InsertOrReplaceAsync(It.IsAny <ITableEntity>()), Times.Once()); }
public async Task ReturnsFalseIfAggregationInactiveAndInputActive() { Aggregation.EndTime = new DateTime(2018, 10, 9); var aggregatedEntity = new TAggregatedEntity { ParentRowKey = AggregationRowKey }; var incident = new Incident() { Source = new IncidentSourceData() { CreateDate = new DateTime(2018, 9, 13) } }; var input = new ParsedIncident(incident, "", ComponentStatus.Up); Table.SetupQuery(aggregatedEntity); Updater .Setup(x => x.UpdateAsync(Aggregation, input.StartTime)) .Returns(Task.CompletedTask); var result = await Strategy.CanBeAggregatedByAsync(input, Aggregation); Assert.False(result); }
public async Task <bool> CanBeAggregatedByAsync(ParsedIncident input, TAggregationEntity aggregationEntity) { using (_logger.Scope("Determining if entity can be linked to aggregation {AggregationRowKey}", aggregationEntity.RowKey)) { if (!_table.GetChildEntities <TChildEntity, TAggregationEntity>(aggregationEntity).ToList().Any()) { // A manually created aggregation will have no children. We cannot use an aggregation that was manually created. // It is also possible that some bug or data issue has broken this aggregation. If that is the case, we cannot use it either. _logger.LogInformation("Cannot link entity to aggregation because it is not linked to any children."); return(false); } // To guarantee that the aggregation reflects the latest information and is actually active, we must update it. await _aggregationUpdater.UpdateAsync(aggregationEntity, input.StartTime); if (!aggregationEntity.IsActive && input.IsActive) { _logger.LogInformation("Cannot link entity to aggregation because it has been deactivated and the incident has not been."); return(false); } _logger.LogInformation("Entity can be linked to aggregation."); return(true); } }
public async Task <IncidentEntity> CreateAsync(ParsedIncident input) { var groupEntity = await _aggregationProvider.GetAsync(input); var affectedPath = _pathProvider.Get(input); using (_logger.Scope("Creating incident for parsed incident with path {AffectedComponentPath}.", affectedPath)) { var incidentEntity = new IncidentEntity( input.Id, groupEntity, affectedPath, input.AffectedComponentStatus, input.StartTime, input.EndTime); await _table.InsertOrReplaceAsync(incidentEntity); if (incidentEntity.AffectedComponentStatus > groupEntity.AffectedComponentStatus) { _logger.LogInformation("Incident {IncidentRowKey} has a greater severity than incident group {GroupRowKey} it was just linked to ({NewSeverity} > {OldSeverity}), updating group's severity.", incidentEntity.RowKey, groupEntity.RowKey, (ComponentStatus)incidentEntity.AffectedComponentStatus, (ComponentStatus)groupEntity.AffectedComponentStatus); groupEntity.AffectedComponentStatus = incidentEntity.AffectedComponentStatus; await _table.ReplaceAsync(groupEntity); } return(incidentEntity); } }
public async Task ReturnsFalseIfNoLinkedEntities() { var aggregatedEntityLinkedToDifferentAggregation = new TAggregatedEntity { ParentRowKey = "wrongRowKey" }; Table.SetupQuery(aggregatedEntityLinkedToDifferentAggregation); var incident = new Incident() { Source = new IncidentSourceData() { CreateDate = new DateTime(2018, 9, 13) } }; var input = new ParsedIncident(incident, "", ComponentStatus.Up); var result = await Strategy.CanBeAggregatedByAsync(input, Aggregation); Assert.False(result); Updater .Verify( x => x.UpdateAsync(It.IsAny <TEntityAggregation>(), It.IsAny <DateTime>()), Times.Never()); }
/// <summary> /// <see cref="EventEntity"/>s should be created using the path of <paramref name="input"/>'s level 1 ancestor. /// </summary> public string Get(ParsedIncident input) { var pathParts = ComponentUtility.GetNames(input.AffectedComponentPath); var topLevelComponentPathParts = pathParts.Take(2).ToArray(); return(ComponentUtility.GetPath(topLevelComponentPathParts)); }
public void ReturnsListOfParsedIncidents() { var failingParser1 = CreateParser(false); var failingParser2 = CreateParser(false); var parsedIncident1 = new ParsedIncident(Incident, "one", ComponentStatus.Degraded); var successfulParser1 = CreateParser(true, parsedIncident1); var parsedIncident2 = new ParsedIncident(Incident, "two", ComponentStatus.Down); var successfulParser2 = CreateParser(true, parsedIncident2); var parsers = new Mock <IIncidentParser>[] { failingParser1, successfulParser1, successfulParser2, failingParser2 }; var aggregateParser = new AggregateIncidentParser( parsers.Select(p => p.Object), Mock.Of <ILogger <AggregateIncidentParser> >()); var result = aggregateParser.ParseIncident(Incident); foreach (var parser in parsers) { parser.Verify(); } Assert.Equal(2, result.Count()); Assert.Contains(parsedIncident1, result); Assert.Contains(parsedIncident2, result); }
public async Task <TAggregationEntity> GetAsync(ParsedIncident input) { TAggregationEntity aggregationEntity = null; var possiblePath = _aggregationPathProvider.Get(input); // Find an aggregation to link to var possibleAggregationsQuery = _table .CreateQuery <TAggregationEntity>() .Where(e => // The aggregation must affect the same path e.AffectedComponentPath == possiblePath && // The aggregation must begin before or at the same time e.StartTime <= input.StartTime); // The aggregation must cover the same time period if (input.IsActive) { // An active input can only be linked to an active aggregation possibleAggregationsQuery = possibleAggregationsQuery .Where(e => e.IsActive); } else { // An inactive input can be linked to an active aggregation or an inactive aggregation that ends after it possibleAggregationsQuery = possibleAggregationsQuery .Where(e => e.IsActive || e.EndTime >= input.EndTime); } var possibleAggregations = possibleAggregationsQuery .ToList(); _logger.LogInformation("Found {AggregationCount} possible aggregations to link entity to with path {AffectedComponentPath}.", possibleAggregations.Count(), possiblePath); foreach (var possibleAggregation in possibleAggregations) { if (await _strategy.CanBeAggregatedByAsync(input, possibleAggregation)) { _logger.LogInformation("Linking entity to aggregation."); aggregationEntity = possibleAggregation; break; } } if (aggregationEntity == null) { _logger.LogInformation("Could not find existing aggregation to link to, creating new aggregation to link entity to."); aggregationEntity = await _aggregationFactory.CreateAsync(input); _logger.LogInformation("Created new aggregation {AggregationRowKey} to link entity to.", aggregationEntity.RowKey); } return(aggregationEntity); }
private Mock <IIncidentParser> CreateParser(bool result, ParsedIncident returnedIncident = null) { var parser = new Mock <IIncidentParser>(); parser .Setup(x => x.TryParseIncident(Incident, out returnedIncident)) .Returns(result) .Verifiable(); return(parser); }
public async Task CreatesEntityAndDoesNotIncreaseSeverity(ComponentStatus existingStatus) { var input = new ParsedIncident(Incident, "the path", ComponentStatus.Degraded); IncidentEntity entity = null; Table .Setup(x => x.InsertOrReplaceAsync(It.IsAny <ITableEntity>())) .Returns(Task.CompletedTask) .Callback <ITableEntity>(e => { Assert.IsType <IncidentEntity>(e); entity = e as IncidentEntity; }); var group = new IncidentGroupEntity { RowKey = "parentRowKey", AffectedComponentStatus = (int)existingStatus }; Provider .Setup(x => x.GetAsync(input)) .ReturnsAsync(group); var expectedPath = "the provided path"; PathProvider .Setup(x => x.Get(input)) .Returns(expectedPath); var result = await Factory.CreateAsync(input); Assert.Equal(entity, result); Assert.Equal(input.Id, entity.IncidentApiId); Assert.Equal(group.RowKey, entity.ParentRowKey); Assert.Equal(expectedPath, entity.AffectedComponentPath); Assert.Equal((int)input.AffectedComponentStatus, entity.AffectedComponentStatus); Assert.Equal(input.StartTime, entity.StartTime); Assert.Equal(input.EndTime, entity.EndTime); Assert.Equal((int)existingStatus, group.AffectedComponentStatus); Table .Verify( x => x.InsertOrReplaceAsync(It.IsAny <IncidentEntity>()), Times.Once()); Table .Verify( x => x.ReplaceAsync(It.IsAny <IncidentGroupEntity>()), Times.Never()); }
public async Task <EventEntity> CreateAsync(ParsedIncident input) { var affectedPath = _pathProvider.Get(input); using (_logger.Scope("Creating event for parsed incident with path {AffectedComponentPath}.", affectedPath)) { var entity = new EventEntity(affectedPath, input.StartTime); await _table.InsertOrReplaceAsync(entity); return(entity); } }
public void GetsAffectedComponentPath(string path) { var incident = new Incident() { Source = new IncidentSourceData() { CreateDate = new DateTime(2018, 10, 10) } }; var input = new ParsedIncident(incident, path, ComponentStatus.Up); var result = Provider.Get(input); Assert.Equal(path, result); }
public async Task <IncidentGroupEntity> CreateAsync(ParsedIncident input) { var eventEntity = await _aggregationProvider.GetAsync(input); var affectedPath = _pathProvider.Get(input); using (_logger.Scope("Creating incident for parsed incident with path {AffectedComponentPath}.", affectedPath)) { var incidentGroupEntity = new IncidentGroupEntity( eventEntity, affectedPath, (ComponentStatus)input.AffectedComponentStatus, input.StartTime); await _table.InsertOrReplaceAsync(incidentGroupEntity); return(incidentGroupEntity); } }
public async Task CreatesParsedIncidents() { var firstIncident = new Incident() { CreateDate = Cursor + TimeSpan.FromMinutes(1), Source = new IncidentSourceData { CreateDate = Cursor + TimeSpan.FromMinutes(5) } }; var secondIncident = new Incident() { CreateDate = Cursor + TimeSpan.FromMinutes(2), Source = new IncidentSourceData { CreateDate = Cursor + TimeSpan.FromMinutes(6) } }; var thirdIncident = new Incident() { CreateDate = Cursor + TimeSpan.FromMinutes(3), Source = new IncidentSourceData { CreateDate = Cursor + TimeSpan.FromMinutes(4) } }; var incidents = new[] { firstIncident, secondIncident, thirdIncident }; SetupClientQuery( Cursor, incidents); var firstFirstParsedIncident = new ParsedIncident(firstIncident, "", ComponentStatus.Up); var firstSecondParsedIncident = new ParsedIncident(firstIncident, "", ComponentStatus.Up); var secondFirstParsedIncident = new ParsedIncident(secondIncident, "", ComponentStatus.Up); var secondSecondParsedIncident = new ParsedIncident(secondIncident, "", ComponentStatus.Up); var thirdParsedIncident = new ParsedIncident(thirdIncident, "", ComponentStatus.Up); Parser .Setup(x => x.ParseIncident(firstIncident)) .Returns(new[] { firstFirstParsedIncident, firstSecondParsedIncident }); Parser .Setup(x => x.ParseIncident(secondIncident)) .Returns(new[] { secondFirstParsedIncident, secondSecondParsedIncident }); Parser .Setup(x => x.ParseIncident(thirdIncident)) .Returns(new[] { thirdParsedIncident }); var lastCreateDate = DateTime.MinValue; Factory .Setup(x => x.CreateAsync(It.IsAny <ParsedIncident>())) .ReturnsAsync(new IncidentEntity()) .Callback <ParsedIncident>(incident => { var nextCreateDate = incident.StartTime; Assert.True(nextCreateDate >= lastCreateDate); lastCreateDate = nextCreateDate; }); var result = await Processor.FetchSince(Cursor); Assert.Equal(incidents.Max(i => i.CreateDate), result); Factory .Verify( x => x.CreateAsync(firstFirstParsedIncident), Times.Once()); Factory .Verify( x => x.CreateAsync(secondFirstParsedIncident), Times.Once()); }
/// <summary> /// <see cref="IncidentEntity"/>s and <see cref="IncidentGroupEntity"/>s should be created using the same path as the <paramref name="input"/>. /// </summary> public string Get(ParsedIncident input) { return(input.AffectedComponentPath); }
public async Task CreatesNewEntityIfNoPossibleAggregation() { var inputPath = "howdy"; var input = new ParsedIncident(Incident, inputPath, ComponentStatus.Degraded); var providedPath = "hello"; PathProvider .Setup(x => x.Get(input)) .Returns(providedPath); var aggregationWithDifferentPath = new TEntityAggregation { AffectedComponentPath = "other path", StartTime = input.StartTime }; var aggregationAfter = new TEntityAggregation { AffectedComponentPath = providedPath, StartTime = input.StartTime + TimeSpan.FromDays(1) }; var aggregationBefore = new TEntityAggregation { AffectedComponentPath = providedPath, StartTime = input.StartTime - TimeSpan.FromDays(2), EndTime = input.StartTime - TimeSpan.FromDays(1) }; var activeAggregationToDeactivate = new TEntityAggregation { AffectedComponentPath = providedPath, StartTime = input.StartTime }; var inactiveAggregationToDeactivate = new TEntityAggregation { AffectedComponentPath = providedPath, StartTime = input.StartTime, EndTime = input.EndTime }; Table.SetupQuery( aggregationWithDifferentPath, aggregationAfter, aggregationBefore, activeAggregationToDeactivate, inactiveAggregationToDeactivate); Strategy .Setup(x => x.CanBeAggregatedByAsync(input, activeAggregationToDeactivate)) .ReturnsAsync(false); Strategy .Setup(x => x.CanBeAggregatedByAsync(input, inactiveAggregationToDeactivate)) .ReturnsAsync(false); var createdAggregation = new TEntityAggregation(); AggregationFactory .Setup(x => x.CreateAsync(input)) .ReturnsAsync(createdAggregation); var result = await Provider.GetAsync(input); Assert.Equal(createdAggregation, result); Strategy .Verify( x => x.CanBeAggregatedByAsync(It.IsAny <ParsedIncident>(), aggregationWithDifferentPath), Times.Never()); Strategy .Verify( x => x.CanBeAggregatedByAsync(It.IsAny <ParsedIncident>(), aggregationAfter), Times.Never()); Strategy .Verify( x => x.CanBeAggregatedByAsync(It.IsAny <ParsedIncident>(), aggregationBefore), Times.Never()); Strategy .Verify( x => x.CanBeAggregatedByAsync(It.IsAny <ParsedIncident>(), activeAggregationToDeactivate), Times.Once()); Strategy .Verify( x => x.CanBeAggregatedByAsync(It.IsAny <ParsedIncident>(), inactiveAggregationToDeactivate), Times.Once()); }