Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.
/ NuGet.Jobs Public archive

Status - add export logic for two-layer aggregation #566

Merged
merged 11 commits into from
Oct 9, 2018
29 changes: 29 additions & 0 deletions src/StatusAggregator/Container/ContainerWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Blob;

namespace StatusAggregator.Container
{
public class ContainerWrapper : IContainerWrapper
{
private readonly CloudBlobContainer _container;

public ContainerWrapper(CloudBlobContainer container)
{
_container = container;
}

public Task CreateIfNotExistsAsync()
{
return _container.CreateIfNotExistsAsync();
}

public Task SaveBlobAsync(string name, string contents)
{
var blob = _container.GetBlockBlobReference(name);
return blob.UploadTextAsync(contents);
}
}
}
17 changes: 17 additions & 0 deletions src/StatusAggregator/Container/IContainerWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Blob;

namespace StatusAggregator.Container
{
/// <summary>
/// Simple wrapper for <see cref="CloudBlobContainer"/> that exists for unit-testing.
/// </summary>
public interface IContainerWrapper
{
Task CreateIfNotExistsAsync();
Task SaveBlobAsync(string name, string contents);
}
}
92 changes: 92 additions & 0 deletions src/StatusAggregator/Export/ComponentExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using Microsoft.Extensions.Logging;
using NuGet.Jobs.Extensions;
using NuGet.Services.Status;
using NuGet.Services.Status.Table;
using StatusAggregator.Factory;
using StatusAggregator.Table;

namespace StatusAggregator.Export
{
public class ComponentExporter : IComponentExporter
{
private readonly ITableWrapper _table;
private readonly IComponentFactory _factory;

private readonly ILogger<ComponentExporter> _logger;

public ComponentExporter(
ITableWrapper table,
IComponentFactory factory,
ILogger<ComponentExporter> logger)
{
_table = table ?? throw new ArgumentNullException(nameof(table));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public IComponent Export()
{
using (_logger.Scope("Exporting active entities to component."))
{
var rootComponent = _factory.Create();

// Apply the active entities to the component tree.
var activeEvents = _table
.GetActiveEntities<EventEntity>()
.ToList()
.Where(e =>
_table
.GetChildEntities<MessageEntity, EventEntity>(e)
.ToList()
.Any())
.ToList();

_logger.LogInformation("Found {EventCount} active events with messages.", activeEvents.Count);

var activeIncidentGroups = activeEvents
.SelectMany(e =>
_table
.GetChildEntities<IncidentGroupEntity, EventEntity>(e)
.Where(i => i.IsActive)
.ToList())
.ToList();

_logger.LogInformation("Found {GroupCount} active incident groups linked to active events with messages.", activeIncidentGroups.Count);

var activeEntities = activeIncidentGroups
.Concat<IComponentAffectingEntity>(activeEvents)
// Only apply entities with a non-Up status.
.Where(e => e.AffectedComponentStatus != (int)ComponentStatus.Up)
// If multiple events are affecting a single region, the event with the highest severity should affect the component.
.GroupBy(e => e.AffectedComponentPath)
.Select(g => g.OrderByDescending(e => e.AffectedComponentStatus).First())
.ToList();

_logger.LogInformation("Active entities affect {PathCount} distinct subcomponents.", activeEntities.Count);
foreach (var activeEntity in activeEntities)
{
using (_logger.Scope("Applying active entity affecting {AffectedComponentPath} of severity {AffectedComponentStatus} at {StartTime} to root component",
activeEntity.AffectedComponentPath, activeEntity.AffectedComponentStatus, activeEntity.StartTime))
{
var currentComponent = rootComponent.GetByPath(activeEntity.AffectedComponentPath);

if (currentComponent == null)
{
_logger.LogWarning("Couldn't find component corresponding to active entities.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogWarning [](start = 36, length = 10)

Why isn't this an exception?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, will fix

continue;
}

currentComponent.Status = (ComponentStatus)activeEntity.AffectedComponentStatus;
}
}

return rootComponent;
}
}
}
}
55 changes: 55 additions & 0 deletions src/StatusAggregator/Export/EventExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NuGet.Jobs.Extensions;
using NuGet.Services.Status;
using NuGet.Services.Status.Table;
using StatusAggregator.Table;

namespace StatusAggregator.Export
{
public class EventExporter : IEventExporter
{
private readonly ITableWrapper _table;
private readonly ILogger<EventExporter> _logger;

public EventExporter(
ITableWrapper table,
ILogger<EventExporter> logger)
{
_table = table ?? throw new ArgumentNullException(nameof(table));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public Event Export(EventEntity eventEntity)
{
using (_logger.Scope("Exporting event {EventRowKey}.", eventEntity.RowKey))
{
var messages = _table.GetChildEntities<MessageEntity, EventEntity>(eventEntity)
.ToList()
// Don't show empty messages.
.Where(m => !string.IsNullOrEmpty(m.Contents))
.ToList();

_logger.LogInformation("Event has {MessageCount} messages that are not empty.", messages.Count);

if (!messages.Any())
{
return null;
}

return new Event(
eventEntity.AffectedComponentPath,
eventEntity.StartTime,
eventEntity.EndTime,
messages
.OrderBy(m => m.Time)
.Select(m => new Message(m.Time, m.Contents)));
}
}
}
}
46 changes: 46 additions & 0 deletions src/StatusAggregator/Export/EventsExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NuGet.Services.Status;
using NuGet.Services.Status.Table;
using StatusAggregator.Table;

namespace StatusAggregator.Export
{
public class EventsExporter : IEventsExporter
{
private readonly TimeSpan _eventVisibilityPeriod;

private readonly ITableWrapper _table;
private readonly IEventExporter _exporter;

private readonly ILogger<EventsExporter> _logger;

public EventsExporter(
ITableWrapper table,
IEventExporter exporter,
StatusAggregatorConfiguration configuration,
ILogger<EventsExporter> logger)
{
_table = table ?? throw new ArgumentNullException(nameof(table));
_exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
_eventVisibilityPeriod = TimeSpan.FromDays(configuration?.EventVisibilityPeriodDays ?? throw new ArgumentNullException(nameof(configuration)));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public IEnumerable<Event> Export(DateTime cursor)
{
return _table
.CreateQuery<EventEntity>()
.Where(e => e.IsActive || (e.EndTime >= cursor - _eventVisibilityPeriod))
.ToList()
.Select(_exporter.Export)
.Where(e => e != null)
.ToList();
}
}
}
15 changes: 15 additions & 0 deletions src/StatusAggregator/Export/IComponentExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using NuGet.Services.Status;

namespace StatusAggregator.Export
{
public interface IComponentExporter
{
/// <summary>
/// Exports the status of the current active entities to an <see cref="IComponent"/>.
/// </summary>
IComponent Export();
}
}
17 changes: 17 additions & 0 deletions src/StatusAggregator/Export/IEventExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using NuGet.Services.Status;
using NuGet.Services.Status.Table;

namespace StatusAggregator.Export
{
public interface IEventExporter
{
/// <summary>
/// Exports <paramref name="eventEntity"/> as a <see cref="Event"/>. If it should not be exported, returns <c>null</c>.
/// </summary>
Event Export(EventEntity eventEntity);
}
}
17 changes: 17 additions & 0 deletions src/StatusAggregator/Export/IEventsExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using NuGet.Services.Status;

namespace StatusAggregator.Export
{
public interface IEventsExporter
{
/// <summary>
/// Exports recent events.
/// </summary>
IEnumerable<Event> Export(DateTime cursor);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using NuGet.Services.Status;
using System;
using System.Threading.Tasks;

namespace StatusAggregator
namespace StatusAggregator.Export
{
public interface IStatusExporter
{
/// <summary>
/// Builds a <see cref="ServiceStatus"/> and exports it to public storage so that it can be consumed by other services.
/// </summary>
Task<ServiceStatus> Export();
Task Export(DateTime cursor);
}
}
}
18 changes: 18 additions & 0 deletions src/StatusAggregator/Export/IStatusSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NuGet.Services.Status;

namespace StatusAggregator.Export
{
public interface IStatusSerializer
{
/// <summary>
/// Serializes <paramref name="rootComponent"/> and <paramref name="recentEvents"/> and saves to storage with a time of <paramref name="cursor"/>.
/// </summary>
Task Serialize(DateTime cursor, IComponent rootComponent, IEnumerable<Event> recentEvents);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
Expand All @@ -7,7 +7,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace StatusAggregator
namespace StatusAggregator.Export
{
/// <summary>
/// Implementation of <see cref="DefaultContractResolver"/> used by <see cref="StatusExporter"/> such that empty fields and arrays are not serialized.
Expand Down Expand Up @@ -70,4 +70,4 @@ private void SetShouldSerializeForIEnumerable(JsonProperty property, MemberInfo
};
}
}
}
}
Loading