Skip to content
This repository has been archived by the owner on Aug 3, 2024. It is now read-only.
/ ServerCommon Public archive

Add status classes and tests #170

Merged
merged 14 commits into from
Jul 13, 2018
Merged

Add status classes and tests #170

merged 14 commits into from
Jul 13, 2018

Conversation

scottbommarito
Copy link
Contributor

Part of NuGet/NuGetGallery#6107

These classes will be shared between the Gallery, Status page, and Status Aggregator job to emit and consume status about the service.

I suggest starting at Status.cs for the most context.

@shishirx34
Copy link
Contributor

1300 lines 😩

@scottbommarito
Copy link
Contributor Author

@shishirx34 - Apologies. It should be an easy read for its length, however. It's a lot of interfaces and relatively simple code.

@@ -105,7 +106,8 @@ Invoke-BuildStep 'Creating artifacts' { `
"src\NuGet.Services.ServiceBus\NuGet.Services.ServiceBus.csproj", `
"src\NuGet.Services.Validation\NuGet.Services.Validation.csproj", `
"src\NuGet.Services.Validation.Issues\NuGet.Services.Validation.Issues.csproj", `
"src\NuGet.Services.Sql\NuGet.Services.Sql.csproj"
"src\NuGet.Services.Sql\NuGet.Services.Sql.csproj", `
"src\NuGet.Services.Status\NuGet.Services.Status.csproj"
Copy link
Contributor

Choose a reason for hiding this comment

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

csproj [](start = 61, length = 6)

Shouldn't you update test.ps1 too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

IEnumerable<IComponent> subComponents)
: this(name, description)
{
SubComponents = subComponents?.Select(s => new SubComponent(s, this))
Copy link
Contributor

Choose a reason for hiding this comment

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

Select [](start = 43, length = 6)

You should ToList this. The IEnumerable could be some crazy LINQ query that people will have to enumerate every time they call SubComponents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Also made this fix to several other component classes

public string Description { get; }
public abstract ComponentStatus Status { get; set; }
public IEnumerable<IComponent> SubComponents { get; }
IEnumerable<IReadOnlyComponent> IReadOnlyComponent.SubComponents => SubComponents;
Copy link
Contributor

Choose a reason for hiding this comment

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

IEnumerable [](start = 8, length = 11)

Should this guy be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the inheritance so that this property no longer needs to exist.


if (component == null)
{
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

break [](start = 20, length = 5)

If I run GetByPath('root', 'this', 'doesnt', 'exist') and only the root component exists, this will return the root IReadOnlyComponent. Would it be better if this returned null instead? If so, I would rename the method to be GetByPathOrNull.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a bug. I added tests to make sure it doesn't break again.


namespace NuGet.Services.Status
{
public class Message : IMessage
Copy link
Contributor

Choose a reason for hiding this comment

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

Message [](start = 17, length = 7)

Does this and Event need interfaces? What value does it add?

// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Newtonsoft.Json;
using System;
Copy link
Contributor

Choose a reason for hiding this comment

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

System [](start = 6, length = 6)

Looks like that VS setting to put System on top got turned off :(

<Version>9.0.1</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Import [](start = 3, length = 6)

This project should be signed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

forgot about that, added now

/// Gets the subcomponent of <paramref name="component"/> by iterating its subcomponents in the order of <paramref name="componentNames"/>.
/// If none exists, returns <c>null</c>.
/// </summary>
public static TComponent GetByNames<TComponent>(this TComponent component, params string[] componentNames)
Copy link
Contributor

Choose a reason for hiding this comment

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

component [](start = 72, length = 9)

Extension methods let the this parameter be null, thus, you should check if component is null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch

/// <summary>
/// Describes the status of an entire service.
/// </summary>
public class Status
Copy link
Member

Choose a reason for hiding this comment

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

Maybe use a more specific name -- ServiceStatus? NuGetServiceStatus?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like NuGetStatus is a little redundant given that it's in the namespace NuGet.Services.Status. Additionally, there's nothing NuGet specific to this class (or any of the classes in this project).

I think ServiceStatus makes sense.

/// <summary>
/// The <see cref="IReadOnlyComponent"/> that describes the entire service. Its <see cref="IReadOnlyComponent.SubComponents"/> represent portions of the service.
/// </summary>
public IReadOnlyComponent RootComponent { get; }
Copy link
Member

Choose a reason for hiding this comment

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

What does RootComponent represent? Gallery?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RootComponent is the parent of Gallery, Search, our CDN, etc.
Basically, it's a component that encapsulates the entire service and reports a status for the entire service.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I will rename this to ServiceRootComponent.

/// <summary>
/// A list of subcomponents that make up this part of the service.
/// </summary>
new IEnumerable<IReadOnlyComponent> SubComponents { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Is new necessary? Why can't it reuse the IComponentRoot<T>.SubComponents?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll remove this.

/// <summary>
/// A list of writable subcomponents that make up this part of the service.
/// </summary>
new IEnumerable<IComponent> SubComponents { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Is new necessary? Why can't it reuse IReadOnlyComponent.SubComponentsorIComponentRoot.SubComponents`?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll remove this, IComponentRoot<IComponent>.SubComponents is sufficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I forgot that this needs to exist so that IComponent.SubComponents is not ambiguous between IEnumerable<IReadOnlyComponent> and IEnumerable<IComponent>.

/// <summary>
/// Default implementation of <see cref="IComponent"/>.
/// </summary>
public abstract class Component : ReadOnlyComponent, IComponent
Copy link
Member

Choose a reason for hiding this comment

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

Do you have examples somewhere of what the the hierarchy of these components is? What LeafComponents, PrimarySecondaryComponents, TreeComponents, etc.

Could there be more specific name? NuGetServiceComponent?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To clarify:

A consumer of status (gallery, status page) is going to only work with IReadOnlyComponent and its implementation, ReadOnlyComponent. Consumers can't set or modify status, so the status of any component doesn't change and status doesn't need to be recalculated at any point.

IComponent and its implementations (which are subclasses of Component) are to be used by status producers (status job) and allow setting status. Because they allow setting status, the status of any component can change at any moment. A change in the status of a subcomponent can affect the status of a component that is its root. The subclasses of Component propagate changes in status differently from subcomponents to root components.

I'll provide some examples for each Component subclass I have here:

TreeComponent:
V3 restore is a component with two subcomponents, one for each of its regions--Global and China.
If either Global or China is down, V3 restore is degraded because both Global and China are used by customers to restore using V3.

PrimarySecondaryComponent:
The gallery is a component with two subcomponents, one for each of its regions--USNC and USSC.
If USNC is up, we don't care about the status of USSC because it's only used if USNC is down.d

LeafComponent:
The USNC region of Gallery is a LeafComponent because it can't be broken down into any subcomponents.
(Although TreeComponent and PrimarySecondaryComponent both support not having subcomponents and behave as LeafComponent does, I created this subclass so that status producers don't have to choose between the two arbitrarily.)

Copy link
Member

Choose a reason for hiding this comment

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

I suggest adding comments w/ your examples to the code.

public abstract class Component : ReadOnlyComponent, IComponent
{
public new abstract ComponentStatus Status { get; set; }
public new IEnumerable<IComponent> SubComponents { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Is there another way to achieve this without the new to replace base properties? It makes the code more complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately no, but I think from a consumer side this makes these classes a lot easier to use because I'm replacing the base properties in an intuitive way. I tried doing this without replacing any properties and it made the code substantially uglier.

I replace Status to add a setter to a property that is read-only on the base interface.

I replace SubComponents to provide the additional restriction that all members of the property on the base interface are of a specific subclass (IComponent instead of IReadOnlyComponent).

public IEnumerable<IReadOnlyComponent> SubComponents { get; }
public string Path => _parent.Path + Constants.ComponentPathDivider + Name;

public ReadOnlyComponentWrapper(IReadOnlyComponent component, IReadOnlyComponent parent)
Copy link
Member

Choose a reason for hiding this comment

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

Is there an alternative to wrapper classes for assigning parent/path? Makes the code more complex, and encapsulates the component types that are wrapped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A component only has a path when accessed as a subcomponent of another component. If we don't wrap or clone the subcomponents, a user could get some very strange results if they reuse the same component in multiple component trees.

In other words:

var a = new Component();
var b = new Component(subcomponents: new[] { a });
var c = new Component(subcomponents: new[] { a });

Console.WriteLine(b.SubComponents.First().Path); // should return "b/a"
Console.WriteLine(c.SubComponents.First().Path); // should return "c/a"

If the constructor of Component set the path of a and did not wrap or clone a, the path set by c's constructor will override the path set by b's constructor, which would create unintended results when accessing a as a subcomponent of b.

I chose wrapping instead of cloning because I think it would be confusing to have

var a = new Component();
var b = new Component(subcomponents: new[] { a });

a.Status = ComponentStatus.Degraded;

Console.WriteLine(b.SubComponents.First().Status);
// outputs ComponentStatus.Up if cloned
// and ComponentStatus.Degraded if wrapped

Note that this class is also internal and is meant to be invisible to the consumer.

Copy link
Member

Choose a reason for hiding this comment

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

I assumed a component would only have a single parent, and would either be child or root node. Guess that's not the case?

Maybe Node would be better suffix than Wrapper since it implies a relational object?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't expect to be creating any components with multiple parents, but I felt this model was easier to use than a model asserting a single parent.

I think I'll add some of the discussion above to a comment in the code to make it clearer. Ultimately, given this is an internal class, I don't think naming is super important as long as the purpose of the class is communicated well.

public IEnumerable<IReadOnlyComponent> SubComponents { get; }
public string Path => _parent.Path + Constants.ComponentPathDivider + Name;

public ReadOnlyComponentWrapper(IReadOnlyComponent component, IReadOnlyComponent parent)
Copy link
Member

Choose a reason for hiding this comment

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

I assumed a component would only have a single parent, and would either be child or root node. Guess that's not the case?

Maybe Node would be better suffix than Wrapper since it implies a relational object?

@scottbommarito scottbommarito merged commit 4aeb1a3 into master Jul 13, 2018
@scottbommarito scottbommarito deleted the sb-status branch July 13, 2018 00:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants