initial commit
This commit is contained in:
Родитель
d91685a5fd
Коммит
a241d802d2
|
@ -348,3 +348,6 @@ MigrationBackup/
|
|||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
/*/src/local.settings.json
|
||||
/*.sln
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to
|
||||
agree to a Contributor License Agreement (CLA) declaring that you have the right to,
|
||||
and actually do, grant us the rights to use your contribution. For details, visit
|
||||
https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need
|
||||
to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
|
||||
instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnPlugins</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnPlugins_Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>MultiCdnApi</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnLibrary</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnLibrary_Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>MultiCdnApi_Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.15.0" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,21 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface ICdnPlugin<T>
|
||||
{
|
||||
bool ProcessPartnerRequest(T partnerRequest, ICollector<ICdnRequest> queue);
|
||||
|
||||
void AddMessagesToSendQueue(ICdnRequest cdnRequest, ICollector<ICdnRequest> msg);
|
||||
|
||||
IList<ICdnRequest> SplitRequestIntoBatches(T partnerRequest, int maxNumUrl);
|
||||
|
||||
bool ValidPartnerRequest(string inputRequest, string resourceID, out T partnerRequest);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using Microsoft.Azure.Storage.Queue;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal interface ICdnQueueProcessor<T>
|
||||
{
|
||||
Task<CloudQueueMessage> ProcessPollRequest(T queueMsg, int maxRetry);
|
||||
|
||||
CloudQueueMessage CompletePollRequest(IRequestInfo requestInfo, T queueMsg);
|
||||
|
||||
Task<CloudQueueMessage> ProcessPurgeRequest(T queueMsg, int maxRetry);
|
||||
|
||||
CloudQueueMessage CompletePurgeRequest(IRequestInfo requestInfo, T queueMsg);
|
||||
|
||||
void AddMessageToQueue(CloudQueue outputQueue, CloudQueueMessage message, T queueMsg);
|
||||
|
||||
CloudQueueMessage CreateCloudQueueMessage(T request, IRequestInfo requestInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class HttpHandler : IHttpHandler
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
public HttpHandler()
|
||||
{
|
||||
this.httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public HttpHandler(DelegatingHandler delegatingHandler)
|
||||
{
|
||||
this.httpClient = new HttpClient(delegatingHandler);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetAsync(string endpoint)
|
||||
{
|
||||
return this.httpClient.GetAsync(endpoint);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> PostAsync(string endpoint, StringContent urls)
|
||||
{
|
||||
return this.httpClient.PostAsync(endpoint, urls);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IHttpHandler : IDisposable
|
||||
{
|
||||
Task<HttpResponseMessage> PostAsync(string endpoint, StringContent urls);
|
||||
|
||||
Task<HttpResponseMessage> GetAsync(string endpoint);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
public class CdnConfiguration
|
||||
{
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - used to deserialize JSON
|
||||
public string Hostname { get; set; }
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - used to deserialize JSON
|
||||
public IDictionary<string, string> CdnWithCredentials { get; set; }
|
||||
|
||||
public CdnConfiguration()
|
||||
{
|
||||
CdnWithCredentials = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public CdnConfiguration(string rawCdnConfiguration)
|
||||
{
|
||||
var cdnConfiguration = JsonSerializer.Deserialize<CdnConfiguration>(rawCdnConfiguration);
|
||||
Hostname = cdnConfiguration.Hostname;
|
||||
CdnWithCredentials = cdnConfiguration.CdnWithCredentials;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Hostname)}: {Hostname}, {nameof(CdnWithCredentials)}: <skipping credentials...>";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
public abstract class CosmosDbEntity : ICosmosDbEntity
|
||||
{
|
||||
public string id { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
public interface ICdnRequest : ICosmosDbEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the PartnerRequest this CdnRequest was batched from
|
||||
/// </summary>
|
||||
public string PartnerRequestID { get; set; }
|
||||
|
||||
public string[] Urls { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public int NumTimesProcessed { get; set; }
|
||||
|
||||
public string RequestBody { get; set; }
|
||||
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id returned by CDN once cache purge is submitted
|
||||
/// </summary>
|
||||
public string CdnRequestId { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public interface ICosmosDbEntity
|
||||
{
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Needs to be lowercase for CosmosDB to use this value as the id of the item")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public string id { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface IPartnerRequest : ICosmosDbEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the UserRequest this PartnerRequest was created from
|
||||
/// </summary>
|
||||
public string UserRequestID { get; set; }
|
||||
|
||||
public string CDN { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public int NumTotalCdnRequests { get; set; }
|
||||
|
||||
public int NumCompletedCdnRequests { get; set; }
|
||||
|
||||
public ISet<string> Urls { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
internal interface IRequestInfo
|
||||
{
|
||||
public string RequestID { get; set; }
|
||||
|
||||
public RequestStatus RequestStatus { get; set; }
|
||||
}
|
||||
|
||||
public enum RequestStatus
|
||||
{
|
||||
Error,
|
||||
Unauthorized,
|
||||
Throttled,
|
||||
Unknown,
|
||||
BatchCreated,
|
||||
MaxRetry,
|
||||
PurgeSubmitted,
|
||||
PurgeCompleted,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class Partner : CosmosDbEntity
|
||||
{
|
||||
public string TenantId {
|
||||
get;
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - used to deserialize Cosmos DB objects
|
||||
set;
|
||||
} // todo: sync: do plugins really need this too?
|
||||
public string Name {
|
||||
get;
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - used to deserialize Cosmos DB objects
|
||||
set;
|
||||
}
|
||||
public string ContactEmail {
|
||||
get;
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - used to deserialize Cosmos DB objects
|
||||
set;
|
||||
}
|
||||
|
||||
public string NotifyContactEmail
|
||||
{
|
||||
get;
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - used to deserialize Cosmos DB objects
|
||||
set;
|
||||
}
|
||||
public IEnumerable<CdnConfiguration> CdnConfigurations {
|
||||
get;
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global, MemberCanBePrivate.Global - used to deserialize Cosmos DB objects
|
||||
set;
|
||||
}
|
||||
|
||||
public Partner(string tenant, string name, string contactEmail, string notifyContactEmail,
|
||||
CdnConfiguration[] cdnConfigurations)
|
||||
{
|
||||
id = Guid.NewGuid().ToString();
|
||||
Name = name;
|
||||
TenantId = tenant;
|
||||
ContactEmail = contactEmail;
|
||||
NotifyContactEmail = notifyContactEmail;
|
||||
CdnConfigurations = cdnConfigurations;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UserRequest : CosmosDbEntity
|
||||
{
|
||||
public UserRequest(string partnerId, string description, string ticketId, string hostname, ISet<string> urls)
|
||||
{
|
||||
PartnerId = partnerId;
|
||||
Description = description;
|
||||
TicketId = ticketId;
|
||||
Hostname = hostname;
|
||||
Urls = urls;
|
||||
id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
public string PartnerId { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// TicketId is an id of an item in a task-tracking system
|
||||
/// </summary>
|
||||
public string TicketId { get; }
|
||||
|
||||
public string Hostname { get; }
|
||||
|
||||
public ISet<string> Urls { get; }
|
||||
|
||||
public int NumTotalPartnerRequests { get; set; }
|
||||
|
||||
public int NumCompletedPartnerRequests { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
|
||||
public abstract class CosmosDbEntityClient: IDisposable
|
||||
{
|
||||
private readonly string cosmosDbId;
|
||||
private readonly string containerId;
|
||||
private readonly string partitionKey = "/id";
|
||||
|
||||
private readonly CosmosClient cosmosClient;
|
||||
protected Container Container;
|
||||
|
||||
protected CosmosDbEntityClient(string connectionString, string databaseId, string containerId)
|
||||
{
|
||||
this.containerId = containerId;
|
||||
this.cosmosDbId = databaseId;
|
||||
CosmosClientOptions clientOptions = new CosmosClientOptions()
|
||||
{
|
||||
SerializerOptions = new CosmosSerializationOptions()
|
||||
{
|
||||
IgnoreNullValues = true
|
||||
}
|
||||
};
|
||||
|
||||
cosmosClient = new CosmosClient(connectionString, clientOptions);
|
||||
}
|
||||
|
||||
protected CosmosDbEntityClient(Container container)
|
||||
{
|
||||
Container = container;
|
||||
}
|
||||
|
||||
protected CosmosDbEntityClient(string connectionString, string databaseId, string containerId, string partitionKey) : this(connectionString, databaseId, containerId)
|
||||
{
|
||||
this.partitionKey = $"/{partitionKey}";
|
||||
}
|
||||
|
||||
protected async Task Create<T>(T item)
|
||||
{
|
||||
if (Container == null)
|
||||
{
|
||||
await CreateContainer();
|
||||
}
|
||||
|
||||
await Container.CreateItemAsync(item);
|
||||
}
|
||||
|
||||
protected async Task Upsert<T>(T item)
|
||||
{
|
||||
if (Container == null)
|
||||
{
|
||||
await CreateContainer();
|
||||
}
|
||||
|
||||
await Container.UpsertItemAsync(item);
|
||||
}
|
||||
|
||||
protected async Task<T> SelectFirstByIdAsync<T>(string id, string indexColumnName = "id")
|
||||
{
|
||||
if (Container == null)
|
||||
{
|
||||
await CreateContainer();
|
||||
}
|
||||
|
||||
using var queryIterator = Container.GetItemQueryIterator<T>(
|
||||
$"SELECT * FROM {containerId} c WHERE c.{indexColumnName} = '{id}'");
|
||||
if (queryIterator != null && queryIterator.HasMoreResults)
|
||||
{
|
||||
var response = await queryIterator.ReadNextAsync();
|
||||
if (response.Count > 0)
|
||||
{
|
||||
return response.First();
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
protected async Task CreateContainer()
|
||||
{
|
||||
var database = await cosmosClient.CreateDatabaseIfNotExistsAsync(cosmosDbId);
|
||||
Container = await database.Database.CreateContainerIfNotExistsAsync(containerId, partitionKey);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (cosmosClient != null)
|
||||
{
|
||||
cosmosClient.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface ICdnRequestTableManager<in T> : IDisposable where T : Enum
|
||||
{
|
||||
public Task CreateCdnRequest(ICdnRequest request, T cdn);
|
||||
|
||||
public Task UpdateCdnRequest(ICdnRequest request, T cdn);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IPartnerRequestTableManager<in T> : IDisposable where T : Enum
|
||||
{
|
||||
public Task CreatePartnerRequest(IPartnerRequest partnerRequest, T cdn);
|
||||
|
||||
public Task UpdatePartnerRequest(IPartnerRequest partnerRequest, T cdn);
|
||||
|
||||
public Task<IPartnerRequest> GetPartnerRequest(string id, T cdn);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CachePurgeLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IRequestTable<T>: IDisposable
|
||||
{
|
||||
public Task CreateItem(T request);
|
||||
|
||||
public Task UpsertItem(T request);
|
||||
|
||||
public Task<T> GetItem(string id);
|
||||
|
||||
public Task<IEnumerable<T>> GetItems();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CachePurgeLibrary\CachePurgeLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.8" />
|
||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnPlugins_Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnPlugins</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnLibrary_Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>MultiCdnApi</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,132 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
internal class AfdPlugin : ICdnPlugin<AfdPartnerRequest>
|
||||
{
|
||||
private static readonly string baseUri = Environment.GetEnvironmentVariable("Afd_BaseUri") ?? "https://www.afdcp.com/api/v2.0/Tenants/";
|
||||
private static readonly string BatchCreated = RequestStatus.BatchCreated.ToString();
|
||||
private readonly int maxNumUrl = EnvironmentConfig.AfdBatchSize;
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
public AfdPlugin(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public AfdPlugin(ILogger logger, int maxNumUrl)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.maxNumUrl = maxNumUrl;
|
||||
}
|
||||
|
||||
public bool ProcessPartnerRequest(AfdPartnerRequest partnerRequest, ICollector<ICdnRequest> queue)
|
||||
{
|
||||
if (TryGetEndpoint(partnerRequest, out var endpoint))
|
||||
{
|
||||
// Split messages into batches and add to queue
|
||||
var batchedRequests = SplitRequestIntoBatches(partnerRequest, maxNumUrl);
|
||||
|
||||
foreach (var req in batchedRequests)
|
||||
{
|
||||
req.Endpoint = endpoint;
|
||||
AddMessagesToSendQueue(req, queue);
|
||||
}
|
||||
|
||||
partnerRequest.Status = BatchCreated;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddMessagesToSendQueue(ICdnRequest cdnRequest, ICollector<ICdnRequest> msg)
|
||||
{
|
||||
string desc = $"CachePurge_{DateTime.UtcNow}";
|
||||
if (cdnRequest is AfdRequest afdRequest && !string.IsNullOrEmpty(afdRequest.Description))
|
||||
{
|
||||
desc = afdRequest.Description;
|
||||
}
|
||||
|
||||
if (cdnRequest.Urls.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var afdRequestBody = new AfdRequestBody()
|
||||
{
|
||||
Urls = cdnRequest.Urls,
|
||||
Description = desc
|
||||
};
|
||||
|
||||
cdnRequest.RequestBody = JsonSerializer.Serialize(afdRequestBody, CdnPluginHelper.JsonSerializerOptions);
|
||||
|
||||
msg.Add(cdnRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AfdPlugin: Exception serializing cdnRequest id={cdnRequest.id}, {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<ICdnRequest> SplitRequestIntoBatches(AfdPartnerRequest partnerRequest, int maxNumUrl)
|
||||
{
|
||||
var partnerRequests = new List<ICdnRequest>();
|
||||
|
||||
var urlLists = CdnPluginHelper.SplitUrlListIntoBatches(partnerRequest.Urls, maxNumUrl);
|
||||
partnerRequest.NumTotalCdnRequests = urlLists.Count;
|
||||
|
||||
foreach (var list in urlLists)
|
||||
{
|
||||
var req = new AfdRequest(partnerRequest.id, partnerRequest.TenantID, partnerRequest.PartnerID, partnerRequest.Description, list);
|
||||
partnerRequests.Add(req);
|
||||
}
|
||||
|
||||
return partnerRequests;
|
||||
}
|
||||
|
||||
public bool TryGetEndpoint(AfdPartnerRequest partnerRequest, out string endpoint)
|
||||
{
|
||||
endpoint = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(partnerRequest.TenantID) || string.IsNullOrEmpty(partnerRequest.PartnerID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
endpoint = baseUri + $"{partnerRequest.TenantID}/Partners/{partnerRequest.PartnerID}/CachePurges";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ValidPartnerRequest(string inputRequest, string resourceID, out AfdPartnerRequest partnerRequest)
|
||||
{
|
||||
partnerRequest = null;
|
||||
|
||||
try
|
||||
{
|
||||
partnerRequest = JsonSerializer.Deserialize<AfdPartnerRequest>(inputRequest, CdnPluginHelper.JsonSerializerOptions);
|
||||
|
||||
return CdnPluginHelper.IsValidRequest(partnerRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AfdPlugin: Exception reading resource ID={resourceID}, {e.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
internal class AkamaiPlugin : ICdnPlugin<AkamaiPartnerRequest>
|
||||
{
|
||||
private const string Production = "production";
|
||||
private const string Staging = "staging";
|
||||
|
||||
private static readonly string baseUri = Environment.GetEnvironmentVariable("Akamai_BaseUri") ?? "https://fakeUri";
|
||||
private static readonly string BatchCreated = RequestStatus.BatchCreated.ToString();
|
||||
|
||||
private readonly int maxNumUrl = EnvironmentConfig.AkamaiBatchSize;
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
// TODO: Implement for delete
|
||||
public AkamaiPlugin(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public AkamaiPlugin(ILogger logger, int maxNumUrl)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.maxNumUrl = maxNumUrl;
|
||||
}
|
||||
|
||||
public bool ProcessPartnerRequest(AkamaiPartnerRequest partnerRequest, ICollector<ICdnRequest> queue)
|
||||
{
|
||||
var endpoint = GetEndpoint(partnerRequest);
|
||||
|
||||
// Split messages into batches and add to queue
|
||||
var batchedRequests = SplitRequestIntoBatches(partnerRequest, maxNumUrl);
|
||||
|
||||
foreach (var req in batchedRequests)
|
||||
{
|
||||
req.Endpoint = endpoint;
|
||||
AddMessagesToSendQueue(req, queue);
|
||||
}
|
||||
|
||||
partnerRequest.Status = BatchCreated;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddMessagesToSendQueue(ICdnRequest cdnRequest, ICollector<ICdnRequest> msg)
|
||||
{
|
||||
if (cdnRequest.Urls.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var akamaiRequestBody = new AkamaiRequestBody()
|
||||
{
|
||||
Objects = cdnRequest.Urls,
|
||||
};
|
||||
|
||||
cdnRequest.RequestBody = JsonSerializer.Serialize(akamaiRequestBody, CdnPluginHelper.JsonSerializerOptions);
|
||||
|
||||
msg.Add(cdnRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AkamaiPlugin: Exception serializing cdnRequest id={cdnRequest.id}, {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<ICdnRequest> SplitRequestIntoBatches(AkamaiPartnerRequest partnerRequest, int maxNumUrl)
|
||||
{
|
||||
var partnerRequests = new List<ICdnRequest>();
|
||||
|
||||
var urlLists = CdnPluginHelper.SplitUrlListIntoBatches(partnerRequest.Urls, maxNumUrl);
|
||||
partnerRequest.NumTotalCdnRequests = urlLists.Count;
|
||||
|
||||
foreach (var list in urlLists)
|
||||
{
|
||||
var req = new AkamaiRequest(partnerRequest.id, list);
|
||||
partnerRequests.Add(req);
|
||||
}
|
||||
|
||||
return partnerRequests;
|
||||
}
|
||||
|
||||
public string GetEndpoint(AkamaiPartnerRequest partnerRequest)
|
||||
{
|
||||
// Use staging as default
|
||||
var endpoint = $"{baseUri}{Staging}";
|
||||
|
||||
if (!string.IsNullOrEmpty(partnerRequest.Network) && partnerRequest.Network.Equals(Production, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
endpoint = $"{baseUri}{Production}";
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public bool ValidPartnerRequest(string inputRequest, string resourceID, out AkamaiPartnerRequest partnerRequest)
|
||||
{
|
||||
partnerRequest = null;
|
||||
|
||||
try
|
||||
{
|
||||
partnerRequest = JsonSerializer.Deserialize<AkamaiPartnerRequest>(inputRequest, CdnPluginHelper.JsonSerializerOptions);
|
||||
|
||||
return CdnPluginHelper.IsValidRequest(partnerRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AkamaiPlugin: Exception reading resource ID={resourceID}, {e.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Storage.Queue;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class AfdQueueProcessor : ICdnQueueProcessor<AfdRequest>
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private static readonly double RequestWaitTime = EnvironmentConfig.RequestWaitTime;
|
||||
|
||||
public AfdQueueProcessor(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void AddMessageToQueue(CloudQueue outputQueue, CloudQueueMessage message, AfdRequest queueMsg)
|
||||
{
|
||||
// increase backoff based on # dequeues
|
||||
var nextVisibleTime = queueMsg.NumTimesProcessed > 0 ? TimeSpan.FromSeconds(queueMsg.NumTimesProcessed * RequestWaitTime) : TimeSpan.FromSeconds(RequestWaitTime);
|
||||
|
||||
outputQueue.AddMessage(message, null, nextVisibleTime);
|
||||
}
|
||||
|
||||
public CloudQueueMessage CreateCloudQueueMessage(AfdRequest request, IRequestInfo requestInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(request.Endpoint))
|
||||
{
|
||||
logger.LogWarning($"AfdQueueProcessor: Request with id={request.id} has empty endpoint");
|
||||
return null;
|
||||
}
|
||||
|
||||
request.Status = requestInfo.RequestStatus.ToString();
|
||||
|
||||
return new CloudQueueMessage(JsonSerializer.Serialize(request));
|
||||
}
|
||||
|
||||
public async Task<CloudQueueMessage> ProcessPurgeRequest(AfdRequest queueMsg, int maxRetry)
|
||||
{
|
||||
if (queueMsg.Urls == null || queueMsg.Urls.Length == 0)
|
||||
{
|
||||
logger.LogWarning($"AfdQueueProcessor: Dropping purge msg with no urls");
|
||||
return null;
|
||||
}
|
||||
|
||||
var requestInfo = new AfdRequestInfo() { RequestStatus = RequestStatus.MaxRetry };
|
||||
|
||||
try
|
||||
{
|
||||
if (queueMsg.NumTimesProcessed < maxRetry)
|
||||
{
|
||||
requestInfo = await AfdRequestProcessor.SendPurgeRequest(queueMsg.Endpoint, new StringContent(queueMsg.RequestBody, Encoding.UTF8, "application/json"), logger);
|
||||
|
||||
return CompletePurgeRequest(requestInfo, queueMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning($"AfdQueueProcessor: Dropping purge msg: queueMsg with num NumTimesProcessed = {queueMsg.NumTimesProcessed} is greater than maxRetry={maxRetry}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueMsg.Status = requestInfo.RequestStatus.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CloudQueueMessage CompletePurgeRequest(IRequestInfo requestInfo, AfdRequest queueMsg)
|
||||
{
|
||||
if (requestInfo.RequestStatus == RequestStatus.PurgeSubmitted && !string.IsNullOrEmpty(requestInfo.RequestID))
|
||||
{
|
||||
// If the request was submitted, add to queue as a poll request
|
||||
queueMsg.NumTimesProcessed = 0;
|
||||
queueMsg.CdnRequestId = requestInfo.RequestID;
|
||||
}
|
||||
else if (requestInfo.RequestStatus == RequestStatus.Throttled || requestInfo.RequestStatus == RequestStatus.Error)
|
||||
{
|
||||
requestInfo.RequestID = null;
|
||||
queueMsg.NumTimesProcessed++;
|
||||
}
|
||||
else if (requestInfo.RequestStatus == RequestStatus.Unauthorized || requestInfo.RequestStatus == RequestStatus.Unknown)
|
||||
{
|
||||
logger.LogError($"AfdQueueProcessor: Request Status {requestInfo.RequestStatus}, {queueMsg.id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return CreateCloudQueueMessage(queueMsg, requestInfo);
|
||||
}
|
||||
|
||||
public async Task<CloudQueueMessage> ProcessPollRequest(AfdRequest queueMsg, int maxRetry)
|
||||
{
|
||||
var requestInfo = new AfdRequestInfo() { RequestStatus = RequestStatus.MaxRetry };
|
||||
|
||||
try
|
||||
{
|
||||
if (queueMsg.NumTimesProcessed < maxRetry)
|
||||
{
|
||||
requestInfo.RequestStatus = await AfdRequestProcessor.SendPollRequest(queueMsg.Endpoint, queueMsg.CdnRequestId, logger);
|
||||
return CompletePollRequest(requestInfo, queueMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning($"AfdQueueProcessor: Dropping poll msg: queueMsg with num NumTimesProcessed = {queueMsg.NumTimesProcessed} is greater than maxRetry={maxRetry}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueMsg.Status = requestInfo.RequestStatus.ToString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public CloudQueueMessage CompletePollRequest(IRequestInfo requestInfo, AfdRequest queueMsg)
|
||||
{
|
||||
if (requestInfo.RequestStatus == RequestStatus.PurgeCompleted)
|
||||
{
|
||||
logger.LogInformation($"AfdQueueProcessor: Completed purge request RequestID={queueMsg.CdnRequestId}, NumTimesProcessed={queueMsg.NumTimesProcessed}, {queueMsg.RequestBody}");
|
||||
return null;
|
||||
}
|
||||
else if (requestInfo.RequestStatus == RequestStatus.Throttled || requestInfo.RequestStatus == RequestStatus.Error)
|
||||
{
|
||||
// Only increase if the request is throttled or has an error
|
||||
// Otherwise, we can keep polling at the current rate until the purge is done
|
||||
queueMsg.NumTimesProcessed++;
|
||||
}
|
||||
|
||||
//re-make polling message and re-add it to the queue
|
||||
return CreateCloudQueueMessage(queueMsg, requestInfo);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Storage.Queue;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
internal class AkamaiQueueProcessor : ICdnQueueProcessor<AkamaiRequest>
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private static readonly double RequestWaitTime = EnvironmentConfig.RequestWaitTime;
|
||||
|
||||
public AkamaiQueueProcessor(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public CloudQueueMessage CreateCloudQueueMessage(AkamaiRequest request, IRequestInfo requestInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(request.Endpoint))
|
||||
{
|
||||
logger.LogWarning($"AkamaiQueueProcessor: Request with id={request.id} has empty endpoint");
|
||||
return null;
|
||||
}
|
||||
|
||||
request.Status = requestInfo.RequestStatus.ToString();
|
||||
|
||||
return new CloudQueueMessage(JsonSerializer.Serialize(request));
|
||||
}
|
||||
|
||||
public void AddMessageToQueue(CloudQueue outputQueue, CloudQueueMessage message, AkamaiRequest queueMsg)
|
||||
{
|
||||
// increase backoff based on # dequeues
|
||||
var nextVisibleTime = queueMsg.NumTimesProcessed > 0 ? TimeSpan.FromSeconds(queueMsg.NumTimesProcessed * RequestWaitTime) : TimeSpan.FromSeconds(RequestWaitTime);
|
||||
|
||||
outputQueue.AddMessage(message, null, nextVisibleTime);
|
||||
}
|
||||
|
||||
public async Task<CloudQueueMessage> ProcessPurgeRequest(AkamaiRequest queueMsg, int maxRetry)
|
||||
{
|
||||
if (queueMsg.Urls == null || queueMsg.Urls.Length == 0)
|
||||
{
|
||||
logger.LogWarning($"AkamaiQueueProcessor: Dropping purge msg with no urls");
|
||||
return null;
|
||||
}
|
||||
|
||||
var requestInfo = new AkamaiRequestInfo() { RequestStatus = RequestStatus.MaxRetry };
|
||||
|
||||
try
|
||||
{
|
||||
if (queueMsg.NumTimesProcessed < maxRetry)
|
||||
{
|
||||
requestInfo = await AkamaiRequestProcessor.SendPurgeRequest(queueMsg.Endpoint, new StringContent(queueMsg.RequestBody, Encoding.UTF8, "application/json"), logger);
|
||||
|
||||
return CompletePurgeRequest(requestInfo, queueMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning($"AkamaiQueueProcessor: Dropping purge msg: queueMsg with num NumTimesProcessed = {queueMsg.NumTimesProcessed} is greater than maxRetry={maxRetry}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueMsg.Status = requestInfo.RequestStatus.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CloudQueueMessage CompletePurgeRequest(IRequestInfo requestInfo, AkamaiRequest queueMsg)
|
||||
{
|
||||
if (requestInfo.RequestStatus == RequestStatus.PurgeCompleted && !string.IsNullOrEmpty(requestInfo.RequestID))
|
||||
{
|
||||
// Don't need to poll Akamai to verify purge completion
|
||||
queueMsg.CdnRequestId = requestInfo.RequestID;
|
||||
|
||||
if (requestInfo is AkamaiRequestInfo akamaiRequestInfo)
|
||||
{
|
||||
queueMsg.SupportId = akamaiRequestInfo.SupportID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else if (requestInfo.RequestStatus == RequestStatus.Throttled || requestInfo.RequestStatus == RequestStatus.Error)
|
||||
{
|
||||
requestInfo.RequestID = null;
|
||||
queueMsg.NumTimesProcessed++;
|
||||
}
|
||||
else if (requestInfo.RequestStatus == RequestStatus.Unauthorized || requestInfo.RequestStatus == RequestStatus.Unknown)
|
||||
{
|
||||
logger.LogError($"AkamaiQueueProcessor: Request Status {requestInfo.RequestStatus}, {queueMsg.id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return CreateCloudQueueMessage(queueMsg, requestInfo);
|
||||
}
|
||||
|
||||
public CloudQueueMessage CompletePollRequest(IRequestInfo requestInfo, AkamaiRequest queueMsg) => throw new NotImplementedException();
|
||||
|
||||
public Task<CloudQueueMessage> ProcessPollRequest(AkamaiRequest queueMsg, int maxRetry) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
|
||||
public class AfdPartnerRequest : PartnerRequest
|
||||
{
|
||||
public string TenantID { get; set; }
|
||||
|
||||
public string PartnerID { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using System;
|
||||
|
||||
public class AfdRequest : CosmosDbEntity, ICdnRequest
|
||||
{
|
||||
public AfdRequest() { }
|
||||
|
||||
public AfdRequest(string partnerRequestID, string tenantId, string partnerId, string description, string[] urls)
|
||||
{
|
||||
this.id = Guid.NewGuid().ToString();
|
||||
PartnerRequestID = partnerRequestID;
|
||||
TenantID = tenantId;
|
||||
PartnerID = partnerId;
|
||||
Description = description;
|
||||
Urls = urls;
|
||||
}
|
||||
|
||||
public string CdnRequestId { get; set; }
|
||||
|
||||
public string PartnerRequestID { get; set; }
|
||||
|
||||
public string TenantID { get; set; }
|
||||
|
||||
public string PartnerID { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string[] Urls { get; set; }
|
||||
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
public int NumTimesProcessed { get; set; }
|
||||
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
public string RequestBody { get; set; }
|
||||
}
|
||||
|
||||
internal class AfdRequestBody
|
||||
{
|
||||
public string Description { get; set; }
|
||||
|
||||
public string[] Urls { get; set; }
|
||||
}
|
||||
|
||||
public class AfdRequestInfo : IRequestInfo
|
||||
{
|
||||
public string RequestID { get; set; }
|
||||
public RequestStatus RequestStatus { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
|
||||
public class AkamaiPartnerRequest : PartnerRequest
|
||||
{
|
||||
public string Network { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class AkamaiRequest : CosmosDbEntity, ICdnRequest
|
||||
{
|
||||
public AkamaiRequest() { }
|
||||
|
||||
public AkamaiRequest(string partnerRequestID, string[] urls)
|
||||
{
|
||||
id = Guid.NewGuid().ToString();
|
||||
PartnerRequestID = partnerRequestID;
|
||||
Urls = urls;
|
||||
}
|
||||
|
||||
public string PartnerRequestID { get; set; }
|
||||
|
||||
public int NumTimesProcessed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PurgeId returned by Akamai
|
||||
/// </summary>
|
||||
public string CdnRequestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Identifier to provide Akamai Technical Support if issues arise.
|
||||
/// </summary>
|
||||
public string SupportId { get; set; }
|
||||
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
public string RequestBody { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public string[] Urls { get; set; }
|
||||
}
|
||||
|
||||
internal class AkamaiRequestBody
|
||||
{
|
||||
[JsonPropertyName("objects")]
|
||||
public string[] Objects { get; set; }
|
||||
|
||||
[JsonPropertyName("hostname")]
|
||||
public string Hostname { get; set; }
|
||||
}
|
||||
|
||||
public class AkamaiRequestInfo : IRequestInfo
|
||||
{
|
||||
public string RequestID { get; set; }
|
||||
|
||||
public RequestStatus RequestStatus { get; set; }
|
||||
|
||||
public string SupportID { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal static class AfdRequestProcessor
|
||||
{
|
||||
private const string RolledOut = "RolledOut";
|
||||
|
||||
private static readonly string LoginUrl = Environment.GetEnvironmentVariable("Afd_LoginUrl") ?? "https://fakeUrl";
|
||||
private static readonly string Resource = Environment.GetEnvironmentVariable("Afd_Resource") ?? "https://fakeResource";
|
||||
private static readonly string AppId = Environment.GetEnvironmentVariable("Afd_AppId") ?? "fakeAppId";
|
||||
private static readonly string AppKey = Environment.GetEnvironmentVariable("Afd_Secret") ?? "fakeSecret";
|
||||
|
||||
private static readonly ReadOnlyMemory<char> Id = "Id".AsMemory();
|
||||
private static readonly ReadOnlyMemory<char> Status = "Status".AsMemory();
|
||||
|
||||
internal static async Task<AfdRequestInfo> SendPurgeRequest(string afdEndpoint, StringContent purgeRequest, ILogger logger)
|
||||
{
|
||||
var azureAuthHandler = new AzureAuthHandler(new HttpClientHandler(), Resource, LoginUrl, AppId, AppKey);
|
||||
|
||||
using var httpHandler = new HttpHandler(azureAuthHandler);
|
||||
|
||||
return await SendPurgeRequest(afdEndpoint, purgeRequest, httpHandler, logger);
|
||||
}
|
||||
|
||||
internal static async Task<RequestStatus> SendPollRequest(string afdEndpoint, string requestId, ILogger logger)
|
||||
{
|
||||
var azureAuthHandler = new AzureAuthHandler(new HttpClientHandler(), Resource, LoginUrl, AppId, AppKey);
|
||||
|
||||
using var httpHandler = new HttpHandler(azureAuthHandler);
|
||||
|
||||
return await SendPollRequest(afdEndpoint, requestId, httpHandler, logger);
|
||||
}
|
||||
|
||||
internal static async Task<RequestStatus> SendPollRequest(string afdEndpoint, string requestId, IHttpHandler httpHandler, ILogger logger)
|
||||
{
|
||||
var requestStatus = RequestStatus.Error;
|
||||
|
||||
if (!string.IsNullOrEmpty(afdEndpoint) && !string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await httpHandler.GetAsync($"{afdEndpoint}/{requestId}");
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
requestStatus = GetRequestStatusFromContent(responseContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestStatus = CdnPluginHelper.GetRequestStatusFromResponseCode(response.StatusCode);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AfdRequestProcessor: Error while sending poll request: {e.Message}\n {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
return requestStatus;
|
||||
}
|
||||
|
||||
internal static async Task<AfdRequestInfo> SendPurgeRequest(string requestEndPoint, StringContent purgeRequest, IHttpHandler httpHandler, ILogger logger)
|
||||
{
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestStatus = RequestStatus.Error
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(requestEndPoint) && purgeRequest != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await httpHandler.PostAsync(requestEndPoint, purgeRequest);
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(responseContent) &&
|
||||
GetRequestID(responseContent, out var requestID))
|
||||
{
|
||||
requestInfo.RequestID = requestID;
|
||||
requestInfo.RequestStatus = RequestStatus.PurgeSubmitted;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation($"AfdRequestProcessor: Purge Request not successful: {response.StatusCode}, {responseContent}");
|
||||
requestInfo.RequestStatus = CdnPluginHelper.GetRequestStatusFromResponseCode(response.StatusCode);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AfdRequestProcessor: Error while sending purge request: {e.Message}\n {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
return requestInfo;
|
||||
}
|
||||
|
||||
internal static bool GetRequestID(string response, out string requestID)
|
||||
{
|
||||
requestID = null;
|
||||
|
||||
using JsonDocument document = JsonDocument.Parse(response);
|
||||
JsonElement root = document.RootElement;
|
||||
|
||||
if (root.TryGetProperty(Id.Span, out var idElement) && idElement.ValueKind == JsonValueKind.Number &&
|
||||
idElement.TryGetInt64(out var ID))
|
||||
{
|
||||
requestID = ID.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static RequestStatus GetRequestStatusFromContent(string result)
|
||||
{
|
||||
if (GetStringProperty(result, Status, out var status))
|
||||
{
|
||||
if (RolledOut.Equals(status, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RequestStatus.PurgeCompleted;
|
||||
}
|
||||
|
||||
return RequestStatus.PurgeSubmitted;
|
||||
}
|
||||
|
||||
return RequestStatus.Unknown;
|
||||
}
|
||||
|
||||
private static bool GetStringProperty(string response, ReadOnlyMemory<char> propertyName, out string propertyValue)
|
||||
{
|
||||
propertyValue = null;
|
||||
|
||||
using JsonDocument document = JsonDocument.Parse(response);
|
||||
JsonElement root = document.RootElement;
|
||||
|
||||
if (root.TryGetProperty(propertyName.Span, out var propVal) && propVal.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
propertyValue = propVal.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class AkamaiAuthHandler : DelegatingHandler
|
||||
{
|
||||
private const string HashType = "SHA256";
|
||||
|
||||
private readonly string clientToken;
|
||||
private readonly string accessToken;
|
||||
private readonly string clientSecret;
|
||||
private readonly KeyedHash hash;
|
||||
|
||||
internal List<string> HeadersToInclude { get; private set; }
|
||||
|
||||
public AkamaiAuthHandler(
|
||||
HttpMessageHandler innerHandler,
|
||||
string clientToken,
|
||||
string accessToken,
|
||||
string clientSecret)
|
||||
: base(innerHandler)
|
||||
{
|
||||
this.clientToken = clientToken ?? throw new ArgumentNullException(nameof(clientToken));
|
||||
this.accessToken = accessToken ?? throw new ArgumentNullException(nameof(accessToken));
|
||||
this.clientSecret = clientSecret ?? throw new ArgumentNullException(nameof(clientSecret));
|
||||
|
||||
this.hash = KeyedHash.HMACSHA256;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var authHeader = await CreateAuthorizationHeader(request);
|
||||
|
||||
request.Headers.Add(HeaderNames.Authorization, authHeader);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<string> CreateAuthorizationHeader(HttpRequestMessage request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HH:mm:ss+0000");
|
||||
var requestData = await GetRequestData(request);
|
||||
|
||||
return GetAuthorizationHeader(timestamp, requestData);
|
||||
}
|
||||
|
||||
internal string GetAuthorizationData(string timestamp)
|
||||
{
|
||||
var nonce = Guid.NewGuid();
|
||||
return
|
||||
$"{hash.Name} client_token={clientToken};" +
|
||||
$"access_token={accessToken};" +
|
||||
$"timestamp={timestamp};" +
|
||||
$"nonce={nonce.ToString().ToLowerInvariant()};";
|
||||
}
|
||||
|
||||
internal async Task<string> GetRequestData(HttpRequestMessage request)
|
||||
{
|
||||
var bodyStream = request.Content != null ? await request.Content.ReadAsByteArrayAsync() : null;
|
||||
|
||||
var requestHash = CdnPluginHelper.ComputeHash(bodyStream.AsSpan(), HashType);
|
||||
|
||||
return $"POST\t{request.RequestUri.Scheme}\t" +
|
||||
$"{request.RequestUri.Host}\t{request.RequestUri.PathAndQuery}\t{string.Empty}\t{requestHash}\t";
|
||||
}
|
||||
|
||||
internal string GetAuthorizationHeader(string timestamp, string requestData)
|
||||
{
|
||||
var authData = GetAuthorizationData(timestamp);
|
||||
|
||||
var signingKey = CdnPluginHelper.ComputeKeyedHash(timestamp, clientSecret, hash.Algorithm);
|
||||
var authSignature = CdnPluginHelper.ComputeKeyedHash(requestData + authData, signingKey, hash.Algorithm);
|
||||
|
||||
return $"{authData}signature={authSignature}";
|
||||
}
|
||||
|
||||
internal struct KeyedHash
|
||||
{
|
||||
public static readonly KeyedHash HMACSHA256 = new KeyedHash()
|
||||
{
|
||||
Name = "EG1-HMAC-SHA256",
|
||||
Algorithm = "HMACSHA256"
|
||||
};
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Algorithm { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal static class AkamaiRequestProcessor
|
||||
{
|
||||
private static readonly string ClientSecret = Environment.GetEnvironmentVariable("Akamai_ClientSecret") ?? "fakesecret";
|
||||
private static readonly string AccessToken = Environment.GetEnvironmentVariable("Akamai_AccessToken") ?? "faketoken";
|
||||
private static readonly string ClientToken = Environment.GetEnvironmentVariable("Akamai_ClientToken") ?? "faketoken";
|
||||
|
||||
private static readonly ReadOnlyMemory<char> PurgeId = "purgeId".AsMemory();
|
||||
private static readonly ReadOnlyMemory<char> SupportId = "supportId".AsMemory();
|
||||
|
||||
internal static async Task<AkamaiRequestInfo> SendPurgeRequest(string endpoint, StringContent purgeRequest, ILogger logger)
|
||||
{
|
||||
var akamaiAuthHandler = new AkamaiAuthHandler(new HttpClientHandler(), ClientToken, AccessToken, ClientSecret);
|
||||
|
||||
using var httpHandler = new HttpHandler(akamaiAuthHandler);
|
||||
|
||||
return await SendPurgeRequest(endpoint, purgeRequest, httpHandler, logger);
|
||||
}
|
||||
|
||||
internal static async Task<AkamaiRequestInfo> SendPurgeRequest(string requestEndPoint, StringContent purgeRequest, IHttpHandler httpHandler, ILogger logger)
|
||||
{
|
||||
var requestInfo = new AkamaiRequestInfo()
|
||||
{
|
||||
RequestStatus = RequestStatus.Error
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(requestEndPoint) && purgeRequest != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await httpHandler.PostAsync(requestEndPoint, purgeRequest);
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(responseContent) &&
|
||||
GetPropertyValue(PurgeId, responseContent, out var requestID) &&
|
||||
GetPropertyValue(SupportId, responseContent, out var supportID))
|
||||
{
|
||||
requestInfo.RequestID = requestID;
|
||||
requestInfo.RequestStatus = RequestStatus.PurgeCompleted;
|
||||
requestInfo.SupportID = supportID;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation($"AkamaiRequestProcessor: Purge Request not successful: {response.StatusCode}, {responseContent}");
|
||||
requestInfo.RequestStatus = CdnPluginHelper.GetRequestStatusFromResponseCode(response.StatusCode);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError($"AkamaiRequestProcessor: Error while sending purge request: {e.Message}\n {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
return requestInfo;
|
||||
}
|
||||
|
||||
internal static bool GetPropertyValue(ReadOnlyMemory<char> propName, string response, out string propValue)
|
||||
{
|
||||
propValue = null;
|
||||
|
||||
using JsonDocument document = JsonDocument.Parse(response);
|
||||
JsonElement root = document.RootElement;
|
||||
|
||||
if (root.TryGetProperty(propName.Span, out var idElement) && idElement.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
propValue = idElement.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Message handler that gets an auth token using client credentials (AppId) based auth from Azure
|
||||
/// active directory and adds the appropriate header for Authorization to the request
|
||||
/// </summary>
|
||||
public class AzureAuthHandler : DelegatingHandler
|
||||
{
|
||||
private readonly string loginUrl;
|
||||
private readonly string resource;
|
||||
private readonly string appId;
|
||||
private readonly string appKey; //from KeyVault
|
||||
|
||||
public AzureAuthHandler(
|
||||
HttpMessageHandler innerContent,
|
||||
string resource,
|
||||
string loginUrl,
|
||||
string appId,
|
||||
string appKey)
|
||||
: base(innerContent)
|
||||
{
|
||||
this.resource = resource ?? throw new ArgumentNullException(nameof(resource));
|
||||
this.loginUrl = loginUrl ?? throw new ArgumentNullException(nameof(loginUrl));
|
||||
this.appId = appId ?? throw new ArgumentNullException(nameof(appId));
|
||||
this.appKey = appKey ?? throw new ArgumentNullException(nameof(appKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This message handler gets a token from AAD and adds it as an auth header to original request
|
||||
/// and submits original request
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var authContext = new AuthenticationContext(this.loginUrl);
|
||||
ClientCredential clientCred = new ClientCredential(this.appId, this.appKey);
|
||||
var authResult = await authContext.AcquireTokenAsync(this.resource, clientCred);
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(
|
||||
authResult.AccessTokenType,
|
||||
authResult.AccessToken);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class PartnerRequest: CosmosDbEntity, IPartnerRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the UserRequest this PartnerRequest was created from
|
||||
/// </summary>
|
||||
public string UserRequestID { get; set; }
|
||||
|
||||
public string CDN { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public int NumTotalCdnRequests { get; set; }
|
||||
|
||||
public int NumCompletedCdnRequests { get; set; }
|
||||
|
||||
public ISet<string> Urls { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class AfdPartnerRequestTable : CosmosDbEntityClient, IRequestTable<AfdPartnerRequest>
|
||||
{
|
||||
public AfdPartnerRequestTable() : base(EnvironmentConfig.CosmosDBConnectionString, EnvironmentConfig.CosmosDatabaseId,
|
||||
EnvironmentConfig.AfdPartnerCollectionName, EnvironmentConfig.PartnerRequestTablePartitionKey) { }
|
||||
|
||||
public AfdPartnerRequestTable(Container container) : base(container) { }
|
||||
|
||||
public async Task CreateItem(AfdPartnerRequest partnerRequest)
|
||||
{
|
||||
await Create(partnerRequest);
|
||||
}
|
||||
|
||||
public async Task<AfdPartnerRequest> GetItem(string id)
|
||||
{
|
||||
return await SelectFirstByIdAsync<AfdPartnerRequest>(id);
|
||||
}
|
||||
|
||||
public async Task UpsertItem(AfdPartnerRequest partnerRequest)
|
||||
{
|
||||
await Upsert(partnerRequest);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<AfdPartnerRequest>> GetItems() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class AfdRequestTable : CosmosDbEntityClient, IRequestTable<AfdRequest>
|
||||
{
|
||||
public AfdRequestTable() : base(EnvironmentConfig.CosmosDBConnectionString, EnvironmentConfig.CosmosDatabaseId,
|
||||
EnvironmentConfig.AfdCdnCollectionName, EnvironmentConfig.CdnRequestTablePartitionKey) { }
|
||||
|
||||
public AfdRequestTable(Container container) : base(container) { }
|
||||
|
||||
public async Task CreateItem(AfdRequest cdnRequest)
|
||||
{
|
||||
await Create(cdnRequest);
|
||||
}
|
||||
|
||||
public Task<AfdRequest> GetItem(string id) => throw new NotImplementedException();
|
||||
|
||||
public async Task UpsertItem(AfdRequest cdnRequest)
|
||||
{
|
||||
await base.Upsert(cdnRequest);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<AfdRequest>> GetItems() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class AkamaiPartnerRequestTable : CosmosDbEntityClient, IRequestTable<AkamaiPartnerRequest>
|
||||
{
|
||||
public AkamaiPartnerRequestTable() : base(EnvironmentConfig.CosmosDBConnectionString, EnvironmentConfig.CosmosDatabaseId,
|
||||
EnvironmentConfig.AkamaiPartnerCollectionName, EnvironmentConfig.PartnerRequestTablePartitionKey) { }
|
||||
|
||||
public AkamaiPartnerRequestTable(Container container) : base(container) { }
|
||||
|
||||
public async Task CreateItem(AkamaiPartnerRequest partnerRequest)
|
||||
{
|
||||
await Create(partnerRequest);
|
||||
}
|
||||
|
||||
public async Task<AkamaiPartnerRequest> GetItem(string id)
|
||||
{
|
||||
return await SelectFirstByIdAsync<AkamaiPartnerRequest>(id);
|
||||
}
|
||||
|
||||
public async Task UpsertItem(AkamaiPartnerRequest partnerRequest)
|
||||
{
|
||||
await Upsert(partnerRequest);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<AkamaiPartnerRequest>> GetItems() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class AkamaiRequestTable : CosmosDbEntityClient, IRequestTable<AkamaiRequest>
|
||||
{
|
||||
public AkamaiRequestTable() : base(EnvironmentConfig.CosmosDBConnectionString, EnvironmentConfig.CosmosDatabaseId,
|
||||
EnvironmentConfig.AkamaiCdnCollectionName, EnvironmentConfig.CdnRequestTablePartitionKey) { }
|
||||
|
||||
public AkamaiRequestTable(Container container) : base(container) { }
|
||||
|
||||
public async Task CreateItem(AkamaiRequest cdnRequest)
|
||||
{
|
||||
await Create(cdnRequest);
|
||||
}
|
||||
|
||||
public Task<AkamaiRequest> GetItem(string id) => throw new NotImplementedException();
|
||||
|
||||
|
||||
public async Task UpsertItem(AkamaiRequest cdnRequest)
|
||||
{
|
||||
await Upsert(cdnRequest);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<AkamaiRequest>> GetItems() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class CdnRequestTableManager : ICdnRequestTableManager<CDN>
|
||||
{
|
||||
private readonly IRequestTable<AfdRequest> afdRequestTable;
|
||||
private readonly IRequestTable<AkamaiRequest> akamaiRequestTable;
|
||||
|
||||
|
||||
public CdnRequestTableManager()
|
||||
{
|
||||
afdRequestTable = new AfdRequestTable();
|
||||
akamaiRequestTable = new AkamaiRequestTable();
|
||||
}
|
||||
|
||||
public CdnRequestTableManager(Container afdRequestContainer, Container akamaiRequestContainer)
|
||||
{
|
||||
afdRequestTable = new AfdRequestTable(afdRequestContainer);
|
||||
akamaiRequestTable = new AkamaiRequestTable(akamaiRequestContainer);
|
||||
}
|
||||
|
||||
public async Task CreateCdnRequest(ICdnRequest request, CDN cdn)
|
||||
{
|
||||
if (cdn == CDN.AFD)
|
||||
{
|
||||
await afdRequestTable.CreateItem(request as AfdRequest);
|
||||
}
|
||||
else if (cdn == CDN.Akamai)
|
||||
{
|
||||
await akamaiRequestTable.CreateItem(request as AkamaiRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateCdnRequest(ICdnRequest cdnRequest, CDN cdn)
|
||||
{
|
||||
if (cdn == CDN.AFD)
|
||||
{
|
||||
await afdRequestTable.UpsertItem(cdnRequest as AfdRequest);
|
||||
}
|
||||
else if (cdn == CDN.Akamai)
|
||||
{
|
||||
await akamaiRequestTable.UpsertItem(cdnRequest as AkamaiRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
afdRequestTable.Dispose();
|
||||
akamaiRequestTable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class PartnerRequestTableManager : IPartnerRequestTableManager<CDN>
|
||||
{
|
||||
private readonly IRequestTable<AfdPartnerRequest> afdPartnerRequestTable;
|
||||
private readonly IRequestTable<AkamaiPartnerRequest> akamaiPartnerRequestTable;
|
||||
|
||||
|
||||
public PartnerRequestTableManager()
|
||||
{
|
||||
this.afdPartnerRequestTable = new AfdPartnerRequestTable();
|
||||
this.akamaiPartnerRequestTable = new AkamaiPartnerRequestTable();
|
||||
}
|
||||
|
||||
public PartnerRequestTableManager(Container afdPartnerRequestContainer, Container akamaiPartnerRequestContainer)
|
||||
{
|
||||
this.afdPartnerRequestTable = new AfdPartnerRequestTable(afdPartnerRequestContainer);
|
||||
this.akamaiPartnerRequestTable = new AkamaiPartnerRequestTable(akamaiPartnerRequestContainer);
|
||||
}
|
||||
|
||||
public async Task CreatePartnerRequest(IPartnerRequest partnerRequest, CDN cdn)
|
||||
{
|
||||
if (cdn == CDN.AFD)
|
||||
{
|
||||
await afdPartnerRequestTable.CreateItem(partnerRequest as AfdPartnerRequest);
|
||||
}
|
||||
else if (cdn == CDN.Akamai)
|
||||
{
|
||||
await akamaiPartnerRequestTable.CreateItem(partnerRequest as AkamaiPartnerRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdatePartnerRequest(IPartnerRequest partnerRequest, CDN cdn)
|
||||
{
|
||||
if (cdn == CDN.AFD)
|
||||
{
|
||||
await afdPartnerRequestTable.UpsertItem(partnerRequest as AfdPartnerRequest);
|
||||
}
|
||||
else if (cdn == CDN.Akamai)
|
||||
{
|
||||
await akamaiPartnerRequestTable.UpsertItem(partnerRequest as AkamaiPartnerRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IPartnerRequest> GetPartnerRequest(string id, CDN cdn)
|
||||
{
|
||||
if (cdn == CDN.AFD)
|
||||
{
|
||||
return await afdPartnerRequestTable.GetItem(id);
|
||||
}
|
||||
else if (cdn == CDN.Akamai)
|
||||
{
|
||||
return await akamaiPartnerRequestTable.GetItem(id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
afdPartnerRequestTable.Dispose();
|
||||
akamaiPartnerRequestTable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class UserRequestTable : CosmosDbEntityClient, IRequestTable<UserRequest>
|
||||
{
|
||||
public UserRequestTable() : base(EnvironmentConfig.CosmosDBConnectionString, EnvironmentConfig.CosmosDatabaseId,
|
||||
EnvironmentConfig.UserRequestCosmosContainerId, EnvironmentConfig.UserRequestTablePartitionKey) {}
|
||||
|
||||
public UserRequestTable(Container container) : base(container) { }
|
||||
|
||||
public async Task CreateItem(UserRequest userRequest)
|
||||
{
|
||||
await Create(userRequest);
|
||||
}
|
||||
|
||||
public async Task UpsertItem(UserRequest userRequest)
|
||||
{
|
||||
await Upsert(userRequest);
|
||||
}
|
||||
|
||||
public async Task<UserRequest> GetItem(string id)
|
||||
{
|
||||
return await SelectFirstByIdAsync<UserRequest>(id);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<UserRequest>> GetItems() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
NOTICES AND INFORMATION
|
||||
Do Not Translate or Localize
|
||||
|
||||
This software incorporates material from third parties.
|
||||
Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com,
|
||||
or you may send a check or money order for US $5.00, including the product name,
|
||||
the open source component name, platform, and version number, to:
|
||||
|
||||
Source Code Compliance Team
|
||||
Microsoft Corporation
|
||||
One Microsoft Way
|
||||
Redmond, WA 98052
|
||||
USA
|
||||
|
||||
Notwithstanding any other terms, you may reverse engineer this software to the extent
|
||||
required to debug changes to any libraries licensed under the GNU Lesser General Public License.
|
||||
|
||||
---------------------------------------------------------
|
||||
|
||||
Antlr4 4.8.0 - BSD-3-Clause
|
||||
|
||||
[The "BSD 3-clause license"]
|
||||
Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
=====
|
||||
|
||||
MIT License for codepointat.js from https://git.io/codepointat
|
||||
MIT License for fromcodepoint.js from https://git.io/vDW1m
|
||||
|
||||
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------
|
|
@ -0,0 +1,129 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
internal static class CdnPluginHelper
|
||||
{
|
||||
internal static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, IgnoreNullValues = true };
|
||||
|
||||
internal static List<string[]> SplitUrlListIntoBatches(ISet<string> urlListToPurge, int batchSize)
|
||||
{
|
||||
var listOfBatches = new List<string[]>();
|
||||
|
||||
batchSize = (urlListToPurge.Count <= batchSize) ? urlListToPurge.Count : batchSize;
|
||||
|
||||
if (urlListToPurge.Count == 0)
|
||||
{
|
||||
return listOfBatches;
|
||||
}
|
||||
|
||||
var batch = new string[batchSize];
|
||||
|
||||
int i = 0, j = 0;
|
||||
|
||||
foreach (var item in urlListToPurge)
|
||||
{
|
||||
if (j == batchSize)
|
||||
{
|
||||
listOfBatches.Add(batch);
|
||||
|
||||
var remainingLength = urlListToPurge.Count - i;
|
||||
var nextBatchSize = remainingLength < batchSize ? remainingLength : batchSize;
|
||||
batch = new string[nextBatchSize];
|
||||
j = 0;
|
||||
}
|
||||
|
||||
batch[j] = item;
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
|
||||
listOfBatches.Add(batch);
|
||||
|
||||
return listOfBatches;
|
||||
}
|
||||
|
||||
internal static string ComputeHash(ReadOnlySpan<byte> input, string hashType)
|
||||
{
|
||||
return TryComputeHash(input, hashType, out var hash) ? Convert.ToBase64String(hash) : string.Empty;
|
||||
}
|
||||
|
||||
internal static bool IsValidRequest(IPartnerRequest partnerRequest)
|
||||
{
|
||||
if (partnerRequest != null && partnerRequest.Urls != null && string.IsNullOrEmpty(partnerRequest.Status) && !string.IsNullOrEmpty(partnerRequest.id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryComputeHash(ReadOnlySpan<byte> input, string hashType, out ReadOnlySpan<byte> output)
|
||||
{
|
||||
output = null;
|
||||
|
||||
if (input.IsEmpty || string.IsNullOrEmpty(hashType)) { return false; }
|
||||
|
||||
using var algorithm = HashAlgorithm.Create(hashType);
|
||||
|
||||
var dest = new byte[algorithm.HashSize].AsSpan();
|
||||
|
||||
if (algorithm.TryComputeHash(input, dest, out int writtenBytes))
|
||||
{
|
||||
output = dest.Slice(0, writtenBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static string ComputeKeyedHash(string data, string key, string hashType)
|
||||
{
|
||||
return TryComputeKeyedHash(Encoding.UTF8.GetBytes(data), key, hashType, out var keyedHash) ? Convert.ToBase64String(keyedHash) : string.Empty;
|
||||
}
|
||||
|
||||
internal static bool TryComputeKeyedHash(ReadOnlySpan<byte> input, string key, string hashType, out ReadOnlySpan<byte> keyedHash)
|
||||
{
|
||||
keyedHash = null;
|
||||
|
||||
if (input.IsEmpty || string.IsNullOrEmpty(hashType) || string.IsNullOrEmpty(key)) { return false; }
|
||||
|
||||
using var algorithm = HMAC.Create(hashType);
|
||||
algorithm.Key = Encoding.UTF8.GetBytes(key);
|
||||
|
||||
keyedHash = algorithm.ComputeHash(input.ToArray());
|
||||
|
||||
var dest = new byte[algorithm.HashSize].AsSpan();
|
||||
|
||||
if (algorithm.TryComputeHash(input, dest, out int writtenBytes))
|
||||
{
|
||||
keyedHash = dest.Slice(0, writtenBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static RequestStatus GetRequestStatusFromResponseCode(HttpStatusCode result)
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
HttpStatusCode.InternalServerError => RequestStatus.Error,
|
||||
HttpStatusCode.TooManyRequests => RequestStatus.Throttled,
|
||||
HttpStatusCode.Unauthorized => RequestStatus.Unauthorized,
|
||||
_ => RequestStatus.Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using System;
|
||||
|
||||
internal static class CdnQueueHelper
|
||||
{
|
||||
public readonly static string Throttled = RequestStatus.Throttled.ToString();
|
||||
public readonly static string Error = RequestStatus.Error.ToString();
|
||||
|
||||
public static bool AddCdnRequestToDB(ICdnRequest queueMsg, int maxRetry)
|
||||
{
|
||||
// If the request is throttled or has a generic error, it's retried until max retry
|
||||
// Only update the DB once its exhausted retries to set the final state correctly while minimizing updates
|
||||
if ((queueMsg.Status.Equals(Throttled, StringComparison.OrdinalIgnoreCase) || queueMsg.Status.Equals(Error, StringComparison.OrdinalIgnoreCase)) && queueMsg.NumTimesProcessed < maxRetry) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using System;
|
||||
|
||||
internal static class CdnRequestHelper
|
||||
{
|
||||
public static IPartnerRequest CreatePartnerRequest(CDN cdn, Partner partner, UserRequest userRequest, string description, string ticketId)
|
||||
{
|
||||
IPartnerRequest partnerRequest;
|
||||
|
||||
if (cdn == CDN.Akamai)
|
||||
{
|
||||
partnerRequest = new AkamaiPartnerRequest();
|
||||
}
|
||||
else if (cdn == CDN.AFD)
|
||||
{
|
||||
partnerRequest = new AfdPartnerRequest
|
||||
{
|
||||
PartnerID = partner.Name,
|
||||
TenantID = partner.TenantId,
|
||||
Description = description + (string.IsNullOrEmpty(ticketId) ? "" : $" ({ticketId})")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"We encountered an unknown CDN: {cdn}");
|
||||
}
|
||||
partnerRequest.id = Guid.NewGuid().ToString();
|
||||
partnerRequest.CDN = cdn.ToString();
|
||||
partnerRequest.Urls = userRequest.Urls;
|
||||
partnerRequest.UserRequestID = userRequest.id.ToString();
|
||||
|
||||
return partnerRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CDN
|
||||
{
|
||||
AFD = 1,
|
||||
Akamai = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// See tests for sample values
|
||||
/// </summary>
|
||||
public static class EnvironmentConfig
|
||||
{
|
||||
internal const string DatabaseName = "CacheOut";
|
||||
internal const string CosmosDBConnectionStringName = "CosmosDBConnection";
|
||||
internal const string BatchQueueConnectionStringName = "CDN_Queue";
|
||||
|
||||
internal const string AfdPartnerCollectionName = "AFD_PartnerRequest";
|
||||
internal const string AfdCdnCollectionName = "AFD_CdnRequest";
|
||||
internal const string AfdBatchQueueName = "AFDBatchQueue";
|
||||
|
||||
internal const string AkamaiPartnerCollectionName = "Akamai_PartnerRequest";
|
||||
internal const string AkamaiCdnCollectionName = "Akamai_CdnRequest";
|
||||
internal const string AkamaiBatchQueueName = "AkamaiBatchQueue";
|
||||
|
||||
internal const string CosmosDBConnection = "CosmosDBConnection";
|
||||
|
||||
public static string CosmosDBConnectionString => Environment.GetEnvironmentVariable(CosmosDBConnection) ?? throw new InvalidOperationException();
|
||||
|
||||
public static string CosmosDatabaseId => Environment.GetEnvironmentVariable("CosmosDatabaseId") ?? "CacheOut";
|
||||
|
||||
public static string UserRequestCosmosContainerId => Environment.GetEnvironmentVariable("UserRequestCosmosContainerId") ?? "UserRequest";
|
||||
|
||||
public static string PartnerCosmosContainerId => Environment.GetEnvironmentVariable("PartnerCosmosContainerId") ?? "Partner";
|
||||
|
||||
public static int MaxRetry => (Environment.GetEnvironmentVariable("Max_Retry") != null) ? Convert.ToInt32(Environment.GetEnvironmentVariable("Max_Retry")) : 5;
|
||||
|
||||
public static string CdnRequestTablePartitionKey => Environment.GetEnvironmentVariable("CdnRequestTablePartitionKey") ?? "PartnerRequestID";
|
||||
|
||||
public static string PartnerRequestTablePartitionKey => Environment.GetEnvironmentVariable("PartnerRequestTablePartitionKey") ?? "UserRequestID";
|
||||
|
||||
public static string UserRequestTablePartitionKey => Environment.GetEnvironmentVariable("UserRequestTablePartitionKey") ?? "id";
|
||||
|
||||
public static int RequestWaitTime = (Environment.GetEnvironmentVariable("Poll_WaitTime") != null) ? Convert.ToInt32(Environment.GetEnvironmentVariable("Poll_WaitTime")) : 2;
|
||||
|
||||
public static int AfdBatchSize = (Environment.GetEnvironmentVariable("Afd_UrlBatchSize")) != null ? Convert.ToInt32(Environment.GetEnvironmentVariable("Afd_UrlBatchSize")) : 200;
|
||||
|
||||
public static int AkamaiBatchSize = (Environment.GetEnvironmentVariable("Akamai_UrlBatchSize")) != null ? Convert.ToInt32(Environment.GetEnvironmentVariable("Akamai_UrlBatchSize")) : 200;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[TestClass]
|
||||
public class AfdPlugin_Test
|
||||
{
|
||||
private static readonly string tenantId = "FakeTenant";
|
||||
private static readonly string partnerId = "FakePartner";
|
||||
private static readonly string id = Guid.NewGuid().ToString();
|
||||
|
||||
private readonly List<ICdnRequest> OutputQueue = new List<ICdnRequest>();
|
||||
private readonly AfdPlugin plugin = new AfdPlugin(Mock.Of<ILogger>(), 500);
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_NullPartnerAndTenantID()
|
||||
{
|
||||
string json =
|
||||
@"{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": """",
|
||||
""partnerId"": """",
|
||||
""Urls"": [""https://fakeUri?pid=1.1""]}";
|
||||
|
||||
Assert.IsFalse(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsFalse(plugin.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_HasPartnerAndTenantID()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": ""{tenantId}"",
|
||||
""partnerId"": ""{partnerId}"",
|
||||
""id"": ""{id}"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1""]}}";
|
||||
|
||||
Assert.IsTrue(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_NoUrl()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": ""{tenantId}"",
|
||||
""partnerId"": ""{partnerId}"",
|
||||
""id"": ""{id}""
|
||||
}}";
|
||||
|
||||
Assert.IsFalse(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_MaxUrl()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""AFD"",
|
||||
""id"": ""{id}"",
|
||||
""tenantId"": ""{tenantId}"",
|
||||
""partnerId"": ""{partnerId}"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.2"",
|
||||
""https://fakeUri?pid=1.3""]}}";
|
||||
|
||||
var plugin2 = new AfdPlugin(Mock.Of<ILogger>(), 2);
|
||||
Assert.IsTrue(plugin2.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin2.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(2, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_BatchCreated()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""AFD"",
|
||||
""Status"": ""BatchCreated"",
|
||||
""id"": ""{id}"",
|
||||
""tenantId"": ""{tenantId}"",
|
||||
""partnerId"": ""{partnerId}"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.2"",
|
||||
""https://fakeUri?pid=1.3""]}}";
|
||||
|
||||
Assert.IsFalse(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_DoesNotAddDuplicate()
|
||||
{
|
||||
var desc = "UnitTest";
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": ""{tenantId}"",
|
||||
""partnerId"": ""{partnerId}"",
|
||||
""id"": ""{id}"",
|
||||
""Description"": ""{desc}"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1""]}}";
|
||||
|
||||
Assert.IsTrue(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
|
||||
var req = OutputQueue[0] as AfdRequest;
|
||||
|
||||
Assert.AreEqual(tenantId, req.TenantID);
|
||||
Assert.AreEqual(partnerId, req.PartnerID);
|
||||
Assert.AreEqual(desc, req.Description);
|
||||
}
|
||||
|
||||
private ICollector<ICdnRequest> CreateCollector()
|
||||
{
|
||||
var collector = new Mock<ICollector<ICdnRequest>>();
|
||||
|
||||
collector.Setup(c => c.Add(It.IsAny<ICdnRequest>())).Callback<ICdnRequest>((s) => OutputQueue.Add(s));
|
||||
|
||||
return collector.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
[TestClass]
|
||||
public class AfdQueueProcessor_Test
|
||||
{
|
||||
private readonly List<ICdnRequest> OutputDB = new List<ICdnRequest>();
|
||||
private readonly AfdQueueProcessor processor = new AfdQueueProcessor(Mock.Of<ILogger>());
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPollRequest_EmptyQueueMessage()
|
||||
{
|
||||
var msg = processor.ProcessPollRequest(new AfdRequest(), 5).Result;
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPollRequest_QueueMessage()
|
||||
{
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100"
|
||||
};
|
||||
|
||||
var msg = processor.ProcessPollRequest(queueMsg, 5).Result;
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(1, queueMsg.NumTimesProcessed);
|
||||
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPollRequest_MaxRetry()
|
||||
{
|
||||
string json =
|
||||
@"{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": ""1"",
|
||||
""partnerId"": ""1"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1""]}";
|
||||
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100",
|
||||
NumTimesProcessed = 5,
|
||||
RequestBody = json,
|
||||
Status = "PurgeSubmitted"
|
||||
};
|
||||
|
||||
var msg = processor.ProcessPollRequest(queueMsg, 5).Result;
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(5, queueMsg.NumTimesProcessed);
|
||||
Assert.IsTrue(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
Assert.AreEqual(RequestStatus.MaxRetry.ToString(), queueMsg.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPollRequest_Completed()
|
||||
{
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100",
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestStatus = RequestStatus.PurgeCompleted
|
||||
};
|
||||
|
||||
var msg = processor.CompletePollRequest(requestInfo, queueMsg);
|
||||
|
||||
Assert.IsNull(msg);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPollRequest_InProgress()
|
||||
{
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100",
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestStatus = RequestStatus.PurgeSubmitted
|
||||
};
|
||||
|
||||
var msg = processor.CompletePollRequest(requestInfo, queueMsg);
|
||||
|
||||
var pollMsg = JsonSerializer.Deserialize<AfdRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(0, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual("100", pollMsg.CdnRequestId);
|
||||
Assert.AreEqual(RequestStatus.PurgeSubmitted.ToString(), pollMsg.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPollRequest_ErrorThrottled()
|
||||
{
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100",
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestStatus = RequestStatus.Error
|
||||
};
|
||||
|
||||
var msg = processor.CompletePollRequest(requestInfo, queueMsg);
|
||||
|
||||
var pollMsg = JsonSerializer.Deserialize<AfdRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(1, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual("100", pollMsg.CdnRequestId);
|
||||
|
||||
requestInfo.RequestStatus = RequestStatus.Throttled;
|
||||
|
||||
msg = processor.CompletePollRequest(requestInfo, queueMsg);
|
||||
|
||||
pollMsg = JsonSerializer.Deserialize<AfdRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(2, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual("100", pollMsg.CdnRequestId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_EmptyQueueMessage()
|
||||
{
|
||||
var msg = processor.ProcessPurgeRequest(new AfdRequest(), 5).Result;
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_QueueMessage()
|
||||
{
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100",
|
||||
RequestBody = "testBody",
|
||||
Urls = new string[]
|
||||
{
|
||||
"https://fakeUri?pid=1.1",
|
||||
}
|
||||
};
|
||||
|
||||
var msg = processor.ProcessPurgeRequest(queueMsg, 5).Result;
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(1, queueMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_MaxRetry()
|
||||
{
|
||||
string json =
|
||||
@"{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": ""1"",
|
||||
""partnerId"": ""1"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1""]}";
|
||||
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 5,
|
||||
RequestBody = json,
|
||||
Urls = new string[]
|
||||
{
|
||||
"https://fakeUri?pid=1.1",
|
||||
},
|
||||
Status = RequestStatus.PurgeSubmitted.ToString()
|
||||
};
|
||||
|
||||
var msg = processor.ProcessPurgeRequest(queueMsg, 5).Result;
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(5, queueMsg.NumTimesProcessed);
|
||||
Assert.IsTrue(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
Assert.AreEqual(RequestStatus.MaxRetry.ToString(), queueMsg.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_SuccessCreatePollMsg()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1,
|
||||
Urls = new string[]
|
||||
{
|
||||
"https://fakeUri?pid=1.1",
|
||||
}
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.PurgeSubmitted
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
var pollMsg = JsonSerializer.Deserialize<AfdRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(0, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(requestId, pollMsg.CdnRequestId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_ThrottledIncreaseNumTimesProcessed()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Throttled
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
var pollMsg = JsonSerializer.Deserialize<AfdRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(2, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(null, pollMsg.CdnRequestId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_ErrorIncreaseNumTimesProcessed()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Error
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
var pollMsg = JsonSerializer.Deserialize<AfdRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(2, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(null, pollMsg.CdnRequestId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_UnknownUnauthorizedReturnNull()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1
|
||||
};
|
||||
|
||||
var requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Unauthorized
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
Assert.IsNull(msg);
|
||||
|
||||
requestInfo = new AfdRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Unknown
|
||||
};
|
||||
|
||||
msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
|
||||
Assert.IsNull(msg);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddCdnRequestToDB_ThrottledError()
|
||||
{
|
||||
var queueMsg = new AfdRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1,
|
||||
Status = RequestStatus.Throttled.ToString()
|
||||
};
|
||||
|
||||
Assert.IsFalse(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
|
||||
queueMsg.Status = RequestStatus.Error.ToString();
|
||||
|
||||
Assert.IsFalse(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[TestClass]
|
||||
public class AfdRequestProcessor_Test
|
||||
{
|
||||
private readonly ILogger logger = Mock.Of<ILogger>();
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_ErrorNoEndpoint()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPurgeRequest(string.Empty, null, logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo.RequestStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_ErrorNoContent()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPurgeRequest("https://fakeUri", null, logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo.RequestStatus);
|
||||
}
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_SuccessSubmitted()
|
||||
{
|
||||
var purgeBody = new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var id = "12345";
|
||||
var requestInfo = AfdRequestProcessor.SendPurgeRequest("https://fakeUri", purgeBody, GetHandler(id), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.PurgeSubmitted, requestInfo.RequestStatus);
|
||||
Assert.AreEqual(id, requestInfo.RequestID);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_UnknownNoRequestID()
|
||||
{
|
||||
var purgeBody = new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var requestInfo = AfdRequestProcessor.SendPurgeRequest("https://fakeUri", purgeBody, GetHandler(), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Unknown, requestInfo.RequestStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_Throttled()
|
||||
{
|
||||
var purgeBody = new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var id = "12345";
|
||||
var requestInfo = AfdRequestProcessor.SendPurgeRequest("https://fakeUri", purgeBody, GetHandler(id, statusCode: HttpStatusCode.TooManyRequests), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Throttled, requestInfo.RequestStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_ErrorNoEndpoint()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest(string.Empty, null, logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_ErrorNoContent()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest("https://fakeUri", null, logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_SuccessSubmitted()
|
||||
{
|
||||
var id = "12345";
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest("https://fakeUri", id, GetHandler(id, "RolledOut"), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.PurgeCompleted, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_SuccessNotStarted()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest("https://fakeUri", "12345", GetHandler(status: "NotStarted"), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.PurgeSubmitted, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_Unauthorized()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest("https://fakeUri", "12345", GetHandler(statusCode: HttpStatusCode.Unauthorized), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Unauthorized, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_Error()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest("https://fakeUri", "12345", GetHandler(statusCode: HttpStatusCode.InternalServerError), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPollRequest_Throttled()
|
||||
{
|
||||
var requestInfo = AfdRequestProcessor.SendPollRequest("https://fakeUri", "12345", GetHandler(statusCode: HttpStatusCode.TooManyRequests), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Throttled, requestInfo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_CorrectRequestID()
|
||||
{
|
||||
var response = @"{
|
||||
""Id"": 2230090,
|
||||
""Description"": ""string"",
|
||||
""Urls"": [
|
||||
""https://fakeUri?pid=1.1""
|
||||
],
|
||||
""CreateTime"": ""0001 -01-01T00:00:00"",
|
||||
""RequestUser"": ""shishar@microsoft.com"",
|
||||
""CompleteTime"": null,
|
||||
""Status"": ""NotStarted"",
|
||||
""PercentComplete"": 0
|
||||
}";
|
||||
Assert.IsTrue(AfdRequestProcessor.GetRequestID(response, out var id));
|
||||
|
||||
Assert.AreEqual("2230090", id);
|
||||
var status = AfdRequestProcessor.GetRequestStatusFromContent(response);
|
||||
Assert.AreEqual(RequestStatus.PurgeSubmitted, status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_NoRequestIDAndStatus()
|
||||
{
|
||||
var response = @"{
|
||||
""Description"": ""string"",
|
||||
""Urls"": [
|
||||
""https://fakeUri?pid=1.1""
|
||||
],
|
||||
""CreateTime"": ""0001 -01-01T00:00:00"",
|
||||
""RequestUser"": ""shishar@microsoft.com"",
|
||||
""CompleteTime"": null,
|
||||
""PercentComplete"": 0
|
||||
}";
|
||||
Assert.IsFalse(AfdRequestProcessor.GetRequestID(response, out _));
|
||||
|
||||
var status = AfdRequestProcessor.GetRequestStatusFromContent(response);
|
||||
Assert.AreEqual(RequestStatus.Unknown, status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_IncorrectFormatRequestIDAndStatus()
|
||||
{
|
||||
var response = @"{
|
||||
""Id"": ""test"",
|
||||
""Description"": ""string"",
|
||||
""Urls"": [
|
||||
""https://fakeUri?pid=1.1""
|
||||
],
|
||||
""CreateTime"": ""0001 -01-01T00:00:00"",
|
||||
""RequestUser"": ""shishar@microsoft.com"",
|
||||
""CompleteTime"": null,
|
||||
""Status"": 1,
|
||||
""PercentComplete"": 0
|
||||
}";
|
||||
Assert.IsFalse(AfdRequestProcessor.GetRequestID(response, out _));
|
||||
|
||||
var status = AfdRequestProcessor.GetRequestStatusFromContent(response);
|
||||
Assert.AreEqual(RequestStatus.Unknown, status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_NoStatus()
|
||||
{
|
||||
var response = @"{
|
||||
""Id"": ""test"",
|
||||
""Description"": ""string"",
|
||||
""Urls"": [
|
||||
""https://fakeUri?pid=1.1""
|
||||
],
|
||||
""CreateTime"": ""0001 -01-01T00:00:00"",
|
||||
""RequestUser"": ""shishar@microsoft.com"",
|
||||
""CompleteTime"": null,
|
||||
""PercentComplete"": 0
|
||||
}";
|
||||
Assert.IsFalse(AfdRequestProcessor.GetRequestID(response, out _));
|
||||
|
||||
var status = AfdRequestProcessor.GetRequestStatusFromContent(response);
|
||||
Assert.AreEqual(RequestStatus.Unknown, status);
|
||||
}
|
||||
|
||||
private static IHttpHandler GetHandler(string requestID = null, string status = "NotStarted", HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||
{
|
||||
var responseText = @$"{{
|
||||
""Description"": ""string"",
|
||||
""Urls"": [
|
||||
""https://fakeUri?pid=1.1""
|
||||
],
|
||||
""CreateTime"": ""0001 - 01 - 01T00: 00:00"",
|
||||
""RequestUser"": ""shishar @microsoft.com"",
|
||||
""CompleteTime"": null,
|
||||
""Status"": ""{status}"",
|
||||
""PercentComplete"": 0";
|
||||
|
||||
if (!string.IsNullOrEmpty(requestID))
|
||||
{
|
||||
responseText += @$", ""Id"": {requestID}";
|
||||
}
|
||||
|
||||
responseText += @"}";
|
||||
|
||||
var response = new HttpResponseMessage()
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(responseText)
|
||||
};
|
||||
|
||||
var handler = new Mock<IHttpHandler>();
|
||||
handler.Setup(h => h.PostAsync(It.IsAny<string>(), It.IsAny<StringContent>())).Returns(Task.FromResult(response));
|
||||
handler.Setup(h => h.GetAsync(It.IsAny<string>())).Returns(Task.FromResult(response));
|
||||
|
||||
return handler.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
[TestClass]
|
||||
public class AkamaiAuthHandler_Test
|
||||
{
|
||||
private readonly string clientToken = "testClientToken";
|
||||
private readonly string accessToken = "testAccessToken";
|
||||
private readonly string secret = "secret";
|
||||
|
||||
private readonly string host = "testuri";
|
||||
|
||||
[TestMethod]
|
||||
public void GetAuthData_Success()
|
||||
{
|
||||
string clientToken = "testClientToken";
|
||||
string accessToken = "testAccessToken";
|
||||
string secret = "secret";
|
||||
var timestamp = new DateTime(1918, 11, 11, 11, 00, 00, DateTimeKind.Utc).ToString("yyyyMMdd'T'HH:mm:ss+0000");
|
||||
|
||||
var handler = new AkamaiAuthHandler(new HttpClientHandler(), clientToken, accessToken, secret);
|
||||
|
||||
string authData = handler.GetAuthorizationData(timestamp);
|
||||
|
||||
var reg = $"EG1-HMAC-SHA256 client_token={clientToken};access_token={accessToken};";
|
||||
reg += "timestamp=19181111T11:00:00\\+0000;nonce=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12};";
|
||||
|
||||
Assert.IsTrue(Regex.IsMatch(authData, reg));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestData_Success()
|
||||
{
|
||||
string clientToken = "testClientToken";
|
||||
string accessToken = "testAccessToken";
|
||||
string secret = "secret";
|
||||
|
||||
var host = "testuri";
|
||||
|
||||
var handler = new AkamaiAuthHandler(new HttpClientHandler(), clientToken, accessToken, secret);
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, $"https://{host}/ccu/1")
|
||||
{
|
||||
Content = new StringContent("TestContent")
|
||||
};
|
||||
|
||||
var data = $"POST\thttps\t{host}\t/ccu/1\t\tuY/AmsDfO7we5eeTFmBPdGL//fCVwcZ248JRd3NkX+k=\t";
|
||||
|
||||
string reqData = handler.GetRequestData(req).Result;
|
||||
|
||||
Assert.AreEqual(data, reqData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestData_NoContent()
|
||||
{
|
||||
string clientToken = "testClientToken";
|
||||
string accessToken = "testAccessToken";
|
||||
string secret = "secret";
|
||||
|
||||
var host = "testuri";
|
||||
|
||||
var handler = new AkamaiAuthHandler(new HttpClientHandler(), clientToken, accessToken, secret);
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, $"https://{host}/ccu/1");
|
||||
|
||||
var data = $"POST\thttps\t{host}\t/ccu/1\t\t\t";
|
||||
|
||||
string reqData = handler.GetRequestData(req).Result;
|
||||
|
||||
Assert.AreEqual(data, reqData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestData_NoPath()
|
||||
{
|
||||
var handler = new AkamaiAuthHandler(new HttpClientHandler(), clientToken, accessToken, secret);
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, $"https://{host}")
|
||||
{
|
||||
Content = new StringContent("TestContent")
|
||||
};
|
||||
|
||||
var data = $"POST\thttps\t{host}\t/\t\tuY/AmsDfO7we5eeTFmBPdGL//fCVwcZ248JRd3NkX+k=\t";
|
||||
|
||||
string reqData = handler.GetRequestData(req).Result;
|
||||
|
||||
Assert.AreEqual(data, reqData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void AkamaiAuthHandler_NullHandler()
|
||||
{
|
||||
_ = new AkamaiAuthHandler(null, null, null, null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void AkamaiAuthHandler_Tokens()
|
||||
{
|
||||
_ = new AkamaiAuthHandler(new HttpClientHandler(), null, null, null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Ignore]
|
||||
public void SendRequestToAkamai_Success()
|
||||
{
|
||||
string clientToken_ = "real client token";
|
||||
string accessToken_ = "real access token";
|
||||
string secret_ = "real secret";
|
||||
|
||||
string endpoint = "https://fakeEndpoint";
|
||||
|
||||
var urls = @"{""objects"": [""https://fakeUri""]}";
|
||||
var purgeRequest = new StringContent(urls, Encoding.UTF8, "application/json");
|
||||
|
||||
using var httpHandler = new HttpHandler(new AkamaiAuthHandler(new HttpClientHandler(), clientToken_, accessToken_, secret_));
|
||||
|
||||
var response = httpHandler.PostAsync(endpoint, purgeRequest).Result;
|
||||
|
||||
Assert.IsTrue(response.IsSuccessStatusCode);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[TestClass]
|
||||
public class AkamaiPlugin_Test
|
||||
{
|
||||
private readonly List<ICdnRequest> OutputQueue = new List<ICdnRequest>();
|
||||
|
||||
private readonly AkamaiPlugin plugin = new AkamaiPlugin(Mock.Of<ILogger>(), 500);
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_NoNetwork()
|
||||
{
|
||||
string json =
|
||||
@"{
|
||||
""CDN"": ""Akamai"",
|
||||
""id"": ""1"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1""]}";
|
||||
|
||||
Assert.IsTrue(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
Assert.IsTrue(OutputQueue[0].Endpoint.EndsWith("staging"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_InvalidNetworkValue()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""Akamai"",
|
||||
""id"": ""1"",
|
||||
""Network"": ""Fake"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1""]}}";
|
||||
|
||||
Assert.IsTrue(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
Assert.IsTrue(OutputQueue[0].Endpoint.EndsWith("staging"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_NoUrl()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""Akamai"",
|
||||
""id"": ""1""
|
||||
}}";
|
||||
|
||||
Assert.IsFalse(plugin.ValidPartnerRequest(json, "1", out var _));
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_MaxUrl()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""Akamai"",
|
||||
""id"": ""1"",
|
||||
""Network"": ""Production"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.2"",
|
||||
""https://fakeUri?pid=1.3""]}}";
|
||||
|
||||
var plugin2 = new AkamaiPlugin(Mock.Of<ILogger>(), 2);
|
||||
Assert.IsTrue(plugin2.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin2.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(2, OutputQueue.Count);
|
||||
Assert.IsTrue(OutputQueue[0].Endpoint.EndsWith("production"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_BatchCreated()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""Akamai"",
|
||||
""id"": ""1"",
|
||||
""Status"": ""BatchCreated""
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.2"",
|
||||
""https://fakeUri?pid=1.3""]}}";
|
||||
|
||||
Assert.IsFalse(plugin.ValidPartnerRequest(json, "1", out var _));
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPartnerRequest_DoesNotAddDuplicate()
|
||||
{
|
||||
string json =
|
||||
@$"{{
|
||||
""CDN"": ""Akamai"",
|
||||
""id"": ""1"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1""]}}";
|
||||
|
||||
Assert.IsTrue(plugin.ValidPartnerRequest(json, "1", out var p));
|
||||
Assert.IsTrue(plugin.ProcessPartnerRequest(p, queue: CreateCollector()));
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
}
|
||||
|
||||
private ICollector<ICdnRequest> CreateCollector()
|
||||
{
|
||||
var collector = new Mock<ICollector<ICdnRequest>>();
|
||||
|
||||
collector.Setup(c => c.Add(It.IsAny<ICdnRequest>())).Callback<ICdnRequest>((s) => OutputQueue.Add(s));
|
||||
|
||||
return collector.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
[TestClass]
|
||||
public class AkamaiQueueProcessor_Test
|
||||
{
|
||||
private readonly List<ICdnRequest> OutputDB = new List<ICdnRequest>();
|
||||
private readonly AkamaiQueueProcessor processor = new AkamaiQueueProcessor(Mock.Of<ILogger>());
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_EmptyQueueMessage()
|
||||
{
|
||||
var msg = processor.ProcessPurgeRequest(new AkamaiRequest(), 5).Result;
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_QueueMessage()
|
||||
{
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
CdnRequestId = "100",
|
||||
RequestBody = "testBody",
|
||||
Urls = new string[]
|
||||
{
|
||||
"https://fakeUri?pid=1.1",
|
||||
}
|
||||
};
|
||||
|
||||
var msg = processor.ProcessPurgeRequest(queueMsg, 5).Result;
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(1, queueMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_MaxRetry()
|
||||
{
|
||||
string json =
|
||||
@"{
|
||||
""CDN"": ""AFD"",
|
||||
""tenantId"": ""1"",
|
||||
""partnerId"": ""1"",
|
||||
""Urls"": [""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1"",
|
||||
""https://fakeUri?pid=1.1""]}";
|
||||
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 5,
|
||||
RequestBody = json,
|
||||
Urls = new string[]
|
||||
{
|
||||
"https://fakeUri?pid=1.1",
|
||||
},
|
||||
Status = RequestStatus.PurgeSubmitted.ToString()
|
||||
};
|
||||
|
||||
var msg = processor.ProcessPurgeRequest(queueMsg, 5).Result;
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(5, queueMsg.NumTimesProcessed);
|
||||
Assert.IsTrue(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
Assert.AreEqual(RequestStatus.MaxRetry.ToString(), queueMsg.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_SuccessDontCreatePollMsg()
|
||||
{
|
||||
var requestId = "12345";
|
||||
var supportId = "testID";
|
||||
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1,
|
||||
Urls = new string[]
|
||||
{
|
||||
"https://fakeUri?pid=1.1",
|
||||
}
|
||||
};
|
||||
|
||||
var requestInfo = new AkamaiRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.PurgeCompleted,
|
||||
SupportID = supportId
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
|
||||
Assert.IsNull(msg);
|
||||
Assert.AreEqual(requestId, queueMsg.CdnRequestId);
|
||||
Assert.AreEqual(supportId, queueMsg.SupportId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_ThrottledIncreaseNumTimesProcessed()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1
|
||||
};
|
||||
|
||||
var requestInfo = new AkamaiRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Throttled
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
var pollMsg = JsonSerializer.Deserialize<AkamaiRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(2, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(null, pollMsg.CdnRequestId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_ErrorIncreaseNumTimesProcessed()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1
|
||||
};
|
||||
|
||||
var requestInfo = new AkamaiRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Error
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
var pollMsg = JsonSerializer.Deserialize<AkamaiRequest>(msg.AsString);
|
||||
|
||||
Assert.IsNotNull(msg);
|
||||
Assert.AreEqual(2, pollMsg.NumTimesProcessed);
|
||||
Assert.AreEqual(null, pollMsg.CdnRequestId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessPurgeRequest_UnknownUnauthorizedReturnNull()
|
||||
{
|
||||
var requestId = "12345";
|
||||
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1
|
||||
};
|
||||
|
||||
var requestInfo = new AkamaiRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Unauthorized
|
||||
};
|
||||
|
||||
var msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
Assert.IsNull(msg);
|
||||
|
||||
requestInfo = new AkamaiRequestInfo()
|
||||
{
|
||||
RequestID = requestId,
|
||||
RequestStatus = RequestStatus.Unknown
|
||||
};
|
||||
|
||||
msg = processor.CompletePurgeRequest(requestInfo, queueMsg);
|
||||
|
||||
Assert.IsNull(msg);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddCdnRequestToDB_ThrottledError()
|
||||
{
|
||||
var queueMsg = new AkamaiRequest()
|
||||
{
|
||||
Endpoint = "http://testendpoint",
|
||||
NumTimesProcessed = 1,
|
||||
Status = RequestStatus.Throttled.ToString()
|
||||
};
|
||||
|
||||
Assert.IsFalse(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
|
||||
queueMsg.Status = RequestStatus.Error.ToString();
|
||||
|
||||
Assert.IsFalse(CdnQueueHelper.AddCdnRequestToDB(queueMsg, 5));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[TestClass]
|
||||
public class AkamaiRequestProcessor_Test
|
||||
{
|
||||
private readonly ILogger logger = Mock.Of<ILogger>();
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_ErrorNoEndpoint()
|
||||
{
|
||||
var requestInfo = AkamaiRequestProcessor.SendPurgeRequest(string.Empty, null, logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo.RequestStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_ErrorNoContent()
|
||||
{
|
||||
var requestInfo = AkamaiRequestProcessor.SendPurgeRequest("https://fakeUri", null, logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Error, requestInfo.RequestStatus);
|
||||
}
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_SuccessSubmitted()
|
||||
{
|
||||
var purgeBody = new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var purgeId = "12345";
|
||||
var supportId = "123";
|
||||
var requestInfo = AkamaiRequestProcessor.SendPurgeRequest("https://fakeUri", purgeBody, GetHandler(purgeId, supportId), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.PurgeCompleted, requestInfo.RequestStatus);
|
||||
Assert.AreEqual(purgeId, requestInfo.RequestID);
|
||||
Assert.AreEqual(supportId, requestInfo.SupportID);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_UnknownNoRequestID()
|
||||
{
|
||||
var purgeBody = new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var requestInfo = AkamaiRequestProcessor.SendPurgeRequest("https://fakeUri", purgeBody, GetHandler(), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Unknown, requestInfo.RequestStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendPurgeRequest_Throttled()
|
||||
{
|
||||
var purgeBody = new StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var id = "12345";
|
||||
var requestInfo = AkamaiRequestProcessor.SendPurgeRequest("https://fakeUri", purgeBody, GetHandler(id, statusCode: HttpStatusCode.TooManyRequests), logger).Result;
|
||||
|
||||
Assert.AreEqual(RequestStatus.Throttled, requestInfo.RequestStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_CorrectRequestID()
|
||||
{
|
||||
var response = @"{
|
||||
""purgeId"": ""2230091"",
|
||||
""supportId"": ""2230091""
|
||||
}";
|
||||
Assert.IsTrue(AkamaiRequestProcessor.GetPropertyValue("purgeId".AsMemory(), response, out var purgeId));
|
||||
|
||||
Assert.AreEqual("2230091", purgeId);
|
||||
|
||||
Assert.IsTrue(AkamaiRequestProcessor.GetPropertyValue("supportId".AsMemory(), response, out var supportId));
|
||||
Assert.AreEqual("2230091", supportId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_NoRequestIDAndStatus()
|
||||
{
|
||||
var response = @"{
|
||||
""Description"": ""string"",
|
||||
""Urls"": [
|
||||
""https://fakeUri?pid=1.1""
|
||||
],
|
||||
""CreateTime"": ""0001 -01-01T00:00:00"",
|
||||
""RequestUser"": ""shishar@microsoft.com"",
|
||||
""CompleteTime"": null,
|
||||
""PercentComplete"": 0
|
||||
}";
|
||||
Assert.IsFalse(AkamaiRequestProcessor.GetPropertyValue("PurgeId".AsMemory(), response, out _));
|
||||
Assert.IsFalse(AkamaiRequestProcessor.GetPropertyValue("supportId".AsMemory(), response, out _));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetRequestStatusFromResponse_IncorrectFormatRequestIDAndStatus()
|
||||
{
|
||||
var response = @"{
|
||||
""purgeId"": 2230091,
|
||||
""supportId"": 2230091
|
||||
}";
|
||||
Assert.IsFalse(AkamaiRequestProcessor.GetPropertyValue("PurgeId".AsMemory(), response, out _));
|
||||
Assert.IsFalse(AkamaiRequestProcessor.GetPropertyValue("supportId".AsMemory(), response, out _));
|
||||
}
|
||||
|
||||
private static IHttpHandler GetHandler(string purgeId = null, string supportId = null, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||
{
|
||||
var responseText = "{";
|
||||
|
||||
if (!string.IsNullOrEmpty(purgeId))
|
||||
{
|
||||
responseText += @$"""purgeId"": ""{purgeId}""";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(supportId))
|
||||
{
|
||||
responseText += @$", ""supportId"": ""{supportId}""";
|
||||
}
|
||||
|
||||
responseText += "}";
|
||||
|
||||
var response = new HttpResponseMessage()
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Content = new StringContent(responseText)
|
||||
};
|
||||
|
||||
var handler = new Mock<IHttpHandler>();
|
||||
handler.Setup(h => h.PostAsync(It.IsAny<string>(), It.IsAny<StringContent>())).Returns(Task.FromResult(response));
|
||||
handler.Setup(h => h.GetAsync(It.IsAny<string>())).Returns(Task.FromResult(response));
|
||||
|
||||
return handler.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary_Test
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
public static class CdnLibraryTestHelper
|
||||
{
|
||||
public static Container MockCosmosDbContainer<T>(IDictionary<string, T> containerContents) where T: ICosmosDbEntity
|
||||
{
|
||||
var responseMock = new Mock<ItemResponse<T>>();
|
||||
|
||||
var containerMock = new Mock<Container>();
|
||||
containerMock
|
||||
.Setup(c => c.CreateItemAsync(
|
||||
It.IsAny<T>(),
|
||||
It.IsAny<PartitionKey?>(),
|
||||
It.IsAny<ItemRequestOptions>(),
|
||||
It.IsAny<CancellationToken>())
|
||||
)
|
||||
.Callback<T, PartitionKey?, ItemRequestOptions, CancellationToken>((p, x, y, z) => containerContents.Add(p.id, p))
|
||||
.ReturnsAsync(responseMock.Object);
|
||||
|
||||
containerMock
|
||||
.Setup(c => c.UpsertItemAsync(
|
||||
It.IsAny<T>(),
|
||||
It.IsAny<PartitionKey?>(),
|
||||
It.IsAny<ItemRequestOptions>(),
|
||||
It.IsAny<CancellationToken>())
|
||||
)
|
||||
.Callback<T, PartitionKey?, ItemRequestOptions, CancellationToken>((p, x, y, z) => containerContents[p.id] = p)
|
||||
.ReturnsAsync(responseMock.Object);
|
||||
|
||||
var feedResponseMock = new Mock<FeedResponse<T>>();
|
||||
|
||||
feedResponseMock.Setup(x => x.Count).Returns(() => containerContents.Count);
|
||||
feedResponseMock.Setup(x => x.GetEnumerator()).Returns(() => containerContents.Values.GetEnumerator());
|
||||
|
||||
var feedIteratorMock = new Mock<FeedIterator<T>>();
|
||||
feedIteratorMock
|
||||
.Setup(f => f.HasMoreResults)
|
||||
.Returns(true);
|
||||
|
||||
feedIteratorMock
|
||||
.Setup(f => f.ReadNextAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(feedResponseMock.Object)
|
||||
.Callback(() => feedIteratorMock
|
||||
.Setup(f => f.HasMoreResults)
|
||||
.Returns(false));
|
||||
|
||||
containerMock
|
||||
.Setup(c => c.GetItemQueryIterator<T>(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<QueryRequestOptions>()))
|
||||
.Returns(feedIteratorMock.Object);
|
||||
|
||||
return containerMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
|
||||
<PackageReference Include="Moq" Version="4.14.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CachePurgeLibrary\CachePurgeLibrary.csproj" />
|
||||
<ProjectReference Include="..\src\CdnLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,209 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnLibrary
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
[TestClass]
|
||||
public class CdnPluginHelper_Test
|
||||
{
|
||||
[TestMethod]
|
||||
public void SplitRequestIntoBatches_MaxNum()
|
||||
{
|
||||
var urls = new HashSet<string>()
|
||||
{
|
||||
"http://url1",
|
||||
"http://url2",
|
||||
"http://url3",
|
||||
"http://url4"
|
||||
};
|
||||
|
||||
int maxNum = 3;
|
||||
|
||||
var result = CdnPluginHelper.SplitUrlListIntoBatches(urls, maxNum);
|
||||
|
||||
Assert.AreEqual(2, result.Count);
|
||||
|
||||
int i = 1;
|
||||
|
||||
foreach (var r in result)
|
||||
{
|
||||
var remainingLength = 4 - i + 1; // Since i is not 0 based
|
||||
|
||||
var batchSize = remainingLength < maxNum ? remainingLength : maxNum;
|
||||
|
||||
Assert.AreEqual(batchSize, r.Length);
|
||||
|
||||
foreach (var url in r)
|
||||
{
|
||||
Assert.AreEqual($"http://url{i}", url);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SplitRequestIntoBatches_LessThanBatchSize()
|
||||
{
|
||||
var urls = new HashSet<string>()
|
||||
{
|
||||
"http://url1",
|
||||
"http://url2",
|
||||
"http://url3",
|
||||
"http://url4"
|
||||
};
|
||||
|
||||
int maxNum = 8;
|
||||
|
||||
var result = CdnPluginHelper.SplitUrlListIntoBatches(urls, maxNum);
|
||||
|
||||
Assert.AreEqual(1, result.Count);
|
||||
|
||||
int i = 1;
|
||||
|
||||
foreach (var r in result)
|
||||
{
|
||||
var remainingLength = 4 - i + 1; // Since i is not 0 based
|
||||
|
||||
var batchSize = remainingLength < maxNum ? remainingLength : maxNum;
|
||||
|
||||
Assert.AreEqual(batchSize, r.Length);
|
||||
|
||||
foreach (var url in r)
|
||||
{
|
||||
Assert.AreEqual($"http://url{i}", url);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SplitRequestIntoBatches_EqualToBatchSize()
|
||||
{
|
||||
var urls = new HashSet<string>()
|
||||
{
|
||||
"http://url1",
|
||||
"http://url2",
|
||||
"http://url3",
|
||||
"http://url4"
|
||||
};
|
||||
|
||||
int maxNum = 4;
|
||||
|
||||
var result = CdnPluginHelper.SplitUrlListIntoBatches(urls, maxNum);
|
||||
|
||||
Assert.AreEqual(1, result.Count);
|
||||
|
||||
int i = 1;
|
||||
|
||||
foreach (var r in result)
|
||||
{
|
||||
var remainingLength = 4 - i + 1; // Since i is not 0 based
|
||||
|
||||
var batchSize = remainingLength < maxNum ? remainingLength : maxNum;
|
||||
|
||||
Assert.AreEqual(batchSize, r.Length);
|
||||
|
||||
foreach (var url in r)
|
||||
{
|
||||
Assert.AreEqual($"http://url{i}", url);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SplitRequestIntoBatches_NoUrl()
|
||||
{
|
||||
var urls = new HashSet<string>();
|
||||
|
||||
int maxNum = 3;
|
||||
|
||||
var result = CdnPluginHelper.SplitUrlListIntoBatches(urls, maxNum);
|
||||
|
||||
Assert.AreEqual(0, result.Count);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void SplitRequestIntoBatches_ModMaxNum()
|
||||
{
|
||||
var urls = new HashSet<string>()
|
||||
{
|
||||
"http://url1",
|
||||
"http://url2",
|
||||
"http://url3",
|
||||
"http://url4"
|
||||
};
|
||||
|
||||
int maxNum = 2;
|
||||
|
||||
var result = CdnPluginHelper.SplitUrlListIntoBatches(urls, maxNum);
|
||||
|
||||
Assert.AreEqual(maxNum, result.Count);
|
||||
|
||||
int i = 1;
|
||||
|
||||
foreach (var r in result)
|
||||
{
|
||||
Assert.AreEqual(maxNum, r.Length);
|
||||
|
||||
foreach (var url in r)
|
||||
{
|
||||
Assert.AreEqual($"http://url{i}", url);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryComputeHash_Success()
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes("Test Request Value");
|
||||
|
||||
var hashString = CdnPluginHelper.ComputeHash(data, "SHA256");
|
||||
|
||||
var algorithm = SHA256.Create();
|
||||
|
||||
var hash = algorithm.ComputeHash(data);
|
||||
|
||||
Assert.AreEqual(Convert.ToBase64String(hash), hashString);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryComputeHash_InvalidInput()
|
||||
{
|
||||
Assert.IsFalse(CdnPluginHelper.TryComputeHash(null, "SHA256", out _));
|
||||
Assert.IsFalse(CdnPluginHelper.TryComputeHash(new byte[2] { 1, 2 }, string.Empty, out _));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryComputeKeyedHash_Success()
|
||||
{
|
||||
var data = "Test Request Value";
|
||||
var key = "testkey";
|
||||
|
||||
var hashString = CdnPluginHelper.ComputeKeyedHash(data, key, "HMACSHA256");
|
||||
|
||||
var algorithm = new HMACSHA256(Encoding.UTF8.GetBytes(key));
|
||||
|
||||
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
|
||||
Assert.AreEqual(Convert.ToBase64String(hash), hashString);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryComputeKeyedHash_InvalidInput()
|
||||
{
|
||||
Assert.IsFalse(CdnPluginHelper.TryComputeKeyedHash(null, null, "HMACSHA256", out _));
|
||||
Assert.IsFalse(CdnPluginHelper.TryComputeKeyedHash(Encoding.UTF8.GetBytes("test"), "test", null, out _));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using CdnLibrary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static class TestEnvironmentConfig
|
||||
{
|
||||
public static readonly Dictionary<string, string> EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
[EnvironmentConfig.CosmosDBConnection] = "CosmosDBConnection", // 'CacheOutTestCosmosDbAuthKey' - for 'cacheouttestdb' Cosmos DB
|
||||
[EnvironmentConfig.CosmosDatabaseId] = "CacheOut", // 'CacheOut' - for 'cacheouttestdb' Cosmos DB
|
||||
|
||||
// 'userRequestToPartnerRequest' - for 'cacheouttestdb' Cosmos DB, for testing only
|
||||
// 'AFD_PartnerRequest' - for 'cacheouttestdb' Cosmos DB, for AFD testing
|
||||
// todo: 'Akamai_CdnRequest' - for 'cacheouttestdb' Cosmos DB, for Akamai testing
|
||||
[EnvironmentConfig.PartnerCosmosContainerId] = "partners", // 'partners' - for 'cacheouttestdb' Cosmos DB
|
||||
[EnvironmentConfig.UserRequestCosmosContainerId] = "userRequests", // 'userRequests' - for 'cacheouttestdb' Cosmos DB
|
||||
};
|
||||
|
||||
public static void SetupTestEnvironment()
|
||||
{
|
||||
foreach (var (envKey, envValue) in EnvironmentVariables)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(envKey, envValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
|
||||
<UserSecretsId>e2c7f1fa-5d0c-47e5-8441-1f52ec2a4998</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.15.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="3.0.7" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.2" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.8" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.11" />
|
||||
<PackageReference Include="Moq" Version="4.14.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>CdnPlugins_Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CdnLibrary\src\CdnLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,98 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Documentation Links:
|
||||
* CosmosDB Trigger: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-cosmosdb-v2-trigger?tabs=csharp
|
||||
* Azure Queue Output: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-output?tabs=csharp
|
||||
*/
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using Microsoft.Azure.Documents;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public class CdnPluginFunctions
|
||||
{
|
||||
private readonly IPartnerRequestTableManager<CDN> partnerRequestTablesManager;
|
||||
|
||||
public CdnPluginFunctions(IPartnerRequestTableManager<CDN> partnerRequestTablesManager)
|
||||
{
|
||||
this.partnerRequestTablesManager = partnerRequestTablesManager;
|
||||
}
|
||||
|
||||
[FunctionName("AkamaiPlugin")]
|
||||
public async Task PurgeAkamai(
|
||||
[CosmosDBTrigger(
|
||||
databaseName: EnvironmentConfig.DatabaseName,
|
||||
collectionName: EnvironmentConfig.AkamaiPartnerCollectionName,
|
||||
ConnectionStringSetting = EnvironmentConfig.CosmosDBConnectionStringName,
|
||||
LeaseCollectionName = "partnerCollectionLeases",
|
||||
CreateLeaseCollectionIfNotExists = true)]
|
||||
IReadOnlyList<Document> partnerRequests,
|
||||
[Queue(
|
||||
EnvironmentConfig.AkamaiBatchQueueName),
|
||||
StorageAccount(EnvironmentConfig.BatchQueueConnectionStringName)]
|
||||
ICollector<ICdnRequest> queue,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"{nameof(PurgeAkamai)} ({nameof(CdnPluginFunctions)}) query: {partnerRequests.Count}");
|
||||
|
||||
if (partnerRequests == null || partnerRequests.Count <= 0) { throw new ArgumentNullException(nameof(partnerRequests)); }
|
||||
|
||||
var plugin = new AkamaiPlugin(log);
|
||||
|
||||
foreach (var r in partnerRequests)
|
||||
{
|
||||
if (plugin.ValidPartnerRequest(r.ToString(), r.Id, out var partnerRequest) && plugin.ProcessPartnerRequest(partnerRequest, queue))
|
||||
{
|
||||
// If cdnRequest creation was successful, update the PartnerRequest in DB with info
|
||||
// regarding # of batch requests created
|
||||
await partnerRequestTablesManager.UpdatePartnerRequest(partnerRequest, CDN.Akamai);
|
||||
}
|
||||
}
|
||||
log.LogInformation($"{nameof(PurgeAkamai)} ({nameof(CdnPluginFunctions)}) finished processing");
|
||||
}
|
||||
|
||||
[FunctionName("AfdPlugin")]
|
||||
public async Task PurgeAfd(
|
||||
[CosmosDBTrigger(
|
||||
databaseName: EnvironmentConfig.DatabaseName,
|
||||
collectionName: EnvironmentConfig.AfdPartnerCollectionName,
|
||||
ConnectionStringSetting = EnvironmentConfig.CosmosDBConnectionStringName,
|
||||
LeaseCollectionName = "partnerCollectionLeases",
|
||||
CreateLeaseCollectionIfNotExists = true)]
|
||||
IReadOnlyList<Document> partnerRequests,
|
||||
[Queue(
|
||||
EnvironmentConfig.AfdBatchQueueName),
|
||||
StorageAccount(EnvironmentConfig.BatchQueueConnectionStringName)]
|
||||
ICollector<ICdnRequest> queue,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"{nameof(PurgeAfd)} ({nameof(CdnPluginFunctions)}) query: {partnerRequests.Count}");
|
||||
|
||||
if (partnerRequests == null || partnerRequests.Count <= 0) { throw new ArgumentNullException(nameof(partnerRequests)); }
|
||||
|
||||
var plugin = new AfdPlugin(log);
|
||||
|
||||
foreach (var r in partnerRequests)
|
||||
{
|
||||
if (plugin.ValidPartnerRequest(r.ToString(), r.Id, out var partnerRequest) && plugin.ProcessPartnerRequest(partnerRequest, queue))
|
||||
{
|
||||
// If cdnRequest creation was successful, update the PartnerRequest in DB with info
|
||||
// regarding # of batch requests created
|
||||
await partnerRequestTablesManager.UpdatePartnerRequest(partnerRequest, CDN.AFD);
|
||||
}
|
||||
}
|
||||
log.LogInformation($"{nameof(PurgeAfd)} ({nameof(CdnPluginFunctions)}) finished processing");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Documentation Links:
|
||||
* CosmosDB Trigger: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-cosmosdb-v2-trigger?tabs=csharp
|
||||
*/
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using Microsoft.Azure.Documents;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class EventCompletionFunctions
|
||||
{
|
||||
private static readonly string PurgeCompleted = RequestStatus.PurgeCompleted.ToString();
|
||||
private static readonly string BatchCreated = RequestStatus.BatchCreated.ToString();
|
||||
private static readonly string PurgeSubmitted = RequestStatus.PurgeSubmitted.ToString();
|
||||
|
||||
private readonly IRequestTable<UserRequest> userRequestTable;
|
||||
private readonly IPartnerRequestTableManager<CDN> partnerRequestTableManager;
|
||||
|
||||
public EventCompletionFunctions(IRequestTable<UserRequest> userRequestTable, IPartnerRequestTableManager<CDN> partnerRequestTableManager)
|
||||
{
|
||||
this.userRequestTable = userRequestTable;
|
||||
this.partnerRequestTableManager = partnerRequestTableManager;
|
||||
}
|
||||
|
||||
internal async Task CompletePurge(ICdnRequest cdnRequest, CDN cdn, ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cdnRequest.Status.Equals(PurgeSubmitted))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var partnerReq = await partnerRequestTableManager.GetPartnerRequest(cdnRequest.PartnerRequestID, cdn);
|
||||
|
||||
if (partnerReq != null && UpdatePartnerRequest(partnerReq, cdnRequest.Status, logger))
|
||||
{
|
||||
await partnerRequestTableManager.UpdatePartnerRequest(partnerReq, cdn);
|
||||
|
||||
await UpdateUserRequest(partnerReq, logger);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var exceptionMsg = e.InnerException?.Message ?? e.Message;
|
||||
logger.LogError(exceptionMsg);
|
||||
}
|
||||
}
|
||||
|
||||
[FunctionName("AfdEventCompletion")]
|
||||
public async Task CompleteAfd(
|
||||
[CosmosDBTrigger(
|
||||
databaseName: EnvironmentConfig.DatabaseName,
|
||||
collectionName: EnvironmentConfig.AfdCdnCollectionName,
|
||||
ConnectionStringSetting = EnvironmentConfig.CosmosDBConnectionStringName,
|
||||
LeaseCollectionName = "cdnCollectionLeases",
|
||||
CreateLeaseCollectionIfNotExists = true)]
|
||||
IReadOnlyList<Document> afdRequests,
|
||||
ILogger logger)
|
||||
{
|
||||
logger.LogInformation($"{nameof(CompleteAfd)} ({nameof(EventCompletionFunctions)}) starting: {afdRequests.Count} requests");
|
||||
|
||||
if (afdRequests == null || afdRequests.Count == 0) { return; }
|
||||
|
||||
foreach (var r in afdRequests)
|
||||
{
|
||||
var purgeRequest = JsonSerializer.Deserialize<AfdRequest>(r.ToString(), CdnPluginHelper.JsonSerializerOptions);
|
||||
|
||||
await CompletePurge(purgeRequest, CDN.AFD, logger);
|
||||
}
|
||||
|
||||
logger.LogInformation($"{nameof(CompleteAfd)} ({nameof(EventCompletionFunctions)}) finished processing");
|
||||
}
|
||||
|
||||
[FunctionName("AkamaiEventCompletion")]
|
||||
public async Task CompleteAkamai(
|
||||
[CosmosDBTrigger(
|
||||
databaseName: EnvironmentConfig.DatabaseName,
|
||||
collectionName: EnvironmentConfig.AkamaiCdnCollectionName,
|
||||
ConnectionStringSetting = EnvironmentConfig.CosmosDBConnectionStringName,
|
||||
LeaseCollectionName = "cdnCollectionLeases",
|
||||
CreateLeaseCollectionIfNotExists = true)]
|
||||
IReadOnlyList<Document> akamaiRequests,
|
||||
ILogger logger)
|
||||
{
|
||||
logger.LogInformation($"{nameof(CompleteAkamai)} ({nameof(EventCompletionFunctions)}) starting: {akamaiRequests.Count} requests");
|
||||
|
||||
if (akamaiRequests == null || akamaiRequests.Count == 0) { return; }
|
||||
|
||||
foreach (var r in akamaiRequests)
|
||||
{
|
||||
var purgeRequest = JsonSerializer.Deserialize<AkamaiRequest>(r.ToString(), CdnPluginHelper.JsonSerializerOptions);
|
||||
|
||||
await CompletePurge(purgeRequest, CDN.Akamai, logger);
|
||||
}
|
||||
|
||||
logger.LogInformation($"{nameof(CompleteAkamai)} ({nameof(EventCompletionFunctions)}) finished processing");
|
||||
}
|
||||
|
||||
internal bool UpdatePartnerRequest(IPartnerRequest partnerRequest, string cdnRequestStatus, ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
partnerRequest.NumCompletedCdnRequests++;
|
||||
|
||||
// If error status set the parentRequest status to the same value
|
||||
if (!cdnRequestStatus.Equals(PurgeCompleted))
|
||||
{
|
||||
partnerRequest.Status = cdnRequestStatus;
|
||||
}
|
||||
|
||||
// ParentRequest status is only overwritten if it's got the default value of created
|
||||
// to prevent overwriting an error status
|
||||
if (partnerRequest.NumCompletedCdnRequests >= partnerRequest.NumTotalCdnRequests && partnerRequest.Status.Equals(BatchCreated))
|
||||
{
|
||||
partnerRequest.Status = cdnRequestStatus;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task UpdateUserRequest(IPartnerRequest partnerRequest, ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
// if the partner request is complete, update the user request
|
||||
if (partnerRequest.NumCompletedCdnRequests >= partnerRequest.NumTotalCdnRequests)
|
||||
{
|
||||
var userReq = await userRequestTable.GetItem(partnerRequest.UserRequestID);
|
||||
|
||||
if (userReq != null && userReq.NumCompletedPartnerRequests < userReq.NumTotalPartnerRequests)
|
||||
{
|
||||
userReq.NumCompletedPartnerRequests++;
|
||||
await userRequestTable.UpsertItem(userReq);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Documentation Links:
|
||||
* Azure Queue Trigger: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=csharp
|
||||
* Azure Queue Output: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-output?tabs=csharp
|
||||
*/
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using Microsoft.Azure.Storage.Queue;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class QueueProcessorFunctions
|
||||
{
|
||||
private static readonly int MaxRetry = EnvironmentConfig.MaxRetry;
|
||||
private readonly ICdnRequestTableManager<CDN> cdnRequestTableManager;
|
||||
|
||||
public QueueProcessorFunctions(ICdnRequestTableManager<CDN> cdnRequestTableManager)
|
||||
{
|
||||
this.cdnRequestTableManager = cdnRequestTableManager;
|
||||
}
|
||||
|
||||
[FunctionName("AfdQueueProcessor")]
|
||||
public async Task ProcessAfd(
|
||||
[QueueTrigger(EnvironmentConfig.AfdBatchQueueName, Connection = EnvironmentConfig.BatchQueueConnectionStringName)] CloudQueueMessage queueMessage,
|
||||
[Queue(EnvironmentConfig.AfdBatchQueueName), StorageAccount(EnvironmentConfig.BatchQueueConnectionStringName)] CloudQueue outputQueue,
|
||||
ILogger logger)
|
||||
{
|
||||
logger.LogInformation($"{nameof(ProcessAfd)} ({nameof(QueueProcessorFunctions)}) start: {queueMessage}");
|
||||
|
||||
var message = queueMessage.AsString;
|
||||
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
logger.LogWarning("AfdQueueProcessor: QueueMessage is empty string");
|
||||
return;
|
||||
}
|
||||
|
||||
var queueMsg = JsonSerializer.Deserialize<AfdRequest>(message);
|
||||
|
||||
if (!ValidCdnRequest(queueMsg))
|
||||
{
|
||||
logger.LogError("AfdQueueProcessor: Invalid CdnRequest");
|
||||
return;
|
||||
}
|
||||
|
||||
var queueProcessor = new AfdQueueProcessor(logger);
|
||||
|
||||
CloudQueueMessage msg = !string.IsNullOrEmpty(queueMsg.CdnRequestId) ? await queueProcessor.ProcessPollRequest(queueMsg, MaxRetry) : await queueProcessor.ProcessPurgeRequest(queueMsg, MaxRetry);
|
||||
|
||||
if (CdnQueueHelper.AddCdnRequestToDB(queueMsg, MaxRetry))
|
||||
{
|
||||
await cdnRequestTableManager.UpdateCdnRequest(queueMsg, CDN.AFD);
|
||||
}
|
||||
|
||||
if (msg != null) { queueProcessor.AddMessageToQueue(outputQueue, msg, queueMsg); }
|
||||
|
||||
logger.LogInformation($"{nameof(ProcessAfd)} ({nameof(QueueProcessorFunctions)}) finished processing");
|
||||
}
|
||||
|
||||
[FunctionName("AkamaiQueueProcessor")]
|
||||
public async Task ProcessAkamai(
|
||||
[QueueTrigger(EnvironmentConfig.AkamaiBatchQueueName, Connection = EnvironmentConfig.BatchQueueConnectionStringName)] CloudQueueMessage queueMessage,
|
||||
[Queue(EnvironmentConfig.AkamaiBatchQueueName), StorageAccount(EnvironmentConfig.BatchQueueConnectionStringName)] CloudQueue outputQueue,
|
||||
ILogger logger)
|
||||
{
|
||||
logger.LogInformation($"{nameof(ProcessAkamai)} ({nameof(QueueProcessorFunctions)}) start: {queueMessage}");
|
||||
|
||||
var message = queueMessage.AsString;
|
||||
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
logger.LogWarning("AkamaiQueueProcessor: QueueMessage is empty string");
|
||||
return;
|
||||
}
|
||||
|
||||
var queueMsg = JsonSerializer.Deserialize<AkamaiRequest>(message);
|
||||
|
||||
if (!ValidCdnRequest(queueMsg))
|
||||
{
|
||||
logger.LogError("AkamaiQueueProcessor: Invalid CdnRequest");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(queueMsg.CdnRequestId))
|
||||
{
|
||||
var queueProcessor = new AkamaiQueueProcessor(logger);
|
||||
|
||||
var msg = await queueProcessor.ProcessPurgeRequest(queueMsg, MaxRetry);
|
||||
|
||||
if (CdnQueueHelper.AddCdnRequestToDB(queueMsg, MaxRetry))
|
||||
{
|
||||
await cdnRequestTableManager.UpdateCdnRequest(queueMsg, CDN.Akamai);
|
||||
}
|
||||
|
||||
if (msg != null) { queueProcessor.AddMessageToQueue(outputQueue, msg, queueMsg); }
|
||||
}
|
||||
logger.LogInformation($"{nameof(ProcessAkamai)} ({nameof(AkamaiQueueProcessor)}) finished processing");
|
||||
}
|
||||
|
||||
private bool ValidCdnRequest(ICdnRequest cdnRequest)
|
||||
{
|
||||
if (cdnRequest != null && !string.IsNullOrEmpty(cdnRequest.id) && cdnRequest.Urls != null && cdnRequest.Urls.Length > 0 && cdnRequest.RequestBody != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using CdnPlugin;
|
||||
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
[assembly: FunctionsStartup(typeof(Startup))]
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
public class Startup : FunctionsStartup
|
||||
{
|
||||
public override void Configure(IFunctionsHostBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<ICdnRequestTableManager<CDN>>((s) => { return new CdnRequestTableManager(); });
|
||||
builder.Services.AddSingleton<IRequestTable<UserRequest>>((s) => { return new UserRequestTable(); });
|
||||
builder.Services.AddSingleton<IPartnerRequestTableManager<CDN>>((s) => { return new PartnerRequestTableManager(); });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
NOTICES AND INFORMATION
|
||||
Do Not Translate or Localize
|
||||
|
||||
This software incorporates material from third parties.
|
||||
Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com,
|
||||
or you may send a check or money order for US $5.00, including the product name,
|
||||
the open source component name, platform, and version number, to:
|
||||
|
||||
Source Code Compliance Team
|
||||
Microsoft Corporation
|
||||
One Microsoft Way
|
||||
Redmond, WA 98052
|
||||
USA
|
||||
|
||||
Notwithstanding any other terms, you may reverse engineer this software to the extent
|
||||
required to debug changes to any libraries licensed under the GNU Lesser General Public License.
|
||||
|
||||
---------------------------------------------------------
|
||||
|
||||
Antlr4 4.8.0 - BSD-3-Clause
|
||||
|
||||
[The "BSD 3-clause license"]
|
||||
Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
=====
|
||||
|
||||
MIT License for codepointat.js from https://git.io/codepointat
|
||||
MIT License for fromcodepoint.js from https://git.io/vDW1m
|
||||
|
||||
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"samplingExcludedTypes": "Request",
|
||||
"isEnabled": true
|
||||
}
|
||||
},
|
||||
"fileLoggingMode": "always",
|
||||
"console": { "isEnabled": "false" },
|
||||
"logLevel": {
|
||||
"default": "Debug"
|
||||
}
|
||||
},
|
||||
"queues": {
|
||||
"maxPollingInterval": 60000
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using CdnLibrary_Test;
|
||||
using Microsoft.Azure.Documents;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[TestClass]
|
||||
public class CdnPlugin_Test
|
||||
{
|
||||
private readonly IDictionary<string, AfdPartnerRequest> afdPartnerRequest = new Dictionary<string, AfdPartnerRequest>();
|
||||
private readonly IDictionary<string, AkamaiPartnerRequest> akamaiPartnerRequest = new Dictionary<string, AkamaiPartnerRequest>();
|
||||
|
||||
private readonly List<ICdnRequest> OutputDB = new List<ICdnRequest>();
|
||||
|
||||
private static readonly string tenantId = "FakeTenant";
|
||||
private static readonly string partnerId = "FakePartner";
|
||||
|
||||
private static readonly string[] urls = new string[] { "https://fakeUrls" };
|
||||
|
||||
CdnPluginFunctions cdnPluginFunctions;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
var afdPartnerRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(afdPartnerRequest);
|
||||
var akamaiPartnerRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(akamaiPartnerRequest);
|
||||
|
||||
var partnerRequestTable = new PartnerRequestTableManager(afdPartnerRequestContainer, akamaiPartnerRequestContainer);
|
||||
|
||||
cdnPluginFunctions = new CdnPluginFunctions(partnerRequestTable);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(AggregateException))]
|
||||
public void PurgeAfd_Fail_Empty()
|
||||
{
|
||||
IReadOnlyList<Document> doc = new List<Document>();
|
||||
|
||||
cdnPluginFunctions.PurgeAfd(doc, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(AggregateException))]
|
||||
public void PurgeAkamai_Fail_Empty()
|
||||
{
|
||||
IReadOnlyList<Document> doc = new List<Document>();
|
||||
|
||||
cdnPluginFunctions.PurgeAkamai(doc, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PurgeAfd_Success()
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var partnerDoc = new Document();
|
||||
partnerDoc.SetPropertyValue("CDN", "AFD");
|
||||
partnerDoc.SetPropertyValue("tenantId", tenantId);
|
||||
partnerDoc.SetPropertyValue("partnerId", partnerId);
|
||||
partnerDoc.SetPropertyValue("Urls", urls);
|
||||
partnerDoc.SetPropertyValue("id", id);
|
||||
|
||||
IReadOnlyList<Document> doc = new List<Document>()
|
||||
{
|
||||
partnerDoc
|
||||
};
|
||||
|
||||
cdnPluginFunctions.PurgeAfd(doc, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
Assert.AreEqual(1, OutputDB.Count);
|
||||
Assert.AreEqual(id, OutputDB[0].PartnerRequestID);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PurgeAkamai_Success()
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
|
||||
var partnerDoc = new Document();
|
||||
partnerDoc.SetPropertyValue("CDN", "Akamai");
|
||||
partnerDoc.SetPropertyValue("tenantId", tenantId);
|
||||
partnerDoc.SetPropertyValue("partnerId", partnerId);
|
||||
partnerDoc.SetPropertyValue("Urls", urls);
|
||||
partnerDoc.SetPropertyValue("id", id);
|
||||
|
||||
IReadOnlyList<Document> doc = new List<Document>()
|
||||
{
|
||||
partnerDoc
|
||||
};
|
||||
|
||||
cdnPluginFunctions.PurgeAkamai(doc, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
Assert.AreEqual(1, OutputDB.Count);
|
||||
Assert.AreEqual(id, OutputDB[0].PartnerRequestID);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PurgeAfd_Fail_NoId()
|
||||
{
|
||||
var partnerDoc = new Document();
|
||||
partnerDoc.SetPropertyValue("CDN", "Afd");
|
||||
partnerDoc.SetPropertyValue("tenantId", tenantId);
|
||||
partnerDoc.SetPropertyValue("partnerId", partnerId);
|
||||
partnerDoc.SetPropertyValue("Urls", urls);
|
||||
|
||||
IReadOnlyList<Document> doc = new List<Document>()
|
||||
{
|
||||
partnerDoc
|
||||
};
|
||||
|
||||
cdnPluginFunctions.PurgeAfd(doc, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PurgeAkamai_Fail_NoId()
|
||||
{
|
||||
var partnerDoc = new Document();
|
||||
partnerDoc.SetPropertyValue("CDN", "Akamai");
|
||||
partnerDoc.SetPropertyValue("tenantId", tenantId);
|
||||
partnerDoc.SetPropertyValue("partnerId", partnerId);
|
||||
partnerDoc.SetPropertyValue("Urls", urls);
|
||||
|
||||
IReadOnlyList<Document> doc = new List<Document>()
|
||||
{
|
||||
partnerDoc
|
||||
};
|
||||
|
||||
cdnPluginFunctions.PurgeAkamai(doc, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
Assert.AreEqual(0, OutputDB.Count);
|
||||
}
|
||||
|
||||
private ICollector<ICdnRequest> CreateCollector()
|
||||
{
|
||||
var collector = new Mock<ICollector<ICdnRequest>>();
|
||||
|
||||
collector.Setup(c => c.Add(It.IsAny<ICdnRequest>())).Callback<ICdnRequest>((s) => OutputDB.Add(s));
|
||||
|
||||
return collector.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CdnLibrary\src\CdnLibrary.csproj" />
|
||||
<ProjectReference Include="..\..\CdnLibrary\tests\CdnLibrary_Test.csproj" />
|
||||
<ProjectReference Include="..\src\CdnPlugins.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,109 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using CdnLibrary_Test;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[TestClass]
|
||||
public class EventCompletion_Test
|
||||
{
|
||||
private readonly ILogger logger = Mock.Of<ILogger>();
|
||||
private static readonly string PurgeCompleted = RequestStatus.PurgeCompleted.ToString();
|
||||
|
||||
private readonly IDictionary<string, AfdPartnerRequest> afdPartnerRequest = new Dictionary<string, AfdPartnerRequest>();
|
||||
private readonly IDictionary<string, AkamaiPartnerRequest> akamaiPartnerRequest = new Dictionary<string, AkamaiPartnerRequest>();
|
||||
|
||||
EventCompletionFunctions completionFunctions;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
var afdPartnerRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(afdPartnerRequest);
|
||||
var akamaiPartnerRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(akamaiPartnerRequest);
|
||||
|
||||
var partnerRequestTable = new PartnerRequestTableManager(afdPartnerRequestContainer, akamaiPartnerRequestContainer);
|
||||
completionFunctions = new EventCompletionFunctions(null, partnerRequestTable);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdatePartnerRequest_Success()
|
||||
{
|
||||
var partnerRequest = new PartnerRequest()
|
||||
{
|
||||
NumCompletedCdnRequests = 0,
|
||||
NumTotalCdnRequests = 1,
|
||||
Status = "BatchCreated"
|
||||
};
|
||||
|
||||
Assert.IsTrue(completionFunctions.UpdatePartnerRequest(partnerRequest, PurgeCompleted, logger));
|
||||
Assert.AreEqual(1, partnerRequest.NumCompletedCdnRequests);
|
||||
Assert.AreEqual(PurgeCompleted, partnerRequest.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdatePartnerRequest_ErrorNoStatus()
|
||||
{
|
||||
var partnerRequest = new PartnerRequest()
|
||||
{
|
||||
NumCompletedCdnRequests = 0,
|
||||
NumTotalCdnRequests = 1
|
||||
};
|
||||
|
||||
Assert.IsFalse(completionFunctions.UpdatePartnerRequest(partnerRequest, PurgeCompleted, logger));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdatePartnerRequest_ErrorStatusPreserved()
|
||||
{
|
||||
var partnerRequest = new PartnerRequest()
|
||||
{
|
||||
NumCompletedCdnRequests = 0,
|
||||
NumTotalCdnRequests = 1,
|
||||
Status = "Error"
|
||||
};
|
||||
|
||||
Assert.IsTrue(completionFunctions.UpdatePartnerRequest(partnerRequest, "Error", logger));
|
||||
Assert.AreEqual(1, partnerRequest.NumCompletedCdnRequests);
|
||||
Assert.AreEqual("Error", partnerRequest.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdatePartnerRequest_NotComplete()
|
||||
{
|
||||
var partnerRequest = new PartnerRequest()
|
||||
{
|
||||
NumCompletedCdnRequests = 0,
|
||||
NumTotalCdnRequests = 2,
|
||||
Status = "BatchCreated"
|
||||
};
|
||||
|
||||
Assert.IsTrue(completionFunctions.UpdatePartnerRequest(partnerRequest, PurgeCompleted, logger));
|
||||
Assert.AreEqual(1, partnerRequest.NumCompletedCdnRequests);
|
||||
Assert.AreEqual("BatchCreated", partnerRequest.Status);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdatePartnerRequest_CdnRequestError()
|
||||
{
|
||||
var partnerRequest = new PartnerRequest()
|
||||
{
|
||||
NumCompletedCdnRequests = 0,
|
||||
NumTotalCdnRequests = 2,
|
||||
Status = "Error"
|
||||
};
|
||||
|
||||
Assert.IsTrue(completionFunctions.UpdatePartnerRequest(partnerRequest, "Error", logger));
|
||||
Assert.AreEqual(1, partnerRequest.NumCompletedCdnRequests);
|
||||
Assert.AreEqual("Error", partnerRequest.Status);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace CdnPlugin
|
||||
{
|
||||
using CdnLibrary;
|
||||
using CdnLibrary_Test;
|
||||
using Microsoft.Azure.Storage;
|
||||
using Microsoft.Azure.Storage.Queue;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
[TestClass]
|
||||
public class QueueProcessor_Test
|
||||
{
|
||||
private readonly IDictionary<string, AfdRequest> afdRequest = new Dictionary<string, AfdRequest>();
|
||||
private readonly IDictionary<string, AkamaiRequest> akamaiRequest = new Dictionary<string, AkamaiRequest>();
|
||||
|
||||
private readonly List<CloudQueueMessage> OutputQueue = new List<CloudQueueMessage>();
|
||||
|
||||
private static readonly string[] urls = new string[] { "https://fakeUrls" };
|
||||
|
||||
private QueueProcessorFunctions queueFunctions;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
var afdRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(afdRequest);
|
||||
var akamaiRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(akamaiRequest);
|
||||
|
||||
var cdnRequestTableManager = new CdnRequestTableManager(afdRequestContainer, akamaiRequestContainer);
|
||||
|
||||
queueFunctions = new QueueProcessorFunctions(cdnRequestTableManager);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessAfd_EmptyRequest()
|
||||
{
|
||||
var request = new AfdRequest();
|
||||
|
||||
var cloudQueueMsg= new CloudQueueMessage(JsonSerializer.Serialize(request));
|
||||
|
||||
queueFunctions.ProcessAfd(cloudQueueMsg, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessAfd_Success()
|
||||
{
|
||||
var afdRequestBody = new AfdRequestBody()
|
||||
{
|
||||
Urls = urls,
|
||||
Description = "Test"
|
||||
};
|
||||
|
||||
var request = new AfdRequest()
|
||||
{
|
||||
id = "1",
|
||||
Urls = urls,
|
||||
RequestBody = JsonSerializer.Serialize(afdRequestBody, CdnPluginHelper.JsonSerializerOptions),
|
||||
Endpoint = "testendpoint"
|
||||
};
|
||||
|
||||
var cloudQueueMsg = new CloudQueueMessage(JsonSerializer.Serialize(request));
|
||||
|
||||
queueFunctions.ProcessAfd(cloudQueueMsg, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessAkamai_Fail()
|
||||
{
|
||||
var request = new AkamaiRequest();
|
||||
|
||||
var cloudQueueMsg = new CloudQueueMessage(JsonSerializer.Serialize(request));
|
||||
|
||||
queueFunctions.ProcessAkamai(cloudQueueMsg, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
|
||||
Assert.AreEqual(0, OutputQueue.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessAkamai_Success()
|
||||
{
|
||||
var afdRequestBody = new AkamaiRequestBody()
|
||||
{
|
||||
Hostname = "testhostname",
|
||||
Objects=urls
|
||||
};
|
||||
|
||||
var request = new AkamaiRequest()
|
||||
{
|
||||
id = "1",
|
||||
Urls = urls,
|
||||
RequestBody = JsonSerializer.Serialize(afdRequestBody, CdnPluginHelper.JsonSerializerOptions),
|
||||
Endpoint = "testendpoint"
|
||||
};
|
||||
|
||||
var cloudQueueMsg = new CloudQueueMessage(JsonSerializer.Serialize(request));
|
||||
|
||||
queueFunctions.ProcessAfd(cloudQueueMsg, CreateCollector(), Mock.Of<ILogger>()).Wait();
|
||||
|
||||
Assert.AreEqual(1, OutputQueue.Count);
|
||||
}
|
||||
|
||||
private CloudQueue CreateCollector()
|
||||
{
|
||||
var collector = new Mock<TestCloudQueue>();
|
||||
|
||||
collector.Setup(c => c.AddMessage(
|
||||
It.IsAny<CloudQueueMessage>(),
|
||||
It.IsAny<TimeSpan?>(),
|
||||
It.IsAny<TimeSpan?>(),
|
||||
It.IsAny<QueueRequestOptions>(),
|
||||
It.IsAny<OperationContext>())).Callback<CloudQueueMessage, TimeSpan?, TimeSpan?, QueueRequestOptions, OperationContext>((s, a, b, c, d) => OutputQueue.Add(s));
|
||||
|
||||
return collector.Object;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestCloudQueue : CloudQueue
|
||||
{
|
||||
public TestCloudQueue() : base(new Uri("http://fakeUri")) { }
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 36 KiB |
|
@ -0,0 +1,23 @@
|
|||
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,111 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class CacheFunctions
|
||||
{
|
||||
// todo: handle exceptions and logging across all Azure Functions
|
||||
private readonly IRequestTable<Partner> partnerTable;
|
||||
private readonly IRequestTable<UserRequest> userRequestTable;
|
||||
private readonly IPartnerRequestTableManager<CDN> partnerRequestTable;
|
||||
|
||||
public CacheFunctions(IRequestTable<Partner> partnerTable,
|
||||
IRequestTable<UserRequest> userRequestTable,
|
||||
IPartnerRequestTableManager<CDN> partnerRequestTable)
|
||||
{
|
||||
this.partnerTable = partnerTable;
|
||||
this.partnerRequestTable = partnerRequestTable;
|
||||
this.userRequestTable = userRequestTable;
|
||||
}
|
||||
|
||||
[FunctionName("CreateCachePurgeRequestByHostname")]
|
||||
public async Task<IActionResult> CreateCachePurgeRequestByHostname(
|
||||
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "{partnerId:guid}/CachePurgeByHostname")]
|
||||
HttpRequest req,
|
||||
string partnerId,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"{nameof(CreateCachePurgeRequestByHostname)}");
|
||||
|
||||
try
|
||||
{
|
||||
if (partnerId == null)
|
||||
{
|
||||
return new StringResult("Invalid Partner Id");
|
||||
}
|
||||
|
||||
var bodyContent = await new StreamReader(req.Body).ReadToEndAsync();
|
||||
var purgeRequest = JsonSerializer.Deserialize<PurgeRequest>(bodyContent);
|
||||
var urls = new HashSet<string>(purgeRequest.Urls);
|
||||
var description = purgeRequest.Description;
|
||||
var ticketId = purgeRequest.TicketId;
|
||||
var hostname = purgeRequest.Hostname;
|
||||
log.LogInformation($"{nameof(CreateCachePurgeRequestByHostname)}: purging {urls.Count} urls for partner {partnerId}");
|
||||
|
||||
var partner = await partnerTable.GetItem(partnerId);
|
||||
var userRequest = new UserRequest(partner.id, description, ticketId, hostname, urls);
|
||||
|
||||
await userRequestTable.CreateItem(userRequest);
|
||||
var userRequestId = userRequest.id;
|
||||
|
||||
foreach (var partnerCdnConfiguration in partner.CdnConfigurations)
|
||||
{
|
||||
var cdnWithCredentials = partnerCdnConfiguration.CdnWithCredentials;
|
||||
foreach (var cdnWithCredential in cdnWithCredentials)
|
||||
{
|
||||
var cdn = Enum.Parse<CDN>(cdnWithCredential.Key);
|
||||
var partnerRequest = CdnRequestHelper.CreatePartnerRequest(cdn, partner, userRequest, description, ticketId);
|
||||
await partnerRequestTable.CreatePartnerRequest(partnerRequest, cdn);
|
||||
userRequest.NumTotalPartnerRequests++;
|
||||
}
|
||||
}
|
||||
|
||||
await userRequestTable.UpsertItem(userRequest);
|
||||
|
||||
return new StringResult(userRequestId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExceptionResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[FunctionName("CachePurgeRequestByHostnameStatus")]
|
||||
public async Task<IActionResult> CachePurgeRequestByHostnameStatus(
|
||||
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "{partnerId}/CachePurgeStatus/{userRequestId}")]
|
||||
HttpRequest req,
|
||||
string partnerId,
|
||||
string userRequestId,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"{nameof(CachePurgeRequestByHostnameStatus)}: {userRequestId} (partnerId={partnerId})");
|
||||
try
|
||||
{
|
||||
var userRequest = await userRequestTable.GetItem(userRequestId);
|
||||
return new UserRequestStatusResult(userRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.LogInformation($"{nameof(CachePurgeRequestByHostnameStatus)}: got exception {e.Message}; {e.StackTrace}");
|
||||
return new ExceptionResult(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public class PartnerFunctions
|
||||
{
|
||||
private const string PartnerIdParameter = "partnerId";
|
||||
|
||||
private readonly IRequestTable<Partner> partnerTable;
|
||||
|
||||
public PartnerFunctions(IRequestTable<Partner> partnerTable)
|
||||
{
|
||||
this.partnerTable = partnerTable;
|
||||
}
|
||||
|
||||
[FunctionName("GetPartner")]
|
||||
public async Task<IActionResult> GetPartner(
|
||||
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "partners/{partnerId:guid}")]
|
||||
HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (req.Query.TryGetValue(PartnerIdParameter, out var id))
|
||||
{
|
||||
var partner = await partnerTable.GetItem(id);
|
||||
return new PartnerResult(partner);
|
||||
}
|
||||
return new StringResult("Please pass in partnerId query parameter");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExceptionResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
[FunctionName("CreatePartner")]
|
||||
public async Task<IActionResult> CreatePartner(
|
||||
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "partners")]
|
||||
HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestContent = await new StreamReader(req.Body).ReadToEndAsync();
|
||||
var createPartnerRequest = JsonSerializer.Deserialize<PartnerConfigRequest>(requestContent);
|
||||
|
||||
log.LogInformation($"{nameof(CreatePartner)}: {createPartnerRequest}");
|
||||
|
||||
var tenant = createPartnerRequest.Tenant;
|
||||
var name = createPartnerRequest.Name;
|
||||
var contactEmail = createPartnerRequest.ContactEmail;
|
||||
var notifyContactEmail = createPartnerRequest.NotifyContactEmail;
|
||||
var cdnConfiguration = createPartnerRequest.CdnConfiguration;
|
||||
var partner = new Partner(tenant, name, contactEmail, notifyContactEmail, new[] { cdnConfiguration });
|
||||
await partnerTable.CreateItem(partner);
|
||||
return new StringResult(partner.id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExceptionResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
[FunctionName("ListPartners")]
|
||||
public async Task<IActionResult> ListPartners(
|
||||
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "partners")]
|
||||
HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
try
|
||||
{
|
||||
var partners = await partnerTable.GetItems();
|
||||
return new EnumerableResult<PartnerResult>(partners.Select(p => new PartnerResult(p)).ToList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExceptionResult(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MultiCdnApi;
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
|
||||
[assembly: FunctionsStartup(typeof(Startup))]
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
public class Startup : FunctionsStartup
|
||||
{
|
||||
public override void Configure(IFunctionsHostBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<IRequestTable<Partner>>((s) => { return new PartnerTable(); });
|
||||
builder.Services.AddSingleton<IRequestTable<UserRequest>>((s) => { return new UserRequestTable(); });
|
||||
builder.Services.AddSingleton<IPartnerRequestTableManager<CDN>>((s) => { return new PartnerRequestTableManager(); });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.15.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.11" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CdnLibrary\src\CdnLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,32 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
|
||||
public class PartnerConfigRequest
|
||||
{
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string Tenant { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string Name { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string ContactEmail { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string NotifyContactEmail { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public CdnConfiguration CdnConfiguration { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Tenant)}: {Tenant}, " +
|
||||
$"{nameof(Name)}: {Name}, " +
|
||||
$"{nameof(ContactEmail)}: {ContactEmail}, " +
|
||||
$"{nameof(NotifyContactEmail)}: {NotifyContactEmail}, " +
|
||||
$"{nameof(CdnConfiguration)}: {CdnConfiguration}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal class PurgeRequest
|
||||
{
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public IEnumerable<string> Urls { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string Description { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string TicketId { get; set; }
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Global - used in JSON serialization/deserialization
|
||||
public string Hostname { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class EnumerableResult<T>: JsonResult where T : JsonResult
|
||||
{
|
||||
public EnumerableResult(IEnumerable<T> values) : base(values) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class ExceptionResult: JsonResult
|
||||
{
|
||||
public ExceptionResult(Exception exception) : base(exception) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class PartnerResult : JsonResult
|
||||
{
|
||||
public PartnerResult(Partner partner) : base(new object())
|
||||
{
|
||||
var cdnConfigurationResults = new List<CdnConfigurationValue>();
|
||||
var partnerCdnConfigurations = partner.CdnConfigurations;
|
||||
foreach (var cdnConfiguration in partnerCdnConfigurations)
|
||||
{
|
||||
var credentials = new Dictionary<string, string>();
|
||||
foreach (var cdnConfigurationCredentialKey in cdnConfiguration.CdnWithCredentials.Keys)
|
||||
{
|
||||
credentials[cdnConfigurationCredentialKey] = string.Empty;
|
||||
}
|
||||
|
||||
cdnConfigurationResults.Add(new CdnConfigurationValue
|
||||
{
|
||||
Hostname = cdnConfiguration.Hostname,
|
||||
CdnCredentials = credentials
|
||||
});
|
||||
}
|
||||
Value = new PartnerValue
|
||||
{
|
||||
Id = partner.id,
|
||||
TenantId = partner.TenantId,
|
||||
Name = partner.Name,
|
||||
ContactEmail = partner.ContactEmail,
|
||||
NotifyContactEmail = partner.NotifyContactEmail,
|
||||
CdnConfigurations = cdnConfigurationResults
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class PartnerValue
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string TenantId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ContactEmail { get; set; }
|
||||
public string NotifyContactEmail { get; set; }
|
||||
public List<CdnConfigurationValue> CdnConfigurations { get; set; }
|
||||
}
|
||||
|
||||
public class CdnConfigurationValue
|
||||
{
|
||||
public string Hostname { get; set; }
|
||||
public IDictionary<string, string> CdnCredentials { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class StringResult: JsonResult
|
||||
{
|
||||
public StringResult(string str) : base(str) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class UserRequestStatusResult : JsonResult
|
||||
{
|
||||
public UserRequestStatusResult(UserRequest userRequest) : base(new object())
|
||||
{
|
||||
Value = new UserRequestStatusValue
|
||||
{
|
||||
Id = userRequest.id,
|
||||
NumCompletedPartnerRequests = userRequest.NumCompletedPartnerRequests,
|
||||
NumTotalPartnerRequests = userRequest.NumTotalPartnerRequests
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class UserRequestStatusValue
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public int NumCompletedPartnerRequests { get; set; }
|
||||
public int NumTotalPartnerRequests { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class PartnerTable : CosmosDbEntityClient, IRequestTable<Partner>
|
||||
{
|
||||
private static readonly string ContainerId = EnvironmentConfig.PartnerCosmosContainerId;
|
||||
|
||||
public PartnerTable() : base(EnvironmentConfig.CosmosDBConnectionString, EnvironmentConfig.CosmosDatabaseId,
|
||||
EnvironmentConfig.PartnerCosmosContainerId, "id") { }
|
||||
|
||||
public PartnerTable(Container container) : base(container) { }
|
||||
|
||||
public async Task<IEnumerable<Partner>> GetItems()
|
||||
{
|
||||
if (Container == null)
|
||||
{
|
||||
await CreateContainer();
|
||||
}
|
||||
|
||||
using var queryIterator = Container.GetItemQueryIterator<Partner>(
|
||||
$"SELECT * FROM {ContainerId} c");
|
||||
var partners = new List<Partner>();
|
||||
while (queryIterator.HasMoreResults)
|
||||
{
|
||||
var feedResponse = await queryIterator.ReadNextAsync();
|
||||
foreach (var partner in feedResponse)
|
||||
{
|
||||
partners.Add(partner);
|
||||
}
|
||||
}
|
||||
return partners;
|
||||
}
|
||||
|
||||
public async Task CreateItem(Partner request)
|
||||
{
|
||||
await Create(request);
|
||||
}
|
||||
|
||||
public async Task UpsertItem(Partner request)
|
||||
{
|
||||
await Upsert(request);
|
||||
}
|
||||
|
||||
public async Task<Partner> GetItem(string id)
|
||||
{
|
||||
return await SelectFirstByIdAsync<Partner>(id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"samplingExcludedTypes": "Request",
|
||||
"isEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using CdnLibrary_Test;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
[TestClass]
|
||||
public class CacheFunctions_Test
|
||||
{
|
||||
private CacheFunctions cacheFunctions;
|
||||
private IPartnerRequestTableManager<CDN> partnerRequestTable;
|
||||
private PartnerTable partnerTable;
|
||||
private IRequestTable<UserRequest> userRequestTable;
|
||||
private string testPartnerId;
|
||||
|
||||
private const string TestDescription = "Test description";
|
||||
private const string TestTicketId = "Test ticket id";
|
||||
private const string TestHostname = "https://test.hostname.com";
|
||||
private const string TestPartnerId = "FakePartner";
|
||||
private const string TestTenantId = "FakeTenant";
|
||||
|
||||
private readonly IDictionary<string, UserRequest> userRequestDict = new Dictionary<string, UserRequest>();
|
||||
private readonly IDictionary<string, AfdPartnerRequest> afdPartnerRequest = new Dictionary<string, AfdPartnerRequest>();
|
||||
private readonly IDictionary<string, AkamaiPartnerRequest> akamaiPartnerRequest = new Dictionary<string, AkamaiPartnerRequest>();
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
partnerTable = new PartnerTable(CdnLibraryTestHelper.MockCosmosDbContainer(new Dictionary<string, Partner>()));
|
||||
|
||||
userRequestTable = new UserRequestTable(CdnLibraryTestHelper.MockCosmosDbContainer(userRequestDict));
|
||||
|
||||
var afdPartnerRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(afdPartnerRequest);
|
||||
var akamaiPartnerRequestContainer = CdnLibraryTestHelper.MockCosmosDbContainer(akamaiPartnerRequest);
|
||||
|
||||
partnerRequestTable = new PartnerRequestTableManager(afdPartnerRequestContainer, akamaiPartnerRequestContainer);
|
||||
|
||||
cacheFunctions = new CacheFunctions(partnerTable, userRequestTable, partnerRequestTable);
|
||||
|
||||
const string testTenantName = TestTenantId;
|
||||
const string testPartnerName = TestPartnerId;
|
||||
const string testContactEmail = "testDri@n/a.com";
|
||||
const string testNotifyContactEmail = "testNotify@n/a.com";
|
||||
const string rawCdnConfiguration = "{\"Hostname\": \"\", \"CdnWithCredentials\": {\"AFD\":\"\", \"Akamai\":\"\"}}";
|
||||
|
||||
var partner = new Partner(testTenantName, testPartnerName, testContactEmail, testNotifyContactEmail, new[] { new CdnConfiguration(rawCdnConfiguration) });
|
||||
partnerTable.CreateItem(partner).Wait();
|
||||
testPartnerId = partner.id;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateCachePurgeRequestByHostname()
|
||||
{
|
||||
var userRequestId = await CallPurgeFunctionWithDefaultParameters();
|
||||
var savedUserRequest = await userRequestTable.GetItem(userRequestId);
|
||||
var partnerRequest = await partnerRequestTable.GetPartnerRequest(savedUserRequest.id, CDN.AFD);
|
||||
AssertIsTestRequest(partnerRequest);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateCachePurgeRequestByHostname_Fail()
|
||||
{
|
||||
var defaultHttpRequest = new DefaultHttpRequest(new DefaultHttpContext())
|
||||
{
|
||||
Body = new MemoryStream(Encoding.UTF8.GetBytes(TestHostname))
|
||||
};
|
||||
var result = await cacheFunctions.CreateCachePurgeRequestByHostname(
|
||||
defaultHttpRequest,
|
||||
null,
|
||||
Mock.Of<ILogger>());
|
||||
Assert.IsTrue(result is JsonResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CreateCachePurgeRequestByHostname_CosmosDbSerialization()
|
||||
{
|
||||
_ = await CallPurgeFunctionWithDefaultParameters();
|
||||
|
||||
var partnerRequest = afdPartnerRequest.First();
|
||||
AssertIsTestRequest(partnerRequest.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCachePurgeStatus()
|
||||
{
|
||||
var userRequestId = await CallPurgeFunctionWithDefaultParameters();
|
||||
var userRequestStatusResult = await CallPurgeStatus(userRequestId);
|
||||
Assert.AreEqual(typeof(UserRequestStatusValue), userRequestStatusResult.Value.GetType());
|
||||
var userRequestStatusValue = (UserRequestStatusValue) userRequestStatusResult.Value;
|
||||
Assert.AreEqual(userRequestId, userRequestStatusValue.Id);
|
||||
Assert.AreEqual(2, userRequestStatusValue.NumTotalPartnerRequests); // we have 2 plugins
|
||||
Assert.AreEqual(0, userRequestStatusValue.NumCompletedPartnerRequests); // 0 because it is not initialized in plugins yet
|
||||
}
|
||||
|
||||
private static void AssertIsTestRequest(IPartnerRequest partnerRequest)
|
||||
{
|
||||
var afdPartnerRequest = partnerRequest as AfdPartnerRequest;
|
||||
Assert.IsNotNull(afdPartnerRequest);
|
||||
Assert.AreEqual($"{TestDescription} ({TestTicketId})", afdPartnerRequest.Description);
|
||||
Assert.AreEqual(1, partnerRequest.Urls.Count);
|
||||
Assert.IsTrue(partnerRequest.Urls.Contains(TestHostname));
|
||||
Assert.AreEqual(CDN.AFD.ToString(), partnerRequest.CDN);
|
||||
Assert.AreEqual(TestTenantId, afdPartnerRequest.TenantID);
|
||||
Assert.AreEqual(TestPartnerId, afdPartnerRequest.PartnerID);
|
||||
Assert.AreEqual(partnerRequest.UserRequestID, partnerRequest.UserRequestID);
|
||||
}
|
||||
|
||||
private async Task<string> CallPurgeFunctionWithDefaultParameters()
|
||||
{
|
||||
var defaultHttpRequest = new DefaultHttpRequest(new DefaultHttpContext())
|
||||
{
|
||||
Body = new MemoryStream(Encoding.UTF8.GetBytes("{" +
|
||||
$@"""Description"": ""{TestDescription}""," +
|
||||
$@"""TicketId"": ""{TestTicketId}""," +
|
||||
$@"""Hostname"": ""{TestHostname}""," +
|
||||
$@"""Urls"": [""{TestHostname}""]" +
|
||||
"}"))
|
||||
};
|
||||
var result = await cacheFunctions.CreateCachePurgeRequestByHostname(
|
||||
defaultHttpRequest,
|
||||
testPartnerId,
|
||||
Mock.Of<ILogger>());
|
||||
Assert.AreEqual(typeof(StringResult), result.GetType());
|
||||
Assert.IsTrue(((StringResult) result).Value is string);
|
||||
return (string) ((StringResult) result).Value;
|
||||
}
|
||||
|
||||
private async Task<UserRequestStatusResult> CallPurgeStatus(string userRequestId)
|
||||
{
|
||||
var defaultHttpRequest = new DefaultHttpRequest(new DefaultHttpContext());
|
||||
var statusResponse = await cacheFunctions.CachePurgeRequestByHostnameStatus(
|
||||
defaultHttpRequest,
|
||||
testPartnerId,
|
||||
userRequestId,
|
||||
Mock.Of<ILogger>());
|
||||
Assert.AreEqual(typeof(UserRequestStatusResult), statusResponse.GetType());
|
||||
var userRequestStatusResult = (UserRequestStatusResult) statusResponse;
|
||||
return userRequestStatusResult;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Teardown()
|
||||
{
|
||||
partnerRequestTable.Dispose();
|
||||
partnerTable.Dispose();
|
||||
userRequestTable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CachePurgeLibrary;
|
||||
using CdnLibrary;
|
||||
using CdnLibrary_Test;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
[TestClass]
|
||||
public class PartnersFunctions_Test
|
||||
{
|
||||
private PartnerFunctions partnerFunctions;
|
||||
private IRequestTable<Partner> partnerTable;
|
||||
|
||||
private const string TenantId = "FakeTenant";
|
||||
private const string Name = "FakePartner";
|
||||
private const string DriContact = "driContact@example.test";
|
||||
private const string NotifyContact = "notifyContact@example.test";
|
||||
private const string TestHostname = "test_hostname";
|
||||
|
||||
private readonly Dictionary<string, Partner> partners = new Dictionary<string, Partner>();
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
partnerTable = new PartnerTable(CdnLibraryTestHelper.MockCosmosDbContainer(partners));
|
||||
partnerFunctions = new PartnerFunctions(partnerTable);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCreatePartner_Success()
|
||||
{
|
||||
var testPartnerResponse = CreateTestPartner();
|
||||
|
||||
Assert.AreEqual(typeof(StringResult), testPartnerResponse.GetType());
|
||||
var partnerGuid = ((StringResult) testPartnerResponse).Value.ToString();
|
||||
Assert.IsTrue(partners.Count > 0);
|
||||
var partner = partnerTable.GetItem(partnerGuid).Result;
|
||||
|
||||
TestPartner(partner);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCreatePartner_Fail()
|
||||
{
|
||||
var createPartnerResponse = partnerFunctions.CreatePartner(new DefaultHttpRequest(new DefaultHttpContext())
|
||||
{
|
||||
Query = new QueryCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
["Test"] = "Bad"
|
||||
}
|
||||
)}, null).Result;
|
||||
|
||||
Assert.AreEqual(typeof(ExceptionResult), createPartnerResponse.GetType());
|
||||
}
|
||||
|
||||
private static void TestPartner(Partner partner)
|
||||
{
|
||||
Assert.AreEqual(TenantId, partner.TenantId);
|
||||
Assert.AreEqual(Name, partner.Name);
|
||||
Assert.AreEqual(DriContact, partner.ContactEmail);
|
||||
Assert.AreEqual(NotifyContact, partner.NotifyContactEmail);
|
||||
var partnerCdnConfigurations = partner.CdnConfigurations.ToList();
|
||||
Assert.AreEqual(1, partnerCdnConfigurations.Count);
|
||||
Assert.AreEqual(TestHostname, partnerCdnConfigurations[0].Hostname);
|
||||
var cdnWithCredentials = partnerCdnConfigurations[0].CdnWithCredentials;
|
||||
Assert.AreEqual(2, cdnWithCredentials.Count);
|
||||
Assert.AreEqual("", cdnWithCredentials[CDN.AFD.ToString()]);
|
||||
Assert.AreEqual("", cdnWithCredentials[CDN.Akamai.ToString()]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void TestPartnerSerialization(JsonResult partner)
|
||||
{
|
||||
Assert.AreEqual(typeof(PartnerValue), partner.Value.GetType());
|
||||
var partnerValue = (PartnerValue) partner.Value;
|
||||
Assert.AreEqual(TenantId, partnerValue.TenantId);
|
||||
Assert.AreEqual(Name, partnerValue.Name);
|
||||
Assert.AreEqual(DriContact, partnerValue.ContactEmail);
|
||||
Assert.AreEqual(NotifyContact, partnerValue.NotifyContactEmail);
|
||||
var partnerCdnConfigurations = partnerValue.CdnConfigurations.ToList();
|
||||
Assert.AreEqual(1, partnerCdnConfigurations.Count);
|
||||
Assert.AreEqual(TestHostname, partnerCdnConfigurations[0].Hostname);
|
||||
var cdnWithCredentials = partnerCdnConfigurations[0].CdnCredentials;
|
||||
Assert.AreEqual(2, cdnWithCredentials.Count);
|
||||
Assert.AreEqual("", cdnWithCredentials[CDN.AFD.ToString()]);
|
||||
Assert.AreEqual("", cdnWithCredentials[CDN.Akamai.ToString()]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetPartner_Success()
|
||||
{
|
||||
var testPartnerResponse = CreateTestPartner();
|
||||
Assert.AreEqual(typeof(StringResult), testPartnerResponse.GetType());
|
||||
var partnerId = ((StringResult) testPartnerResponse).Value.ToString();
|
||||
|
||||
var partnerResult = partnerFunctions.GetPartner(new DefaultHttpRequest(new DefaultHttpContext())
|
||||
{
|
||||
Query = new QueryCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
["partnerId"] = partnerId
|
||||
})
|
||||
}, null).Result;
|
||||
|
||||
Assert.AreEqual(typeof(PartnerResult), partnerResult.GetType());
|
||||
|
||||
var partner = (PartnerResult) partnerResult;
|
||||
TestPartnerSerialization(partner);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetPartner_Fail()
|
||||
{
|
||||
var partnerResult = partnerFunctions.GetPartner(new DefaultHttpRequest(new DefaultHttpContext())
|
||||
{
|
||||
Query = new QueryCollection()
|
||||
}, null).Result;
|
||||
|
||||
Assert.AreEqual(typeof(StringResult), partnerResult.GetType());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestListPartners()
|
||||
{
|
||||
this.partners.Clear();
|
||||
CreateTestPartner();
|
||||
var partnersResponse =
|
||||
partnerFunctions.ListPartners(new DefaultHttpRequest(new DefaultHttpContext()), null).Result;
|
||||
Assert.AreEqual(typeof(EnumerableResult<PartnerResult>), partnersResponse.GetType());
|
||||
var partnersValue = ((EnumerableResult<PartnerResult>) partnersResponse).Value;
|
||||
|
||||
var retrievedPartners = partnersValue as IEnumerable<PartnerResult>;
|
||||
Assert.IsNotNull(retrievedPartners);
|
||||
var retrievedPartnersList = retrievedPartners.ToList();
|
||||
|
||||
Assert.AreEqual(1, retrievedPartnersList.Count);
|
||||
TestPartnerSerialization(retrievedPartnersList.First());
|
||||
}
|
||||
|
||||
private IActionResult CreateTestPartner()
|
||||
{
|
||||
var createPartnerResponse = partnerFunctions.CreatePartner(new DefaultHttpRequest(new DefaultHttpContext())
|
||||
{
|
||||
Body = new MemoryStream(Encoding.UTF8.GetBytes("{" +
|
||||
$@"""Tenant"": ""{TenantId}""," +
|
||||
$@"""Name"": ""{Name}""," +
|
||||
$@"""ContactEmail"": ""{DriContact}""," +
|
||||
$@"""NotifyContactEmail"": ""{NotifyContact}""," +
|
||||
$@"""CdnConfiguration"": {{""Hostname"": ""{TestHostname}"", ""CdnWithCredentials"": {{""AFD"":"""", ""Akamai"":""""}}}}" +
|
||||
"}"))
|
||||
}, Mock.Of<ILogger>());
|
||||
return createPartnerResponse.Result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/* -----------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
namespace MultiCdnApi
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CachePurgeLibrary;
|
||||
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
public class FunctionsStartup_Test
|
||||
{
|
||||
private IFunctionsHostBuilder functionsHostBuilder;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var multiCdnFunctionsStartup = new Startup();
|
||||
functionsHostBuilder = Mock.Of<IFunctionsHostBuilder>(b => b.Services == new ServiceCollection());
|
||||
multiCdnFunctionsStartup.Configure(functionsHostBuilder);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetupPartnerTable()
|
||||
{
|
||||
var partnerTableService = FindServiceByType(functionsHostBuilder, typeof(IRequestTable<Partner>));
|
||||
Assert.IsNotNull(partnerTableService);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetupUserRequestTable()
|
||||
{
|
||||
var userRequestTable = FindServiceByType(functionsHostBuilder, typeof(IRequestTable<UserRequest>));
|
||||
Assert.IsNotNull(userRequestTable);
|
||||
}
|
||||
|
||||
private static ServiceDescriptor FindServiceByType(IFunctionsHostBuilder functionsHostBuilder, Type type)
|
||||
{
|
||||
return functionsHostBuilder.Services.First(t => t.ServiceType == type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CdnLibrary\src\CdnLibrary.csproj" />
|
||||
<ProjectReference Include="..\..\CdnLibrary\tests\CdnLibrary_Test.csproj" />
|
||||
<ProjectReference Include="..\src\MultiCdnApi.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
53
README.md
53
README.md
|
@ -1,33 +1,32 @@
|
|||
# Project
|
||||
|
||||
> This repo has been populated by an initial template to help get you started. Please
|
||||
> make sure to update the content to build a great experience for community-building.
|
||||
# CachePurge: Cache Invalidation and Purge for multiple CDNs
|
||||
CachePurge is a centralized service that allows the user to invalidate and purge cache from multiple CDNs easily. CacheOut is implemented in C# using the dotnet core framework
|
||||
|
||||
As the maintainer of this project, please make a few updates:
|
||||
## Introduction
|
||||
CachePurge uses a combination of [Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/), [CosmosDB](https://docs.microsoft.com/en-us/azure/cosmos-db/) and [Azure Queues](https://docs.microsoft.com/en-us/azure/storage/queues/storage-queues-introduction) to create a workflow that begins upon receiving a purge request from the user.
|
||||
|
||||
- Improving this README.MD file to provide a great experience
|
||||
- Updating SUPPORT.MD with content about this project's support experience
|
||||
- Understanding the security reporting process in SECURITY.MD
|
||||
- Remove this section from the README
|
||||
The design is as follows:
|
||||
![Cache Purge Initial Design](InitialDesign.png)
|
||||
|
||||
## Contributing
|
||||
# Getting Started
|
||||
#### Local Setup
|
||||
- Refer to this doc for prerequisites: [Code and Test Azure functions locally](https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-local)
|
||||
- Clone this repo to your local drive
|
||||
- Once your local environment is setup, edit [EnvironmentConfig.cs](CdnLibrary/src/Utils/EnvironmentConfig.cs) with your config values
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
||||
#### Making changes
|
||||
The project structure is divided into the following:
|
||||
a) **CachePurgeLibrary**:
|
||||
- non-CDN specific code that is utilized throughout the solution
|
||||
- no changes needed if you're not making significant structural changes to the project
|
||||
b) **CdnLibrary**:
|
||||
- CDN library code used in CdnPlugins and MultiCdnApi
|
||||
- will need to edit this project heavily to add your own custom CDN specific processing logic
|
||||
c) **CdnPlugins**:
|
||||
- contains CdnPlugin functions and EventCompletion function
|
||||
- change as needed based on CDNs used
|
||||
d) **MultiCdnApi**:
|
||||
- contains user facing API
|
||||
- change as needed based on CDNs and user input
|
||||
|
||||
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Trademarks
|
||||
|
||||
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
||||
trademarks or logos is subject to and must follow
|
||||
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
||||
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
||||
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
||||
To make changes to the CDNs for e.g. to add or remove a CDN, you will need to add your custom CDN logic to [CdnLibrary](CdnLibrary/src/CdnLibrary.csproj). Propagate the changes down to [CdnPlugins](CdnPlugins/src/CdnPlugins.csproj) and [MultiCdnApi](MultiCdnApi/src/MultiCdnApi.csproj).
|
10
SUPPORT.md
10
SUPPORT.md
|
@ -1,13 +1,3 @@
|
|||
# TODO: The maintainer of this repo has not yet edited this file
|
||||
|
||||
**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
|
||||
|
||||
- **No CSS support:** Fill out this template with information about how to file issues and get help.
|
||||
- **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
|
||||
- **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
|
||||
|
||||
*Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
|
||||
|
||||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
|
Загрузка…
Ссылка в новой задаче