Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add storage connection strings for each distinct purpose in the gallery #4568

Merged
merged 1 commit into from
Aug 18, 2017
Merged

Add storage connection strings for each distinct purpose in the gallery #4568

merged 1 commit into from
Aug 18, 2017

Conversation

joelverhagen
Copy link
Member

Replaced configuration Gallery.AzureStorageConnectionString with new variables:

  • Gallery.AzureStorage.Auditing.ConnectionString
  • Gallery.AzureStorage.Content.ConnectionString
  • Gallery.AzureStorage.Errors.ConnectionString
  • Gallery.AzureStorage.NuGetExe.ConnectionString
  • Gallery.AzureStorage.Packages.ConnectionString
  • Gallery.AzureStorage.Statistics.ConnectionString
  • Gallery.AzureStorage.Uploads.ConnectionString

Why the dots in the configuration name? I think the proper mental model here is that each storage purpose (e.g. Errors) may eventually have additional properties. For example, the Gallery.AzureStorageReadAccessGeoRedundant setting could become account-specific or we could configure the table/container name that Uploads go into (for example). For that reason, I imagined the future where Gallery.AzureStorage.Statistics would refer to an object with a set of properties. Today there is only one property: ConnectionString.

Why the underscores in the property names? Today, the code that populates the AppConfiguration instance does not work on complex objects -- just simple properties. The underscore is basically a way to mimic complex objects in a flat list of properties like IAppConfiguration.

The basis of this implementation is telling our DI container (Autofac) which connection string to use for different services. We now need N instances of CloudBlobFileStorageService, instead of just one. Since this means we will have multiple implementations of IFileStorageService available in the DI container, we need a way to differentiate between them. We do this with keyed services. For more information about this general DI project, see here:
http://docs.autofac.org/en/latest/faq/select-by-context.html

After this PR is approved and before I merge the PR, I will add the new configuration values to our DEV, INT, and PROD configuration files (.cscfg).

Fix #4554.

@joelverhagen
Copy link
Member Author

Note that the meat of the work is done in src/NuGetGallery/App_Start/DefaultDependenciesModule.cs, which is showing as collapsed in the GitHub PR diff view. You must explicitly click Load diff to see these changed.


public async Task<bool> IsAvailableAsync()
{
return await _auditContainer.ExistsAsync();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you just return here without await?

Copy link
Member Author

Choose a reason for hiding this comment

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

In general I prefer await since it eases refactoring, but I don't care much. I'll remove the await.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

public async Task<bool> IsAvailableAsync()
{
var container = GetCloudBlobContainer();
return await container.ExistsAsync();
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing here, is await actually needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@@ -49,5 +45,18 @@ public CloudReportService(string connectionString, bool readAccessGeoRedundant)

return new StatisticsReport(content, blob.Properties.LastModified?.UtcDateTime);
}

private CloudBlobContainer GetCloudBlobContainer()
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the CloudBlobContainer be cached? Or do we want to fetch a fresh container reference each time?

Copy link
Member Author

Choose a reason for hiding this comment

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

I prefer to keep this out of scope -- we would need to analyze whether we would be regressing anything here by caching.


var connectionStringToBindingKey = dependents
.GroupBy(d => d.AzureStorageConnectionString ?? DefaultBindingKey)
.ToDictionary(g => g.Key, g => string.Join(" ", g.Select(d => d.ImplementationType)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should you use d.ImplementationType.FullName here instead to be consistent with StorageDependent.Create?

Copy link
Member Author

Choose a reason for hiding this comment

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

This should actually be g.Select(d => d.BindingKey).

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

var completedBindingKeys = new HashSet<string>();
foreach (var dependent in GetStorageDependents(configuration.Current))
{
if (!completedBindingKeys.Contains(dependent.BindingKey))
Copy link
Contributor

Choose a reason for hiding this comment

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

You could just call Add(..) without ! here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

public Type ImplementationType { get; }

/// <summary>
/// The storage dependent's interface tyep.
Copy link
Contributor

Choose a reason for hiding this comment

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

type

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, a tyepo

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

public string AzureStorage_Statistics_ConnectionString { get; set; }

[DisplayName("AzureStorage.Uploads.ConnectionString")]
public string AzureStorage_Uploads_ConnectionString { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why use underscores in the property names?

Copy link
Member Author

Choose a reason for hiding this comment

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

Why the underscores in the property names? Today, the code that populates the AppConfiguration instance does not work on complex objects -- just simple properties. The underscore is basically a way to mimic complex objects in a flat list of properties like IAppConfiguration.

@@ -19,7 +20,7 @@ public class StatusService : IStatusService
{
private readonly HttpClient _httpClient = new HttpClient();
private readonly IEntitiesContext _entities;
private readonly IFileStorageService _fileStorageService;
private readonly IEnumerable<ICloudStorageAvailabilityCheck> _cloudStorageAvailabilityChecks;
Copy link
Contributor

Choose a reason for hiding this comment

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

Use ICollection instead of IEnumerable. The code potentially reads it multiple times and generally, IEnumerable does not support multiple iterations over it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, good point. Who knows how Autofac implements this (I don't care to know)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

@scottbommarito scottbommarito left a comment

Choose a reason for hiding this comment

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

nitpicks, looks awesome

string AzureStorage_Errors_ConnectionString { get; set; }

/// <summary>
/// The Azure Storage connection string used downloading nuget.exe.
Copy link
Contributor

Choose a reason for hiding this comment

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

used downloading -> used for downloading

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

public class DefaultDependenciesModuleFacts
{
[Fact]
public void AllTypesDependingOnIFileStorageServiceAreReturnedByGetStorageDependents()
Copy link
Contributor

Choose a reason for hiding this comment

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

These tests are amazing and absolutely get rid of any concerns I had about the DI approach.

Great job.

/// Determines whether a cloud storage object (e.g. a blob container) is available. The purpose of this interface
/// is to assess a gallery instance's connection to Azure Storage.
/// </summary>
public interface ICloudStorageAvailabilityCheck
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: semantically, ICloudStorageAvailabilityCheck doesn't make a whole lot of sense--this is an object that can be checked for availability, not an availability check itself. Something along the lines of ICloudStorageResource or IStatusDependency would make more sense to me (there's nothing about this resource that implies that we must be checking a cloud storage object for availability).

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.


private static void ConfigureForAzureStorage(ContainerBuilder builder, IGalleryConfigurationService configuration)
{
var completedBindingKeys = new HashSet<string>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the complexity of this code, it may be beneficial to add some comments here, much like you did in the summary of this PR, describing what this code is and what it's accomplishing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

/// <summary>
/// Represents a type that depends on <see cref="IFileStorageService"/>.
/// </summary>
internal class StorageDependent
Copy link
Contributor

Choose a reason for hiding this comment

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

This file is already massive, it may be better to move this as well as GetStorageDependents to its own file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

{
const string DefaultBindingKey = "Default";

var dependents = new[]
Copy link
Contributor

Choose a reason for hiding this comment

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

May want to add a comment here detailing that this array must be added to when a new StorageDependent is added.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.


namespace NuGetGallery
{
public class DefaultDependenciesModuleFacts
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: These are really just StorageDependentFacts. As suggested above, you should move StorageDependent to its own file and then rename this class.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@joelverhagen joelverhagen merged commit bb6201d into NuGet:dev Aug 18, 2017
@joelverhagen joelverhagen deleted the storage-config branch August 22, 2017 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants