Upgrading WebJobs.Host.Storage to v12 Azure Storage sdk (#2764)

Changes in this PR:
- Upgrading WebJobs.Host.Storage to use v12 Azure storage SDK
  - New major version of WebJobs.Host.Storage
  - New public types for IDistributedLockManager implementation, IAzureBlobStorageProvider
- Removing Track1 Microsoft.Azure.WebJobs.Extensions.Storage from solution and build files (code remains in repo) 
- Migrating E2E and Functional Tests to use the Track2 Microsoft.Azure.WebJobs.Extensions.Storage packages
This commit is contained in:
karshinlin 2021-10-15 19:25:56 -04:00 коммит произвёл GitHub
Родитель 8c90eb9bf2
Коммит b6d5b52da5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
81 изменённых файлов: 2105 добавлений и 5461 удалений

Просмотреть файл

@ -20,7 +20,6 @@ $projects =
"src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.Sources.csproj",
"src\Microsoft.Azure.WebJobs.Logging\WebJobs.Logging.csproj",
"src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj",
"src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj",
"src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj",
"test\Microsoft.Azure.WebJobs.Host.TestCommon\WebJobs.Host.TestCommon.csproj"

Просмотреть файл

@ -35,14 +35,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{3B089351
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "WebJobs.Shared", "src\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.shproj", "{ADD036F5-2170-4B05-9E0A-C2ED0A08A929}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.Storage", "src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj", "{A9733406-267C-4A53-AB07-D3A834E22153}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Host.Storage", "src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj", "{DED33098-FE99-436C-96CC-B59A30BEF027}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "WebJobs.Shared.Storage", "src\Microsoft.Azure.WebJobs.Shared.Storage\WebJobs.Shared.Storage.shproj", "{6BED7F8A-A199-4D9D-85D1-6856EE3292C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.Storage.UnitTests", "test\Microsoft.Azure.Webjobs.Extensions.Storage.UnitTests\WebJobs.Extensions.Storage.UnitTests.csproj", "{0CC5741F-ACDA-4DB8-9C17-074E8896F244}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "WebJobs.Protocols", "src\Microsoft.Azure.WebJobs.Protocols\WebJobs.Protocols.shproj", "{6FCD0852-6019-4CD5-9B7E-0DE021A72BD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FakeAzureStorage", "test\FakeStorage\FakeAzureStorage.csproj", "{337B79EB-A3CB-4CE0-A7F2-DD5E638AC882}"
@ -59,8 +55,6 @@ Global
src\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.projitems*{39554abe-d7b7-45ff-8d7c-56432abcde3d}*SharedItemsImports = 5
src\Microsoft.Azure.WebJobs.Shared.Storage\Microsoft.Azure.WebJobs.Shared.Storage.projitems*{6bed7f8a-a199-4d9d-85d1-6856ee3292c6}*SharedItemsImports = 13
src\Microsoft.Azure.WebJobs.Protocols\Microsoft.Azure.WebJobs.Protocols.projitems*{6fcd0852-6019-4cd5-9b7e-0de021a72bd7}*SharedItemsImports = 13
src\Microsoft.Azure.WebJobs.Shared.Storage\Microsoft.Azure.WebJobs.Shared.Storage.projitems*{a9733406-267c-4a53-ab07-d3a834e22153}*SharedItemsImports = 5
src\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.projitems*{a9733406-267c-4a53-ab07-d3a834e22153}*SharedItemsImports = 5
src\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.projitems*{add036f5-2170-4b05-9e0a-c2ed0a08a929}*SharedItemsImports = 13
src\Microsoft.Azure.WebJobs.Shared.Storage\Microsoft.Azure.WebJobs.Shared.Storage.projitems*{ded33098-fe99-436c-96cc-b59a30bef027}*SharedItemsImports = 5
src\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.projitems*{ded33098-fe99-436c-96cc-b59a30bef027}*SharedItemsImports = 5
@ -110,18 +104,10 @@ Global
{340AB554-5482-4B3D-B65F-46DFF5AF1684}.Debug|Any CPU.Build.0 = Debug|Any CPU
{340AB554-5482-4B3D-B65F-46DFF5AF1684}.Release|Any CPU.ActiveCfg = Release|Any CPU
{340AB554-5482-4B3D-B65F-46DFF5AF1684}.Release|Any CPU.Build.0 = Release|Any CPU
{A9733406-267C-4A53-AB07-D3A834E22153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9733406-267C-4A53-AB07-D3A834E22153}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9733406-267C-4A53-AB07-D3A834E22153}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9733406-267C-4A53-AB07-D3A834E22153}.Release|Any CPU.Build.0 = Release|Any CPU
{DED33098-FE99-436C-96CC-B59A30BEF027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DED33098-FE99-436C-96CC-B59A30BEF027}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DED33098-FE99-436C-96CC-B59A30BEF027}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DED33098-FE99-436C-96CC-B59A30BEF027}.Release|Any CPU.Build.0 = Release|Any CPU
{0CC5741F-ACDA-4DB8-9C17-074E8896F244}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CC5741F-ACDA-4DB8-9C17-074E8896F244}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CC5741F-ACDA-4DB8-9C17-074E8896F244}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CC5741F-ACDA-4DB8-9C17-074E8896F244}.Release|Any CPU.Build.0 = Release|Any CPU
{337B79EB-A3CB-4CE0-A7F2-DD5E638AC882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{337B79EB-A3CB-4CE0-A7F2-DD5E638AC882}.Debug|Any CPU.Build.0 = Debug|Any CPU
{337B79EB-A3CB-4CE0-A7F2-DD5E638AC882}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -145,7 +131,6 @@ Global
{C6B834AB-7B6A-47AE-A7C3-C102B0C861FF} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{340AB554-5482-4B3D-B65F-46DFF5AF1684} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{0CC5741F-ACDA-4DB8-9C17-074E8896F244} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{337B79EB-A3CB-4CE0-A7F2-DD5E638AC882} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{C5E1A8E8-711F-4377-A8BD-7DB58E6C580D} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{11702A4B-8402-4082-BE38-4F0C2CBBF61D} = {C5E1A8E8-711F-4377-A8BD-7DB58E6C580D}

Просмотреть файл

@ -2,8 +2,7 @@
<PropertyGroup>
<!-- Packages can have independent versions and only increment when released -->
<Version>3.0.31$(VersionSuffix)</Version>
<ExtensionsStorageVersion>4.0.5$(VersionSuffix)</ExtensionsStorageVersion>
<HostStorageVersion>4.0.4$(VersionSuffix)</HostStorageVersion>
<HostStorageVersion>5.0.0-beta.1$(VersionSuffix)</HostStorageVersion>
<LoggingVersion>4.0.2$(VersionSuffix)</LoggingVersion>
<TargetFramework>netstandard2.0</TargetFramework>

Просмотреть файл

@ -4,8 +4,7 @@ $projects =
"test\Microsoft.Azure.WebJobs.Host.UnitTests",
"test\Microsoft.Azure.WebJobs.Host.FunctionalTests",
"test\Microsoft.Azure.WebJobs.Logging.FunctionalTests",
"test\Microsoft.Azure.WebJobs.Host.EndToEndTests",
"test\Microsoft.Azure.Webjobs.Extensions.Storage.UnitTests"
"test\Microsoft.Azure.WebJobs.Host.EndToEndTests"
foreach ($project in $projects)

Просмотреть файл

@ -18,7 +18,8 @@ namespace SampleHost
.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices()
.AddAzureStorage()
.AddAzureStorageBlobs()
.AddAzureStorageQueues()
.AddServiceBus()
.AddEventHubs();
})

Просмотреть файл

@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventHubs" Version="3.0.6" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.1.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.0-beta.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
@ -22,7 +23,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj" />

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Azure.WebJobs.Host.Storage
{
/// <summary>
/// Handles instantiating Azure storage clients from an <see cref="IConfiguration"/> source.
/// Certain configuration settings are used to instantiate the clients. These include settings
/// necessary to construct the Azure service URIs and settings to specify credential related
/// information (i.e. clientId, tenantId, etc. where applicable).
/// <see cref="Microsoft.Extensions.Azure.ClientFactory"/> is where a bulk of the
/// <see cref="IConfiguration"/> source is read.
/// This implementation adds extra configuration by using <see cref="StorageServiceUriOptions"/> to bind to
/// a particular <see cref="IConfigurationSection"/>.
/// </summary>
internal class AzureStorageProvider : IAzureBlobStorageProvider
{
private readonly BlobServiceClientProvider _blobServiceClientProvider;
private readonly ILogger<AzureStorageProvider> _logger;
private readonly IConfiguration _configuration;
private readonly IOptionsMonitor<JobHostInternalStorageOptions> _storageOptions;
public AzureStorageProvider(IConfiguration configuration, IOptionsMonitor<JobHostInternalStorageOptions> options, ILogger<AzureStorageProvider> logger, AzureComponentFactory componentFactory, AzureEventSourceLogForwarder logForwarder)
{
_configuration = configuration;
_storageOptions = options;
_logger = logger;
_blobServiceClientProvider = new BlobServiceClientProvider(componentFactory, logForwarder);
}
public virtual bool TryCreateHostingBlobContainerClient(out BlobContainerClient blobContainerClient)
{
if (_storageOptions?.CurrentValue.InternalSasBlobContainer != null)
{
blobContainerClient = new BlobContainerClient(new Uri(_storageOptions.CurrentValue.InternalSasBlobContainer));
_logger.LogDebug($"Using storage account {blobContainerClient.AccountName} and container {blobContainerClient.Name} for hosting BlobContainerClient.");
return true;
}
if (!TryCreateBlobServiceClientFromConnection(ConnectionStringNames.Storage, out BlobServiceClient blobServiceClient))
{
_logger.LogDebug($"Could not create BlobContainerClient using Connection: {ConnectionStringNames.Storage}");
blobContainerClient = default;
return false;
}
blobContainerClient = blobServiceClient.GetBlobContainerClient(HostContainerNames.Hosts);
return true;
}
public virtual bool TryCreateBlobServiceClientFromConnection(string connection, out BlobServiceClient client)
{
var connectionToUse = connection ?? ConnectionStringNames.Storage;
try
{
client = _blobServiceClientProvider.Create(connectionToUse, _configuration);
return true;
}
catch (Exception e)
{
_logger.LogDebug($"Could not create BlobServiceClient. Exception: {e}");
client = default;
return false;
}
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Azure.Core;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Azure.WebJobs.Host.Storage
{
/// <summary>
/// Pass-through class to create a <see cref="BlobServiceClient"/> using <see cref="AzureComponentFactory"/>.
/// This class uses additional configuration settings for a specified connection in addition to those in <see cref="AzureComponentFactory"/>.
/// To support scenarios where a storage connection (i.e. AzureWebJobsStorage) needs to reference multiple serviceUris (blob and queue),
/// properties in <see cref="StorageServiceUriOptions"/> are supported to create the Azure blob service URI.
/// </summary>
internal class BlobServiceClientProvider : StorageClientProvider<BlobServiceClient, BlobClientOptions>
{
/// <summary>
/// Initializes a new instance of the <see cref="BlobServiceClientProvider"/> class that uses the registered Azure services to create a BlobServiceClient.
/// </summary>
/// <param name="componentFactory">The Azure factory responsible for creating clients. <see cref="AzureComponentFactory"/></param>
/// <param name="logForwarder">Log forwarder that forwards events to ILogger. <see cref="AzureEventSourceLogForwarder"/></param>
public BlobServiceClientProvider(AzureComponentFactory componentFactory, AzureEventSourceLogForwarder logForwarder)
: base(componentFactory, logForwarder) { }
/// <summary>
/// Provides logic to create a <see cref="BlobServiceClient"/> from an <see cref="IConfiguration"/> source. This class
/// supports using configuration settings (AccountName and BlobServiceUri) in <see cref="StorageServiceUriOptions"/> in
/// addition to those in <see cref="AzureComponentFactory"/>.
/// If no serviceUri can be constructed using the additional settings, this method falls back to the logic in
/// <see cref="AzureComponentFactory"/>.
/// </summary>
/// <param name="configuration">Configuration to retrieve settings from.</param>
/// <param name="tokenCredential">Credential for the client.</param>
/// <param name="options">Options to configure the client.</param>
/// <returns>An instance of <see cref="BlobServiceClient"/></returns>
protected override BlobServiceClient CreateClient(IConfiguration configuration, TokenCredential tokenCredential, BlobClientOptions options)
{
// If connection string is present, it will be honored first; bypass creating a serviceUri
if (!IsConnectionStringPresent(configuration))
{
var serviceUri = configuration.Get<StorageServiceUriOptions>().GetBlobServiceUri();
if (serviceUri != null)
{
return new BlobServiceClient(serviceUri, tokenCredential, options);
}
}
return base.CreateClient(configuration, tokenCredential, options);
}
/// <summary>
/// An options class that constructs a storage service URI from a different properties.
/// These properties are specific to WebJobs, as there may be other relevant properties used downstream
/// to create storage clients.
/// <seealso cref="Microsoft.Extensions.Azure.ClientFactory" />
/// A storage service URI can be built using just the account name along with default
/// parameters for Scheme and Endpoint Suffix.
/// </summary>
internal class StorageServiceUriOptions
{
private const string DefaultScheme = "https";
private const string DefaultEndpointSuffix = "core.windows.net";
/// <summary>
/// The resource URI for blob storage. If this property is given explicitly, it will be
/// honored over the AccountName property.
/// </summary>
public string BlobServiceUri { get; set; }
/// <summary>
/// The name of the storage account.
/// </summary>
public string AccountName { get; set; }
/// <summary>
/// Constructs the blob service URI from the properties in this class.
/// First checks if BlobServiceUri is specified. If not, the AccountName is used
/// to construct a blob service URI with https scheme and core.windows.net endpoint suffix.
/// </summary>
/// <returns>Service URI to Azure blob storage</returns>
public Uri GetBlobServiceUri()
{
if (!string.IsNullOrEmpty(BlobServiceUri))
{
return new Uri(BlobServiceUri);
}
else if (!string.IsNullOrEmpty(AccountName))
{
var uri = string.Format(CultureInfo.InvariantCulture, "{0}://{1}.blob.{2}", DefaultScheme, AccountName, DefaultEndpointSuffix);
return new Uri(uri);
}
return default;
}
}
}
}

Просмотреть файл

@ -4,15 +4,14 @@
#nullable enable
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Azure;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@ -20,17 +19,16 @@ namespace Microsoft.Azure.WebJobs.Host
{
internal class BlobStorageConcurrencyStatusRepository : IConcurrencyStatusRepository
{
private const string HostContainerName = "azure-webjobs-hosts";
private readonly IHostIdProvider _hostIdProvider;
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
private CloudBlobContainer? _blobContainer;
private readonly IAzureBlobStorageProvider _blobStorageProvider;
private BlobContainerClient? _blobContainerClient;
public BlobStorageConcurrencyStatusRepository(IConfiguration configuration, IHostIdProvider hostIdProvider, ILoggerFactory loggerFactory)
public BlobStorageConcurrencyStatusRepository(IHostIdProvider hostIdProvider, ILoggerFactory loggerFactory, IAzureBlobStorageProvider azureStorageProvider)
{
_configuration = configuration;
_hostIdProvider = hostIdProvider;
_logger = loggerFactory.CreateLogger(LogCategories.Concurrency);
_blobStorageProvider = azureStorageProvider;
}
public async Task<HostConcurrencySnapshot?> ReadAsync(CancellationToken cancellationToken)
@ -39,11 +37,12 @@ namespace Microsoft.Azure.WebJobs.Host
try
{
CloudBlobContainer? container = await GetContainerAsync(cancellationToken);
if (container != null)
BlobContainerClient? containerClient = await GetContainerClientAsync(cancellationToken);
if (containerClient != null)
{
CloudBlockBlob blob = container.GetBlockBlobReference(blobPath);
string content = await blob.DownloadTextAsync(cancellationToken);
BlobClient blobClient = containerClient.GetBlobClient(blobPath);
string content = await blobClient.DownloadTextAsync(cancellationToken: cancellationToken);
if (!string.IsNullOrEmpty(content))
{
var result = JsonConvert.DeserializeObject<HostConcurrencySnapshot>(content);
@ -51,8 +50,9 @@ namespace Microsoft.Azure.WebJobs.Host
}
}
}
catch (StorageException stex) when (stex.RequestInformation?.HttpStatusCode == 404)
catch (RequestFailedException exception) when (exception.Status == 404)
{
// we haven't recorded a status yet
return null;
}
catch (Exception e)
@ -70,16 +70,12 @@ namespace Microsoft.Azure.WebJobs.Host
try
{
CloudBlobContainer? container = await GetContainerAsync(cancellationToken);
if (container != null)
BlobContainerClient? containerClient = await GetContainerClientAsync(cancellationToken);
if (containerClient != null)
{
CloudBlockBlob blob = container.GetBlockBlobReference(blobPath);
using (StreamWriter writer = new StreamWriter(await blob.OpenWriteAsync(cancellationToken)))
{
var content = JsonConvert.SerializeObject(snapshot);
await writer.WriteAsync(content);
}
BlobClient blobClient = containerClient.GetBlobClient(blobPath);
var content = JsonConvert.SerializeObject(snapshot);
await blobClient.UploadTextAsync(content, overwrite: true, cancellationToken: cancellationToken);
}
}
catch (Exception e)
@ -89,21 +85,14 @@ namespace Microsoft.Azure.WebJobs.Host
}
}
internal async Task<CloudBlobContainer?> GetContainerAsync(CancellationToken cancellationToken)
internal async Task<BlobContainerClient?> GetContainerClientAsync(CancellationToken cancellationToken)
{
if (_blobContainer == null)
if (_blobContainerClient == null && _blobStorageProvider.TryCreateHostingBlobContainerClient(out _blobContainerClient))
{
string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
if (!string.IsNullOrEmpty(storageConnectionString) && CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account))
{
var client = account.CreateCloudBlobClient();
_blobContainer = client.GetContainerReference(HostContainerName);
await _blobContainer.CreateIfNotExistsAsync(cancellationToken);
}
await _blobContainerClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
}
return _blobContainer;
return _blobContainerClient;
}
internal async Task<string> GetBlobPathAsync(CancellationToken cancellationToken)

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Azure.Storage.Blobs;
namespace Microsoft.Azure.WebJobs.Host.Storage
{
/// <summary>
/// Provides methods for creating Azure blob storage clients, ensuring all necessary configuration is applied.
/// Implementations are responsible instantiating these clients and using desired options, credentials, or service URIs.
/// </summary>
public interface IAzureBlobStorageProvider
{
/// <summary>
/// Attempts to create a client for the hosting container used for internal storage.
/// </summary>
/// <returns>A <see cref="BlobContainerClient"/> for the hosting container.</returns>
bool TryCreateHostingBlobContainerClient(out BlobContainerClient blobContainerClient);
/// <summary>
/// Attempts to create the <see cref="BlobServiceClient"/> from the specified connection.
/// </summary>
/// <param name="connection">connection name to use.</param>
/// <param name="client"><see cref="BlobServiceClient"/> to instantiate.</param>
/// <returns>Whether the attempt was successful.</returns>
bool TryCreateBlobServiceClientFromConnection(string connection, out BlobServiceClient client);
}
}

Просмотреть файл

@ -1,37 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Extensions.Options;
using Microsoft.Azure.Storage.Blob;
using System;
namespace Microsoft.Azure.WebJobs
{
/// <summary>
/// Config object for providing a container for distributed lock manager.
/// This is hydrated from a <see cref="JobHostInternalStorageOptions"/>
/// </summary>
public class DistributedLockManagerContainerProvider
{
public DistributedLockManagerContainerProvider() { }
public DistributedLockManagerContainerProvider(IOptions<JobHostInternalStorageOptions> x )
{
var sasBlobContainer = x.Value.InternalSasBlobContainer;
if (sasBlobContainer != null)
{
var uri = new Uri(sasBlobContainer);
this.InternalContainer = new CloudBlobContainer(uri);
}
}
/// <summary>
/// A SAS to a Blob Container. This allows services to create blob leases and do distributed locking.
/// If this is set, <see cref="JobHostConfiguration.StorageConnectionString"/> and
/// <see cref="JobHostConfiguration.DashboardConnectionString"/> can be set to null and the runtime will use the container.
/// </summary>
public CloudBlobContainer InternalContainer { get; set; }
}
/// <summary>
/// The storage configuration that the JobHost needs for its own operations (independent of binding)
/// For example, this can support <see cref="SingletonAttribute"/>, blob leases, timers, etc.

Просмотреть файл

@ -1,57 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage.Blob;
using WebJobs.Host.Storage.Logging;
namespace Microsoft.Azure.WebJobs.Host.Loggers
{
// Wrap facilities for logging a function's output.
// This means capturing console out, redirecting to a TraceWriter that is available at a blob.
// Handle incremental updates to get real-time updates for long running functions.
internal sealed class BlobFunctionOutputDefinition : IFunctionOutputDefinition
{
private readonly CloudBlobClient _client;
private readonly LocalBlobDescriptor _outputBlob;
private readonly LocalBlobDescriptor _parameterLogBlob;
public BlobFunctionOutputDefinition(CloudBlobClient client, LocalBlobDescriptor outputBlob, LocalBlobDescriptor parameterLogBlob)
{
_client = client;
_outputBlob = outputBlob;
_parameterLogBlob = parameterLogBlob;
}
public LocalBlobDescriptor OutputBlob
{
get { return _outputBlob; }
}
public LocalBlobDescriptor ParameterLogBlob
{
get { return _parameterLogBlob; }
}
public IFunctionOutput CreateOutput()
{
var blob = GetBlockBlobReference(_outputBlob);
return UpdateOutputLogCommand.Create(blob);
}
public IRecurrentCommand CreateParameterLogUpdateCommand(IReadOnlyDictionary<string, IWatcher> watches, ILogger logger)
{
return new UpdateParameterLogCommand(watches, GetBlockBlobReference(_parameterLogBlob), logger);
}
private CloudBlockBlob GetBlockBlobReference(LocalBlobDescriptor descriptor)
{
var container = _client.GetContainerReference(descriptor.ContainerName);
return container.GetBlockBlobReference(descriptor.BlobName);
}
}
}

Просмотреть файл

@ -1,52 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Storage;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.Storage.Blob;
namespace WebJobs.Host.Storage.Logging
{
internal class BlobFunctionOutputLogger : IFunctionOutputLogger
{
private readonly CloudBlobDirectory _outputLogDirectory;
public BlobFunctionOutputLogger(CloudBlobClient client)
: this(client.GetContainerReference(HostContainerNames.Hosts).GetDirectoryReference(HostDirectoryNames.OutputLogs))
{
}
private BlobFunctionOutputLogger(CloudBlobDirectory outputLogDirectory)
{
_outputLogDirectory = outputLogDirectory;
}
public async Task<IFunctionOutputDefinition> CreateAsync(IFunctionInstance instance, CancellationToken cancellationToken)
{
await _outputLogDirectory.Container.CreateIfNotExistsAsync(cancellationToken);
string namePrefix = instance.Id.ToString("N");
LocalBlobDescriptor outputBlob = CreateDescriptor(_outputLogDirectory, namePrefix + ".txt");
LocalBlobDescriptor parameterLogBlob = CreateDescriptor(_outputLogDirectory, namePrefix + ".params.txt");
return new BlobFunctionOutputDefinition(_outputLogDirectory.ServiceClient, outputBlob, parameterLogBlob);
}
private static LocalBlobDescriptor CreateDescriptor(CloudBlobDirectory directory, string name)
{
var blob = directory.SafeGetBlockBlobReference(name);
return new LocalBlobDescriptor
{
ContainerName = blob.Container.Name,
BlobName = blob.Name
};
}
}
}

Просмотреть файл

@ -1,117 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.Dispatch;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Queues.Listeners;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Extensions.Logging;
namespace WebJobs.Host.Storage.Logging
{
// $$$
// Wires up V1 dashboard logging
internal class DashboardLoggingSetup : IDashboardLoggingSetup
{
private readonly IWebJobsExceptionHandler _exceptionHandler;
private readonly ILoggerFactory _loggerFactory;
private readonly IFunctionInstanceLogger _functionInstanceLogger;
private readonly IFunctionExecutor _functionExecutor;
private readonly SharedQueueHandler _sharedQueueHandler;
private readonly ILoadBalancerQueue _queueFactory;
private readonly StorageAccountOptions _storageAccountOptions;
public DashboardLoggingSetup(
StorageAccountOptions storageAccountOptions,
IWebJobsExceptionHandler exceptionHandler,
ILoggerFactory loggerFactory,
IFunctionInstanceLogger functionInstanceLogger,
IFunctionExecutor functionExecutor,
SharedQueueHandler sharedQueueHandler,
ILoadBalancerQueue queueFactory
)
{
_storageAccountOptions = storageAccountOptions;
_exceptionHandler = exceptionHandler;
_loggerFactory = loggerFactory;
_functionInstanceLogger = functionInstanceLogger;
_functionExecutor = functionExecutor;
_sharedQueueHandler = sharedQueueHandler;
_queueFactory = queueFactory;
}
public bool Setup(
IFunctionIndex functions,
IListenerFactory functionsListenerFactory,
out IFunctionExecutor hostCallExecutor,
out IListener listener,
out HostOutputMessage hostOutputMessage,
string hostId,
CancellationToken shutdownToken)
{
string sharedQueueName = HostQueueNames.GetHostQueueName(hostId);
var sharedQueue = sharedQueueName;
IListenerFactory sharedQueueListenerFactory = new HostMessageListenerFactory(_queueFactory, sharedQueue,
_exceptionHandler, _loggerFactory, functions,
_functionInstanceLogger, _functionExecutor);
Guid hostInstanceId = Guid.NewGuid();
string instanceQueueName = HostQueueNames.GetHostQueueName(hostInstanceId.ToString("N"));
var instanceQueue = instanceQueueName;
IListenerFactory instanceQueueListenerFactory = new HostMessageListenerFactory(_queueFactory, instanceQueue,
_exceptionHandler, _loggerFactory, functions,
_functionInstanceLogger, _functionExecutor);
HeartbeatDescriptor heartbeatDescriptor = new HeartbeatDescriptor
{
SharedContainerName = HostContainerNames.Hosts,
SharedDirectoryName = HostDirectoryNames.Heartbeats + "/" + hostId,
InstanceBlobName = hostInstanceId.ToString("N"),
ExpirationInSeconds = (int)HeartbeatIntervals.ExpirationInterval.TotalSeconds
};
var dashboardAccount = _storageAccountOptions.GetDashboardStorageAccount();
DelegatingHandler delegatingHandler = _storageAccountOptions.DelegatingHandlerProvider?.Create();
var blob = new CloudBlobClient(dashboardAccount.BlobStorageUri, dashboardAccount.Credentials, delegatingHandler)
.GetContainerReference(heartbeatDescriptor.SharedContainerName)
.GetBlockBlobReference(heartbeatDescriptor.SharedDirectoryName + "/" + heartbeatDescriptor.InstanceBlobName);
IRecurrentCommand heartbeatCommand = new UpdateHostHeartbeatCommand(new HeartbeatCommand(blob));
IEnumerable<MethodInfo> indexedMethods = functions.ReadAllMethods();
Assembly hostAssembly = JobHostContextFactory.GetHostAssembly(indexedMethods);
string displayName = hostAssembly != null ? AssemblyNameCache.GetName(hostAssembly).Name : "Unknown";
hostOutputMessage = new JobHostContextFactory.DataOnlyHostOutputMessage
{
HostInstanceId = hostInstanceId,
HostDisplayName = displayName,
SharedQueueName = sharedQueueName,
InstanceQueueName = instanceQueueName,
Heartbeat = heartbeatDescriptor,
WebJobRunIdentifier = WebJobRunIdentifier.Current
};
hostCallExecutor = JobHostContextFactory.CreateHostCallExecutor(instanceQueueListenerFactory, heartbeatCommand,
_exceptionHandler, shutdownToken, _functionExecutor);
IListenerFactory hostListenerFactory = new CompositeListenerFactory(functionsListenerFactory,
sharedQueueListenerFactory, instanceQueueListenerFactory);
listener = JobHostContextFactory.CreateHostListener(hostListenerFactory, _sharedQueueHandler, heartbeatCommand, _exceptionHandler, shutdownToken);
return true;
}
}
}

Просмотреть файл

@ -1,85 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Extensions.Logging;
namespace WebJobs.Host.Storage.Logging
{
internal class DefaultLoggerProvider : IHostInstanceLoggerProvider, IFunctionInstanceLoggerProvider, IFunctionOutputLoggerProvider
{
private bool _loggersSet;
private IHostInstanceLogger _hostInstanceLogger;
private IFunctionInstanceLogger _functionInstanceLogger;
private IFunctionOutputLogger _functionOutputLogger;
private ILoggerFactory _loggerFactory;
private StorageAccountOptions _storageAccountOptions;
public DefaultLoggerProvider(StorageAccountOptions storageAccountOptions, ILoggerFactory loggerFactory)
{
_storageAccountOptions = storageAccountOptions;
_loggerFactory = loggerFactory;
}
async Task<IHostInstanceLogger> IHostInstanceLoggerProvider.GetAsync(CancellationToken cancellationToken)
{
await EnsureLoggersAsync(cancellationToken);
return _hostInstanceLogger;
}
async Task<IFunctionInstanceLogger> IFunctionInstanceLoggerProvider.GetAsync(CancellationToken cancellationToken)
{
await EnsureLoggersAsync(cancellationToken);
return _functionInstanceLogger;
}
async Task<IFunctionOutputLogger> IFunctionOutputLoggerProvider.GetAsync(CancellationToken cancellationToken)
{
await EnsureLoggersAsync(cancellationToken);
return _functionOutputLogger;
}
private void EnsureLoggers()
{
if (_loggersSet)
{
return;
}
IFunctionInstanceLogger functionLogger = new FunctionInstanceLogger(_loggerFactory);
if (_storageAccountOptions.Dashboard != null)
{
var dashboardAccount = _storageAccountOptions.GetDashboardStorageAccount();
DelegatingHandler delegatingHandler = _storageAccountOptions.DelegatingHandlerProvider?.Create();
// Create logging against a live Azure account.
var dashboardBlobClient = new CloudBlobClient(dashboardAccount.BlobStorageUri, dashboardAccount.Credentials, delegatingHandler);
IPersistentQueueWriter<PersistentQueueMessage> queueWriter = new PersistentQueueWriter<PersistentQueueMessage>(dashboardBlobClient);
PersistentQueueLogger queueLogger = new PersistentQueueLogger(queueWriter);
_hostInstanceLogger = queueLogger;
_functionInstanceLogger = new CompositeFunctionInstanceLogger(queueLogger, functionLogger);
_functionOutputLogger = new BlobFunctionOutputLogger(dashboardBlobClient);
}
else
{
// No auxillary logging. Logging interfaces are nops or in-memory.
_hostInstanceLogger = new NullHostInstanceLogger();
_functionInstanceLogger = functionLogger;
_functionOutputLogger = new NullFunctionOutputLogger();
}
_loggersSet = true;
}
private Task EnsureLoggersAsync(CancellationToken cancellationToken)
{
return Task.Run(() => EnsureLoggers(), cancellationToken);
}
}
}

Просмотреть файл

@ -1,47 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace WebJobs.Host.Storage.Logging
{
internal sealed class LoggerProviderFactory
{
private readonly ILoggerFactory _loggerFactory;
private readonly bool _hasFastTableHook;
private readonly Lazy<object> _loggerProvider;
private readonly StorageAccountOptions _storageAccountOptions;
public LoggerProviderFactory(
IOptions<StorageAccountOptions> storageAccountOptions,
ILoggerFactory loggerFactory,
IEventCollectorFactory fastLoggerFactory = null)
{
_storageAccountOptions = storageAccountOptions.Value;
_loggerFactory = loggerFactory;
_hasFastTableHook = fastLoggerFactory != null;
_loggerProvider = new Lazy<object>(CreateLoggerProvider);
}
private object CreateLoggerProvider()
{
// $$$ if this is null, we should have registered different DI components
bool noDashboardStorage = _storageAccountOptions.Dashboard == null;
if (_hasFastTableHook && noDashboardStorage)
{
return new FastTableLoggerProvider(_loggerFactory);
}
return new DefaultLoggerProvider(_storageAccountOptions, _loggerFactory);
}
public T GetLoggerProvider<T>() where T : class => _loggerProvider.Value as T;
}
}

Просмотреть файл

@ -1,56 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
namespace WebJobs.Host.Storage.Logging
{
internal class PersistentQueueLogger : IHostInstanceLogger, IFunctionInstanceLogger
{
private readonly IPersistentQueueWriter<PersistentQueueMessage> _queueWriter;
public PersistentQueueLogger(IPersistentQueueWriter<PersistentQueueMessage> queueWriter)
{
if (queueWriter == null)
{
throw new ArgumentNullException("queueWriter");
}
_queueWriter = queueWriter;
}
public Task LogHostStartedAsync(HostStartedMessage message, CancellationToken cancellationToken)
{
return _queueWriter.EnqueueAsync(message, cancellationToken);
}
public Task<string> LogFunctionStartedAsync(FunctionStartedMessage message, CancellationToken cancellationToken)
{
return _queueWriter.EnqueueAsync(message, cancellationToken);
}
public Task LogFunctionCompletedAsync(FunctionCompletedMessage message, CancellationToken cancellationToken)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
return _queueWriter.EnqueueAsync(message, cancellationToken);
}
public Task DeleteLogFunctionStartedAsync(string startedMessageId, CancellationToken cancellationToken)
{
if (String.IsNullOrEmpty(startedMessageId))
{
throw new ArgumentNullException("startedMessageId");
}
return _queueWriter.DeleteAsync(startedMessageId, cancellationToken);
}
}
}

Просмотреть файл

@ -1,45 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Timers;
namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class UpdateHostHeartbeatCommand : IRecurrentCommand
{
private readonly IHeartbeatCommand _heartbeatCommand;
public UpdateHostHeartbeatCommand(IHeartbeatCommand heartbeatCommand)
{
if (heartbeatCommand == null)
{
throw new ArgumentNullException("heartbeatCommand");
}
_heartbeatCommand = heartbeatCommand;
}
public async Task<bool> TryExecuteAsync(CancellationToken cancellationToken)
{
try
{
await _heartbeatCommand.BeatAsync(cancellationToken);
return true;
}
catch (StorageException exception)
{
if (exception.IsServerSideError())
{
return false;
}
throw;
}
}
}
}

Просмотреть файл

@ -1,219 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Azure.Storage.Blob;
namespace WebJobs.Host.Storage.Logging
{
// Flush on a timer so that we get updated output.
// Flush will come on a different thread, so we need to have thread-safe
// access between the Reader (ToString) and the Writers (which are happening as our
// caller uses the textWriter that we return).
internal sealed class UpdateOutputLogCommand : IRecurrentCommand, IDisposable, IFunctionOutput
{
// Contents for what's written. Owned by the timer thread.
private readonly StringWriter _innerWriter;
private readonly CloudBlockBlob _outputBlob;
private readonly Func<string, CancellationToken, Task> _uploadCommand;
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
// Thread-safe access to _innerWriter so that user threads can write to it.
private readonly TextWriter _synchronizedWriter;
private object _writerSyncLock = new object();
private bool _disposed;
private string _existingContent = null;
private UpdateOutputLogCommand(CloudBlockBlob outputBlob, Func<string, CancellationToken, Task> uploadCommand)
{
_outputBlob = outputBlob;
_innerWriter = new StringWriter(CultureInfo.InvariantCulture);
_synchronizedWriter = TextWriter.Synchronized(_innerWriter);
_uploadCommand = uploadCommand;
}
public IRecurrentCommand UpdateCommand
{
get
{
ThrowIfDisposed();
return this;
}
}
public TextWriter Output
{
get
{
ThrowIfDisposed();
return _synchronizedWriter;
}
}
public static UpdateOutputLogCommand Create(CloudBlockBlob outputBlob)
{
return Create(outputBlob, (contents, innerToken) => UploadTextAsync(outputBlob, contents, innerToken));
}
public static UpdateOutputLogCommand Create(CloudBlockBlob outputBlob, Func<string, CancellationToken, Task> uploadCommand)
{
if (outputBlob == null)
{
throw new ArgumentNullException("outputBlob");
}
else if (uploadCommand == null)
{
throw new ArgumentNullException("uploadCommand");
}
return new UpdateOutputLogCommand(outputBlob, uploadCommand);
}
public async Task<bool> TryExecuteAsync(CancellationToken cancellationToken)
{
await UpdateOutputBlob(cancellationToken);
return true;
}
public void Dispose()
{
if (!_disposed)
{
_innerWriter.Dispose();
_synchronizedWriter.Dispose();
_disposed = true;
}
}
public async Task SaveAndCloseAsync(FunctionInstanceLogEntry item, CancellationToken cancellationToken)
{
await UpdateOutputBlob(cancellationToken, flushAndClose: true);
}
private async Task<string> GetExistingContent(CancellationToken cancellationToken)
{
if (_existingContent == null)
{
_existingContent = await ReadBlobAsync(_outputBlob, cancellationToken);
if (_existingContent != null)
{
// This can happen if the function was running previously and the
// node crashed. Save previous output, could be useful for diagnostics.
StringWriter stringWriter = new StringWriter();
stringWriter.WriteLine("Previous execution information:");
stringWriter.WriteLine(_existingContent);
var lastTime = await GetBlobModifiedUtcTimeAsync(_outputBlob, cancellationToken);
if (lastTime.HasValue)
{
var delta = DateTime.UtcNow - lastTime.Value;
stringWriter.WriteLine("... Last write at {0}, {1} ago", lastTime, delta);
}
stringWriter.WriteLine("========================");
}
else
{
_existingContent = string.Empty;
}
}
return _existingContent;
}
private async Task UpdateOutputBlob(CancellationToken cancellationToken, bool flushAndClose = false)
{
ThrowIfDisposed();
string snapshot;
lock (_writerSyncLock)
{
if (flushAndClose)
{
_synchronizedWriter.Flush();
}
// Explicitly specify the length. Without this, any writes occurring
// during the call to ToString() could throw.
StringBuilder innerBuilder = _innerWriter.GetStringBuilder();
snapshot = innerBuilder.ToString(0, innerBuilder.Length);
if (flushAndClose)
{
_synchronizedWriter.Close();
_innerWriter.Close();
}
}
// when we write the output blob, ensure that we always include
// any preexisting contents
string existingText = await GetExistingContent(cancellationToken);
StringBuilder sb = new StringBuilder(existingText);
sb.Append(snapshot);
snapshot = sb.ToString();
// Make sure we can only do one of these at a time to prevent Md5Mismatch errors
await _semaphoreSlim.WaitAsync();
try
{
await _uploadCommand.Invoke(snapshot, cancellationToken);
}
finally
{
_semaphoreSlim.Release();
}
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
}
private static Task UploadTextAsync(CloudBlockBlob outputBlob, string contents, CancellationToken cancellationToken)
{
return outputBlob.UploadTextAsync(contents, cancellationToken: cancellationToken);
}
private static async Task<DateTime?> GetBlobModifiedUtcTimeAsync(ICloudBlob blob, CancellationToken cancellationToken)
{
if (!await blob.ExistsAsync())
{
return null; // no blob, no time.
}
var lastModified = blob.Properties.LastModified;
return lastModified.HasValue ? (DateTime?)lastModified.Value.UtcDateTime : null;
}
[DebuggerNonUserCode]
private static async Task<string> ReadBlobAsync(ICloudBlob blob, CancellationToken cancellationToken)
{
try
{
// Beware! Blob.DownloadText does not strip the BOM!
using (var stream = await blob.OpenReadAsync(cancellationToken))
using (StreamReader sr = new StreamReader(stream, detectEncodingFromByteOrderMarks: true))
{
cancellationToken.ThrowIfCancellationRequested();
string data = await sr.ReadToEndAsync();
return data;
}
}
catch
{
return null;
}
}
}
}

Просмотреть файл

@ -1,96 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage.Blob;
using Newtonsoft.Json;
namespace WebJobs.Host.Storage.Logging
{
internal sealed class UpdateParameterLogCommand : IRecurrentCommand
{
private readonly IReadOnlyDictionary<string, IWatcher> _watches;
private readonly CloudBlockBlob _parameterLogBlob;
private readonly ILogger _logger;
private string _lastContent;
public UpdateParameterLogCommand(IReadOnlyDictionary<string, IWatcher> watches, CloudBlockBlob parameterLogBlob, ILogger logger)
{
if (parameterLogBlob == null)
{
throw new ArgumentNullException("parameterLogBlob");
}
else if (watches == null)
{
throw new ArgumentNullException("watches");
}
_parameterLogBlob = parameterLogBlob;
_logger = logger;
_watches = watches;
}
public static void AddLogs(IReadOnlyDictionary<string, IWatcher> watches,
IDictionary<string, ParameterLog> collector)
{
foreach (KeyValuePair<string, IWatcher> item in watches)
{
IWatcher watch = item.Value;
if (watch == null)
{
continue;
}
ParameterLog status = watch.GetStatus();
if (status == null)
{
continue;
}
collector.Add(item.Key, status);
}
}
public async Task<bool> TryExecuteAsync(CancellationToken cancellationToken)
{
Dictionary<string, ParameterLog> logs = new Dictionary<string, ParameterLog>();
AddLogs(_watches, logs);
string content = JsonConvert.SerializeObject(logs, JsonSerialization.Settings);
try
{
if (_lastContent == content)
{
// If it hasn't change, then don't re upload stale content.
return true;
}
_lastContent = content;
await _parameterLogBlob.UploadTextAsync(content, cancellationToken: cancellationToken);
return true;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e)
{
// Not fatal if we can't update parameter status.
// But at least log what happened for diagnostics in case it's an infrastructure bug.
string msg = "---- Parameter status update failed ----";
_logger?.LogWarning(0, e, msg);
return false;
}
}
}
}

Просмотреть файл

@ -1,22 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using Microsoft.Azure.WebJobs;
namespace Microsoft.Extensions.Hosting
{
public static class RuntimeStorageWebJobsBuilderExtensions
{
// WebJobs v1 Classic logging. Needed for dashboard.
[Obsolete("Dashboard is being deprecated. Use AppInsights.")]
public static IWebJobsBuilder AddDashboardLogging(this IWebJobsBuilder builder)
{
builder.Services.AddDashboardLogging();
return builder;
}
// Make the Runtime itself use storage for its internal operations.
// Uses v1 app settings, via a LegacyConfigSetup object.
public static IWebJobsBuilder AddAzureStorageCoreServices(this IWebJobsBuilder builder)

Просмотреть файл

@ -5,37 +5,38 @@ using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Extensions.Storage;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
namespace Microsoft.Azure.WebJobs.Host
{
/// <summary>
/// Provides a CloudBlob lease-based implementation of the <see cref="IDistributedLockManager"/> service for singleton locking.
/// This class can be overridden to control where the container comes from.
/// The default derived implementation is <see cref="CloudBlobContainerDistributedLockManager"/> which is container based.
/// Hosts can provide a derived implementation to leverage the accountName and allow different hosts to share.
/// Provides a BlobClient lease-based implementation of the <see cref="IDistributedLockManager"/> service for singleton locking.
/// </summary>
public abstract class StorageBaseDistributedLockManager : IDistributedLockManager
internal class BlobLeaseDistributedLockManager : IDistributedLockManager
{
internal const string FunctionInstanceMetadataKey = "FunctionInstance";
// Convention for container name to use.
public const string DefaultContainerName = HostContainerNames.Hosts;
private readonly ConcurrentDictionary<string, CloudBlobDirectory> _lockDirectoryMap = new ConcurrentDictionary<string, CloudBlobDirectory>(StringComparer.OrdinalIgnoreCase);
internal const string SingletonLocks = "locks";
private readonly ILogger _logger;
private readonly IAzureBlobStorageProvider _blobStorageProvider;
private readonly ConcurrentDictionary<string, BlobContainerClient> _lockBlobContainerClientMap = new ConcurrentDictionary<string, BlobContainerClient>(StringComparer.OrdinalIgnoreCase);
public StorageBaseDistributedLockManager(
ILoggerFactory loggerFactory) // Take an ILoggerFactory since that's a DI component.
public BlobLeaseDistributedLockManager(
ILoggerFactory loggerFactory,
IAzureBlobStorageProvider azureStorageProvider) // Take an ILoggerFactory since that's a DI component.
{
_logger = loggerFactory.CreateLogger(LogCategories.Singleton);
_blobStorageProvider = azureStorageProvider;
}
public Task<bool> RenewAsync(IDistributedLock lockHandle, CancellationToken cancellationToken)
@ -47,27 +48,26 @@ namespace Microsoft.Azure.WebJobs.Host
public async Task ReleaseLockAsync(IDistributedLock lockHandle, CancellationToken cancellationToken)
{
SingletonLockHandle singletonLockHandle = (SingletonLockHandle)lockHandle;
await ReleaseLeaseAsync(singletonLockHandle.Blob, singletonLockHandle.LeaseId, cancellationToken);
await ReleaseLeaseAsync(singletonLockHandle.BlobLeaseClient, singletonLockHandle.LeaseId, cancellationToken);
}
public async virtual Task<string> GetLockOwnerAsync(string account, string lockId, CancellationToken cancellationToken)
{
var lockDirectory = GetLockDirectory(account);
var lockBlob = lockDirectory.SafeGetBlockBlobReference(lockId);
var lockBlob = this.GetContainerClient(account).GetBlobClient(GetLockPath(lockId));
await ReadLeaseBlobMetadata(lockBlob, cancellationToken);
var blobProperties = await ReadLeaseBlobMetadata(lockBlob, cancellationToken);
// if the lease is Available, then there is no current owner
// (any existing owner value is the last owner that held the lease)
if (lockBlob.Properties.LeaseState == LeaseState.Available &&
lockBlob.Properties.LeaseStatus == LeaseStatus.Unlocked)
if (blobProperties != null &&
blobProperties.LeaseState == LeaseState.Available &&
blobProperties.LeaseStatus == LeaseStatus.Unlocked)
{
return null;
}
string owner = string.Empty;
lockBlob.Metadata.TryGetValue(FunctionInstanceMetadataKey, out owner);
string owner = default;
blobProperties?.Metadata.TryGetValue(FunctionInstanceMetadataKey, out owner);
return owner;
}
@ -79,8 +79,7 @@ namespace Microsoft.Azure.WebJobs.Host
TimeSpan lockPeriod,
CancellationToken cancellationToken)
{
var lockDirectory = GetLockDirectory(account);
var lockBlob = lockDirectory.SafeGetBlockBlobReference(lockId);
var lockBlob = this.GetContainerClient(account).GetBlobClient(GetLockPath(lockId));
string leaseId = await TryAcquireLeaseAsync(lockBlob, lockPeriod, proposedLeaseId, cancellationToken);
if (string.IsNullOrEmpty(leaseId))
@ -93,34 +92,74 @@ namespace Microsoft.Azure.WebJobs.Host
await WriteLeaseBlobMetadata(lockBlob, leaseId, lockOwnerId, cancellationToken);
}
SingletonLockHandle lockHandle = new SingletonLockHandle(leaseId, lockId, lockBlob, lockPeriod);
SingletonLockHandle lockHandle = new SingletonLockHandle(leaseId, lockId, this.GetBlobLeaseClient(lockBlob, leaseId), lockPeriod);
return lockHandle;
}
protected abstract CloudBlobContainer GetContainer(string accountName);
internal CloudBlobDirectory GetLockDirectory(string accountName)
protected virtual BlobContainerClient GetContainerClient(string connectionName)
{
if (string.IsNullOrEmpty(accountName))
if (string.IsNullOrEmpty(connectionName))
{
accountName = string.Empty; // must be non-null for a dictionary key
// Dictionary lookup needs non-null string
connectionName = string.Empty;
}
CloudBlobDirectory storageDirectory = null;
if (!_lockDirectoryMap.TryGetValue(accountName, out storageDirectory))
// First check the cache if we have a BlobContainerClient for this connection
if (_lockBlobContainerClientMap.TryGetValue(connectionName, out BlobContainerClient blobContainerClient))
{
var container = this.GetContainer(accountName);
storageDirectory = container.GetDirectoryReference(HostDirectoryNames.SingletonLocks);
_lockDirectoryMap[accountName] = storageDirectory;
return blobContainerClient;
}
return storageDirectory;
BlobContainerClient containerClient = CreateBlobContainerClient(connectionName);
_lockBlobContainerClientMap[connectionName] = containerClient;
return containerClient;
}
private static async Task<string> TryAcquireLeaseAsync(
CloudBlockBlob blob,
// Helper method to retrieve a new BlobContainerClient with ability to override the default storage account
private BlobContainerClient CreateBlobContainerClient(string connectionName)
{
if (!string.IsNullOrEmpty(connectionName))
{
if (_blobStorageProvider.TryCreateBlobServiceClientFromConnection(connectionName, out BlobServiceClient client))
{
return client.GetBlobContainerClient(HostContainerNames.Hosts);
}
else
{
throw new InvalidOperationException($"Could not create BlobContainerClient for {typeof(BlobLeaseDistributedLockManager).Name} with Connection: {connectionName}.");
}
}
else
{
if (!_blobStorageProvider.TryCreateHostingBlobContainerClient(out BlobContainerClient blobContainerClient))
{
throw new InvalidOperationException($"Could not create BlobContainerClient for {typeof(BlobLeaseDistributedLockManager).Name}.");
}
return blobContainerClient;
}
}
internal string GetLockPath(string lockId)
{
return string.Format(CultureInfo.InvariantCulture, "{0}/{1}", SingletonLocks, lockId);
}
// Allows the extension method to be mocked for testing
protected virtual BlobLeaseClient GetBlobLeaseClient(BlobClient blobClient, string proposedLeaseId)
{
return blobClient.GetBlobLeaseClient(proposedLeaseId);
}
// Allows the extension method to be mocked for testing
protected virtual BlobContainerClient GetParentBlobContainerClient(BlobClient blobClient)
{
return blobClient.GetParentBlobContainerClient();
}
private async Task<string> TryAcquireLeaseAsync(
BlobClient blobClient,
TimeSpan leasePeriod,
string proposedLeaseId,
CancellationToken cancellationToken)
@ -130,24 +169,18 @@ namespace Microsoft.Azure.WebJobs.Host
{
// Optimistically try to acquire the lease. The blob may not yet
// exist. If it doesn't we handle the 404, create it, and retry below
return await blob.AcquireLeaseAsync(leasePeriod, proposedLeaseId, accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
var leaseResponse = await GetBlobLeaseClient(blobClient, proposedLeaseId).AcquireAsync(leasePeriod, cancellationToken: cancellationToken);
return leaseResponse.Value.LeaseId;
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.RequestInformation != null)
if (exception.Status == 409)
{
if (exception.RequestInformation.HttpStatusCode == 409)
{
return null;
}
else if (exception.RequestInformation.HttpStatusCode == 404)
{
blobDoesNotExist = true;
}
else
{
throw;
}
return null;
}
else if (exception.Status == 404)
{
blobDoesNotExist = true;
}
else
{
@ -157,16 +190,16 @@ namespace Microsoft.Azure.WebJobs.Host
if (blobDoesNotExist)
{
await TryCreateAsync(blob, cancellationToken);
await TryCreateAsync(blobClient, cancellationToken);
try
{
return await blob.AcquireLeaseAsync(leasePeriod, null, accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
var leaseResponse = await GetBlobLeaseClient(blobClient, proposedLeaseId).AcquireAsync(leasePeriod, cancellationToken: cancellationToken);
return leaseResponse.Value.LeaseId;
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.RequestInformation != null &&
exception.RequestInformation.HttpStatusCode == 409)
if (exception.Status == 409)
{
return null;
}
@ -180,33 +213,22 @@ namespace Microsoft.Azure.WebJobs.Host
return null;
}
private static async Task ReleaseLeaseAsync(CloudBlockBlob blob, string leaseId, CancellationToken cancellationToken)
private static async Task ReleaseLeaseAsync(BlobLeaseClient blobLeaseClient, string leaseId, CancellationToken cancellationToken)
{
try
{
// Note that this call returns without throwing if the lease is expired. See the table at:
// http://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
await blob.ReleaseLeaseAsync(
accessCondition: new AccessCondition { LeaseId = leaseId },
options: null,
operationContext: null,
cancellationToken: cancellationToken);
await blobLeaseClient.ReleaseAsync(cancellationToken: cancellationToken);
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.RequestInformation != null)
if (exception.Status == 404 ||
exception.Status == 409)
{
if (exception.RequestInformation.HttpStatusCode == 404 ||
exception.RequestInformation.HttpStatusCode == 409)
{
// if the blob no longer exists, or there is another lease
// now active, there is nothing for us to release so we can
// ignore
}
else
{
throw;
}
// if the blob no longer exists, or there is another lease
// now active, there is nothing for us to release so we can
// ignore
}
else
{
@ -215,33 +237,29 @@ namespace Microsoft.Azure.WebJobs.Host
}
}
private static async Task<bool> TryCreateAsync(CloudBlockBlob blob, CancellationToken cancellationToken)
private async Task<bool> TryCreateAsync(BlobClient blobClient, CancellationToken cancellationToken)
{
bool isContainerNotFoundException = false;
try
{
await blob.UploadTextAsync(string.Empty, cancellationToken: cancellationToken);
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty)))
{
await blobClient.UploadAsync(stream, cancellationToken: cancellationToken);
}
return true;
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.RequestInformation != null)
if (exception.Status == 404)
{
if (exception.RequestInformation.HttpStatusCode == 404)
{
isContainerNotFoundException = true;
}
else if (exception.RequestInformation.HttpStatusCode == 409 ||
exception.RequestInformation.HttpStatusCode == 412)
{
// The blob already exists, or is leased by someone else
return false;
}
else
{
throw;
}
isContainerNotFoundException = true;
}
else if (exception.Status == 409 ||
exception.Status == 412)
{
// The blob already exists, or is leased by someone else
return false;
}
else
{
@ -251,26 +269,29 @@ namespace Microsoft.Azure.WebJobs.Host
Debug.Assert(isContainerNotFoundException);
var container = blob.Container;
var container = GetParentBlobContainerClient(blobClient);
try
{
await container.CreateIfNotExistsAsync(cancellationToken);
await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
}
catch (StorageException exc)
when (exc.RequestInformation.HttpStatusCode == 409 && string.Compare("ContainerBeingDeleted", exc.RequestInformation.ExtendedErrorInformation?.ErrorCode) == 0)
catch (RequestFailedException exception)
when (exception.Status == 409 && string.Compare("ContainerBeingDeleted", exception.ErrorCode) == 0)
{
throw new StorageException("The host container is pending deletion and currently inaccessible.");
throw new RequestFailedException("The host container is pending deletion and currently inaccessible.");
}
try
{
await blob.UploadTextAsync(string.Empty, cancellationToken: cancellationToken);
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty)))
{
await blobClient.UploadAsync(stream, cancellationToken: cancellationToken);
}
return true;
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.RequestInformation != null &&
(exception.RequestInformation.HttpStatusCode == 409 || exception.RequestInformation.HttpStatusCode == 412))
if (exception.Status == 409 || exception.Status == 412)
{
// The blob already exists, or is leased by someone else
return false;
@ -282,29 +303,32 @@ namespace Microsoft.Azure.WebJobs.Host
}
}
private static async Task WriteLeaseBlobMetadata(CloudBlockBlob blob, string leaseId, string functionInstanceId, CancellationToken cancellationToken)
private static async Task WriteLeaseBlobMetadata(BlobClient blobClient, string leaseId, string functionInstanceId, CancellationToken cancellationToken)
{
blob.Metadata.Add(FunctionInstanceMetadataKey, functionInstanceId);
await blob.SetMetadataAsync(
accessCondition: new AccessCondition { LeaseId = leaseId },
options: null,
operationContext: null,
cancellationToken: cancellationToken);
var blobProperties = await ReadLeaseBlobMetadata(blobClient, cancellationToken);
if (blobProperties != null)
{
blobProperties.Metadata[FunctionInstanceMetadataKey] = functionInstanceId;
await blobClient.SetMetadataAsync(
blobProperties.Metadata,
new BlobRequestConditions { LeaseId = leaseId },
cancellationToken: cancellationToken);
}
}
private static async Task ReadLeaseBlobMetadata(CloudBlockBlob blob, CancellationToken cancellationToken)
private static async Task<BlobProperties> ReadLeaseBlobMetadata(BlobClient blobClient, CancellationToken cancellationToken)
{
try
{
await blob.FetchAttributesAsync(accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
var propertiesResponse = await blobClient.GetPropertiesAsync(cancellationToken: cancellationToken);
return propertiesResponse.Value;
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.RequestInformation != null &&
exception.RequestInformation.HttpStatusCode == 404)
if (exception.Status == 404)
{
// the blob no longer exists
return null;
}
else
{
@ -324,37 +348,36 @@ namespace Microsoft.Azure.WebJobs.Host
{
}
public SingletonLockHandle(string leaseId, string lockId, CloudBlockBlob blob, TimeSpan leasePeriod)
public SingletonLockHandle(string leaseId, string lockId, BlobLeaseClient blobLeaseClient, TimeSpan leasePeriod)
{
this.LeaseId = leaseId;
this.LockId = lockId;
this._leasePeriod = leasePeriod;
this.Blob = blob;
this.BlobLeaseClient = blobLeaseClient;
}
public string LeaseId { get; internal set; }
public string LockId { get; internal set; }
public CloudBlockBlob Blob { get; internal set; }
public BlobLeaseClient BlobLeaseClient { get; internal set; }
public async Task<bool> RenewAsync(ILogger logger, CancellationToken cancellationToken)
{
try
{
AccessCondition condition = new AccessCondition
{
LeaseId = this.LeaseId
};
DateTimeOffset requestStart = DateTimeOffset.UtcNow;
await this.Blob.RenewLeaseAsync(condition, null, null, cancellationToken);
await this.BlobLeaseClient.RenewAsync(cancellationToken: cancellationToken);
_lastRenewal = DateTime.UtcNow;
_lastRenewalLatency = _lastRenewal - requestStart;
// The next execution should occur after a normal delay.
return true;
}
catch (StorageException exception)
catch (RequestFailedException exception)
{
if (exception.IsServerSideError())
// indicates server-side error
if (exception.Status >= 500 && exception.Status < 600)
{
string msg = string.Format(CultureInfo.InvariantCulture, "Singleton lock renewal failed for blob '{0}' with error code {1}.",
this.LockId, FormatErrorCode(exception));
@ -380,17 +403,11 @@ namespace Microsoft.Azure.WebJobs.Host
}
}
private static string FormatErrorCode(StorageException exception)
private static string FormatErrorCode(RequestFailedException exception)
{
int statusCode;
if (!exception.TryGetStatusCode(out statusCode))
{
return "''";
}
string message = exception.Status.ToString(CultureInfo.InvariantCulture);
string message = statusCode.ToString(CultureInfo.InvariantCulture);
string errorCode = exception.GetErrorCode();
string errorCode = exception.ErrorCode;
if (errorCode != null)
{

Просмотреть файл

@ -1,35 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage.Blob;
namespace Microsoft.Azure.WebJobs.Host
{
/// <summary>
/// A Singleton manager service that uses a blob lease within the given container.
/// Caller decides whether the container could come from a SAS connection string or a full storage account.
/// Hosts can set the <see cref="IDistributedLockManager"/> service to completely replace this.
/// </summary>
public class CloudBlobContainerDistributedLockManager : StorageBaseDistributedLockManager
{
private readonly CloudBlobContainer _container;
public CloudBlobContainerDistributedLockManager(
CloudBlobContainer container,
ILoggerFactory logger) : base(logger)
{
_container = container;
}
protected override CloudBlobContainer GetContainer(string accountName)
{
if (!string.IsNullOrWhiteSpace(accountName))
{
throw new InvalidOperationException("Must replace singleton lease manager to support multiple accounts");
}
return _container;
}
}
}

Просмотреть файл

@ -1,62 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Azure.Storage;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace Microsoft.Azure.WebJobs
{
// $$$ Validate these? And what are their capabilities?
internal class StorageAccountOptionsSetup : IConfigureOptions<StorageAccountOptions>
{
private readonly IConfiguration _configuration;
private readonly IDelegatingHandlerProvider _delegatingHandlerProvider;
public StorageAccountOptionsSetup(IConfiguration configuration, IDelegatingHandlerProvider delegatingHandlerProvider)
{
_configuration = configuration;
_delegatingHandlerProvider = delegatingHandlerProvider;
}
public void Configure(StorageAccountOptions options)
{
if (options.Dashboard == null)
{
options.Dashboard = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Dashboard);
}
if (options.Storage == null)
{
options.Storage = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
}
}
}
internal class StorageAccountOptions
{
// Property names here must match existing names.
public string Dashboard { get; set; }
public string Storage { get; set; }
/// <summary>
/// The DelegatingHandlerProvider to be used when creating storage clients.
/// </summary>
public IDelegatingHandlerProvider DelegatingHandlerProvider { get; set; }
public CloudStorageAccount GetDashboardStorageAccount()
{
CloudStorageAccount account;
CloudStorageAccount.TryParse(this.Dashboard, out account);
return account;
}
public CloudStorageAccount GetStorageAccount()
{
CloudStorageAccount account;
CloudStorageAccount.TryParse(this.Storage, out account);
return account;
}
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using Azure.Core;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Azure.WebJobs.Host.Storage
{
/// <summary>
/// Passthrough-class to create Azure storage service clients using using <see cref="AzureComponentFactory"/>.
/// If the connection is not specified, it uses a default account.
/// </summary>
internal abstract class StorageClientProvider<TClient, TClientOptions> where TClientOptions : ClientOptions
{
private readonly AzureComponentFactory _componentFactory;
private readonly AzureEventSourceLogForwarder _logForwarder;
/// <summary>
/// Initializes a new instance of the <see cref="StorageClientProvider{TClient, TClientOptions}"/> class that uses the registered Azure services.
/// </summary>
/// <param name="componentFactory">The Azure factory responsible for creating clients. <see cref="AzureComponentFactory"/>.</param>
/// <param name="logForwarder">Log forwarder that forwards events to ILogger. <see cref="AzureEventSourceLogForwarder"/>.</param>
public StorageClientProvider(AzureComponentFactory componentFactory, AzureEventSourceLogForwarder logForwarder)
{
_componentFactory = componentFactory;
_logForwarder = logForwarder;
_logForwarder.Start();
}
public virtual TClient Create(string name, INameResolver resolver, IConfiguration configuration)
{
var resolvedName = resolver.ResolveWholeString(name);
return this.Create(resolvedName, configuration);
}
public virtual TClient Create(string name, IConfiguration configuration)
{
if (string.IsNullOrWhiteSpace(name))
{
name = ConnectionStringNames.Storage; // default
}
IConfigurationSection connectionSection = configuration?.GetWebJobsConnectionSection(name);
if (connectionSection == null || !connectionSection.Exists())
{
// Not found
throw new InvalidOperationException($"Storage account connection string '{IConfigurationExtensions.GetPrefixedConnectionStringName(name)}' does not exist. Make sure that it is a defined App Setting.");
}
var credential = _componentFactory.CreateTokenCredential(connectionSection);
var options = CreateClientOptions(connectionSection);
return CreateClient(connectionSection, credential, options);
}
protected virtual TClient CreateClient(IConfiguration configuration, TokenCredential tokenCredential, TClientOptions options)
{
return (TClient)_componentFactory.CreateClient(typeof(TClient), configuration, tokenCredential, options);
}
/// <summary>
/// Checks if the specified <see cref="IConfiguration"/> object has a value. This is assumed to be a connection string.
/// </summary>
/// <param name="configuration">The <see cref="IConfiguration"/> to retrieve the value from.</param>
/// <returns>true if this <see cref="IConfiguration"/> object is a connection string; false otherwise.</returns>
protected bool IsConnectionStringPresent(IConfiguration configuration)
{
return configuration is IConfigurationSection section && section.Value != null;
}
private TClientOptions CreateClientOptions(IConfiguration configuration)
{
var clientOptions = (TClientOptions)_componentFactory.CreateClientOptions(typeof(TClientOptions), null, configuration);
return clientOptions;
}
}
}

Просмотреть файл

@ -2,89 +2,60 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using WebJobs.Host.Storage.Logging;
namespace Microsoft.Extensions.Hosting
{
public static class StorageServiceCollectionExtensions
{
[Obsolete("Dashboard is being deprecated. Use AppInsights.")]
public static IServiceCollection AddDashboardLogging(this IServiceCollection services)
{
services.TryAddSingleton<LoggerProviderFactory>();
services.TryAddSingleton<IFunctionOutputLoggerProvider>(p => p.GetRequiredService<LoggerProviderFactory>().GetLoggerProvider<IFunctionOutputLoggerProvider>());
services.TryAddSingleton<IFunctionOutputLogger>(p => p.GetRequiredService<IFunctionOutputLoggerProvider>().GetAsync(CancellationToken.None).GetAwaiter().GetResult());
services.TryAddSingleton<IFunctionInstanceLoggerProvider>(p => p.GetRequiredService<LoggerProviderFactory>().GetLoggerProvider<IFunctionInstanceLoggerProvider>());
services.TryAddSingleton<IFunctionInstanceLogger>(p => p.GetRequiredService<IFunctionInstanceLoggerProvider>().GetAsync(CancellationToken.None).GetAwaiter().GetResult());
services.TryAddSingleton<IHostInstanceLoggerProvider>(p => p.GetRequiredService<LoggerProviderFactory>().GetLoggerProvider<IHostInstanceLoggerProvider>());
services.TryAddSingleton<IHostInstanceLogger>(p => p.GetRequiredService<IHostInstanceLoggerProvider>().GetAsync(CancellationToken.None).GetAwaiter().GetResult());
return services;
}
public static void AddAzureStorageCoreServices(this IServiceCollection services)
{
// Replace existing runtime services with storage-backed implementations.
// Add runtime services that depend on storage.
services.AddSingleton<IDistributedLockManager>(provider => Create(provider));
// Used specifically for the CloudBlobContainerDistributedLockManager implementation
services.TryAddSingleton<DistributedLockManagerContainerProvider>();
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<StorageAccountOptions>, StorageAccountOptionsSetup>());
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<JobHostInternalStorageOptions>, CoreWebJobsOptionsSetup<JobHostInternalStorageOptions>>());
services.TryAddSingleton<IDelegatingHandlerProvider, DefaultDelegatingHandlerProvider>();
// Adds necessary Azure services to create clients
services.AddAzureClientsCore();
services.TryAddSingleton<IAzureBlobStorageProvider, AzureStorageProvider>();
services.AddSingleton<IConcurrencyStatusRepository, BlobStorageConcurrencyStatusRepository>();
}
// This is only called if the host didn't already provide an implementation
private static IDistributedLockManager Create(IServiceProvider provider)
{
// $$$ get rid of LegacyConfig
var opts = provider.GetRequiredService<IOptions<StorageAccountOptions>>();
var blobStorageProvider = provider.GetRequiredService<IAzureBlobStorageProvider>();
var loggerFactory = provider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<IDistributedLockManager>();
var sas = provider.GetService<DistributedLockManagerContainerProvider>();
CloudBlobContainer container;
if (sas != null && sas.InternalContainer != null)
IDistributedLockManager lockManager;
if (blobStorageProvider.TryCreateHostingBlobContainerClient(out _))
{
container = sas.InternalContainer;
lockManager = new BlobLeaseDistributedLockManager(loggerFactory, blobStorageProvider);
}
else
{
var config = opts.Value;
CloudStorageAccount account = config.GetStorageAccount();
if (account == null)
{
return new InMemoryDistributedLockManager();
}
var blobClient = new CloudBlobClient(account.BlobStorageUri, account.Credentials, config.DelegatingHandlerProvider?.Create());
container = blobClient.GetContainerReference(HostContainerNames.Hosts);
// If there is an error getting the container client,
// register an InMemoryDistributedLockManager.
lockManager = new InMemoryDistributedLockManager();
}
var lockManager = new CloudBlobContainerDistributedLockManager(container, loggerFactory);
logger.LogDebug($"Using {lockManager.GetType().Name}");
return lockManager;
}
}

Просмотреть файл

@ -2,25 +2,12 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.WebJobs.Host;
namespace Microsoft.Azure.WebJobs.Extensions.Storage
{
static class Utility
{
// CloudBlobDirectory has a private ctor, so we can't actually override it.
// This overload is unit-testable
internal static CloudBlockBlob SafeGetBlockBlobReference(this CloudBlobDirectory dir, string blobName)
{
var container = dir.Container;
var prefix = dir.Prefix; // already ends in /
var blob = container.GetBlockBlobReference(prefix + blobName);
return blob;
}
internal static int GetProcessorCount()
{
int processorCount = 1;

Просмотреть файл

@ -24,15 +24,10 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.Azure.WebJobs.Protocols\HeartbeatCommand.cs" Link="Logging\HeartbeatCommand.cs" />
<Compile Include="..\Microsoft.Azure.WebJobs.Protocols\PersistentQueueReader.cs" Link="Logging\PersistentQueueReader.cs" />
<Compile Include="..\Microsoft.Azure.WebJobs.Protocols\PersistentQueueWriter.cs" Link="Logging\PersistentQueueWriter.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Storage.Blob" Version="11.1.7" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.9.0" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.1.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

Просмотреть файл

@ -93,5 +93,43 @@ namespace Microsoft.Extensions.Configuration
return false;
}
/// <summary>
/// Looks for an <see cref="IConfigurationSection"/> corresponding to the connectionName parameter.
/// </summary>
/// <param name="configuration">The <see cref="IConfiguration"/> instance to use.</param>
/// <param name="connectionName">Name of the section to search for.</param>
/// <returns></returns>
public static IConfigurationSection GetWebJobsConnectionSection(this IConfiguration configuration, string connectionName)
{
// first try prefixing
string prefixedConnectionStringName = GetPrefixedConnectionStringName(connectionName);
IConfigurationSection section = GetConnectionStringOrSettingSection(configuration, prefixedConnectionStringName);
if (!section.Exists())
{
// next try a direct unprefixed lookup
section = GetConnectionStringOrSettingSection(configuration, connectionName);
}
return section;
}
/// <summary>
/// Looks for a connection string by first checking the ConnectionStrings section, and then the root.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="connectionName">The connection string key.</param>
/// <returns>Configuration section corresponding to the connectionName</returns>
private static IConfigurationSection GetConnectionStringOrSettingSection(this IConfiguration configuration, string connectionName)
{
var connectionStringSection = configuration?.GetSection("ConnectionStrings").GetSection(connectionName);
if (connectionStringSection.Exists())
{
return connectionStringSection;
}
return configuration?.GetSection(connectionName);
}
}
}

Просмотреть файл

@ -1,73 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Shared.Protocol;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Specialized;
internal static class BlobStorageExtensions
{
public static Task SetServicePropertiesAsync(this CloudBlobClient sdk, ServiceProperties properties, CancellationToken cancellationToken)
public static async Task UploadTextAsync(this BlobClient blobClient, string content, bool overwrite = false, CancellationToken cancellationToken = default)
{
return sdk.SetServicePropertiesAsync(properties, requestOptions: null, operationContext: null, cancellationToken: cancellationToken);
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
await blobClient.UploadAsync(stream, overwrite: overwrite, cancellationToken: cancellationToken);
}
}
public static Task<ServiceProperties> GetServicePropertiesAsync(this CloudBlobClient sdk, CancellationToken cancellationToken)
public static async Task UploadTextAsync(this BlockBlobClient blockBlobClient, string content, CancellationToken cancellationToken = default)
{
return sdk.GetServicePropertiesAsync(cancellationToken);
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
await blockBlobClient.UploadAsync(stream, cancellationToken: cancellationToken);
}
}
public static Task<CloudBlobStream> OpenWriteAsync(this CloudBlockBlob sdk, CancellationToken cancellationToken)
public static async Task<string> DownloadTextAsync(this BlobClient blobClient, CancellationToken cancellationToken = default)
{
return sdk.OpenWriteAsync(accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task<string> DownloadTextAsync(this CloudBlockBlob sdk, CancellationToken cancellationToken)
{
return sdk.DownloadTextAsync(encoding: null, accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task UploadTextAsync(this CloudBlockBlob sdk, string content, Encoding encoding = null, AccessCondition accessCondition = null,
BlobRequestOptions options = null, OperationContext operationContext = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return sdk.UploadTextAsync(content, encoding, accessCondition, options, operationContext,
cancellationToken);
}
public static Task DeleteAsync(this CloudBlockBlob sdk, CancellationToken cancellationToken)
{
return sdk.DeleteAsync(DeleteSnapshotsOption.None, accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task CreateIfNotExistsAsync(this CloudBlobContainer sdk, CancellationToken cancellationToken)
{
return sdk.CreateIfNotExistsAsync(BlobContainerPublicAccessType.Off, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task<Stream> OpenReadAsync(this ICloudBlob sdk, CancellationToken cancellationToken)
{
return sdk.OpenReadAsync(accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task<string> AcquireLeaseAsync(this CloudBlockBlob sdk, TimeSpan? leaseTime, string proposedLeaseId,
CancellationToken cancellationToken)
{
return sdk.AcquireLeaseAsync(leaseTime, proposedLeaseId, accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task FetchAttributesAsync(this ICloudBlob sdk, CancellationToken cancellationToken)
{
return sdk.FetchAttributesAsync(accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
}
public static Task<ICloudBlob> GetBlobReferenceFromServerAsync(this CloudBlobContainer sdk, string blobName, CancellationToken cancellationToken)
{
return sdk.GetBlobReferenceFromServerAsync(blobName, accessCondition: null, options: null, operationContext: null, cancellationToken: cancellationToken);
var downloadResponse = await blobClient.DownloadAsync(cancellationToken: cancellationToken);
using (StreamReader reader = new StreamReader(downloadResponse.Value.Content, true))
{
string content = reader.ReadToEnd();
return content;
}
}
}

Просмотреть файл

@ -10,7 +10,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)BlobStorageExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)StorageExceptionsExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CommonUtility.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WebJobsStorageDelegatingHandler.cs" />
</ItemGroup>

Просмотреть файл

@ -1,818 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
namespace Microsoft.Azure.WebJobs
{
/// <summary>Provides extension methods for the <see cref="StorageException"/> class.</summary>
internal static class StorageExceptionExtensions
{
public static bool IsServerSideError(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
int statusCode = result.HttpStatusCode;
return statusCode >= 500 && statusCode < 600;
}
/// <summary>
/// Determines whether the exception is due to a 400 Bad Request error with the error code PopReceiptMismatch.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 400 Bad Request error with the error code
/// PopReceiptMismatch; otherwise <see langword="false"/>.
/// </returns>
public static bool IsBadRequestPopReceiptMismatch(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 400)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "PopReceiptMismatch";
}
/// <summary>Determines whether the exception is due to a 409 Conflict error.</summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error; otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflict(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
return result.HttpStatusCode == 409;
}
/// <summary>
/// Determines whether the exception is due to a 409 Conflict error with the error code BlobAlreadyExists.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error with the error code
/// BlobAlreadyExists; otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflictBlobAlreadyExists(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 409)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "BlobAlreadyExists";
}
/// <summary>
/// Determines whether the exception is due to a 409 Conflict error with the error code LeaseAlreadyPresent.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error with the error code
/// LeaseAlreadyPresent; otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflictLeaseAlreadyPresent(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 409)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "LeaseAlreadyPresent";
}
/// <summary>
/// Determines whether the exception is due to a 409 Conflict error with the error code
/// LeaseIdMismatchWithLeaseOperation.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error with the error code
/// LeaseIdMismatchWithLeaseOperation; otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflictLeaseIdMismatchWithLeaseOperation(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 409)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "LeaseIdMismatchWithLeaseOperation";
}
/// <summary>
/// Determines whether the exception is due to a 409 Conflict error with the error code QueueBeingDeleted.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error with the error code
/// QueueBeingDeleted; otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflictQueueBeingDeleted(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 409)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "QueueBeingDeleted";
}
/// <summary>
/// Determines whether the exception is due to a 409 Conflict error with the error code QueueBeingDeleted or
/// QueueDisabled.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error with the error code QueueBeingDeleted
/// or QueueDisabled; otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflictQueueBeingDeletedOrDisabled(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 409)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "QueueBeingDeleted";
}
/// <summary>
/// Determines whether the exception is due to a 409 Conflict error with the error code QueueDisabled.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 409 Conflict error with the error code QueueDisabled;
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsConflictQueueDisabled(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 409)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "QueueDisabled";
}
/// <summary>Determines whether the exception is due to a 404 Not Found error.</summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error; otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
return result.HttpStatusCode == 404;
}
/// <summary>
/// Determines whether the exception is due to a 404 Not Found error with the error code BlobNotFound.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error with the error code BlobNotFound;
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFoundBlobNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 404)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "BlobNotFound";
}
/// <summary>
/// Determines whether the exception is due to a 404 Not Found error with the error code ContainerNotFound.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error with the error code
/// ContainerNotFound; otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFoundContainerNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 404)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "ContainerNotFound";
}
/// <summary>
/// Determines whether the exception is due to a 404 Not Found error with the error code BlobNotFound or
/// ContainerNotFound.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error with the error code BlobNotFound or
/// ContainerNotFound; otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFoundBlobOrContainerNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 404)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
string errorCode = extendedInformation.ErrorCode;
return errorCode == "BlobNotFound" || errorCode == "ContainerNotFound";
}
/// <summary>
/// Determines whether the exception is due to a 404 Not Found error with the error code MessageNotFound or
/// QueueNotFound.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error with the error code MessageNotFound
/// or QueueNotFound; otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFoundMessageOrQueueNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 404)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
string errorCode = extendedInformation.ErrorCode;
return errorCode == "MessageNotFound" || errorCode == "QueueNotFound";
}
/// <summary>
/// Determines whether the exception is due to a 404 Not Found error with the error code MessageNotFound.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error with the error code MessageNotFound;
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFoundMessageNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 404)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "MessageNotFound";
}
/// <summary>
/// Determines whether the exception is due to a 404 Not Found error with the error code QueueNotFound.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 404 Not Found error with the error code QueueNotFound;
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsNotFoundQueueNotFound(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 404)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "QueueNotFound";
}
/// <summary>Determines whether the exception occurred despite a 200 OK response.</summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception occurred despite a 200 OK response; otherwise
/// <see langword="false"/>.
/// </returns>
public static bool IsOk(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
return result.HttpStatusCode == 200;
}
/// <summary>Determines whether the exception is due to a 412 Precondition Failed error.</summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 412 Precondition Failed error; otherwise
/// <see langword="false"/>.
/// </returns>
public static bool IsPreconditionFailed(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
return result.HttpStatusCode == 412;
}
/// <summary>
/// Determines whether the exception is due to a 412 Precondition Failed error with the error code
/// ConditionNotMet.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 412 Precondition Failed error with the error code
/// ConditionNotMet; otherwise <see langword="false"/>.
/// </returns>
public static bool IsPreconditionFailedConditionNotMet(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 412)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "ConditionNotMet";
}
/// <summary>
/// Determines whether the exception is due to a 412 Precondition Failed error with the error code
/// LeaseIdMissing.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 412 Precondition Failed error with the error code
/// LeaseIdMissing; otherwise <see langword="false"/>.
/// </returns>
public static bool IsPreconditionFailedLeaseIdMissing(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 412)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "LeaseIdMissing";
}
/// <summary>
/// Determines whether the exception is due to a 412 Precondition Failed error with the error code LeaseLost.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>
/// <see langword="true"/> if the exception is due to a 412 Precondition Failed error with the error code
/// LeaseLost; otherwise <see langword="false"/>.
/// </returns>
public static bool IsPreconditionFailedLeaseLost(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
if (result.HttpStatusCode != 412)
{
return false;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return false;
}
return extendedInformation.ErrorCode == "LeaseLost";
}
/// <summary>
/// Determines whether the exception is due to a task cancellation.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns><see langword="true"/> if the inner exception is a <see cref="TaskCanceledException"/>. Otherwise, <see langword="false"/>.</returns>
public static bool IsTaskCanceled(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
return exception.InnerException is TaskCanceledException;
}
/// <summary>
/// Returns the status code from the storage exception, or null.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <param name="statusCode">When this method returns, contains the status code.</param>
/// <returns>Returns true if there was a status code; otherwise, false.</returns>
public static bool TryGetStatusCode(this StorageException exception, out int statusCode)
{
statusCode = 0;
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return false;
}
statusCode = result.HttpStatusCode;
return true;
}
/// <summary>
/// Returns the error code from storage exception, or null.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>The error code, or null.</returns>
public static string GetErrorCode(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
RequestResult result = exception.RequestInformation;
if (result == null)
{
return null;
}
StorageExtendedErrorInformation extendedInformation = result.ExtendedErrorInformation;
if (extendedInformation == null)
{
return null;
}
return extendedInformation.ErrorCode;
}
/// <summary>
/// Returns a custom detailed error message for a StorageException. This is a workaround for bad error messages
/// returned by Azure Storage.
/// </summary>
/// <param name="exception">The storage exception.</param>
/// <returns>The error message.</returns>
public static string GetDetailedErrorMessage(this StorageException exception)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
string message = exception.Message;
if (exception.RequestInformation != null)
{
message += $" (HTTP status code {exception.RequestInformation.HttpStatusCode.ToString()}: "
+ $"{exception.RequestInformation.ExtendedErrorInformation?.ErrorCode}. "
+ $"{exception.RequestInformation.ExtendedErrorInformation?.ErrorMessage})";
}
return message;
}
}
}

Просмотреть файл

@ -11,8 +11,4 @@
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,48 +0,0 @@
using System;
using BenchmarkDotNet.Attributes;
using Microsoft.Azure.Storage.Queue;
using Microsoft.Azure.WebJobs.Host.Queues;
using Newtonsoft.Json.Linq;
namespace Benchmarks
{
[MemoryDiagnoser]
public class WebjobsExtensionsStorage
{
readonly CloudQueueMessage _minimal;
readonly CloudQueueMessage _moderate;
public WebjobsExtensionsStorage()
{
var expected = Guid.NewGuid();
var minimal = new JObject
{
{"$AzureWebJobsParentId", new JValue(expected.ToString())}
};
_minimal = new CloudQueueMessage(minimal.ToString());
var moderate = new JObject
{
{"$AzureWebJobsParentId", new JValue(expected.ToString())},
{"BlobName", new JValue("some/path/to/blob")},
{"Date", new JValue(DateTime.Now)},
{"Number", new JValue(1234324)},
};
_moderate = new CloudQueueMessage(moderate.ToString());
}
[Benchmark]
public Guid? QueueCausalityManager_GetOwner_Minimal()
{
return QueueCausalityManager.GetOwner(_minimal);
}
[Benchmark]
public Guid? QueueCausalityManager_GetOwner_Moderate()
{
return QueueCausalityManager.GetOwner(_moderate);
}
}
}

Просмотреть файл

@ -1256,12 +1256,12 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
public void StopListening()
{
_applicationInsightsListener.Stop();
_tcs?.Cancel(false);
if (_listenTask != null && !_listenTask.IsCompleted)
{
_listenTask.Wait();
}
_applicationInsightsListener.Stop();
_tcs?.Dispose();
_listenTask = null;

Просмотреть файл

@ -7,22 +7,22 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Queues;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Queue;
using Xunit;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
{
public class HttpDependencyCollectionTests : IDisposable
public class HttpDependencyCollectionTests : IAsyncLifetime
{
private const string TestArtifactPrefix = "e2etestsai";
private const string OutputQueueNamePattern = TestArtifactPrefix + "out%rnd%";
@ -41,10 +41,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
private string _outputQueueName;
private string _triggerQueueName;
private CloudBlobClient _blobClient;
private RandomNameResolver _resolver;
private CloudQueueClient _queueClient;
private CloudQueue _triggerQueue;
private BlobServiceClient _blobServiceClient;
private QueueServiceClient _queueServiceClient;
private QueueClient _triggerQueueClient;
[Fact]
public async Task BindingsAreNotReportedWhenFiltered()
@ -65,6 +65,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
Assert.Empty(_channel.Telemetries.OfType<DependencyTelemetry>().ToList());
}
// TODO: Analyze Track2 changes for differences in this test from Track1
[Fact]
public async Task BlobBindingsAreReported()
{
@ -81,15 +82,20 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
}
RequestTelemetry request = _channel.Telemetries.OfType<RequestTelemetry>().Single(r => r.Name == nameof(BlobInputAndOutputBindings));
var leafDependencies = new List<DependencyTelemetry>();
GetLeafDependenciesOfRequest(_channel.Telemetries.OfType<DependencyTelemetry>(), request.Id, leafDependencies);
List<DependencyTelemetry> dependencies = _channel.Telemetries.OfType<DependencyTelemetry>().ToList();
List<DependencyTelemetry> inDependencies = dependencies
var containerDependencies = dependencies.Where(d => d.Properties.ContainsKey("Container")).ToList();
List<DependencyTelemetry> inDependencies = containerDependencies
.Where(d => d.Properties.Single(p => p.Key == "Container").Value == _inputContainerName).ToList();
List<DependencyTelemetry> outDependencies = dependencies
List<DependencyTelemetry> outDependencies = containerDependencies
.Where(d => d.Properties.Single(p => p.Key == "Container").Value == _outputContainerName).ToList();
// only bindings calls are reported
Assert.Equal(dependencies.Count, inDependencies.Count + outDependencies.Count);
Assert.Equal(containerDependencies.Count, inDependencies.Count + outDependencies.Count);
foreach (var inputDep in inDependencies)
{
@ -99,7 +105,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
"in",
nameof(BlobInputAndOutputBindings),
request.Context.Operation.Id,
request.Id);
inputDep.Context.Operation.ParentId); // ParentId won't be the parentId of the RequestTelemetry since it is Dependency Tree
// Check that the ParentId for inputDep can be traced back to RequestTelemtry
Assert.Contains(leafDependencies, d => d.Id == inputDep.Id);
}
foreach (var outputDep in outDependencies)
@ -110,16 +119,38 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
"out",
nameof(BlobInputAndOutputBindings),
request.Context.Operation.Id,
request.Id);
outputDep.Context.Operation.ParentId); // ParentId won't be the parentId of the RequestTelemetry since it is Dependency Tree
// Check that the ParentId for outputDep can be traced back to RequestTelemtry
Assert.Contains(leafDependencies, d => d.Id == outputDep.Id);
}
// PUT container, HEAD blob, PUT lease, PUT content
Assert.True(outDependencies.Select(d => d.Name).Distinct().Count() <= 4);
// since there could be failures and retries, we should expect more
Assert.True(outDependencies.Count <= 4);
// HEAD blob, GET blob
Assert.True(inDependencies.Count >= 2);
Assert.True(inDependencies.Select(d => d.Name).Distinct().Count() >= 2);
// NEED TO REEXAMINE WITH TRACK2 EXTENSIONS
// since there could be failures and retries, we should expect more
//Assert.True(outDependencies.Count <= 4);
//Assert.True(inDependencies.Count >= 2);
}
private static void GetLeafDependenciesOfRequest(IEnumerable<DependencyTelemetry> dependencies, string currId, List<DependencyTelemetry> leafDependencies)
{
var children = dependencies.Where(d => d.Context.Operation.ParentId == currId);
if (!children.Any())
{
leafDependencies.Add(dependencies.Single(d => d.Id == currId));
return;
}
foreach (var dependency in children)
{
GetLeafDependenciesOfRequest(dependencies, dependency.Id, leafDependencies);
}
}
[Fact]
@ -182,6 +213,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
Assert.Single(_channel.Telemetries.OfType<DependencyTelemetry>());
}
// TODO: Analyze Track2 changes for differences in this test from Track1
[Fact]
public async Task BlobTriggerCallsAreReported()
{
@ -193,11 +225,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
await Task.Delay(3000);
Assert.Empty(_channel.Telemetries.OfType<DependencyTelemetry>());
CloudBlobContainer container = _blobClient.GetContainerReference(_triggerContainerName);
await container.CreateIfNotExistsAsync();
CloudBlockBlob blob = container.GetBlockBlobReference("triggerBlob");
await blob.UploadTextAsync("TestData");
var containerClient = _blobServiceClient.GetBlobContainerClient(_triggerContainerName);
await containerClient.CreateIfNotExistsAsync();
var blobClient = containerClient.GetBlobClient("triggerBlob");
blobClient.UploadTextAsync("TestData", overwrite: true).Wait();
_functionWaitHandle.WaitOne();
// let host run for a while to write output queue message
@ -207,15 +238,20 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
}
RequestTelemetry request = _channel.Telemetries.OfType<RequestTelemetry>().Single();
var leafDependencies = new List<DependencyTelemetry>();
GetLeafDependenciesOfRequest(_channel.Telemetries.OfType<DependencyTelemetry>(), request.Id, leafDependencies);
List<DependencyTelemetry> dependencies = _channel.Telemetries.OfType<DependencyTelemetry>().ToList();
List<DependencyTelemetry> blobDependencies = dependencies.Where(d => d.Type == "Azure blob").ToList();
List<DependencyTelemetry> queueDependencies = dependencies.Where(d => d.Type == "Azure queue").ToList();
var azureDependencies = dependencies.Where(d => d.Type.StartsWith("Azure")).ToList();
List<DependencyTelemetry> blobDependencies = azureDependencies.Where(d => d.Type == "Azure blob").ToList();
List<DependencyTelemetry> queueDependencies = azureDependencies.Where(d => d.Type == "Azure queue").ToList();
Assert.True(dependencies.Count >= 3);
Assert.True(azureDependencies.Count >= 3);
// only bindings calls are reported
Assert.Equal(dependencies.Count, blobDependencies.Count + queueDependencies.Count);
Assert.Equal(azureDependencies.Count, blobDependencies.Count + queueDependencies.Count);
// one HEAD, one GET to download blob
Assert.True(blobDependencies.Count >= 2);
@ -231,7 +267,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
"triggerBlob",
nameof(BlobTrigger),
request.Context.Operation.Id,
request.Id);
inputDep.Context.Operation.ParentId); // ParentId won't be the parentId of the RequestTelemetry since it is Dependency Tree
// Check that the ParentId for outputDep can be traced back to RequestTelemtry
Assert.Contains(leafDependencies, d => d.Id == inputDep.Id);
}
ValidateQueueDependency(
@ -239,9 +278,13 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
_outputQueueName,
nameof(BlobTrigger),
request.Context.Operation.Id,
request.Id);
queueDependencies.First().Context.Operation.ParentId); // ParentId won't be the parentId of the RequestTelemetry since it is Dependency Tree
// Check that the ParentId for outputDep can be traced back to RequestTelemtry
Assert.Contains(leafDependencies, d => d.Id == queueDependencies.First().Id);
}
// TODO: Analyze Track2 changes for differences in this test from Track1
[Fact]
public async Task QueueTriggerCallsAreReported()
{
@ -253,7 +296,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
await Task.Delay(3000);
Assert.Empty(_channel.Telemetries.OfType<DependencyTelemetry>());
await _triggerQueue.AddMessageAsync(new CloudQueueMessage("TestData"));
await _triggerQueueClient.SendMessageAsync("TestData");
_functionWaitHandle.WaitOne();
// let host run for a while to write output queue message
@ -263,16 +306,21 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
}
RequestTelemetry request = _channel.Telemetries.OfType<RequestTelemetry>().Single();
var leafDependencies = new List<DependencyTelemetry>();
GetLeafDependenciesOfRequest(_channel.Telemetries.OfType<DependencyTelemetry>(), request.Id, leafDependencies);
List<DependencyTelemetry> dependencies = _channel.Telemetries.OfType<DependencyTelemetry>().ToList();
List<DependencyTelemetry> bindingsDependencies = dependencies
var containerDependencies = dependencies.Where(d => d.Properties.ContainsKey("Container")).ToList();
List<DependencyTelemetry> bindingsDependencies = containerDependencies
.Where(d => d.Properties.Single(p => p.Key == LogConstants.CategoryNameKey).Value == LogCategories.Bindings).ToList();
// only bindings calls are reported, queue read happens before that and is not reported
Assert.True(dependencies.Count >= 4);
Assert.True(containerDependencies.Count >= 4);
// PUT container, HEAD blob, PUT lease, PUT content
Assert.Equal(dependencies.Count, bindingsDependencies.Count);
Assert.Equal(containerDependencies.Count, bindingsDependencies.Count);
foreach (var outputDep in bindingsDependencies)
{
@ -282,7 +330,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
"out1",
nameof(QueueTrigger),
request.Context.Operation.Id,
request.Id);
outputDep.Context.Operation.ParentId); // ParentId won't be the parentId of the RequestTelemetry since it is Dependency Tree
// Check that the ParentId for outputDep can be traced back to RequestTelemtry
Assert.Contains(leafDependencies, d => d.Id == outputDep.Id);
}
}
@ -372,7 +423,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<HttpDependencyCollectionTests>(b =>
{
b.AddAzureStorage();
// Necessary for Blob/Queue bindings
b.AddAzureStorageBlobs();
// Track2 Queues Extensions expects Base64 encoded message; Track2 Queues package no longer encodes by default (Track1 used Base64 default)
b.AddAzureStorageQueues(o => o.MessageEncoding = QueueMessageEncoding.None);
})
.ConfigureServices(services =>
{
@ -392,10 +446,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
TelemetryConfiguration telemteryConfiguration = host.Services.GetService<TelemetryConfiguration>();
telemteryConfiguration.TelemetryChannel = _channel;
StorageAccountProvider provider = host.Services.GetService<StorageAccountProvider>();
CloudStorageAccount storageAccount = provider.GetHost().SdkObject;
_blobClient = storageAccount.CreateCloudBlobClient();
_queueClient = storageAccount.CreateCloudQueueClient();
var configuration = host.Services.GetService<IConfiguration>();
_blobServiceClient = TestHelpers.GetTestBlobServiceClient();
_queueServiceClient = TestHelpers.GetTestQueueServiceClient();
_inputContainerName = _resolver.ResolveInString(InputContainerNamePattern);
_outputContainerName = _resolver.ResolveInString(OutputContainerNamePattern);
@ -403,27 +457,53 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
_outputQueueName = _resolver.ResolveInString(OutputQueueNamePattern);
_triggerQueueName = _resolver.ResolveInString(TriggerQueueNamePattern);
CloudBlobContainer inContainer = _blobClient.GetContainerReference(_inputContainerName);
CloudBlobContainer outContainer = _blobClient.GetContainerReference(_outputContainerName);
CloudBlobContainer triggerContainer = _blobClient.GetContainerReference(_triggerContainerName);
CloudQueue outputQueue = _queueClient.GetQueueReference(_outputQueueName);
_triggerQueue = _queueClient.GetQueueReference(_triggerQueueName);
var inContainer = _blobServiceClient.GetBlobContainerClient(_inputContainerName);
var outContainer = _blobServiceClient.GetBlobContainerClient(_outputContainerName);
var triggerContainer = _blobServiceClient.GetBlobContainerClient(_triggerContainerName);
var outputQueue = _queueServiceClient.GetQueueClient(_outputQueueName);
_triggerQueueClient = _queueServiceClient.GetQueueClient(_triggerQueueName);
inContainer.CreateIfNotExistsAsync().Wait();
outContainer.CreateIfNotExistsAsync().Wait();
triggerContainer.CreateIfNotExistsAsync().Wait();
outputQueue.CreateIfNotExistsAsync().Wait();
_triggerQueue.CreateIfNotExistsAsync().Wait();
inContainer.CreateIfNotExists();
outContainer.CreateIfNotExists();
triggerContainer.CreateIfNotExists();
outputQueue.CreateIfNotExists();
_triggerQueueClient.CreateIfNotExists();
CloudBlockBlob inBlob = inContainer.GetBlockBlobReference("in");
inBlob.UploadTextAsync("TestData").Wait();
var blobClient = inContainer.GetBlobClient("in");
blobClient.UploadTextAsync("TestData", overwrite: true).Wait();
return host;
}
public void Dispose()
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
_channel?.Dispose();
await CleanBlobsAsync();
await CleanQueuesAsync();
}
private async Task CleanBlobsAsync()
{
if (_blobServiceClient != null)
{
await foreach (var testBlob in _blobServiceClient.GetBlobContainersAsync(prefix: TestArtifactPrefix))
{
await _blobServiceClient.DeleteBlobContainerAsync(testBlob.Name);
}
}
}
private async Task CleanQueuesAsync()
{
if (_queueServiceClient != null)
{
await foreach (var testQueue in _queueServiceClient.GetQueuesAsync(prefix: TestArtifactPrefix))
{
await _queueServiceClient.DeleteQueueAsync(testQueue.Name);
}
}
}
}
}

Просмотреть файл

@ -407,7 +407,6 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests.ApplicationInsights
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<ServiceBusRequestAndDependencyCollectionTests>(b =>
{
b.AddAzureStorage();
b.AddServiceBus(o =>
{
// We'll complete these ourselves as we don't

Просмотреть файл

@ -4,18 +4,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Azure.Storage.Queues;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Queue;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
public class AsyncCancellationEndToEndTests : IDisposable
public class AsyncCancellationEndToEndTests : IAsyncLifetime
{
private const string TestArtifactPrefix = "asynccancele2e";
private const string QueueName = TestArtifactPrefix + "%rnd%";
@ -27,7 +24,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private static EventWaitHandle _functionCompleted;
private static bool _tokenCancelled;
private readonly CloudStorageAccount _storageAccount;
private readonly QueueServiceClient _queueServiceClient;
private readonly RandomNameResolver _resolver;
private readonly IHost _host;
@ -38,7 +35,9 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
_host = new HostBuilder()
.ConfigureDefaultTestHost<AsyncCancellationEndToEndTests>(b =>
{
b.AddAzureStorage();
// Necessary for Blob/Queue bindings
b.AddAzureStorageBlobs();
b.AddAzureStorageQueues();
})
.ConfigureServices(services =>
{
@ -46,8 +45,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
})
.Build();
var accountProvider = _host.Services.GetService<StorageAccountProvider>();
_storageAccount = accountProvider.GetHost().SdkObject;
_queueServiceClient = TestHelpers.GetTestQueueServiceClient();
_invokeInFunction = () => { };
_tokenCancelled = false;
@ -55,17 +53,23 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
_functionCompleted = new ManualResetEvent(initialState: false);
}
public void Dispose()
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
_functionStarted.Dispose();
_functionCompleted.Dispose();
if (_storageAccount != null)
await CleanQueuesAsync();
}
private async Task CleanQueuesAsync()
{
if (_queueServiceClient != null)
{
CloudQueueClient queueClient = _storageAccount.CreateCloudQueueClient();
foreach (var testQueue in queueClient.ListQueuesSegmentedAsync(TestArtifactPrefix, null).Result.Results)
await foreach (var testQueue in _queueServiceClient.GetQueuesAsync(prefix: TestArtifactPrefix))
{
testQueue.DeleteAsync().Wait();
await _queueServiceClient.DeleteQueueAsync(testQueue.Name);
}
}
}

Просмотреть файл

@ -11,18 +11,21 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Queues;
using Azure.Storage.Queues.Models;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Queues;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Queue;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
@ -43,7 +46,8 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private const string TriggerDetailsMessageStart = "Trigger Details:";
private static CloudStorageAccount _storageAccount;
private static QueueServiceClient _queueServiceClient;
private static BlobServiceClient _blobServiceClient;
private static RandomNameResolver _resolver;
private readonly IHostBuilder _hostBuilder;
@ -52,7 +56,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private static string _finalBlobContent;
private static TimeSpan _timeoutJobDelay;
private readonly CloudQueue _testQueue;
private readonly QueueClient _testQueueClient;
private readonly TestFixture _fixture;
public AsyncChainEndToEndTests(TestFixture fixture)
@ -60,10 +64,14 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
_fixture = fixture;
_resolver = new RandomNameResolver();
int processorCount = Extensions.Storage.Utility.GetProcessorCount();
_hostBuilder = new HostBuilder()
.ConfigureDefaultTestHost<AsyncChainEndToEndTests>(b =>
{
b.AddAzureStorage();
// Necessary for Blob/Queue bindings
b.AddAzureStorageQueues();
b.AddAzureStorageBlobs();
})
.ConfigureServices(services =>
{
@ -71,6 +79,8 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
services.Configure<QueuesOptions>(o =>
{
o.MaxPollingInterval = TimeSpan.FromSeconds(2);
o.BatchSize = 16;
o.NewBatchThreshold = 8 * processorCount; // workaround for static isDynamicSku and ProcessorCount variables in T2 extensions
});
services.Configure<FunctionResultAggregatorOptions>(o =>
{
@ -78,17 +88,20 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
});
});
_storageAccount = fixture.StorageAccount;
_blobServiceClient = fixture.BlobServiceClient;
_queueServiceClient = fixture.QueueServiceClient;
// This is how long the Timeout test functions will wait for cancellation.
_timeoutJobDelay = TimeSpan.FromSeconds(30);
CloudQueueClient queueClient = _storageAccount.CreateCloudQueueClient();
string queueName = _resolver.ResolveInString(TestQueueName);
_testQueue = queueClient.GetQueueReference(queueName);
if (!_testQueue.CreateIfNotExistsAsync().Result)
_testQueueClient = _queueServiceClient.GetQueueClient(queueName);
var response = _testQueueClient.CreateIfNotExistsAsync().Result;
if (response == null)
{
_testQueue.ClearAsync().Wait();
// Queue already exists, need to clear it
_testQueueClient.ClearMessagesAsync().Wait();
}
}
@ -105,6 +118,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
string[] loggerOutputLines = loggerProvider.GetAllLogMessages()
.Where(p => p.FormattedMessage != null)
.Where(p => p.Category != "Azure.Core")
.SelectMany(p => p.FormattedMessage.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries))
.OrderBy(p => p)
.ToArray();
@ -146,13 +160,14 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
"User TraceWriter log 1",
"User TraceWriter log 2",
"Starting JobHost",
"Stopped the listener 'Microsoft.Azure.WebJobs.Host.Listeners.CompositeListener' for function 'BlobToBlobAsync'",
"Stopped the listener 'Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener' for function 'QueueToBlobAsync'",
"Stopped the listener 'Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener' for function 'QueueToQueueAsync'",
"Stopped the listener 'Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.CompositeListener' for function 'BlobToBlobAsync'",
"Stopped the listener 'Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener' for function 'QueueToBlobAsync'",
"Stopped the listener 'Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener' for function 'QueueToQueueAsync'",
"Stopping JobHost",
"Stopping the listener 'Microsoft.Azure.WebJobs.Host.Listeners.CompositeListener' for function 'BlobToBlobAsync'",
"Stopping the listener 'Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener' for function 'QueueToBlobAsync'",
"Stopping the listener 'Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener' for function 'QueueToQueueAsync'",
"Stopping the listener 'Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.CompositeListener' for function 'BlobToBlobAsync'",
"Stopping the listener 'Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener' for function 'QueueToBlobAsync'",
"Stopping the listener 'Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener' for function 'QueueToQueueAsync'",
"registered http endpoint", // logged in Microsoft.Azure.WebJobs.Extensions.Blobs.BlobsExtensionConfigProvider
"QueuesOptions",
"{",
" \"BatchSize\": 16",
@ -160,6 +175,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
" \"MaxPollingInterval\": \"00:00:02\",",
string.Format(" \"NewBatchThreshold\": {0},", 8 * processorCount),
" \"VisibilityTimeout\": \"00:00:00\"",
" \"MessageEncoding\": \"Base64\"",
"}",
"LoggerFilterOptions",
"{",
@ -181,7 +197,16 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
"}",
"BlobsOptions",
"{",
" \"CentralizedPoisonQueue\": false",
string.Format(" \"MaxDegreeOfParallelism\": {0}", 8 * processorCount),
"}",
"QueuesOptions", // This QueuesOptions are an internal type within Microsoft.Azure.WebJobs.Extensions.Storage.Blobs
"{",
" \"BatchSize\": 16",
string.Format(" \"NewBatchThreshold\": {0},", 8 * processorCount),
" \"MaxPollingInterval\": \"00:01:00\",",
" \"MaxDequeueCount\": 5,",
" \"VisibilityTimeout\": \"00:00:00\"",
" \"MessageEncoding\": \"Base64\"",
"}",
"SingletonOptions",
"{",
@ -206,7 +231,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
string[] triggerDetailsLoggerOutput = loggerOutputLines
.Where(m => m.StartsWith(TriggerDetailsMessageStart)).ToArray();
string expectedPattern = "Trigger Details: MessageId: (.*), DequeueCount: [0-9]+, InsertionTime: (.*)";
string expectedPattern = "Trigger Details: MessageId: (.*), DequeueCount: [0-9]+, InsertedOn: (.*)";
foreach (string msg in triggerDetailsLoggerOutput)
{
@ -220,7 +245,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
using (_functionCompletedEvent = new ManualResetEvent(initialState: false))
{
CustomQueueProcessorFactory queueProcessorFactory = new CustomQueueProcessorFactory();
TestQueueProcessorFactory queueProcessorFactory = new TestQueueProcessorFactory();
_hostBuilder.ConfigureServices(services =>
{
@ -229,10 +254,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
await AsyncChainEndToEndInternal(_hostBuilder);
Assert.Equal(2, queueProcessorFactory.CustomQueueProcessors.Count);
Assert.True(queueProcessorFactory.CustomQueueProcessors.All(p => p.Context.Queue.Name.StartsWith("asynce2eq")));
Assert.True(queueProcessorFactory.CustomQueueProcessors.Sum(p => p.BeginProcessingCount) >= 2);
Assert.True(queueProcessorFactory.CustomQueueProcessors.Sum(p => p.CompleteProcessingCount) >= 2);
Assert.Equal(2, queueProcessorFactory.TestQueueProcessors.Count);
Assert.True(queueProcessorFactory.TestQueueProcessors.All(p => p.Context.Queue.Name.StartsWith("asynce2eq")));
Assert.True(queueProcessorFactory.TestQueueProcessors.Sum(p => p.BeginProcessingCount) >= 2);
Assert.True(queueProcessorFactory.TestQueueProcessors.Sum(p => p.CompleteProcessingCount) >= 2);
}
}
@ -243,8 +268,8 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
TestLoggerProvider loggerProvider = await AsyncChainEndToEndInternal(_hostBuilder);
// Validate Logger
bool hasError = loggerProvider.GetAllLogMessages().Where(p => p.FormattedMessage != null && p.FormattedMessage.Contains("Error")).Any();
// Validate Logger
bool hasError = loggerProvider.GetAllLogMessages().Where(p => p.FormattedMessage != null && p.Category != "Azure.Core" && p.FormattedMessage.Contains("Error")).Any();
Assert.False(hasError);
IEnumerable<string> userLogMessages = loggerProvider.GetAllLogMessages()
@ -410,13 +435,12 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
IHost host = _hostBuilder.Build();
JobHost jobHost = host.GetJobHost();
var blobClient = _fixture.StorageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("test-output");
if (await container.ExistsAsync())
var blobContainerClient = _blobServiceClient.GetBlobContainerClient("test-output");
if (await blobContainerClient.ExistsAsync())
{
foreach (CloudBlockBlob blob in (await container.ListBlobsSegmentedAsync(null)).Results)
await foreach (var blob in blobContainerClient.GetBlobsAsync())
{
await blob.DeleteAsync();
await blobContainerClient.DeleteBlobIfExistsAsync(blob.Name);
}
}
@ -428,12 +452,24 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
await jobHost.CallAsync(methodInfo, arguments);
// We expect 3 separate blobs to have been written
var blobs = (await container.ListBlobsSegmentedAsync(null)).Results.Cast<CloudBlockBlob>().ToArray();
Assert.Equal(3, blobs.Length);
foreach (var blob in blobs)
int blobCount = 0;
await foreach (var blob in blobContainerClient.GetBlobsAsync())
{
string content = await blob.DownloadTextAsync();
string content = await blobContainerClient.GetBlobClient(blob.Name).DownloadTextAsync();
Assert.Equal("Test Value", content.Trim(new char[] { '\uFEFF', '\u200B' }));
blobCount++;
}
Assert.Equal(3, blobCount);
// Test Cleanup
if (await blobContainerClient.ExistsAsync())
{
await foreach (var blob in blobContainerClient.GetBlobsAsync())
{
await blobContainerClient.DeleteBlobIfExistsAsync(blob.Name);
}
}
}
@ -643,15 +679,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
CancellationToken token,
TraceWriter trace)
{
CloudBlobClient blobClient = _storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(_resolver.ResolveInString(ContainerName));
CloudBlockBlob blob = container.GetBlockBlobReference(NonWebJobsBlobName);
string blobContent = await blob.DownloadTextAsync();
var content = await _blobServiceClient.GetBlobContainerClient(_resolver.ResolveInString(ContainerName)).GetBlobClient(NonWebJobsBlobName).DownloadTextAsync(cancellationToken: token);
trace.Info("User TraceWriter log 1");
await output.AddAsync(blobContent + message);
await output.AddAsync(content + message);
}
public static async Task QueueToBlobAsync(
@ -742,55 +773,44 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
userMessageCallback: () => $"Function did not complete in {waitTimeout} ms. Current time: {DateTime.UtcNow.ToString("HH:mm:ss.fff")}{Environment.NewLine}{loggerProvider.GetLogString()}");
}
private class CustomQueueProcessorFactory : IQueueProcessorFactory
private class TestQueueProcessorFactory : IQueueProcessorFactory
{
public List<CustomQueueProcessor> CustomQueueProcessors = new List<CustomQueueProcessor>();
public List<TestQueueProcessor> TestQueueProcessors = new List<TestQueueProcessor>();
public QueueProcessor Create(QueueProcessorFactoryContext context)
public QueueProcessor Create(QueueProcessorOptions context)
{
// demonstrates how the Queue.ServiceClient options can be configured
context.Queue.ServiceClient.DefaultRequestOptions.ServerTimeout = TimeSpan.FromSeconds(30);
// demonstrates how queue options can be customized
context.Queue.EncodeMessage = true;
// demonstrates how batch processing behavior and other knobs
// can be customized
context.BatchSize = 30;
context.NewBatchThreshold = 100;
context.MaxPollingInterval = TimeSpan.FromSeconds(15);
CustomQueueProcessor processor = new CustomQueueProcessor(context);
CustomQueueProcessors.Add(processor);
return processor;
var queueProcessor = new TestQueueProcessor(context);
TestQueueProcessors.Add(queueProcessor);
return queueProcessor;
}
}
public class CustomQueueProcessor : QueueProcessor
private class TestQueueProcessor : QueueProcessor
{
public int BeginProcessingCount = 0;
public int CompleteProcessingCount = 0;
public CustomQueueProcessor(QueueProcessorFactoryContext context) : base(context)
public TestQueueProcessor(QueueProcessorOptions context)
: base(context)
{
Context = context;
}
public QueueProcessorFactoryContext Context { get; private set; }
internal QueueProcessorOptions Context { get; private set; }
public override Task<bool> BeginProcessingMessageAsync(CloudQueueMessage message, CancellationToken cancellationToken)
protected override Task<bool> BeginProcessingMessageAsync(QueueMessage message, CancellationToken cancellationToken)
{
BeginProcessingCount++;
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
public override Task CompleteProcessingMessageAsync(CloudQueueMessage message, FunctionResult result, CancellationToken cancellationToken)
protected override Task CompleteProcessingMessageAsync(QueueMessage message, FunctionResult result, CancellationToken cancellationToken)
{
CompleteProcessingCount++;
return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
}
protected override async Task ReleaseMessageAsync(CloudQueueMessage message, FunctionResult result, TimeSpan visibilityTimeout, CancellationToken cancellationToken)
protected override async Task ReleaseMessageAsync(QueueMessage message, FunctionResult result, TimeSpan visibilityTimeout, CancellationToken cancellationToken)
{
// demonstrates how visibility timeout for failed messages can be customized
// the logic here could implement exponential backoff, etc.
@ -800,8 +820,11 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
}
public class TestFixture : IDisposable
public class TestFixture : IAsyncLifetime
{
public readonly BlobServiceClient BlobServiceClient;
public readonly QueueServiceClient QueueServiceClient;
public TestFixture()
{
// We know the tests are using the default storage provider, so pull that out
@ -809,32 +832,43 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<TestFixture>(b =>
{
b.AddAzureStorage();
// Necessary for Blob/Queue bindings
b.AddAzureStorageBlobs();
b.AddAzureStorageQueues();
})
.Build();
var provider = host.Services.GetService<StorageAccountProvider>();
StorageAccount = provider.GetHost().SdkObject;
BlobServiceClient = TestHelpers.GetTestBlobServiceClient();
QueueServiceClient = TestHelpers.GetTestQueueServiceClient();
}
public CloudStorageAccount StorageAccount
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
get;
private set;
await CleanBlobsAsync();
await CleanQueuesAsync();
}
public void Dispose()
private async Task CleanBlobsAsync()
{
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
foreach (var testContainer in blobClient.ListContainersSegmentedAsync(TestArtifactsPrefix, null).Result.Results)
if (BlobServiceClient != null)
{
testContainer.DeleteAsync().Wait();
await foreach (var testBlob in BlobServiceClient.GetBlobContainersAsync(prefix: TestArtifactsPrefix))
{
await BlobServiceClient.DeleteBlobContainerAsync(testBlob.Name);
}
}
}
CloudQueueClient queueClient = StorageAccount.CreateCloudQueueClient();
foreach (var testQueue in queueClient.ListQueuesSegmentedAsync(TestArtifactsPrefix, null).Result.Results)
private async Task CleanQueuesAsync()
{
if (QueueServiceClient != null)
{
testQueue.DeleteAsync().Wait();
await foreach (var testQueue in QueueServiceClient.GetQueuesAsync(prefix: TestArtifactsPrefix))
{
await QueueServiceClient.DeleteQueueAsync(testQueue.Name);
}
}
}
}
@ -860,7 +894,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
public Task OnUnhandledExceptionAsync(ExceptionDispatchInfo exceptionInfo)
{
// TODO: FACAVAL - Validate this, tests are stepping over each other.
if (!(exceptionInfo.SourceException is StorageException storageException &&
if (!(exceptionInfo.SourceException is RequestFailedException storageException &&
storageException?.InnerException is TaskCanceledException))
{
UnhandledExceptionInfos.Add(exceptionInfo);

Просмотреть файл

@ -1,583 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Table;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Queue;
using Microsoft.Azure.WebJobs.Host.Queues;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
using CloudStorageAccount = Microsoft.Azure.Storage.CloudStorageAccount;
using TableStorageAccount = Microsoft.Azure.Cosmos.Table.CloudStorageAccount;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
/// <summary>
/// Various E2E tests that use only the public surface and the real Azure storage
/// </summary>
public class AzureStorageEndToEndTests : IClassFixture<AzureStorageEndToEndTests.TestFixture>
{
private const string TestArtifactsPrefix = "e2etest";
private const string ContainerName = TestArtifactsPrefix + "container%rnd%";
private const string BlobName = "testblob";
private const string TableName = TestArtifactsPrefix + "table%rnd%";
private const string HostStartQueueName = TestArtifactsPrefix + "startqueue%rnd%";
private const string TestQueueName = TestArtifactsPrefix + "queue%rnd%";
private const string TestQueueNameEtag = TestArtifactsPrefix + "etag2equeue%rnd%";
private const string DoneQueueName = TestArtifactsPrefix + "donequeue%rnd%";
private const string BadMessageQueue1 = TestArtifactsPrefix + "-badmessage1-%rnd%";
private const string BadMessageQueue2 = TestArtifactsPrefix + "-badmessage2-%rnd%";
private static int _badMessage1Calls;
private static int _badMessage2Calls;
private static EventWaitHandle _startWaitHandle;
private static EventWaitHandle _functionChainWaitHandle;
private CloudStorageAccount _storageAccount;
private TableStorageAccount _tableStorageAccount;
private RandomNameResolver _resolver;
private static object testResult;
private static string _lastMessageId;
private static string _lastMessagePopReceipt;
public AzureStorageEndToEndTests(TestFixture fixture)
{
_storageAccount = fixture.StorageAccount;
_tableStorageAccount = fixture.TableStorageAccount;
}
/// <summary>
/// Used to syncronize the application start and blob creation
/// </summary>
public static void NotifyStart(
[QueueTrigger(HostStartQueueName)] string input)
{
_startWaitHandle.Set();
}
/// <summary>
/// Covers:
/// - blob binding to custom object
/// - blob trigger
/// - queue writing
/// - blob name pattern binding
/// </summary>
public static void BlobToQueue(
[BlobTrigger(ContainerName + @"/{name}")] string input,
string name,
[Queue(TestQueueNameEtag)] out CustomObject output)
{
// TODO: Use CustomObject as param when POCO blob supported:
// https://github.com/Azure/azure-webjobs-sdk/issues/995
var inputObject = JsonConvert.DeserializeObject<CustomObject>(input);
CustomObject result = new CustomObject()
{
Text = inputObject.Text + " " + name,
Number = inputObject.Number + 1
};
output = result;
}
/// <summary>
/// Covers:
/// - queue binding to custom object
/// - queue trigger
/// - table writing
/// </summary>
public static void QueueToICollectorAndQueue(
[QueueTrigger(TestQueueNameEtag)] CustomObject e2equeue,
[Table(TableName)] ICollector<ITableEntity> table,
[Queue(TestQueueName)] out CustomObject output)
{
const string tableKeys = "testETag";
DynamicTableEntity result = new DynamicTableEntity
{
PartitionKey = tableKeys,
RowKey = tableKeys,
Properties = new Dictionary<string, EntityProperty>()
{
{ "Text", new EntityProperty("before") },
{ "Number", new EntityProperty("1") }
}
};
table.Add(result);
result.Properties["Text"] = new EntityProperty("after");
result.ETag = "*";
table.Add(result);
output = e2equeue;
}
/// <summary>
/// Covers:
/// - queue binding to custom object
/// - queue trigger
/// - table writing
/// </summary>
public static void QueueToTable(
[QueueTrigger(TestQueueName)] CustomObject e2equeue,
[Table(TableName)] CloudTable table,
[Queue(DoneQueueName)] out string e2edone)
{
const string tableKeys = "test";
CustomTableEntity result = new CustomTableEntity
{
PartitionKey = tableKeys,
RowKey = tableKeys,
Text = e2equeue.Text + " " + "QueueToTable",
Number = e2equeue.Number + 1
};
table.ExecuteAsync(TableOperation.InsertOrReplace(result)).Wait();
// Write a queue message to signal the scenario completion
e2edone = "done";
}
/// <summary>
/// Notifies the completion of the scenario
/// </summary>
public static void NotifyCompletion(
[QueueTrigger(DoneQueueName)] string e2edone)
{
_functionChainWaitHandle.Set();
}
/// <summary>
/// We'll insert a bad message. It should get here okay. It will
/// then pass it on to the next trigger.
/// </summary>
public static void BadMessage_CloudQueueMessage(
[QueueTrigger(BadMessageQueue1)] CloudQueueMessage badMessageIn,
[Queue(BadMessageQueue2)] out CloudQueueMessage badMessageOut,
TraceWriter log)
{
_badMessage1Calls++;
badMessageOut = badMessageIn;
}
public static void BadMessage_String(
[QueueTrigger(BadMessageQueue2)] string message,
TraceWriter log)
{
_badMessage2Calls++;
}
[NoAutomaticTrigger]
public static void TableWithFilter(
[QueueTrigger("test")] Person person,
[Table(TableName, Filter = "(Age gt {Age}) and (Location eq '{Location}')")] JArray results)
{
testResult = results;
}
// Uncomment the Fact attribute to run
// [Fact(Timeout = 20 * 60 * 1000)]
public async Task AzureStorageEndToEndSlow()
{
await EndToEndTest(uploadBlobBeforeHostStart: false);
}
[Fact]
public async Task AzureStorageEndToEndFast()
{
await EndToEndTest(uploadBlobBeforeHostStart: true);
}
[Fact]
public async Task TableFilterTest()
{
// Reinitialize the name resolver to avoid conflicts
_resolver = new RandomNameResolver();
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<AzureStorageEndToEndTests>(b =>
{
b.AddAzureStorage();
})
.ConfigureServices(services =>
{
services.AddSingleton<INameResolver>(_resolver);
})
.Build();
// write test entities
string testTableName = _resolver.ResolveInString(TableName);
CloudTableClient tableClient = _tableStorageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference(testTableName);
await table.CreateIfNotExistsAsync();
var operation = new TableBatchOperation();
operation.Insert(new Person
{
PartitionKey = "1",
RowKey = "1",
Name = "Lary",
Age = 20,
Location = "Seattle"
});
operation.Insert(new Person
{
PartitionKey = "1",
RowKey = "2",
Name = "Moe",
Age = 35,
Location = "Seattle"
});
operation.Insert(new Person
{
PartitionKey = "1",
RowKey = "3",
Name = "Curly",
Age = 45,
Location = "Texas"
});
operation.Insert(new Person
{
PartitionKey = "1",
RowKey = "4",
Name = "Bill",
Age = 28,
Location = "Tam O'Shanter"
});
await table.ExecuteBatchAsync(operation);
JobHost jobHost = host.GetJobHost();
var methodInfo = GetType().GetMethod(nameof(TableWithFilter));
var input = new Person { Age = 25, Location = "Seattle" };
string json = JsonConvert.SerializeObject(input);
var arguments = new { person = json };
await jobHost.CallAsync(methodInfo, arguments);
// wait for test results to appear
await TestHelpers.Await(() => testResult != null);
JArray results = (JArray)testResult;
Assert.Single(results);
input = new Person { Age = 25, Location = "Tam O'Shanter" };
json = JsonConvert.SerializeObject(input);
arguments = new { person = json };
await jobHost.CallAsync(methodInfo, arguments);
await TestHelpers.Await(() => testResult != null);
results = (JArray)testResult;
Assert.Single(results);
Assert.Equal("Bill", (string)results[0]["Name"]);
}
private async Task EndToEndTest(bool uploadBlobBeforeHostStart)
{
// Reinitialize the name resolver to avoid conflicts
_resolver = new RandomNameResolver();
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<AzureStorageEndToEndTests>(b =>
{
b.AddAzureStorage();
})
.ConfigureServices(services =>
{
services.AddSingleton<INameResolver>(_resolver);
})
.Build();
if (uploadBlobBeforeHostStart)
{
// The function will be triggered fast because the blob is already there
await UploadTestObject();
}
// The jobs host is started
JobHost jobHost = host.GetJobHost();
_functionChainWaitHandle = new ManualResetEvent(initialState: false);
await host.StartAsync();
if (!uploadBlobBeforeHostStart)
{
await WaitForTestFunctionsToStart();
await UploadTestObject();
}
var waitTime = TimeSpan.FromSeconds(15);
bool signaled = _functionChainWaitHandle.WaitOne(waitTime);
// Stop the host and wait for it to finish
await host.StopAsync();
Assert.True(signaled, $"[{DateTime.UtcNow.ToString("HH:mm:ss.fff")}] Function chain did not complete in {waitTime}. Logs:{Environment.NewLine}{host.GetTestLoggerProvider().GetLogString()}");
// Verify
await VerifyTableResultsAsync();
}
[Fact]
public async Task BadQueueMessageE2ETests()
{
// This test ensures that the host does not crash on a bad message (it previously did)
// Insert a bad message into a queue that should:
// - trigger BadMessage_CloudQueueMessage, which will put it into a second queue that will
// - trigger BadMessage_String, which should fail
// - BadMessage_String should fail repeatedly until it is moved to the poison queue
// The test will watch that poison queue to know when to complete
// Reinitialize the name resolver to avoid conflicts
_resolver = new RandomNameResolver();
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<AzureStorageEndToEndTests>(b =>
{
b.AddAzureStorage();
})
.ConfigureServices(services =>
{
// use a custom processor so we can grab the Id and PopReceipt
services.AddSingleton<IQueueProcessorFactory>(new TestQueueProcessorFactory());
services.AddSingleton<INameResolver>(_resolver);
})
.Build();
TestLoggerProvider loggerProvider = host.GetTestLoggerProvider();
// The jobs host is started
host.Start();
// Construct a bad message:
// - use a GUID as the content, which is not a valid base64 string
// - pass 'true', to indicate that it is a base64 string
string messageContent = Guid.NewGuid().ToString();
var message = new CloudQueueMessage(messageContent, true);
var queueClient = _storageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference(_resolver.ResolveInString(BadMessageQueue1));
await queue.CreateIfNotExistsAsync();
await queue.ClearAsync();
// the poison queue will end up off of the second queue
var poisonQueue = queueClient.GetQueueReference(_resolver.ResolveInString(BadMessageQueue2) + "-poison");
await poisonQueue.DeleteIfExistsAsync();
await queue.AddMessageAsync(message);
CloudQueueMessage poisonMessage = null;
await TestHelpers.Await(async () =>
{
bool done = false;
if (await poisonQueue.ExistsAsync())
{
poisonMessage = await poisonQueue.GetMessageAsync();
done = poisonMessage != null;
if (done)
{
// Sleep briefly, then make sure the other message has been deleted.
// If so, trying to delete it again will throw an error.
Thread.Sleep(1000);
// The message is in the second queue
var queue2 = queueClient.GetQueueReference(_resolver.ResolveInString(BadMessageQueue2));
Azure.Storage.StorageException ex = await Assert.ThrowsAsync<Azure.Storage.StorageException>(
() => queue2.DeleteMessageAsync(_lastMessageId, _lastMessagePopReceipt));
Assert.Equal("MessageNotFound", ex.RequestInformation.ExtendedErrorInformation.ErrorCode);
}
}
var logs = loggerProvider.GetAllLogMessages();
return done;
});
await host.StopAsync();
// find the raw string to compare it to the original
Assert.NotNull(poisonMessage);
var propInfo = typeof(CloudQueueMessage).GetProperty("RawString", BindingFlags.Instance | BindingFlags.NonPublic);
string rawString = propInfo.GetValue(poisonMessage) as string;
Assert.Equal(messageContent, rawString);
// Make sure the functions were called correctly
Assert.Equal(1, _badMessage1Calls);
Assert.Equal(0, _badMessage2Calls);
// Validate Logger
var loggerErrors = loggerProvider.GetAllLogMessages().Where(l => l.Level == Microsoft.Extensions.Logging.LogLevel.Error);
Assert.True(loggerErrors.All(t => t.Exception.InnerException.InnerException is FormatException));
}
private async Task UploadTestObject()
{
string testContainerName = _resolver.ResolveInString(ContainerName);
CloudBlobContainer container = _storageAccount.CreateCloudBlobClient().GetContainerReference(testContainerName);
await container.CreateIfNotExistsAsync();
// The test blob
CloudBlockBlob testBlob = container.GetBlockBlobReference(BlobName);
CustomObject testObject = new CustomObject()
{
Text = "Test",
Number = 42
};
await testBlob.UploadTextAsync(JsonConvert.SerializeObject(testObject));
}
private async Task WaitForTestFunctionsToStart()
{
_startWaitHandle = new ManualResetEvent(initialState: false);
string startQueueName = _resolver.ResolveInString(HostStartQueueName);
CloudQueueClient queueClient = _storageAccount.CreateCloudQueueClient();
CloudQueue queue = queueClient.GetQueueReference(startQueueName);
await queue.CreateIfNotExistsAsync();
await queue.AddMessageAsync(new CloudQueueMessage(String.Empty));
_startWaitHandle.WaitOne(30000);
}
private async Task VerifyTableResultsAsync()
{
string testTableName = _resolver.ResolveInString(TableName);
CloudTableClient tableClient = _tableStorageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference(testTableName);
Assert.True(await table.ExistsAsync(), "Result table not found");
TableQuery query = new TableQuery()
.Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "test"),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "test")))
.Take(1);
DynamicTableEntity result = (await table.ExecuteQuerySegmentedAsync(query, null)).FirstOrDefault();
// Ensure expected row found
Assert.NotNull(result);
Assert.Equal("Test testblob QueueToTable", result.Properties["Text"].StringValue);
Assert.Equal(44, result.Properties["Number"].Int32Value);
query = new TableQuery()
.Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "testETag"),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "testETag")))
.Take(1);
result = (await table.ExecuteQuerySegmentedAsync(query, null)).FirstOrDefault();
// Ensure expected row found
Assert.NotNull(result);
Assert.Equal("after", result.Properties["Text"].StringValue);
}
private class CustomTableEntity : TableEntity
{
public string Text { get; set; }
public int Number { get; set; }
}
public class Person : TableEntity
{
public int Age { get; set; }
public string Location { get; set; }
public string Name { get; set; }
}
private class TestQueueProcessorFactory : IQueueProcessorFactory
{
public QueueProcessor Create(QueueProcessorFactoryContext context)
{
return new TestQueueProcessor(context);
}
}
private class TestQueueProcessor : QueueProcessor
{
public TestQueueProcessor(QueueProcessorFactoryContext context)
: base(context)
{
}
public override Task<bool> BeginProcessingMessageAsync(CloudQueueMessage message, CancellationToken cancellationToken)
{
_lastMessageId = message.Id;
_lastMessagePopReceipt = message.PopReceipt;
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
}
public class TestFixture : IDisposable
{
public TestFixture()
{
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<TestFixture>(b =>
{
b.AddAzureStorage();
})
.Build();
var provider = host.Services.GetService<StorageAccountProvider>();
StorageAccount = provider.GetHost().SdkObject;
TableStorageAccount = provider.GetHost().TableSdkObject;
}
public CloudStorageAccount StorageAccount
{
get;
private set;
}
public TableStorageAccount TableStorageAccount
{
get;
private set;
}
public void Dispose()
{
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
foreach (var testContainer in blobClient.ListContainersSegmentedAsync(TestArtifactsPrefix, null).Result.Results)
{
testContainer.DeleteAsync().Wait();
}
CloudQueueClient queueClient = StorageAccount.CreateCloudQueueClient();
foreach (var testQueue in queueClient.ListQueuesSegmentedAsync(TestArtifactsPrefix, null).Result.Results)
{
testQueue.DeleteAsync().Wait();
}
CloudTableClient tableClient = TableStorageAccount.CreateCloudTableClient();
foreach (var testTable in tableClient.ListTablesSegmentedAsync(TestArtifactsPrefix, null).Result.Results)
{
testTable.DeleteAsync().Wait();
}
}
}
}
}

Просмотреть файл

@ -1,846 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
public class BlobBindingEndToEndTests : IClassFixture<BlobBindingEndToEndTests.TestFixture>
{
private const string TestArtifactPrefix = "e2etestbindings";
private const string ContainerName = TestArtifactPrefix + "-%rnd%";
private const string OutputContainerName = TestArtifactPrefix + "-out%rnd%";
private const string PageBlobContainerName = TestArtifactPrefix + "pageblobs-%rnd%";
private const string AppendBlobContainerName = TestArtifactPrefix + "appendblobs-%rnd%";
private const string HierarchicalBlobContainerName = TestArtifactPrefix + "subblobs-%rnd%";
private const string TestData = "TestData";
private readonly TestFixture _fixture;
private static int _numBlobsRead;
public BlobBindingEndToEndTests(TestFixture fixture)
{
_fixture = fixture;
_numBlobsRead = 0;
}
[Fact]
public async Task BindToCloudBlobContainer()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("CloudBlobContainerBinding"));
Assert.Equal(6, _numBlobsRead);
}
[Fact]
public async Task BindToCloudBlobDirectory()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("CloudBlobDirectoryBinding"));
Assert.Equal(3, _numBlobsRead);
}
[Fact]
public async Task BindToCloudBlobContainer_WithModelBinding()
{
TestPoco poco = new TestPoco
{
A = _fixture.NameResolver.ResolveWholeString(ContainerName)
};
string json = JsonConvert.SerializeObject(poco);
var arguments = new { poco = json };
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("CloudBlobContainerBinding_WithModelBinding"), arguments);
Assert.Equal(6, _numBlobsRead);
}
[Fact]
public async Task BindToCloudBlockBlob_WithUrlBinding()
{
// get url for the test blob
CloudBlockBlob blob = _fixture.BlobContainer.GetBlockBlobReference("blob1");
TestPoco poco = new TestPoco
{
A = blob.Uri.ToString()
};
string json = JsonConvert.SerializeObject(poco);
var arguments = new { poco = json };
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("CloudBlockBlobBinding_WithUrlBinding"), arguments);
Assert.Equal(1, _numBlobsRead);
}
[Fact]
public async Task BindToCloudBlob_WithModelBinding_Fail()
{
TestPoco poco = new TestPoco
{
A = _fixture.NameResolver.ResolveWholeString(ContainerName)
};
string json = JsonConvert.SerializeObject(poco);
var arguments = new { poco = json };
var ex = await Assert.ThrowsAsync<FunctionInvocationException>(() =>
_fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("CloudBlockBlobBinding_WithUrlBinding"), arguments));
// CloudBlockBlobBinding_WithUrlBinding is suppose to bind to a blob
Assert.Equal($"Invalid blob path specified : '{poco.A}'. Blob identifiers must be in the format 'container/blob'.", ex.InnerException.InnerException.Message);
}
[Fact]
public async Task BindToCloudBlobContainer_WithUrlBinding_Fail()
{
// get url for the test blob
CloudBlockBlob blob = _fixture.BlobContainer.GetBlockBlobReference("blob1");
TestPoco poco = new TestPoco
{
A = blob.Uri.ToString()
};
string json = JsonConvert.SerializeObject(poco);
var arguments = new { poco = json };
var ex = await Assert.ThrowsAsync<FunctionInvocationException>(() =>
_fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("CloudBlobContainerBinding_WithModelBinding"), arguments));
// CloudBlobContainerBinding_WithModelBinding is suppose to bind to a container
Assert.IsType<FormatException>(ex.InnerException.InnerException);
}
[Fact]
public async Task BindToIEnumerableCloudBlockBlob_WithPrefixFilter()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudBlockBlobBinding_WithPrefixFilter"));
Assert.Equal(3, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableCloudBlockBlob_WithPrefixFilter_NoMatchingBlobs()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudBlockBlobBinding_WithPrefixFilter_NoMatchingBlobs"));
Assert.Equal(0, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableCloudBlockBlob_WithPrefixFilter_HierarchicalBlobs()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudBlockBlobBinding_WithPrefixFilter_HierarchicalBlobs"));
Assert.Equal(2, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableCloudBlockBlob_WithPrefixFilter_HierarchicalBlobs_UsesFlatBlobListing()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudBlockBlobBinding_WithPrefixFilter_HierarchicalBlobs_UsesFlatBlobListing"));
Assert.Equal(3, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableCloudBlockBlob_WithModelBinding()
{
TestPoco poco = new TestPoco
{
A = _fixture.NameResolver.ResolveWholeString(ContainerName),
B = "bl"
};
string json = JsonConvert.SerializeObject(poco);
var arguments = new { poco = json };
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudBlockBlobBinding_WithModelBinding"), arguments);
Assert.Equal(3, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableCloudPageBlob()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudPageBlobBinding"));
Assert.Equal(2, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableCloudAppendBlob()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableCloudAppendBlobBinding"));
Assert.Equal(3, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableString()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableStringBinding"));
Assert.Equal(6, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableStream()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableStreamBinding"));
Assert.Equal(6, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableTextReader()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableTextReaderBinding"));
Assert.Equal(6, _numBlobsRead);
}
[Fact]
public async Task BindToIEnumerableICloudBlob()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("IEnumerableICloudBlobBinding"));
Assert.Equal(6, _numBlobsRead);
}
[Fact]
public async Task BindToByteArray()
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("ByteArrayBinding"));
Assert.Equal(1, _numBlobsRead);
}
[Theory]
[InlineData("StringBinding_Block")]
[InlineData("StringBinding_Page")]
[InlineData("StringBinding_Append")]
public async Task BindToString(string functionName)
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod(functionName));
Assert.Equal(1, _numBlobsRead);
}
[Theory]
[InlineData("StreamBindingReadable_Block")]
[InlineData("StreamBindingReadable_Page")]
[InlineData("StreamBindingReadable_Append")]
public async Task BindToStream(string functionName)
{
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod(functionName));
Assert.Equal(1, _numBlobsRead);
}
[Fact]
public async Task BindToOutString()
{
CloudBlockBlob blob = _fixture.BlobContainer.GetBlockBlobReference("overwrite");
Assert.Equal(TestData, await blob.DownloadTextAsync());
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("OutStringBinding_Block"));
string text = null;
using (var reader = new StreamReader(await blob.OpenReadAsync()))
{
text = reader.ReadToEnd();
}
Assert.Equal("overwritten", text);
await blob.UploadTextAsync(TestData);
}
[Theory]
[InlineData("OutStringBinding_Page")]
[InlineData("OutStringBinding_Append")]
public async Task BindToOutString_Fails(string functionName)
{
var ex = await Assert.ThrowsAsync<FunctionInvocationException>(() =>
_fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod(functionName)));
var innerEx = ex.InnerException.InnerException;
Assert.Equal("Cannot bind to page or append blobs using 'out string', 'TextWriter', or writable 'Stream' parameters.", innerEx.Message);
}
[Fact]
public async Task BindToTextWriter()
{
CloudBlockBlob blob = _fixture.BlobContainer.GetBlockBlobReference("overwrite");
Assert.Equal(TestData, await blob.DownloadTextAsync());
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("OutStringBinding_Block"));
string text = null;
using (var reader = new StreamReader(await blob.OpenReadAsync()))
{
text = reader.ReadToEnd();
}
Assert.Equal("overwritten", text);
await blob.UploadTextAsync(TestData);
}
[Theory]
[InlineData("TextWriterBinding_Page")]
[InlineData("TextWriterBinding_Append")]
public async Task BindToTextWriter_Fails(string functionName)
{
var ex = await Assert.ThrowsAsync<FunctionInvocationException>(() =>
_fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod(functionName)));
var innerEx = ex.InnerException.InnerException;
Assert.Equal("Cannot bind to page or append blobs using 'out string', 'TextWriter', or writable 'Stream' parameters.", innerEx.Message);
}
[Fact]
public async Task BindToByteArray_Output()
{
// if the function sets the output binding to null, no blob
// should be written
var arguments = new { input = "null" };
var method = typeof(BlobBindingEndToEndTests).GetMethod("ByteArrayOutputBinding");
await _fixture.JobHost.CallAsync(method, arguments);
CloudBlockBlob blob = _fixture.OutputBlobContainer.GetBlockBlobReference("blob1");
Assert.False(await blob.ExistsAsync());
// if the function sets a value, the blob should be written
arguments = new { input = TestData };
await _fixture.JobHost.CallAsync(method, arguments);
Assert.True(await blob.ExistsAsync());
string result = await blob.DownloadTextAsync();
Assert.Equal(TestData, result);
}
[Fact]
public async Task BindToByteArray_Trigger()
{
var arguments = new { blob = string.Format("{0}/{1}", _fixture.NameResolver.ResolveWholeString(ContainerName), "blob1") };
await _fixture.JobHost.CallAsync(typeof(BlobBindingEndToEndTests).GetMethod("ByteArrayTriggerBinding"), arguments);
Assert.Equal(1, _numBlobsRead);
}
[Fact]
public async Task BlobTriggerSingletonListener_LockIsHeld()
{
await _fixture.VerifyLockState("WebJobs.Internal.Blobs.Listener", LeaseState.Leased, LeaseStatus.Locked);
}
// This function just exists to force initialization of the
// blob listener pipeline
public static void TestBlobTrigger([BlobTrigger("test/test")] string blob)
{
}
[NoAutomaticTrigger]
public static void CloudBlobContainerBinding(
[Blob(ContainerName)] CloudBlobContainer container)
{
var blobs = container.ListBlobsSegmentedAsync(null).Result;
foreach (CloudBlockBlob blob in blobs.Results)
{
string content = blob.DownloadTextAsync().Result;
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Results.Count();
}
[NoAutomaticTrigger]
public static async Task CloudBlobDirectoryBinding(
[Blob(HierarchicalBlobContainerName + "/sub")] CloudBlobDirectory directory)
{
var directoryItems = await directory.ListBlobsSegmentedAsync(null);
var blobs = directoryItems.Results.OfType<CloudBlockBlob>();
foreach (CloudBlockBlob blob in blobs)
{
string content = blob.DownloadTextAsync().Result;
Assert.Equal(TestData, content);
}
_numBlobsRead += blobs.Count();
CloudBlobDirectory subDirectory = directoryItems.Results.OfType<CloudBlobDirectory>().Single();
CloudBlockBlob subBlob = (await subDirectory.ListBlobsSegmentedAsync(null)).Results.Cast<CloudBlockBlob>().Single();
Assert.Equal(TestData, await subBlob.DownloadTextAsync());
_numBlobsRead += 1;
}
[NoAutomaticTrigger]
public static void CloudBlobContainerBinding_WithModelBinding(
[QueueTrigger("testqueue")] TestPoco poco,
[Blob("{A}")] CloudBlobContainer container)
{
CloudBlobContainerBinding(container);
}
[NoAutomaticTrigger]
public static void CloudBlockBlobBinding_WithUrlBinding(
[QueueTrigger("testqueue")] TestPoco poco,
[Blob("{A}")] string blob)
{
Assert.Equal(TestData, blob);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static async Task IEnumerableCloudBlockBlobBinding_WithPrefixFilter(
[Blob(ContainerName + "/blo")] IEnumerable<CloudBlockBlob> blobs)
{
foreach (var blob in blobs)
{
string content = await blob.DownloadTextAsync();
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static void IEnumerableCloudBlockBlobBinding_WithPrefixFilter_NoMatchingBlobs(
[Blob(ContainerName + "/dne")] IEnumerable<CloudBlockBlob> blobs)
{
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static async Task IEnumerableCloudBlockBlobBinding_WithPrefixFilter_HierarchicalBlobs(
[Blob(HierarchicalBlobContainerName + "/sub/bl")] IEnumerable<CloudBlockBlob> blobs)
{
foreach (var blob in blobs)
{
string content = await blob.DownloadTextAsync();
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Count();
}
// Ensure that a flat blob listing is used, meaning if a route prefix covers
// sub directries, blobs within those sub directories are returned. Users can bind
// to CloudBlobDirectory if they want to operate on directories.
[NoAutomaticTrigger]
public static async Task IEnumerableCloudBlockBlobBinding_WithPrefixFilter_HierarchicalBlobs_UsesFlatBlobListing(
[Blob(HierarchicalBlobContainerName + "/sub")] IEnumerable<CloudBlockBlob> blobs)
{
foreach (var blob in blobs)
{
string content = await blob.DownloadTextAsync();
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static async Task IEnumerableCloudBlockBlobBinding_WithModelBinding(
[QueueTrigger("testqueue")] TestPoco poco,
[Blob("{A}/{B}ob")] IEnumerable<CloudBlockBlob> blobs)
{
foreach (var blob in blobs)
{
string content = await blob.DownloadTextAsync();
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static async Task IEnumerableCloudPageBlobBinding(
[Blob(PageBlobContainerName)] IEnumerable<CloudPageBlob> blobs)
{
foreach (var blob in blobs)
{
byte[] bytes = new byte[512];
int byteCount = await blob.DownloadToByteArrayAsync(bytes, 0);
string content = Encoding.UTF8.GetString(bytes, 0, byteCount);
Assert.True(content.StartsWith(TestData));
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static async Task IEnumerableCloudAppendBlobBinding(
[Blob(AppendBlobContainerName)] IEnumerable<CloudAppendBlob> blobs)
{
foreach (var blob in blobs)
{
string content = await blob.DownloadTextAsync();
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static void IEnumerableStringBinding(
[Blob(ContainerName)] IEnumerable<string> blobs)
{
foreach (var blob in blobs)
{
Assert.Equal(TestData, blob);
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static void IEnumerableStreamBinding(
[Blob(ContainerName)] IEnumerable<Stream> blobs)
{
foreach (var blobStream in blobs)
{
using (StreamReader reader = new StreamReader(blobStream))
{
string content = reader.ReadToEnd();
Assert.Equal(TestData, content);
}
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static void IEnumerableTextReaderBinding(
[Blob(ContainerName)] IEnumerable<TextReader> blobs)
{
foreach (var blob in blobs)
{
string content = blob.ReadToEnd();
Assert.Equal(TestData, content);
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static async Task IEnumerableICloudBlobBinding(
[Blob(ContainerName)] IEnumerable<ICloudBlob> blobs)
{
foreach (var blob in blobs)
{
Stream stream = await blob.OpenReadAsync(null, null, null);
using (StreamReader reader = new StreamReader(stream))
{
string content = reader.ReadToEnd();
Assert.Equal(TestData, content);
}
}
_numBlobsRead = blobs.Count();
}
[NoAutomaticTrigger]
public static void StringBinding_Block(
[Blob(ContainerName + "/blob1")] string blob)
{
Assert.Equal(TestData, blob);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void StringBinding_Page(
[Blob(PageBlobContainerName + "/blob1")] string blob)
{
blob = blob.Trim('\0');
Assert.Equal(TestData, blob);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void StringBinding_Append(
[Blob(AppendBlobContainerName + "/blob1")] string blob)
{
Assert.Equal(TestData, blob);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void OutStringBinding_Block(
[Blob(ContainerName + "/overwrite")] out string blob)
{
blob = "overwritten";
}
[NoAutomaticTrigger]
public static void OutStringBinding_Page(
[Blob(PageBlobContainerName + "/blob1")] out string blob)
{
// this wil fail before getting this far
blob = TestData;
}
[NoAutomaticTrigger]
public static void OutStringBinding_Append(
[Blob(AppendBlobContainerName + "/blob1")] out string blob)
{
// this will fail before getting this far
blob = TestData;
}
[NoAutomaticTrigger]
public static void TextWriterBinding_Block(
[Blob(ContainerName + "/overwrite")] TextWriter blob)
{
// this will fail
blob.Write("overwritten");
}
[NoAutomaticTrigger]
public static void TextWriterBinding_Page(
[Blob(PageBlobContainerName + "/blob1")] TextWriter blob)
{
// this will fail
}
[NoAutomaticTrigger]
public static void TextWriterBinding_Append(
[Blob(AppendBlobContainerName + "/blob1")] TextWriter blob)
{
// this will fail
}
[NoAutomaticTrigger]
public static void StreamBindingReadable_Block(
[Blob(ContainerName + "/blob1", FileAccess.Read)] Stream blobStream)
{
using (StreamReader reader = new StreamReader(blobStream))
{
string content = reader.ReadToEnd();
Assert.Equal(TestData, content);
}
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void StreamBindingReadable_Page(
[Blob(ContainerName + "/blob1", FileAccess.Read)] Stream blobStream)
{
using (StreamReader reader = new StreamReader(blobStream))
{
string content = reader.ReadToEnd();
Assert.Equal(TestData, content);
}
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void StreamBindingReadable_Append(
[Blob(ContainerName + "/blob1", FileAccess.Read)] Stream blobStream)
{
using (StreamReader reader = new StreamReader(blobStream))
{
string content = reader.ReadToEnd();
Assert.Equal(TestData, content);
}
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void ByteArrayBinding(
[Blob(ContainerName + "/blob1")] byte[] blob)
{
string result = Encoding.UTF8.GetString(blob);
Assert.Equal(TestData, result);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void ByteArrayBinding_Page(
[Blob(PageBlobContainerName + "/blob1")] byte[] blob)
{
string result = Encoding.UTF8.GetString(blob);
Assert.Equal(TestData, result);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void ByteArrayBinding_Append(
[Blob(AppendBlobContainerName + "/blob1")] byte[] blob)
{
string result = Encoding.UTF8.GetString(blob);
Assert.Equal(TestData, result);
_numBlobsRead = 1;
}
[NoAutomaticTrigger]
public static void ByteArrayOutputBinding(string input,
[Blob(OutputContainerName + "/blob1")] out byte[] output)
{
if (input == "null")
{
output = null;
}
else
{
output = Encoding.UTF8.GetBytes(input);
}
}
[NoAutomaticTrigger]
public static void ByteArrayTriggerBinding(
[BlobTrigger(ContainerName)] byte[] blob)
{
string result = Encoding.UTF8.GetString(blob);
Assert.Equal(TestData, result);
_numBlobsRead = 1;
}
public class TestFixture : IAsyncLifetime
{
public async Task InitializeAsync()
{
RandomNameResolver nameResolver = new RandomNameResolver();
Host = new HostBuilder()
.ConfigureDefaultTestHost<BlobBindingEndToEndTests>(b =>
{
b.AddAzureStorage();
RuntimeStorageWebJobsBuilderExtensions.AddAzureStorageCoreServices(b);
})
.ConfigureServices(services =>
{
services.AddSingleton<INameResolver>(nameResolver);
})
.Build();
JobHost = Host.GetJobHost();
var provider = Host.Services.GetService<StorageAccountProvider>();
StorageAccount = provider.GetHost().SdkObject;
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
BlobContainer = blobClient.GetContainerReference(nameResolver.ResolveInString(ContainerName));
Assert.False(await BlobContainer.ExistsAsync());
await BlobContainer.CreateAsync();
OutputBlobContainer = blobClient.GetContainerReference(nameResolver.ResolveInString(OutputContainerName));
CloudBlobContainer pageBlobContainer = blobClient.GetContainerReference(nameResolver.ResolveInString(PageBlobContainerName));
Assert.False(await pageBlobContainer.ExistsAsync());
await pageBlobContainer.CreateAsync();
CloudBlobContainer hierarchicalBlobContainer = blobClient.GetContainerReference(nameResolver.ResolveInString(HierarchicalBlobContainerName));
Assert.False(await hierarchicalBlobContainer.ExistsAsync());
await hierarchicalBlobContainer.CreateAsync();
CloudBlobContainer appendBlobContainer = blobClient.GetContainerReference(nameResolver.ResolveInString(AppendBlobContainerName));
Assert.False(await appendBlobContainer.ExistsAsync());
await appendBlobContainer.CreateAsync();
await Host.StartAsync();
// upload some test blobs
CloudBlockBlob blob = BlobContainer.GetBlockBlobReference("blob1");
await blob.UploadTextAsync(TestData);
blob = BlobContainer.GetBlockBlobReference("blob2");
await blob.UploadTextAsync(TestData);
blob = BlobContainer.GetBlockBlobReference("blob3");
await blob.UploadTextAsync(TestData);
blob = BlobContainer.GetBlockBlobReference("file1");
await blob.UploadTextAsync(TestData);
blob = BlobContainer.GetBlockBlobReference("file2");
await blob.UploadTextAsync(TestData);
blob = BlobContainer.GetBlockBlobReference("overwrite");
await blob.UploadTextAsync(TestData);
// add a couple hierarchical blob paths
blob = hierarchicalBlobContainer.GetBlockBlobReference("sub/blob1");
await blob.UploadTextAsync(TestData);
blob = hierarchicalBlobContainer.GetBlockBlobReference("sub/blob2");
await blob.UploadTextAsync(TestData);
blob = hierarchicalBlobContainer.GetBlockBlobReference("sub/sub/blob3");
await blob.UploadTextAsync(TestData);
blob = hierarchicalBlobContainer.GetBlockBlobReference("blob4");
await blob.UploadTextAsync(TestData);
byte[] bytes = new byte[512];
byte[] testBytes = Encoding.UTF8.GetBytes(TestData);
for (int i = 0; i < testBytes.Length; i++)
{
bytes[i] = testBytes[i];
}
CloudPageBlob pageBlob = pageBlobContainer.GetPageBlobReference("blob1");
await pageBlob.UploadFromByteArrayAsync(bytes, 0, bytes.Length);
pageBlob = pageBlobContainer.GetPageBlobReference("blob2");
await pageBlob.UploadFromByteArrayAsync(bytes, 0, bytes.Length);
CloudAppendBlob appendBlob = appendBlobContainer.GetAppendBlobReference("blob1");
await appendBlob.UploadTextAsync(TestData);
appendBlob = appendBlobContainer.GetAppendBlobReference("blob2");
await appendBlob.UploadTextAsync(TestData);
appendBlob = appendBlobContainer.GetAppendBlobReference("blob3");
await appendBlob.UploadTextAsync(TestData);
}
public IHost Host
{
get;
private set;
}
public JobHost JobHost
{
get;
private set;
}
public INameResolver NameResolver => Host.Services.GetService<INameResolver>();
public string HostId => Host.Services.GetService<IHostIdProvider>().GetHostIdAsync(CancellationToken.None).Result;
public CloudStorageAccount StorageAccount
{
get;
private set;
}
public CloudBlobContainer BlobContainer
{
get;
private set;
}
public CloudBlobContainer OutputBlobContainer
{
get;
private set;
}
public async Task VerifyLockState(string lockId, LeaseState state, LeaseStatus status)
{
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("azure-webjobs-hosts");
string blobName = string.Format("locks/{0}/{1}", HostId, lockId);
var lockBlob = container.GetBlockBlobReference(blobName);
Assert.True(await lockBlob.ExistsAsync());
await lockBlob.FetchAttributesAsync();
Assert.Equal(state, lockBlob.Properties.LeaseState);
Assert.Equal(status, lockBlob.Properties.LeaseStatus);
}
public async Task DisposeAsync()
{
await Host.StopAsync();
// $$$ reenalbe this
VerifyLockState("WebJobs.Internal.Blobs.Listener", LeaseState.Available, LeaseStatus.Unlocked).Wait();
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
foreach (var testContainer in (await blobClient.ListContainersSegmentedAsync(TestArtifactPrefix, null)).Results)
{
await testContainer.DeleteAsync();
}
}
}
public class TestPoco
{
public string A { get; set; }
public string B { get; set; }
}
}
}

Просмотреть файл

@ -3,12 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage.Blob;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@ -46,18 +46,17 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
_loggerProvider = new TestLoggerProvider();
_loggerFactory.AddProvider(_loggerProvider);
IConfiguration configuration = new ConfigurationBuilder().AddEnvironmentVariables().Build();
_mockHostIdProvider = new Mock<IHostIdProvider>(MockBehavior.Strict);
_mockHostIdProvider.Setup(p => p.GetHostIdAsync(CancellationToken.None)).ReturnsAsync(TestHostId);
_repository = new BlobStorageConcurrencyStatusRepository(configuration, _mockHostIdProvider.Object, _loggerFactory);
_repository = new BlobStorageConcurrencyStatusRepository(_mockHostIdProvider.Object, _loggerFactory, TestHelpers.GetTestAzureBlobStorageProvider());
}
[Fact]
public async Task GetContainerAsync_ReturnsExpectedContainer()
public async Task GetContainerClientAsync_ReturnsExpectedContainer()
{
CloudBlobContainer container = await _repository.GetContainerAsync(CancellationToken.None);
Assert.Equal(HostContainerNames.Hosts, container.Name);
BlobContainerClient blobContainerClient = await _repository.GetContainerClientAsync(CancellationToken.None);
Assert.Equal(HostContainerNames.Hosts, blobContainerClient.Name);
}
[Fact]
@ -74,33 +73,41 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
await DeleteTestBlobsAsync();
var path = await _repository.GetBlobPathAsync(CancellationToken.None);
CloudBlobContainer container = await _repository.GetContainerAsync(CancellationToken.None);
CloudBlockBlob blob = container.GetBlockBlobReference(path);
bool exists = await blob.ExistsAsync();
BlobContainerClient blobContainerClient = await _repository.GetContainerClientAsync(CancellationToken.None);
BlobClient blobClient = blobContainerClient.GetBlobClient(path);
bool exists = await blobClient.ExistsAsync();
Assert.False(exists);
await _repository.WriteAsync(_testSnapshot, CancellationToken.None);
exists = await blob.ExistsAsync();
exists = await blobClient.ExistsAsync();
Assert.True(exists);
var content = await blob.DownloadTextAsync();
string content = await blobClient.DownloadTextAsync();
var result = JsonConvert.DeserializeObject<HostConcurrencySnapshot>(content);
Assert.True(_testSnapshot.Equals(result));
// upload again and ensure the existing blob is replaced
_testSnapshot.NumberOfCores += 2;
await _repository.WriteAsync(_testSnapshot, CancellationToken.None);
content = await blobClient.DownloadTextAsync();
result = JsonConvert.DeserializeObject<HostConcurrencySnapshot>(content);
Assert.Equal(_testSnapshot.NumberOfCores, result.NumberOfCores);
}
[Fact]
public async Task ReadAsyncAsync_ReadsExpectedBlob()
public async Task ReadAsync_ReadsExpectedBlob()
{
await DeleteTestBlobsAsync();
string path = await _repository.GetBlobPathAsync(CancellationToken.None);
CloudBlobContainer container = await _repository.GetContainerAsync(CancellationToken.None);
CloudBlockBlob blob = container.GetBlockBlobReference(path);
BlobContainerClient blobContainerClient = await _repository.GetContainerClientAsync(CancellationToken.None);
BlobClient blobClient = blobContainerClient.GetBlobClient(path);
string content = JsonConvert.SerializeObject(_testSnapshot);
await blob.UploadTextAsync(content);
await blobClient.UploadTextAsync(content, overwrite: true);
var result = await _repository.ReadAsync(CancellationToken.None);
@ -108,7 +115,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
[Fact]
public async Task ReadAsyncAsync_NoSnapshot_ReturnsNull()
public async Task ReadAsync_NoSnapshot_ReturnsNull()
{
await DeleteTestBlobsAsync();
@ -121,9 +128,12 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
public async Task NoStorageConnection_HandledGracefully()
{
IConfiguration configuration = new ConfigurationBuilder().Build();
var localRepository = new BlobStorageConcurrencyStatusRepository(configuration, _mockHostIdProvider.Object, _loggerFactory);
var mockBlobStorageProvider = new Mock<IAzureBlobStorageProvider>(MockBehavior.Strict);
BlobContainerClient blobContainerClient = null;
mockBlobStorageProvider.Setup(p => p.TryCreateHostingBlobContainerClient(out blobContainerClient)).Returns(false);
var localRepository = new BlobStorageConcurrencyStatusRepository(_mockHostIdProvider.Object, _loggerFactory, mockBlobStorageProvider.Object);
var container = await localRepository.GetContainerAsync(CancellationToken.None);
var container = await localRepository.GetContainerClientAsync(CancellationToken.None);
Assert.Null(container);
await localRepository.WriteAsync(new HostConcurrencySnapshot(), CancellationToken.None);
@ -134,12 +144,13 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private async Task DeleteTestBlobsAsync()
{
CloudBlobContainer container = await _repository.GetContainerAsync(CancellationToken.None);
var blobs = container.ListBlobs($"concurrency/{TestHostId}", useFlatBlobListing: true);
foreach (var blob in blobs.Cast<CloudBlockBlob>())
BlobContainerClient blobContainerClient = await _repository.GetContainerClientAsync(CancellationToken.None);
var blobItems = blobContainerClient.GetBlobsByHierarchyAsync(prefix: $"concurrency/{TestHostId}");
await foreach (var blob in blobItems)
{
await blob.DeleteAsync();
BlobClient blobClient = blobContainerClient.GetBlobClient(blob.Blob.Name);
await blobClient.DeleteAsync();
}
}
}
}
}

Просмотреть файл

@ -1,304 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
public class BlobTriggerEndToEndTests : IDisposable
{
private const string TestArtifactPrefix = "e2etests";
private const string SingleTriggerContainerName = TestArtifactPrefix + "singletrigger-%rnd%";
private const string PoisonTestContainerName = TestArtifactPrefix + "poison-%rnd%";
private const string TestBlobName = "test";
private const string BlobChainContainerName = TestArtifactPrefix + "blobchain-%rnd%";
private const string BlobChainTriggerBlobName = "blob";
private const string BlobChainTriggerBlobPath = BlobChainContainerName + "/" + BlobChainTriggerBlobName;
private const string BlobChainCommittedQueueName = "committed";
private const string BlobChainIntermediateBlobPath = BlobChainContainerName + "/" + "blob.middle";
private const string BlobChainOutputBlobName = "blob.out";
private const string BlobChainOutputBlobPath = BlobChainContainerName + "/" + BlobChainOutputBlobName;
private readonly CloudBlobContainer _testContainer;
private readonly CloudStorageAccount _storageAccount;
private readonly RandomNameResolver _nameResolver;
private static object _syncLock = new object();
public BlobTriggerEndToEndTests()
{
_nameResolver = new RandomNameResolver();
// pull from a default host
var host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage();
})
.Build();
var provider = host.Services.GetService<StorageAccountProvider>();
_storageAccount = provider.GetHost().SdkObject;
CloudBlobClient blobClient = _storageAccount.CreateCloudBlobClient();
_testContainer = blobClient.GetContainerReference(_nameResolver.ResolveInString(SingleTriggerContainerName));
Assert.False(_testContainer.ExistsAsync().Result);
_testContainer.CreateAsync().Wait();
}
public IHostBuilder NewBuilder<TProgram>(TProgram program, Action<IWebJobsBuilder> configure = null)
{
var activator = new FakeActivator();
activator.Add(program);
return new HostBuilder()
.ConfigureDefaultTestHost<TProgram>(b =>
{
b.AddAzureStorage();
configure?.Invoke(b);
})
.ConfigureServices(services =>
{
services.AddSingleton<IJobActivator>(activator);
services.AddSingleton<INameResolver>(_nameResolver);
});
}
public class Poison_Program
{
public List<string> _poisonBlobMessages = new List<string>();
public void BlobProcessorPrimary(
[BlobTrigger(PoisonTestContainerName + "/{name}")] string input)
{
// throw to generate a poison blob message
throw new Exception();
}
// process the poison queue for the primary storage account
public void PoisonBlobQueueProcessorPrimary(
[QueueTrigger("webjobs-blobtrigger-poison")] JObject message)
{
lock (_syncLock)
{
string blobName = (string)message["BlobName"];
_poisonBlobMessages.Add(blobName);
}
}
public void BlobProcessorSecondary(
[StorageAccount("SecondaryStorage")]
[BlobTrigger(PoisonTestContainerName + "/{name}")] string input)
{
// throw to generate a poison blob message
throw new Exception();
}
// process the poison queue for the secondary storage account
public void PoisonBlobQueueProcessorSecondary(
[StorageAccount("SecondaryStorage")]
[QueueTrigger("webjobs-blobtrigger-poison")] JObject message)
{
lock (_syncLock)
{
string blobName = (string)message["BlobName"];
_poisonBlobMessages.Add(blobName);
}
}
}
public class BlobGetsProcessedOnlyOnce_SingleHost_Program
{
public int _timesProcessed;
public ManualResetEvent _completedEvent;
public void SingleBlobTrigger(
[BlobTrigger(SingleTriggerContainerName + "/{name}")] string sleepTimeInSeconds)
{
Interlocked.Increment(ref _timesProcessed);
int sleepTime = int.Parse(sleepTimeInSeconds) * 1000;
Thread.Sleep(sleepTime);
_completedEvent.Set();
}
}
public class BlobChainTest_Program
{
public ManualResetEvent _completedEvent;
public void BlobChainStepOne(
[BlobTrigger(BlobChainTriggerBlobPath)] TextReader input,
[Blob(BlobChainIntermediateBlobPath)] TextWriter output)
{
string content = input.ReadToEnd();
output.Write(content);
}
public void BlobChainStepTwo(
[BlobTrigger(BlobChainIntermediateBlobPath)] TextReader input,
[Blob(BlobChainOutputBlobPath)] TextWriter output,
[Queue(BlobChainCommittedQueueName)] out string committed)
{
string content = input.ReadToEnd();
output.Write("*" + content + "*");
committed = String.Empty;
}
public void BlobChainStepThree([QueueTrigger(BlobChainCommittedQueueName)] string ignore)
{
_completedEvent.Set();
}
}
[Theory]
[InlineData("AzureWebJobsSecondaryStorage")]
[InlineData("AzureWebJobsStorage")]
public async Task PoisonMessage_CreatedInCorrectStorageAccount(string storageAccountSetting)
{
var storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable(storageAccountSetting));
var blobClient = storageAccount.CreateCloudBlobClient();
var containerName = _nameResolver.ResolveInString(PoisonTestContainerName);
var container = blobClient.GetContainerReference(containerName);
await container.CreateAsync();
var blobName = Guid.NewGuid().ToString();
CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
await blob.UploadTextAsync("0");
var prog = new Poison_Program();
var host = NewBuilder(prog).Build();
using (host)
{
host.Start();
// wait for the poison message to be handled
await TestHelpers.Await(() =>
{
return prog._poisonBlobMessages.Contains(blobName);
});
}
}
[Fact]
public async Task BlobGetsProcessedOnlyOnce_SingleHost()
{
CloudBlockBlob blob = _testContainer.GetBlockBlobReference(TestBlobName);
await blob.UploadTextAsync("0");
int timeToProcess;
var prog = new BlobGetsProcessedOnlyOnce_SingleHost_Program();
// make sure they both have the same id
var host = NewBuilder(prog, builder => builder.UseHostId(Guid.NewGuid().ToString("N")))
.Build();
// Process the blob first
using (prog._completedEvent = new ManualResetEvent(initialState: false))
using (host)
{
DateTime startTime = DateTime.Now;
host.Start();
Assert.True(prog._completedEvent.WaitOne(TimeSpan.FromSeconds(60)));
timeToProcess = (int)(DateTime.Now - startTime).TotalMilliseconds;
Assert.Equal(1, prog._timesProcessed);
string[] loggerOutputLines = host.GetTestLoggerProvider().GetAllLogMessages()
.Where(p => p.FormattedMessage != null)
.SelectMany(p => p.FormattedMessage.Split(Environment.NewLine, StringSplitOptions.None))
.ToArray();
var executions = loggerOutputLines.Where(p => p.Contains("Executing"));
Assert.Single(executions);
Assert.StartsWith(string.Format("Executing 'BlobGetsProcessedOnlyOnce_SingleHost_Program.SingleBlobTrigger' (Reason='New blob detected: {0}/{1}', Id=", blob.Container.Name, blob.Name), executions.Single());
await host.StopAsync();
// Can't restart
Assert.Throws<InvalidOperationException>(() => host.Start());
}
Assert.Equal(1, prog._timesProcessed);
} // host
[Fact]
public async Task BlobChainTest()
{
// write the initial trigger blob to start the chain
var blobClient = _storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference(_nameResolver.ResolveInString(BlobChainContainerName));
await container.CreateIfNotExistsAsync();
CloudBlockBlob blob = container.GetBlockBlobReference(BlobChainTriggerBlobName);
await blob.UploadTextAsync("0");
var prog = new BlobChainTest_Program();
var host = NewBuilder(prog).Build();
using (prog._completedEvent = new ManualResetEvent(initialState: false))
using (host)
{
host.Start();
Assert.True(prog._completedEvent.WaitOne(TimeSpan.FromSeconds(60)));
}
}
[Fact]
public async Task BlobGetsProcessedOnlyOnce_MultipleHosts()
{
await _testContainer
.GetBlockBlobReference(TestBlobName)
.UploadTextAsync("10");
var prog = new BlobGetsProcessedOnlyOnce_SingleHost_Program();
string hostId = Guid.NewGuid().ToString("N");
var host1 = NewBuilder(prog, builder=>builder.UseHostId(hostId))
.Build();
var host2 = NewBuilder(prog, builder => builder.UseHostId(hostId))
.Build();
using (prog._completedEvent = new ManualResetEvent(initialState: false))
using (host1)
using (host2)
{
host1.Start();
host2.Start();
Assert.True(prog._completedEvent.WaitOne(TimeSpan.FromSeconds(60)));
}
Assert.Equal(1, prog._timesProcessed);
}
public void Dispose()
{
CloudBlobClient blobClient = _storageAccount.CreateCloudBlobClient();
foreach (var testContainer in blobClient.ListContainersSegmentedAsync(TestArtifactPrefix, null).Result.Results)
{
testContainer.DeleteAsync();
}
}
}
}

Просмотреть файл

@ -8,10 +8,8 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Storage.Queue;
using Newtonsoft.Json.Linq;
using Xunit;
using Xunit.Abstractions;
@ -28,9 +26,6 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private static ITestOutputHelper _output;
private static Stopwatch _stopwatch = new Stopwatch();
private CloudQueue _sharedQueue;
private CloudQueue _poisonQueue;
// Each test should set this up; it will be used during cleanup.
private IHost _host;
@ -75,11 +70,12 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
// each test will have a unique hostId so that consecutive test run will not be affected by clean up code
b.UseHostId(Guid.NewGuid().ToString("N"))
.AddAzureStorage()
// Necessary for Queue bindings
.AddAzureStorageQueues()
.AddExtension<DispatchQueueTestConfig>();
})
.Build();
{
_funcInvocation = new ConcurrentStringSet();
@ -107,11 +103,12 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
// each test will have a unique hostId so that consecutive test run will not be affected by clean up code
b.UseHostId(Guid.NewGuid().ToString("N"))
.AddAzureStorage()
// Necessary for Queue bindings
.AddAzureStorageQueues()
.AddExtension<DispatchQueueTestConfig>();
})
.Build();
{
_funcInvocation = new ConcurrentStringSet();
@ -136,11 +133,12 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
// each test will have a different hostId
// and therefore a different sharedQueue and poisonQueue
CloudQueueClient client = _host.GetStorageAccount().CreateCloudQueueClient();
_sharedQueue = client.GetQueueReference("azure-webjobs-shared-" + _host.Services.GetService<IHostIdProvider>().GetHostIdAsync(CancellationToken.None).Result);
_poisonQueue = client.GetQueueReference("azure-webjobs-poison-" + _host.Services.GetService<IHostIdProvider>().GetHostIdAsync(CancellationToken.None).Result);
_sharedQueue.DeleteIfExistsAsync().Wait();
_poisonQueue.DeleteIfExistsAsync().Wait();
var queueServiceClient = TestHelpers.GetTestQueueServiceClient();
var sharedQueue = queueServiceClient?.GetQueueClient("azure-webjobs-shared-" + _host.Services.GetService<IHostIdProvider>().GetHostIdAsync(CancellationToken.None).Result);
var poisonQueue = queueServiceClient?.GetQueueClient("azure-webjobs-poison-" + _host.Services.GetService<IHostIdProvider>().GetHostIdAsync(CancellationToken.None).Result);
sharedQueue?.DeleteIfExistsAsync().Wait();
poisonQueue?.DeleteIfExistsAsync().Wait();
_host.Dispose();
}

Просмотреть файл

@ -279,10 +279,9 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
.ConfigureDefaultTestHost<TProg>(b =>
{
b.UseHostId(TestHostId)
.AddAzureStorage()
.AddExtension<TestTriggerAttributeBindingProvider>();
RuntimeStorageWebJobsBuilderExtensions.AddAzureStorageCoreServices(b);
b.AddAzureStorageCoreServices();
b.Services.AddOptions<ConcurrencyOptions>().Configure(options =>
{
@ -298,9 +297,6 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
});
})
.ConfigureServices(services =>
{
})
.ConfigureLogging((context, b) =>
{
b.SetMinimumLevel(LogLevel.Information);

Просмотреть файл

@ -4,14 +4,14 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Azure.Storage.Blobs;
using Azure.Storage.Sas;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Moq;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
@ -31,26 +31,16 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
// Create a real Blob Container Sas URI
var account1 = CloudStorageAccount.Parse(acs);
var client = account1.CreateCloudBlobClient();
var container = client.GetContainerReference(containerName);
await container.CreateIfNotExistsAsync(); // this will throw if acs is bad
var blobServiceClient = new BlobServiceClient(acs);
var containerClient = blobServiceClient.GetBlobContainerClient(containerName);
await containerClient.CreateIfNotExistsAsync(); // this will throw if acs is bad;
var fakeSasUri = containerClient.GenerateSasUri(BlobContainerSasPermissions.Read | BlobContainerSasPermissions.Write | BlobContainerSasPermissions.List, DateTime.UtcNow.AddDays(10));
var now = DateTime.UtcNow;
var sig = container.GetSharedAccessSignature(new SharedAccessBlobPolicy
{
Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List,
SharedAccessStartTime = now.AddDays(-10),
SharedAccessExpiryTime = now.AddDays(10)
});
var fakeSasUri = container.Uri + sig;
var prog = new BasicProg();
IHost host = new HostBuilder()
.ConfigureDefaultTestHost(prog, b =>
{
b.AddAzureStorage();
RuntimeStorageWebJobsBuilderExtensions.AddAzureStorageCoreServices(b);
})
.ConfigureAppConfiguration(config =>
@ -58,16 +48,19 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
// Set env to the SAS container and clear out all other storage.
config.AddInMemoryCollection(new Dictionary<string, string>()
{
{ "AzureWebJobs:InternalSasBlobContainer", fakeSasUri },
{ "AzureWebJobs:InternalSasBlobContainer", fakeSasUri.ToString() },
{ "AzureWebJobsStorage", null },
{ "AzureWebJobsDashboard", null }
});
})
.Build();
var internalOptions = host.Services.GetService<DistributedLockManagerContainerProvider>();
var internalOptions = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
Assert.NotNull(internalOptions);
Assert.Equal(container.Name, internalOptions.InternalContainer.Name);
Assert.Equal(fakeSasUri.ToString(), internalOptions.Value.InternalSasBlobContainer);
Assert.True(host.Services.GetService<IAzureBlobStorageProvider>().TryCreateHostingBlobContainerClient(out BlobContainerClient actualContainer));
Assert.Equal(containerClient.Name, actualContainer.Name);
await host.GetJobHost().CallAsync(nameof(BasicProg.Foo));

Просмотреть файл

@ -1,385 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Queue;
using Microsoft.Azure.Cosmos.Table;
using Newtonsoft.Json.Linq;
using Xunit;
using CloudStorageAccount = Microsoft.Azure.Storage.CloudStorageAccount;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
public class MultipleStorageAccountsEndToEndTests : IClassFixture<MultipleStorageAccountsEndToEndTests.TestFixture>
{
private const string TestArtifactPrefix = "e2etestmultiaccount";
private const string Input = TestArtifactPrefix + "-input-%rnd%";
private const string Output = TestArtifactPrefix + "-output-%rnd%";
private const string InputTableName = TestArtifactPrefix + "tableinput%rnd%";
private const string OutputTableName = TestArtifactPrefix + "tableinput%rnd%";
private const string TestData = "TestData";
private const string Secondary = "SecondaryStorage";
private static CloudStorageAccount primaryAccountResult;
private static CloudStorageAccount secondaryAccountResult;
private readonly TestFixture _fixture;
public MultipleStorageAccountsEndToEndTests(TestFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task BlobToBlob_DifferentAccounts_PrimaryToSecondary_Succeeds()
{
CloudBlockBlob resultBlob = null;
await TestHelpers.Await(async () =>
{
resultBlob = (CloudBlockBlob)(await _fixture.OutputContainer2.ListBlobsSegmentedAsync("blob1", null)).Results.SingleOrDefault();
return resultBlob != null;
});
string data = await resultBlob.DownloadTextAsync();
Assert.Equal("blob1", resultBlob.Name);
Assert.Equal(TestData, data);
}
[Fact]
public async Task BlobToBlob_DifferentAccounts_SecondaryToPrimary_Succeeds()
{
CloudBlockBlob resultBlob = null;
await TestHelpers.Await(async () =>
{
resultBlob = (CloudBlockBlob)(await _fixture.OutputContainer1.ListBlobsSegmentedAsync(null)).Results.SingleOrDefault();
return resultBlob != null;
});
string data = await resultBlob.DownloadTextAsync();
Assert.Equal("blob2", resultBlob.Name);
Assert.Equal(TestData, data);
}
[Fact]
public async Task QueueToQueue_DifferentAccounts_PrimaryToSecondary_Succeeds()
{
CloudQueueMessage resultMessage = null;
await TestHelpers.Await(async () =>
{
resultMessage = await _fixture.OutputQueue2.GetMessageAsync();
return resultMessage != null;
});
Assert.Equal(TestData, resultMessage.AsString);
}
[Theory]
[InlineData("QueueToBlob_DifferentAccounts_PrimaryToSecondary_NameResolver")]
[InlineData("QueueToBlob_DifferentAccounts_PrimaryToSecondary_FullSettingName")]
public async Task QueueToBlob_DifferentAccounts_PrimaryToSecondary_NameResolver_Succeeds(string methodName)
{
var method = typeof(MultipleStorageAccountsEndToEndTests).GetMethod(methodName);
string name = Guid.NewGuid().ToString();
JObject jObject = new JObject
{
{ "Name", name },
{ "Value", TestData }
};
await _fixture.JobHost.CallAsync(method, new { input = jObject.ToString() });
var blobReference = await _fixture.OutputContainer2.GetBlobReferenceFromServerAsync(name);
await TestHelpers.Await(() => blobReference.ExistsAsync());
string data;
using (var memoryStream = new MemoryStream())
{
await blobReference.DownloadToStreamAsync(memoryStream);
memoryStream.Position = 0;
using (var reader = new StreamReader(memoryStream))
{
data = reader.ReadToEnd();
}
}
Assert.Equal(TestData, data);
}
[Fact]
public async Task QueueToQueue_DifferentAccounts_SecondaryToPrimary_Succeeds()
{
CloudQueueMessage resultMessage = null;
await TestHelpers.Await(async () =>
{
resultMessage = await _fixture.OutputQueue1.GetMessageAsync();
return resultMessage != null;
});
Assert.Equal(TestData, resultMessage.AsString);
}
[Fact]
public async Task Table_PrimaryAndSecondary_Succeeds()
{
await _fixture.JobHost.CallAsync(typeof(MultipleStorageAccountsEndToEndTests).GetMethod("Table_PrimaryAndSecondary"));
TestTableEntity entity1 = null;
TestTableEntity entity2 = null;
await TestHelpers.Await(async () =>
{
TableResult result = await _fixture.OutputTable1.ExecuteAsync(TableOperation.Retrieve<TestTableEntity>("test", "test"));
if (result != null)
{
entity1 = (TestTableEntity)result.Result;
}
result = await _fixture.OutputTable2.ExecuteAsync(TableOperation.Retrieve<TestTableEntity>("test", "test"));
if (result != null)
{
entity2 = (TestTableEntity)result.Result;
}
return entity1 != null && entity2 != null;
});
Assert.Equal(TestData, entity1.Text);
Assert.Equal(TestData, entity2.Text);
}
[Fact]
public async Task CloudStorageAccount_PrimaryAndSecondary_Succeeds()
{
await _fixture.JobHost.CallAsync(typeof(MultipleStorageAccountsEndToEndTests).GetMethod(nameof(MultipleStorageAccountsEndToEndTests.BindToCloudStorageAccount)));
Assert.Equal(_fixture.Account1.SdkObject.Credentials.AccountName, primaryAccountResult.Credentials.AccountName);
Assert.Equal(_fixture.Account2.SdkObject.Credentials.AccountName, secondaryAccountResult.Credentials.AccountName);
}
public static void BlobToBlob_DifferentAccounts_PrimaryToSecondary(
[BlobTrigger(Input + "/{name}")] string input,
[Blob(Output + "/{name}", Connection = Secondary)] out string output)
{
output = input;
}
public static void BlobToBlob_DifferentAccounts_SecondaryToPrimary(
[BlobTrigger(Input + "/{name}", Connection = Secondary)] string input,
[Blob(Output + "/{name}")] out string output)
{
output = input;
}
public static void QueueToQueue_DifferentAccounts_PrimaryToSecondary(
[QueueTrigger(Input)] string input,
[Queue(Output, Connection = Secondary)] out string output)
{
output = input;
}
[NoAutomaticTrigger]
public static void QueueToBlob_DifferentAccounts_PrimaryToSecondary_NameResolver(
[QueueTrigger("test")] Message input,
[Blob(Output + "/{Name}", Connection = "%test_account%")] out string output)
{
output = input.Value;
}
[NoAutomaticTrigger]
public static void QueueToBlob_DifferentAccounts_PrimaryToSecondary_FullSettingName(
[QueueTrigger("test")] Message input,
[Blob(Output + "/{Name}", Connection = "AzureWebJobsSecondaryStorage")] out string output)
{
output = input.Value;
}
public static void QueueToQueue_DifferentAccounts_SecondaryToPrimary(
[QueueTrigger(Input, Connection = Secondary)] string input,
[Queue(Output)] out string output)
{
output = input;
}
[NoAutomaticTrigger]
public async static Task Table_PrimaryAndSecondary(
[Table(OutputTableName)] CloudTable primaryOutput,
[Table(OutputTableName, Connection = Secondary)] CloudTable secondaryOutput)
{
TestTableEntity entity = new TestTableEntity
{
PartitionKey = "test",
RowKey = "test",
Text = TestData
};
await primaryOutput.ExecuteAsync(TableOperation.InsertOrReplace(entity));
await secondaryOutput.ExecuteAsync(TableOperation.InsertOrReplace(entity));
}
[NoAutomaticTrigger]
public static void BindToCloudStorageAccount(
CloudStorageAccount primary,
[StorageAccount(Secondary)] CloudStorageAccount secondary)
{
primaryAccountResult = primary;
secondaryAccountResult = secondary;
}
private class TestNameResolver : RandomNameResolver
{
public override string Resolve(string name)
{
if (name == "test_account")
{
return "SecondaryStorage";
}
return base.Resolve(name);
}
}
public class TestFixture : IAsyncLifetime
{
public async Task InitializeAsync()
{
RandomNameResolver nameResolver = new TestNameResolver();
Host = new HostBuilder()
.ConfigureDefaultTestHost<MultipleStorageAccountsEndToEndTests>(b =>
{
b.AddAzureStorage();
})
.ConfigureServices(services =>
{
services.AddSingleton<INameResolver>(nameResolver);
})
.Build();
Account1 = Host.GetStorageAccount();
var config = Host.Services.GetService<IConfiguration>();
string secondaryConnectionString = config[$"AzureWebJobs{Secondary}"];
Account2 = StorageAccount.NewFromConnectionString(secondaryConnectionString);
await CleanContainersAsync();
CloudBlobClient blobClient1 = Account1.CreateCloudBlobClient();
string inputName = nameResolver.ResolveInString(Input);
CloudBlobContainer inputContainer1 = blobClient1.GetContainerReference(inputName);
await inputContainer1.CreateIfNotExistsAsync();
string outputName = nameResolver.ResolveWholeString(Output);
OutputContainer1 = blobClient1.GetContainerReference(outputName);
await OutputContainer1.CreateIfNotExistsAsync();
CloudBlobClient blobClient2 = Account2.CreateCloudBlobClient();
CloudBlobContainer inputContainer2 = blobClient2.GetContainerReference(inputName);
await inputContainer2.CreateIfNotExistsAsync();
OutputContainer2 = blobClient2.GetContainerReference(outputName);
await OutputContainer2.CreateIfNotExistsAsync();
CloudQueueClient queueClient1 = Account1.CreateCloudQueueClient();
CloudQueue inputQueue1 = queueClient1.GetQueueReference(inputName);
await inputQueue1.CreateIfNotExistsAsync();
OutputQueue1 = queueClient1.GetQueueReference(outputName);
await OutputQueue1.CreateIfNotExistsAsync();
CloudQueueClient queueClient2 = Account2.CreateCloudQueueClient();
CloudQueue inputQueue2 = queueClient2.GetQueueReference(inputName);
await inputQueue2.CreateIfNotExistsAsync();
OutputQueue2 = queueClient2.GetQueueReference(outputName);
await OutputQueue2.CreateIfNotExistsAsync();
CloudTableClient tableClient1 = Account1.CreateCloudTableClient();
string outputTableName = nameResolver.ResolveWholeString(OutputTableName);
OutputTable1 = tableClient1.GetTableReference(outputTableName);
OutputTable2 = Account2.CreateCloudTableClient().GetTableReference(outputTableName);
// upload some test blobs to the input containers of both storage accounts
CloudBlockBlob blob = inputContainer1.GetBlockBlobReference("blob1");
await blob.UploadTextAsync(TestData);
blob = inputContainer2.GetBlockBlobReference("blob2");
await blob.UploadTextAsync(TestData);
// upload some test queue messages to the input queues of both storage accounts
await inputQueue1.AddMessageAsync(new CloudQueueMessage(TestData));
await inputQueue2.AddMessageAsync(new CloudQueueMessage(TestData));
Host.Start();
}
public JobHost JobHost => Host.GetJobHost();
public IHost Host
{
get;
private set;
}
public StorageAccount Account1 { get; private set; }
public StorageAccount Account2 { get; private set; }
public CloudBlobContainer OutputContainer1 { get; private set; }
public CloudBlobContainer OutputContainer2 { get; private set; }
public CloudQueue OutputQueue1 { get; private set; }
public CloudQueue OutputQueue2 { get; private set; }
public CloudTable OutputTable1 { get; private set; }
public CloudTable OutputTable2 { get; private set; }
public async Task DisposeAsync()
{
await Host.StopAsync();
await CleanContainersAsync();
}
private async Task CleanContainersAsync()
{
await Clean(Account1);
await Clean(Account2);
}
}
private async static Task Clean(StorageAccount account)
{
CloudBlobClient blobClient = account.CreateCloudBlobClient();
foreach (var testContainer in (await blobClient.ListContainersSegmentedAsync(TestArtifactPrefix, null)).Results)
{
await testContainer.DeleteAsync();
}
CloudTableClient tableClient = account.CreateCloudTableClient();
foreach (var table in await tableClient.ListTablesSegmentedAsync(TestArtifactPrefix, null))
{
await table.DeleteAsync();
}
CloudQueueClient queueClient = account.CreateCloudQueueClient();
foreach (var queue in (await queueClient.ListQueuesSegmentedAsync(TestArtifactPrefix, null)).Results)
{
await queue.DeleteAsync();
}
}
public class TestTableEntity : TableEntity
{
public string Text { get; set; }
}
public class Message
{
public string Name { get; set; }
public string Value { get; set; }
}
}
}

Просмотреть файл

@ -2,21 +2,18 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Diagnostics.Tracing;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Azure.Storage.Queues;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Storage.Queue;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
public class ParallelExecutionTests : IDisposable
public class ParallelExecutionTests : IAsyncLifetime
{
private const string TestArtifactPrefix = "e2etestparallelqueue";
private const string TestQueueName = TestArtifactPrefix + "-%rnd%";
@ -30,7 +27,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private static int _maxSimultaneouslyRunningFunctions;
private static ManualResetEvent _allMessagesProcessed;
private CloudQueueClient _queueClient;
private QueueServiceClient _queueServiceClient;
public static async Task ParallelQueueTrigger([QueueTrigger(TestQueueName)] int sleepTimeInSeconds)
{
@ -81,32 +78,35 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
else
{
Environment.SetEnvironmentVariable(Constants.AzureWebsiteSku, "dynamic");
Environment.SetEnvironmentVariable(Constants.AzureWebsiteSku, "Dynamic");
}
RandomNameResolver nameResolver = new RandomNameResolver();
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<ParallelExecutionTests>(b =>
{
b.AddAzureStorage();
b.AddAzureStorageQueues(o =>
{
o.BatchSize = batchSize;
o.MessageEncoding = QueueMessageEncoding.None;
o.NewBatchThreshold = isDynamicSku ? (batchSize / 2) : (batchSize / 2) * processorCount; // To get around static isDynamicSku variable in T2 storage extensions
});
})
.ConfigureServices(services =>
{
services.AddSingleton<INameResolver>(nameResolver);
services.Configure<QueuesOptions>(o => o.BatchSize = batchSize);
})
.ConfigureAppConfiguration(c => c.AddEnvironmentVariables())
.Build();
StorageAccount storageAccount = host.GetStorageAccount();
_queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue = _queueClient.GetQueueReference(nameResolver.ResolveInString(TestQueueName));
await queue.CreateIfNotExistsAsync();
_queueServiceClient = TestHelpers.GetTestQueueServiceClient();
var queueClient = _queueServiceClient.GetQueueClient(nameResolver.ResolveInString(TestQueueName));
await queueClient.CreateIfNotExistsAsync();
for (int i = 0; i < _numberOfQueueMessages; i++)
{
int sleepTimeInSeconds = i % 2 == 0 ? 5 : 1;
await queue.AddMessageAsync(new CloudQueueMessage(sleepTimeInSeconds.ToString()));
await queueClient.SendMessageAsync(sleepTimeInSeconds.ToString());
}
using (_allMessagesProcessed = new ManualResetEvent(initialState: false))
@ -130,15 +130,22 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
}
public void Dispose()
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
if (_queueClient != null)
await CleanQueuesAsync();
}
private async Task CleanQueuesAsync()
{
if (_queueServiceClient != null)
{
foreach (var testQueue in _queueClient.ListQueuesSegmentedAsync(TestArtifactPrefix, null).Result.Results)
await foreach (var testQueue in _queueServiceClient.GetQueuesAsync(prefix: TestArtifactPrefix))
{
testQueue.DeleteAsync().Wait();
await _queueServiceClient.DeleteQueueAsync(testQueue.Name);
}
}
}
}
}
}

Просмотреть файл

@ -7,10 +7,13 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Queues;
using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.TestCommon;
@ -19,9 +22,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Queue;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
@ -38,8 +38,9 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private Random _rand = new Random(314159);
private static RandomNameResolver _resolver = new TestNameResolver();
private static CloudBlobDirectory _lockDirectory;
private static CloudBlobDirectory _secondaryLockDirectory;
private static BlobContainerClient _lockDirectoryContainerClient;
private static BlobContainerClient _secondaryLockDirectoryContainerClient;
public SingletonEndToEndTests()
{
@ -254,13 +255,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
[Fact]
public async Task SingletonFunction_StorageAccountOverride()
{
IHost host = CreateTestJobHost<TestJobs1>(1, (hostBuilder) =>
{
hostBuilder.ConfigureServices((services) =>
{
services.AddSingleton<IDistributedLockManager, CustomLockManager>();
});
});
IHost host = CreateTestJobHost<TestJobs1>(1);
await host.StartAsync();
MethodInfo method = typeof(TestJobs1).GetMethod(nameof(TestJobs1.SingletonJob_StorageAccountOverride));
@ -272,25 +267,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
// make sure the lease blob was only created in the secondary account
await VerifyLeaseDoesNotExistAsync(method, SingletonScope.Function, null);
await VerifyLeaseState(method, SingletonScope.Function, null, LeaseState.Available, LeaseStatus.Unlocked, directory: _secondaryLockDirectory);
}
// Allow a host to override container resolution.
class CustomLockManager : StorageBaseDistributedLockManager
{
private readonly StorageAccountProvider _storage;
public CustomLockManager(ILoggerFactory logger, StorageAccountProvider storage) : base(logger)
{
_storage = storage;
}
protected override CloudBlobContainer GetContainer(string accountName)
{
var account = _storage.Get(accountName);
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference(StorageBaseDistributedLockManager.DefaultContainerName);
return container;
}
await VerifyLeaseState(method, SingletonScope.Function, null, LeaseState.Available, LeaseStatus.Unlocked, directory: _secondaryLockDirectoryContainerClient);
}
[Fact]
@ -338,24 +315,25 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
host.Dispose();
}
internal async static Task VerifyLeaseState(MethodInfo method, SingletonScope scope, string scopeId, LeaseState leaseState, LeaseStatus leaseStatus, CloudBlobDirectory directory = null)
internal async static Task VerifyLeaseState(MethodInfo method, SingletonScope scope, string scopeId, LeaseState leaseState, LeaseStatus leaseStatus, BlobContainerClient directory = null)
{
string lockId = FormatLockId(method, scope, scopeId);
CloudBlobDirectory lockDirectory = directory ?? _lockDirectory;
CloudBlockBlob lockBlob = lockDirectory.GetBlockBlobReference(lockId);
await lockBlob.FetchAttributesAsync();
Assert.Equal(leaseState, lockBlob.Properties.LeaseState);
Assert.Equal(leaseStatus, lockBlob.Properties.LeaseStatus);
BlobContainerClient lockDirectory = directory ?? _lockDirectoryContainerClient;
BlobClient blobClient = lockDirectory.GetBlobClient(lockId);
var properties = await blobClient.GetPropertiesAsync();
Assert.Equal(leaseState, properties.Value.LeaseState);
Assert.Equal(leaseStatus, properties.Value.LeaseStatus);
}
internal static async Task VerifyLeaseDoesNotExistAsync(MethodInfo method, SingletonScope scope, string scopeId, CloudBlobDirectory directory = null)
internal static async Task VerifyLeaseDoesNotExistAsync(MethodInfo method, SingletonScope scope, string scopeId, BlobContainerClient directory = null)
{
string lockId = FormatLockId(method, scope, scopeId);
CloudBlobDirectory lockDirectory = directory ?? _lockDirectory;
CloudBlockBlob lockBlob = lockDirectory.GetBlockBlobReference(lockId);
Assert.False(await lockBlob.ExistsAsync());
BlobContainerClient lockDirectory = directory ?? _lockDirectoryContainerClient;
BlobClient blobClient = lockDirectory.GetBlobClient(lockId);
Assert.False(await blobClient.ExistsAsync());
}
private static string FormatLockId(MethodInfo method, SingletonScope scope, string scopeId)
@ -375,7 +353,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
lockId += scopeId;
}
lockId = string.Format("{0}/{1}", TestHostId, lockId);
lockId = string.Format("locks/{0}/{1}", TestHostId, lockId);
return lockId;
}
@ -402,7 +380,7 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
SingletonScope.Function,
null,
LeaseState.Leased, LeaseStatus.Locked,
_secondaryLockDirectory);
_secondaryLockDirectoryContainerClient);
await Task.Delay(50);
}
@ -636,10 +614,14 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
var hostBuilder = new HostBuilder()
.ConfigureDefaultTestHost<TProg>(b =>
{
b.UseHostId(TestHostId)
.AddAzureStorage()
.AddExtension<TestTriggerAttributeBindingProvider>();
RuntimeStorageWebJobsBuilderExtensions.AddAzureStorageCoreServices(b);
b.UseHostId(TestHostId);
// For Queue and Blob Triggers
b.AddAzureStorageBlobs();
b.AddAzureStorageQueues();
b.AddAzureStorageCoreServices();
b.AddExtension<TestTriggerAttributeBindingProvider>();
})
.ConfigureTestLogger()
.ConfigureServices(services =>
@ -807,82 +789,73 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
}
private class TestFixture : IDisposable
private class TestFixture : IAsyncLifetime
{
private Lazy<StorageAccount> _storageAccountLazy = new Lazy<StorageAccount>(GetStorageAccount);
public BlobServiceClient BlobServiceClient;
public BlobServiceClient SecondaryBlobServiceClient;
public QueueServiceClient QueueServiceClient;
public StorageAccount StorageAccount => _storageAccountLazy.Value;
public TestFixture()
{
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
_lockDirectory = blobClient.GetContainerReference("azure-webjobs-hosts").GetDirectoryReference("locks");
string secondaryConnectionString = GetConfigurationString($"AzureWebJobs{Secondary}");
var secondaryStorageAccount = CloudStorageAccount.Parse(secondaryConnectionString);
blobClient = secondaryStorageAccount.CreateCloudBlobClient();
_secondaryLockDirectory = blobClient.GetContainerReference("azure-webjobs-hosts").GetDirectoryReference("locks");
}
private static StorageAccount GetStorageAccount()
{
// Create a default host since we know that's where the account
// is coming from
IHost host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage();
b.AddAzureStorageCoreServices();
})
.Build();
return host.GetStorageAccount();
BlobServiceClient = TestHelpers.GetTestBlobServiceClient();
QueueServiceClient = TestHelpers.GetTestQueueServiceClient();
_lockDirectoryContainerClient = BlobServiceClient.GetBlobContainerClient(HostContainerNames.Hosts);
SecondaryBlobServiceClient = TestHelpers.GetTestBlobServiceClient($"AzureWebJobs{Secondary}");
_secondaryLockDirectoryContainerClient = SecondaryBlobServiceClient.GetBlobContainerClient(HostContainerNames.Hosts);
}
private static string GetConfigurationString(string key)
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
// Create a default host since we know that's where the account
// is coming from
IHost host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
await CleanBlobsAsync(BlobServiceClient);
await CleanBlobsAsync(SecondaryBlobServiceClient);
await CleanQueuesAsync();
}
private async Task CleanBlobsAsync(BlobServiceClient blobServiceClient)
{
if (blobServiceClient != null)
{
var containerClient = blobServiceClient.GetBlobContainerClient(HostContainerNames.Hosts);
if (await containerClient.ExistsAsync())
{
b.AddAzureStorage();
})
.Build();
return host.Services.GetService<IConfiguration>()[key];
}
public void Dispose()
{
if (StorageAccount != null)
{
Clean().Wait();
}
}
private async Task Clean()
{
CloudQueueClient queueClient = StorageAccount.CreateCloudQueueClient();
var queuesResult = await queueClient.ListQueuesSegmentedAsync(TestArtifactsPrefix, null);
foreach (var queue in queuesResult.Results)
{
await queue.DeleteAsync();
}
CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
CloudBlobContainer hostContainer = blobClient.GetContainerReference("azure-webjobs-hosts");
var blobs = await hostContainer.ListBlobsSegmentedAsync(string.Format("locks/{0}", TestHostId), useFlatBlobListing: true, blobListingDetails: BlobListingDetails.None,
maxResults: null, currentToken: null, options: null, operationContext: null);
foreach (CloudBlockBlob lockBlob in blobs.Results)
{
try
{
await lockBlob.DeleteAsync();
await foreach (var testBlob in containerClient.GetBlobsAsync(prefix: string.Format("locks/{0}", TestHostId)))
{
try
{
await containerClient.DeleteBlobAsync(testBlob.Name);
}
catch (RequestFailedException)
{
// best effort - might fail if there is an active
// lease on the blob
}
}
}
catch (StorageException)
}
}
private async Task CleanQueuesAsync()
{
if (QueueServiceClient != null)
{
await foreach (var testQueue in QueueServiceClient.GetQueuesAsync(prefix: TestArtifactsPrefix))
{
// best effort - might fail if there is an active
// lease on the blob
await QueueServiceClient.DeleteQueueAsync(testQueue.Name);
}
}
}

Просмотреть файл

@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
{
static class TestExtensions
{
public static StorageAccount GetStorageAccount(this IHost host)
{
var provider = host.Services.GetRequiredService<StorageAccountProvider>(); // $$$ ok?
return provider.GetHost();
}
}
}

Просмотреть файл

@ -34,10 +34,10 @@
<PackageReference Include="Moq" Version="4.7.145" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.0-beta.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj" />

Просмотреть файл

@ -0,0 +1,103 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
public class AzureStorageProviderTests
{
private const string StorageConnection = "AzureWebJobsStorage";
[Fact]
public async Task ConnectionStringSectionUsed()
{
var testConfiguration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddTestSettings()
.Build();
var testData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "ConnectionStrings:AzureWebJobsStorage", testConfiguration.GetWebJobsConnectionString(StorageConnection) },
{ "AzureWebJobsStorage", "" }
};
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(testData)
.Build();
var azureStorageProvider = GetAzureStorageProvider(configuration);
Assert.True(azureStorageProvider.TryCreateHostingBlobContainerClient(out BlobContainerClient container));
await VerifyContainerClientAvailable(container);
Assert.True(azureStorageProvider.TryCreateBlobServiceClientFromConnection(ConnectionStringNames.Storage, out BlobServiceClient blobServiceClient));
await VerifyBlobServiceClientAvailable(blobServiceClient);
}
[Fact]
public void NoConnectionThrowsException()
{
var configuration = new ConfigurationBuilder()
.Build();
var azureStorageProvider = GetAzureStorageProvider(configuration);
Assert.False(azureStorageProvider.TryCreateHostingBlobContainerClient(out _));
Assert.False(azureStorageProvider.TryCreateBlobServiceClientFromConnection(ConnectionStringNames.Storage, out _));
}
private async Task VerifyBlobServiceClientAvailable(BlobServiceClient client)
{
try
{
var propertiesResponse = await client.GetPropertiesAsync();
Assert.True(true);
}
catch (Exception e)
{
Assert.False(true, $"Could not establish connection to BlobService. {e}");
}
}
private async Task VerifyContainerClientAvailable(BlobContainerClient client)
{
try
{
var propertiesResponse = await client.GetPropertiesAsync();
Assert.True(true);
}
catch (Exception e)
{
Assert.False(true, $"Could not establish connection to BlobService. {e}");
}
}
private static IAzureBlobStorageProvider GetAzureStorageProvider(IConfiguration configuration, JobHostInternalStorageOptions storageOptions = null)
{
IHost tempHost = new HostBuilder()
.ConfigureServices(services =>
{
// Override configuration
services.AddSingleton(configuration);
services.AddAzureStorageCoreServices();
if (storageOptions != null)
{
services.AddTransient<IOptions<JobHostInternalStorageOptions>>(s => new OptionsWrapper<JobHostInternalStorageOptions>(storageOptions));
}
}).Build();
var azureStorageProvider = tempHost.Services.GetRequiredService<IAzureBlobStorageProvider>();
return azureStorageProvider;
}
}
}

Просмотреть файл

@ -0,0 +1,94 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
/// <summary>
/// Tests whether the StorageClientProvider can properly create a client and send a request
/// </summary>
public class BlobServiceClientProviderTests
{
private const string StorageConnection = "AzureWebJobsStorage";
private readonly BlobServiceClientProvider _blobServiceClientProvider;
private readonly IConfiguration _configuration;
public BlobServiceClientProviderTests()
{
_configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddTestSettings()
.Build();
IHost tempHost = new HostBuilder()
.ConfigureServices(services =>
{
services.AddAzureStorageCoreServices();
}).Build();
var componentFactory = tempHost.Services.GetRequiredService<AzureComponentFactory>();
var logForwarder = tempHost.Services.GetRequiredService<AzureEventSourceLogForwarder>();
_blobServiceClientProvider = new BlobServiceClientProvider(componentFactory, logForwarder);
}
[Fact]
public async Task Create_ReturnsValidClient()
{
var client = _blobServiceClientProvider.Create(StorageConnection, _configuration);
Assert.NotNull(client);
await VerifyServiceAvailable(client);
}
[Fact]
public async Task Create_NameResolverReturnsValidClient()
{
var resolver = new DefaultNameResolver(_configuration);
var client = _blobServiceClientProvider.Create(StorageConnection, resolver, _configuration);
await VerifyServiceAvailable(client);
}
[Fact]
public async Task Create_ConnectionStringSectionChecked()
{
var testData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "ConnectionStrings:AzureWebJobsStorage", Environment.GetEnvironmentVariable(StorageConnection) },
{ "AzureWebJobsStorage", "" }
};
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(testData)
.AddTestSettings()
.Build();
var client = _blobServiceClientProvider.Create(StorageConnection, configuration);
await VerifyServiceAvailable(client);
}
private async Task VerifyServiceAvailable(BlobServiceClient client)
{
try
{
var propertiesResponse = await client.GetPropertiesAsync();
Assert.True(true);
}
catch (Exception e)
{
Assert.False(true, $"Could not establish connection to BlobService. {e}");
}
}
}
}

Просмотреть файл

@ -2,20 +2,12 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Triggers;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Storage;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Bindings.Data
@ -41,7 +33,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Bindings.Data
BindingFlags.Static | BindingFlags.NonPublic);
Assert.NotNull(method); // Guard
FunctionIndexer indexer = FunctionIndexerFactory.Create(CloudStorageAccount.DevelopmentStorageAccount);
FunctionIndexer indexer = FunctionIndexerFactory.Create();
IFunctionIndexCollector stubIndex = new Mock<IFunctionIndexCollector>().Object;
// Act & Assert

Просмотреть файл

@ -31,12 +31,12 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
_jobActivator = new DefaultJobActivator(serviceProvider);
}
public void Call(StorageAccount account, Type programType, MethodInfo method,
public void Call(Type programType, MethodInfo method,
IDictionary<string, object> arguments, params Type[] cloudBlobStreamBinderTypes)
{
// Arrange
TaskCompletionSource<object> backgroundTaskSource = new TaskCompletionSource<object>();
IHost host = CreateConfigurationForManualCompletion<object>(account,
IHost host = CreateConfigurationForManualCompletion<object>(
programType, backgroundTaskSource, cloudBlobStreamBinderTypes: cloudBlobStreamBinderTypes);
Task backgroundTask = backgroundTaskSource.Task;
@ -71,12 +71,12 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
// Stops running the host as soon as the program marks the task as completed.
public TResult Call<TResult>(StorageAccount account, Type programType, MethodInfo method,
public TResult Call<TResult>(Type programType, MethodInfo method,
IDictionary<string, object> arguments, Action<TaskCompletionSource<TResult>> setTaskSource)
{
// Arrange
TaskCompletionSource<TResult> taskSource = new TaskCompletionSource<TResult>();
var serviceProvider = CreateConfigurationForManualCompletion<TResult>(account, programType,
var serviceProvider = CreateConfigurationForManualCompletion<TResult>(programType,
taskSource);
Task<TResult> task = taskSource.Task;
setTaskSource.Invoke(taskSource);
@ -117,12 +117,12 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
// Stops running the host as soon as the program marks the task as completed.
public Exception CallFailure(StorageAccount account, Type programType, MethodInfo method,
public Exception CallFailure(Type programType, MethodInfo method,
IDictionary<string, object> arguments)
{
// Arrange
TaskCompletionSource<object> backgroundTaskSource = new TaskCompletionSource<object>();
var host = CreateConfigurationForCallFailure(account, programType,
var host = CreateConfigurationForCallFailure(programType,
backgroundTaskSource);
Task backgroundTask = backgroundTaskSource.Task;
@ -147,76 +147,70 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
}
public IHost CreateConfigurationForCallFailure(StorageAccount storageAccount,
Type programType, TaskCompletionSource<object> taskSource)
public IHost CreateConfigurationForCallFailure(Type programType, TaskCompletionSource<object> taskSource)
{
return CreateConfiguration<object>(storageAccount, programType, taskSource, new NullFunctionInstanceLogger());
return CreateConfiguration<object>(programType, taskSource, new NullFunctionInstanceLogger());
}
private IHost CreateConfigurationForInstanceFailure(StorageAccount storageAccount,
Type programType, TaskCompletionSource<Exception> taskSource)
private IHost CreateConfigurationForInstanceFailure(Type programType, TaskCompletionSource<Exception> taskSource)
{
return CreateConfiguration<Exception>(storageAccount, programType, taskSource,
return CreateConfiguration<Exception>(programType, taskSource,
new ExpectInstanceFailureTaskFunctionInstanceLogger(taskSource));
}
public IHost CreateConfigurationForInstanceSuccess(StorageAccount storageAccount,
public IHost CreateConfigurationForInstanceSuccess(
Type programType, TaskCompletionSource<object> taskSource, IExtensionRegistry extensions = null)
{
return CreateConfiguration<object>(storageAccount, programType, taskSource,
return CreateConfiguration<object>(programType, taskSource,
new ExpectInstanceSuccessTaskFunctionInstanceLogger(taskSource), extensions);
}
public IHost CreateConfigurationForManualCompletion<TResult>(StorageAccount storageAccount,
public IHost CreateConfigurationForManualCompletion<TResult>(
Type programType, TaskCompletionSource<TResult> taskSource, IExtensionRegistry extensions = null, params Type[] cloudBlobStreamBinderTypes)
{
IEnumerable<string> ignoreFailureFunctionIds = null;
return CreateConfigurationForManualCompletion<TResult>(storageAccount, programType, taskSource,
return CreateConfigurationForManualCompletion<TResult>(programType, taskSource,
ignoreFailureFunctionIds, extensions, cloudBlobStreamBinderTypes);
}
private IHost CreateConfigurationForManualCompletion<TResult>(
StorageAccount storageAccount, Type programType, TaskCompletionSource<TResult> taskSource,
Type programType, TaskCompletionSource<TResult> taskSource,
IEnumerable<string> ignoreFailureFunctions, IExtensionRegistry extensions = null, params Type[] cloudBlobStreamBinderTypes)
{
return CreateConfigurationForManualCompletion<TResult>(storageAccount, programType,
return CreateConfigurationForManualCompletion<TResult>(programType,
_jobActivator, taskSource, ignoreFailureFunctions, extensions);
}
private static IHost CreateConfigurationForManualCompletion<TResult>(
StorageAccount storageAccount, Type programType, IJobActivator activator,
Type programType, IJobActivator activator,
TaskCompletionSource<TResult> taskSource, IEnumerable<string> ignoreFailureFunctions, IExtensionRegistry extensions = null)
{
return CreateConfiguration<TResult>(storageAccount, programType, activator, taskSource,
return CreateConfiguration<TResult>(programType, activator, taskSource,
new ExpectManualCompletionFunctionInstanceLogger<TResult>(taskSource, false, ignoreFailureFunctions), extensions);
}
private IHost CreateConfiguration<TResult>(StorageAccount storageAccount, Type programType, TaskCompletionSource<TResult> taskSource,
private IHost CreateConfiguration<TResult>(Type programType, TaskCompletionSource<TResult> taskSource,
IFunctionInstanceLogger functionInstanceLogger, IExtensionRegistry extensions = null)
{
return CreateConfiguration<TResult>(storageAccount, programType,
return CreateConfiguration<TResult>(programType,
_jobActivator, taskSource, functionInstanceLogger, extensions);
}
private static IHost CreateConfiguration<TResult>(StorageAccount storageAccount, Type programType,
private static IHost CreateConfiguration<TResult>(Type programType,
IJobActivator activator, TaskCompletionSource<TResult> taskSource,
IFunctionInstanceLogger functionInstanceLogger, IExtensionRegistry extensions = null)
{
StorageAccountProvider storageAccountProvider = null; // new FakeStorageAccountProvider(storageAccount); $$$
IWebJobsExceptionHandler exceptionHandler = new TaskBackgroundExceptionHandler<TResult>(taskSource);
return new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.UseHostId(Guid.NewGuid().ToString("N"))
.AddAzureStorage();
b.UseHostId(Guid.NewGuid().ToString("N"));
}, programType)
.ConfigureServices(services =>
{
// services.AddSingleton<IOptionsFactory<JobHostQueuesOptions>, FakeQueuesOptionsFactory>(); $$$ ???
services.AddSingletonIfNotNull(storageAccountProvider);
services.AddSingletonIfNotNull(activator);
services.AddSingletonIfNotNull(exceptionHandler);
services.AddSingletonIfNotNull(extensions);
@ -229,43 +223,42 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
// Stops running the host as soon as the first function logs completion.
public void RunTrigger(StorageAccount account, Type programType, IExtensionRegistry extensions = null)
public void RunTrigger(Type programType, IExtensionRegistry extensions = null)
{
// Arrange
TaskCompletionSource<object> taskSource = new TaskCompletionSource<object>();
var serviceProvider = CreateConfigurationForInstanceSuccess(account, programType, taskSource, extensions);
var serviceProvider = CreateConfigurationForInstanceSuccess(programType, taskSource, extensions);
// Act & Assert
RunTrigger<object>(serviceProvider, taskSource.Task);
}
// Stops running the host as soon as the program marks the task as completed.
public TResult RunTrigger<TResult>(StorageAccount account, Type programType,
Action<TaskCompletionSource<TResult>> setTaskSource)
public TResult RunTrigger<TResult>(Type programType, Action<TaskCompletionSource<TResult>> setTaskSource)
{
return RunTrigger<TResult>(account, programType, setTaskSource, _jobActivator,
return RunTrigger<TResult>(programType, setTaskSource, _jobActivator,
ignoreFailureFunctions: null);
}
public TResult RunTrigger<TResult>(StorageAccount account, Type programType,
public TResult RunTrigger<TResult>(Type programType,
IJobActivator activator, Action<TaskCompletionSource<TResult>> setTaskSource)
{
return RunTrigger<TResult>(account, programType, setTaskSource, activator, ignoreFailureFunctions: null);
return RunTrigger<TResult>(programType, setTaskSource, activator, ignoreFailureFunctions: null);
}
public TResult RunTrigger<TResult>(StorageAccount account, Type programType,
public TResult RunTrigger<TResult>(Type programType,
Action<TaskCompletionSource<TResult>> setTaskSource, IEnumerable<string> ignoreFailureFunctions)
{
return RunTrigger<TResult>(account, programType, setTaskSource, _jobActivator, ignoreFailureFunctions);
return RunTrigger<TResult>(programType, setTaskSource, _jobActivator, ignoreFailureFunctions);
}
public TResult RunTrigger<TResult>(StorageAccount account, Type programType,
public TResult RunTrigger<TResult>(Type programType,
Action<TaskCompletionSource<TResult>> setTaskSource, IJobActivator activator,
IEnumerable<string> ignoreFailureFunctions)
{
// Arrange
TaskCompletionSource<TResult> taskSource = new TaskCompletionSource<TResult>();
var serviceProvider = CreateConfigurationForManualCompletion<TResult>(account, programType,
var serviceProvider = CreateConfigurationForManualCompletion<TResult>(programType,
activator, taskSource, ignoreFailureFunctions);
Task<TResult> task = taskSource.Task;
setTaskSource.Invoke(taskSource);
@ -316,11 +309,11 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
// Stops running the host as soon as the first function logs completion.
public Exception RunTriggerFailure(StorageAccount account, Type programType)
public Exception RunTriggerFailure(Type programType)
{
// Arrange
TaskCompletionSource<Exception> taskSource = new TaskCompletionSource<Exception>();
IHost host = CreateConfigurationForInstanceFailure(account, programType, taskSource);
IHost host = CreateConfigurationForInstanceFailure(programType, taskSource);
// The task for failed function invocation (should complete successfully with a non-null exception).
Task<Exception> task = taskSource.Task;
@ -349,12 +342,12 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
// Stops running the host as soon as the program marks the task as completed.
public Exception RunTriggerFailure<TResult>(StorageAccount account, Type programType,
public Exception RunTriggerFailure<TResult>(Type programType,
Action<TaskCompletionSource<TResult>> setTaskSource)
{
// Arrange
TaskCompletionSource<Exception> failureTaskSource = new TaskCompletionSource<Exception>();
IHost host = CreateConfigurationForInstanceFailure(account, programType, failureTaskSource);
IHost host = CreateConfigurationForInstanceFailure(programType, failureTaskSource);
TaskCompletionSource<TResult> successTaskSource = new TaskCompletionSource<TResult>();
// The task for failed function invocation (should complete successfully with an exception).
@ -403,4 +396,4 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
}
}
}
}

Просмотреть файл

@ -147,10 +147,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
const int N = 5;
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<ILoggerFunctions>(b =>
{
b.AddAzureStorage();
})
.ConfigureDefaultTestHost<ILoggerFunctions>()
.ConfigureServices(services =>
{
services.AddSingleton<IAsyncCollector<FunctionInstanceLogEntry>>(mockAggregator.Object);

Просмотреть файл

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Azure.Storage;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
@ -16,33 +15,22 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
{
internal static class FunctionIndexerFactory
{
public class FakeStorageAccountProvider : StorageAccountProvider
{
public FakeStorageAccountProvider()
: base(null)
{
}
public override StorageAccount Get(string name)
{
return StorageAccount.New(CloudStorageAccount.DevelopmentStorageAccount);
}
}
public static FunctionIndexer Create(CloudStorageAccount account = null, INameResolver nameResolver = null,
public static FunctionIndexer Create(INameResolver nameResolver = null,
IExtensionRegistry extensionRegistry = null, ILoggerFactory loggerFactory = null)
{
IHost host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.UseHostId("testhost")
.AddAzureStorage()
.AddServiceBus();
b.UseHostId("testhost");
// Needed for Blob/Queue triggers and bindings
b.AddAzureStorageBlobs();
b.AddAzureStorageQueues();
b.AddServiceBus();
})
.ConfigureServices(services =>
{
services.AddSingleton<StorageAccountProvider>(new FakeStorageAccountProvider());
if (nameResolver != null)
{
services.AddSingleton<INameResolver>(nameResolver);

Просмотреть файл

@ -43,8 +43,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
}
}
private static void BadTableName([Table(@"#")] IDictionary<Tuple<string, string>, object> t) { }
private static void MultipleQueueParams([QueueTrigger("p123")] int p123, [QueueTrigger("p234")] int p234) { }
private static void QueueNestedIEnumerable([Queue("myoutputqueue")] ICollection<IEnumerable<Payload>> myoutputqueue) { }

Просмотреть файл

@ -9,11 +9,8 @@ using System.Threading;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Cosmos.Table;
using Moq;
using Xunit;
using CloudStorageAccount = Microsoft.Azure.Storage.CloudStorageAccount;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
{
@ -25,8 +22,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
MethodInfo method = typeof(FunctionIndexerIntegrationTests).GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
Assert.NotNull(method);
FunctionIndexer indexer = FunctionIndexerFactory.Create(CloudStorageAccount.DevelopmentStorageAccount,
nameResolver);
FunctionIndexer indexer = FunctionIndexerFactory.Create(nameResolver);
Tuple<FunctionDescriptor, IFunctionDefinition> indexEntry = null;
Mock<IFunctionIndexCollector> indexMock = new Mock<IFunctionIndexCollector>(MockBehavior.Strict);
@ -61,7 +57,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Assert.NotNull(descriptor);
var parameters = descriptor.Parameters;
Assert.Single(parameters);
Assert.IsType<BlobParameterDescriptor>(parameters.First());
}
private static void NameResolver([Blob(@"input/%name%")] TextReader inputs) { }
@ -79,10 +74,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Assert.Single(parameters);
ParameterDescriptor firstParameter = parameters.First();
Assert.Equal("inputs", firstParameter.Name);
Assert.IsType<BlobParameterDescriptor>(firstParameter);
BlobParameterDescriptor blobParameter = (BlobParameterDescriptor)firstParameter;
Assert.Equal(@"input", blobParameter.ContainerName);
Assert.Equal(@"VALUE", blobParameter.BlobName);
}
public static void AutoTrigger1([BlobTrigger(@"daas-test-input/{name}.csv")] TextReader inputs) { }
@ -95,7 +86,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Assert.NotNull(func);
var parameters = func.Parameters;
Assert.Single(parameters);
Assert.IsType<BlobTriggerParameterDescriptor>(parameters.First());
Assert.Equal("BlobTriggerParameterDescriptor", parameters.First().GetType().Name);
}
[NoAutomaticTrigger]
@ -132,25 +123,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Assert.Null(entry);
}
private static void Table([Table("TableName")] CloudTable table) { }
[Fact]
public void TestTable()
{
FunctionDescriptor func = IndexMethod("Table").Item1;
Assert.NotNull(func);
var parameters = func.Parameters;
Assert.Single(parameters);
ParameterDescriptor firstParameter = parameters.First();
Assert.NotNull(firstParameter);
Assert.Equal("table", firstParameter.Name);
Assert.IsType<TableParameterDescriptor>(firstParameter);
TableParameterDescriptor typedTableParameter = (TableParameterDescriptor)firstParameter;
Assert.Equal("TableName", typedTableParameter.TableName);
}
public static void QueueTrigger([QueueTrigger("inputQueue")] int queueValue) { }
[Fact]
@ -163,9 +135,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Assert.Single(parameters);
ParameterDescriptor firstParameter = parameters.First();
Assert.IsType<QueueTriggerParameterDescriptor>(firstParameter);
QueueTriggerParameterDescriptor queueParameter = (QueueTriggerParameterDescriptor)firstParameter;
Assert.Equal("inputqueue", queueParameter.QueueName); // queue name gets normalized.
Assert.Equal("QueueTriggerParameterDescriptor", firstParameter.GetType().Name);
Assert.Equal("queueValue", firstParameter.Name); // parameter name does not.
}
@ -185,8 +155,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Assert.Single(parameters);
ParameterDescriptor firstParameter = parameters.First();
QueueParameterDescriptor queueParameter = (QueueParameterDescriptor)firstParameter;
Assert.Equal("inputqueue", queueParameter.QueueName); // queue name gets normalized.
Assert.Equal("inputQueue", firstParameter.Name); // parameter name does not.
}
@ -213,9 +181,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
ParameterDescriptor firstParameter = parameters.ElementAt(0);
Assert.Equal("input", firstParameter.Name);
Assert.IsType<BlobTriggerParameterDescriptor>(firstParameter);
BlobTriggerParameterDescriptor blobParameter = (BlobTriggerParameterDescriptor)firstParameter;
Assert.Equal("container", blobParameter.ContainerName);
Assert.Equal("BlobTriggerParameterDescriptor", firstParameter.GetType().Name);
ParameterDescriptor secondParameter = parameters.ElementAt(1);
Assert.Equal("unbound", secondParameter.Name);
@ -237,9 +203,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
ParameterDescriptor firstParameter = parameters.ElementAt(0);
Assert.Equal("input", firstParameter.Name);
Assert.IsType<BlobTriggerParameterDescriptor>(firstParameter);
BlobTriggerParameterDescriptor blobParameter = (BlobTriggerParameterDescriptor)firstParameter;
Assert.Equal("container", blobParameter.ContainerName);
Assert.Equal("BlobTriggerParameterDescriptor", firstParameter.GetType().Name);
ParameterDescriptor secondParameter = parameters.ElementAt(1);
Assert.Equal("bound", secondParameter.Name);

Просмотреть файл

@ -9,7 +9,6 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Core;
using Microsoft.Azure.Storage;
using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Indexers;
@ -589,7 +588,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
BindingFlags.Static | BindingFlags.Public);
Assert.NotNull(method); // Guard
FunctionIndexer indexer = FunctionIndexerFactory.Create(CloudStorageAccount.DevelopmentStorageAccount);
FunctionIndexer indexer = FunctionIndexerFactory.Create();
var indexCollector = new TestIndexCollector();
// Act & Assert

Просмотреть файл

@ -140,7 +140,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
IHost host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage();
b.AddAzureStorageBlobs();
b.AddAzureStorageQueues();
})
.Build();
@ -194,48 +195,15 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
var queueTriggerAttr = GetAttr<QueueTriggerAttribute>(metadataProvider, new { QueueName = "q1" });
Assert.Equal("q1", queueTriggerAttr.QueueName);
// Table
var tableAttr = GetAttr<TableAttribute>(metadataProvider, new { TableName = "t1" });
Assert.Equal("t1", tableAttr.TableName);
tableAttr = GetAttr<TableAttribute>(metadataProvider, new { TableName = "t1", partitionKey = "pk", Filter = "f1" });
Assert.Equal("t1", tableAttr.TableName);
Assert.Equal("pk", tableAttr.PartitionKey);
Assert.Equal(null, tableAttr.RowKey);
Assert.Equal("f1", tableAttr.Filter);
}
[Fact]
public void DefaultTypeForTable()
{
var host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage();
})
.Build();
var metadataProvider = host.CreateMetadataProvider();
var t1 = metadataProvider.GetDefaultType(new TableAttribute("table1"), FileAccess.Read, null);
Assert.Equal(typeof(JArray), t1);
var t2 = metadataProvider.GetDefaultType(new TableAttribute("table1", "pk", "rk"), FileAccess.Read, null);
Assert.Equal(typeof(JObject), t2);
var t3 = metadataProvider.GetDefaultType(new TableAttribute("table1"), FileAccess.Write, null);
Assert.Equal(typeof(IAsyncCollector<JObject>), t3);
}
[Fact]
public void DefaultTypeForQueue()
{
var host = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage();
b.AddAzureStorageQueues();
})
.Build();

Просмотреть файл

@ -8,8 +8,9 @@ using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage.Blob;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -29,12 +30,12 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
.UseEnvironment("Development")
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage()
.AddAzureStorageCoreServices();
b.AddAzureStorageQueues();
b.AddAzureStorageCoreServices();
});
IHost host = hostBuilder.Build();
var config = host.Services.GetService<DistributedLockManagerContainerProvider>();
var config = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
Assert.True(hostingEnvironment.IsDevelopment());
@ -53,12 +54,12 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
.UseEnvironment("Production")
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage()
.AddAzureStorageCoreServices();
b.AddAzureStorageQueues();
b.AddAzureStorageCoreServices();
});
IHost host = hostBuilder.Build();
var config = host.Services.GetService<DistributedLockManagerContainerProvider>();
var config = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
Assert.False(hostingEnvironment.IsDevelopment());
@ -78,8 +79,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
.UseEnvironment("Development")
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage()
.AddAzureStorageCoreServices();
b.AddAzureStorageQueues();
b.AddAzureStorageCoreServices();
})
.ConfigureServices(services =>
{
@ -95,7 +96,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
});
IHost host = hostBuilder.Build();
var config = host.Services.GetService<DistributedLockManagerContainerProvider>();
var config = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
Assert.True(hostingEnvironment.IsDevelopment());
@ -148,8 +149,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
var hostBuilder = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage()
.AddAzureStorageCoreServices();
b.AddAzureStorageCoreServices();
})
.ConfigureAppConfiguration(c =>
{
@ -158,11 +158,14 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
IHost host = hostBuilder.Build();
var config = host.Services.GetService<DistributedLockManagerContainerProvider>();
var config = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
var blobStorageProvider = host.Services.GetService<IAzureBlobStorageProvider>();
var container = config.InternalContainer;
var container = config.Value.InternalSasBlobContainer;
Assert.NotNull(container);
Assert.Equal(container.Name, "myContainer"); // specified in sas.
Assert.True(blobStorageProvider.TryCreateHostingBlobContainerClient(out BlobContainerClient containerClient));
Assert.Equal("myContainer", containerClient.Name); // specified in sas.
}
}
@ -173,8 +176,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
var hostBuilder = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage()
.AddAzureStorageCoreServices();
b.AddAzureStorageCoreServices();
})
.ConfigureAppConfiguration(c =>
{
@ -184,19 +186,19 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
hostBuilder.ConfigureServices((ctx, services) =>
{
var uri2 = new Uri("https://contoso.blob.core.windows.net/myContainer2?signature=foo");
services.AddSingleton<DistributedLockManagerContainerProvider>(new DistributedLockManagerContainerProvider()
{
InternalContainer = new CloudBlobContainer(uri2)
});
services.Configure<JobHostInternalStorageOptions>(o => o.InternalSasBlobContainer = uri2.ToString());
});
IHost host = hostBuilder.Build();
var config = host.Services.GetService<DistributedLockManagerContainerProvider>();
var config = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
var blobStorageProvider = host.Services.GetService<IAzureBlobStorageProvider>();
var container = config.InternalContainer;
var container = config.Value.InternalSasBlobContainer;
Assert.NotNull(container);
Assert.Equal(container.Name, "myContainer2"); // specified in sas.
Assert.True(blobStorageProvider.TryCreateHostingBlobContainerClient(out BlobContainerClient containerClient));
Assert.Equal("myContainer2", containerClient.Name); // specified in sas.
}
// Verify that JobHostConfig pulls a Sas container from appsettings.
@ -208,8 +210,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
var hostBuilder = new HostBuilder()
.ConfigureDefaultTestHost(b =>
{
b.AddAzureStorage()
.AddAzureStorageCoreServices();
b.AddAzureStorageCoreServices();
})
.ConfigureAppConfiguration(c =>
{
@ -221,11 +222,14 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
IHost host = hostBuilder.Build();
var config = host.Services.GetService<DistributedLockManagerContainerProvider>();
var config = host.Services.GetService<IOptions<JobHostInternalStorageOptions>>();
var blobStorageProvider = host.Services.GetService<IAzureBlobStorageProvider>();
var container = config.InternalContainer;
var container = config.Value.InternalSasBlobContainer;
Assert.NotNull(container);
Assert.Equal(container.Name, "myContainer3"); // specified in sas.
Assert.True(blobStorageProvider.TryCreateHostingBlobContainerClient(out BlobContainerClient containerClient));
Assert.Equal("myContainer3", containerClient.Name); // specified in sas.
}
// Test that we can explicitly disable storage and call through a function
@ -304,4 +308,4 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
}
}
}
}
}

Просмотреть файл

@ -10,9 +10,8 @@ using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Queue;
using Azure.Storage.Blobs;
using Azure.Storage.Queues.Models;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Listeners;
@ -374,7 +373,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
[Fact]
public async Task CallAsync_WithDictionary()
{
var host = JobHostFactory.Create<ProgramSimple>(null);
var host = JobHostFactory.Create<ProgramSimple>();
var value = "abc";
ProgramSimple._value = null;
@ -389,7 +388,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
[InlineData("test")]
public async Task CallAsync_WithObject(string methodName)
{
var host = JobHostFactory.Create<ProgramSimple>(null);
var host = JobHostFactory.Create<ProgramSimple>();
var x = "abc";
ProgramSimple._value = null;
@ -404,7 +403,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
{
// Arrange
ProgramWithCancellationToken.Cleanup();
var host = JobHostFactory.Create<ProgramWithCancellationToken>(null);
var host = JobHostFactory.Create<ProgramWithCancellationToken>();
using (CancellationTokenSource source = new CancellationTokenSource())
{
@ -429,7 +428,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
string expectedStackTrace = expectedExceptionInfo.SourceException.StackTrace;
ThrowingProgram.ExceptionInfo = expectedExceptionInfo;
var host = JobHostFactory.Create<ThrowingProgram>(null);
var host = JobHostFactory.Create<ThrowingProgram>();
MethodInfo methodInfo = typeof(ThrowingProgram).GetMethod("Throw");
// Act & Assert
@ -445,179 +444,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
}
}
[Fact]
public async Task BlobTrigger_ProvidesBlobTriggerBindingData()
{
try
{
// Arrange
var host = new HostBuilder()
.ConfigureDefaultTestHost<BlobTriggerBindingDataProgram>(c =>
{
c.AddAzureStorage();
})
.Build()
.GetJobHost();
using (host)
{
CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
MethodInfo methodInfo = typeof(BlobTriggerBindingDataProgram).GetMethod(nameof(BlobTriggerBindingDataProgram.OnBlob));
string containerName = "a";
string blobName = "b";
string expectedPath = containerName + "/" + blobName;
CloudBlobContainer container = account.CreateCloudBlobClient().GetContainerReference(containerName);
ICloudBlob blob = container.GetBlockBlobReference(blobName);
// Act
await host.CallAsync(methodInfo, new { blob = blob });
// Assert
Assert.Equal(expectedPath, BlobTriggerBindingDataProgram.BlobTrigger);
}
}
finally
{
BlobTriggerBindingDataProgram.BlobTrigger = null;
}
}
[Fact]
public async Task QueueTrigger_ProvidesQueueTriggerBindingData()
{
try
{
// Arrange
var host = new HostBuilder()
.ConfigureDefaultTestHost<QueueTriggerBindingDataProgram>(c =>
{
c.AddAzureStorage();
})
.Build()
.GetJobHost();
using (host)
{
MethodInfo methodInfo = typeof(QueueTriggerBindingDataProgram).GetMethod(nameof(QueueTriggerBindingDataProgram.OnQueue));
string expectedMessage = "a";
// Act
await host.CallAsync(methodInfo, new { message = expectedMessage });
// Assert
Assert.Equal(expectedMessage, QueueTriggerBindingDataProgram.QueueTrigger);
}
}
finally
{
QueueTriggerBindingDataProgram.QueueTrigger = null;
}
}
[Fact]
public async Task QueueTrigger_WithTextualByteArrayMessage_ProvidesQueueTriggerBindingData()
{
try
{
// Arrange
var host = new HostBuilder()
.ConfigureDefaultTestHost<QueueTriggerBindingDataProgram>(c =>
{
c.AddAzureStorage();
})
.Build()
.GetJobHost();
using (host)
{
MethodInfo methodInfo = typeof(QueueTriggerBindingDataProgram).GetMethod(nameof(QueueTriggerBindingDataProgram.OnQueue));
string expectedMessage = "abc";
CloudQueueMessage message = new CloudQueueMessage(expectedMessage);
Assert.Equal(expectedMessage, message.AsString); // Guard
// Act
await host.CallAsync(methodInfo, new { message });
// Assert
Assert.Equal(expectedMessage, QueueTriggerBindingDataProgram.QueueTrigger);
}
}
finally
{
QueueTriggerBindingDataProgram.QueueTrigger = null;
}
}
[Fact]
public async Task QueueTrigger_WithNonTextualByteArrayMessageUsingQueueTriggerBindingData_Throws()
{
try
{
// Arrange
var host = new HostBuilder()
.ConfigureDefaultTestHost<QueueTriggerBindingDataProgram>(c =>
{
c.AddAzureStorage();
})
.Build()
.GetJobHost();
using (host)
{
MethodInfo methodInfo = typeof(QueueTriggerBindingDataProgram).GetMethod(nameof(QueueTriggerBindingDataProgram.OnQueue));
byte[] contents = new byte[] { 0x00, 0xFF }; // Not valid UTF-8
CloudQueueMessage message = new CloudQueueMessage(contents);
// Act & Assert
FunctionInvocationException exception = await Assert.ThrowsAsync<FunctionInvocationException>(
() => host.CallAsync(methodInfo, new { message = message }));
// This exeption shape/message could be better, but it's meets a minimum acceptibility threshold.
Assert.Equal("Exception binding parameter 'queueTrigger'", exception.InnerException.Message);
Exception innerException = exception.InnerException.InnerException;
Assert.IsType<InvalidOperationException>(innerException);
Assert.Equal("Binding data does not contain expected value 'queueTrigger'.", innerException.Message);
}
}
finally
{
QueueTriggerBindingDataProgram.QueueTrigger = null;
}
}
[Fact]
public async Task QueueTrigger_WithNonTextualByteArrayMessageNotUsingQueueTriggerBindingData_DoesNotThrow()
{
try
{
// Arrange
var host = new HostBuilder()
.ConfigureDefaultTestHost<QueueTriggerBindingDataProgram>(c =>
{
c.AddAzureStorage();
})
.Build()
.GetJobHost();
using (host)
{
MethodInfo methodInfo = typeof(QueueTriggerBindingDataProgram).GetMethod(nameof(QueueTriggerBindingDataProgram.ProcessQueueAsBytes));
byte[] expectedBytes = new byte[] { 0x00, 0xFF }; // Not valid UTF-8
CloudQueueMessage message = new CloudQueueMessage(expectedBytes);
// Act
await host.CallAsync(methodInfo, new { message = message });
// Assert
Assert.Equal(QueueTriggerBindingDataProgram.Bytes, expectedBytes);
}
}
finally
{
QueueTriggerBindingDataProgram.QueueTrigger = null;
}
}
[Fact]
[Trait("Category", "secretsrequired")]
public async Task IndexingExceptions_CanBeHandledByLogger()
@ -632,7 +458,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
var builder = new HostBuilder()
.ConfigureDefaultTestHost<BindingErrorsProgram>(b =>
{
b.AddAzureStorage();
b.AddAzureStorageBlobs();
b.AddAzureStorageQueues();
})
.ConfigureLogging(logging =>
{
@ -649,27 +476,21 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
Assert.True(fex.Handled);
Assert.Equal("BindingErrorsProgram.Invalid", fex.MethodName);
// verify that the binding error was logged
Assert.Equal(11, errorLogger.GetLogMessages().Count);
// Skip validating the initial 'Starting JobHost' message and the OptionsFormatters
LogMessage logMessage = errorLogger.GetLogMessages()[7];
Assert.Equal("Error indexing method 'BindingErrorsProgram.Invalid'", logMessage.FormattedMessage);
Assert.Same(fex, logMessage.Exception);
Assert.Equal("Invalid container name: invalid$=+1", logMessage.Exception.InnerException.Message);
logMessage = errorLogger.GetLogMessages()[8];
Assert.Equal("Function 'BindingErrorsProgram.Invalid' failed indexing and will be disabled.", logMessage.FormattedMessage);
// verify that the binding error was logged
var bindingErrorLog = errorLogger.GetLogMessages().Single(l => l.FormattedMessage.Equals("Error indexing method 'BindingErrorsProgram.Invalid'"));
Assert.NotNull(bindingErrorLog);
Assert.Same(fex, bindingErrorLog.Exception);
var disabledLog = errorLogger.GetLogMessages().Single(l => l.FormattedMessage.Equals("Function 'BindingErrorsProgram.Invalid' failed indexing and will be disabled."));
Assert.NotNull(disabledLog);
// verify that the valid function was still indexed
logMessage = errorLogger.GetLogMessages()[9];
Assert.True(logMessage.FormattedMessage.Contains("Found the following functions"));
Assert.True(logMessage.FormattedMessage.Contains("BindingErrorsProgram.Valid"));
Assert.Contains(errorLogger.GetLogMessages(), p => p.FormattedMessage.Contains("Found the following functions") && p.FormattedMessage.Contains("BindingErrorsProgram.Valid"));
// verify that the job host was started successfully
logMessage = errorLogger.GetLogMessages()[10];
Assert.Equal("Job host started", logMessage.FormattedMessage);
Assert.Contains(errorLogger.GetLogMessages(), p => p.FormattedMessage.Equals("Job host started"));
await host.StopAsync();
}
@ -744,32 +565,6 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
}
}
private class BlobTriggerBindingDataProgram
{
public static string BlobTrigger { get; set; }
public static void OnBlob([BlobTrigger("ignore/{name}")] ICloudBlob blob, string blobTrigger)
{
BlobTrigger = blobTrigger;
}
}
private class QueueTriggerBindingDataProgram
{
public static string QueueTrigger { get; set; }
public static byte[] Bytes { get; set; }
public static void OnQueue([QueueTrigger("ignore")] CloudQueueMessage message, string queueTrigger)
{
QueueTrigger = queueTrigger;
}
public static void ProcessQueueAsBytes([QueueTrigger("ignore")] byte[] message)
{
Bytes = message;
}
}
private class BindingErrorsProgram
{
// Invalid function

Просмотреть файл

@ -1,67 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Protocols
{
public class FunctionStartedMessageExtensionsTests
{
[Fact]
public void FormatReason_QueueTriggeredFunction_ReturnsExpectedReason()
{
FunctionStartedMessage message = new FunctionStartedMessage();
QueueTriggerParameterDescriptor triggerParameterDescriptor = new QueueTriggerParameterDescriptor
{
Name = "paramName",
QueueName = "testqueue"
};
FunctionDescriptor function = new FunctionDescriptor
{
Parameters = new ParameterDescriptor[] { triggerParameterDescriptor },
TriggerParameterDescriptor = triggerParameterDescriptor
};
message.Function = function;
message.Reason = ExecutionReason.AutomaticTrigger;
string result = message.FormatReason();
Assert.Equal("New queue message detected on 'testqueue'.", result);
}
[Fact]
public void FormatReason_BlobTriggeredFunction_ReturnsExpectedReason()
{
FunctionStartedMessage message = new FunctionStartedMessage();
message.Reason = ExecutionReason.AutomaticTrigger;
BlobTriggerParameterDescriptor triggerParameterDescriptor = new BlobTriggerParameterDescriptor
{
Name = "paramName"
};
FunctionDescriptor function = new FunctionDescriptor()
{
Parameters = new ParameterDescriptor[] { triggerParameterDescriptor },
TriggerParameterDescriptor = triggerParameterDescriptor
};
message.Function = function;
message.Arguments = new Dictionary<string, string>() { { "paramName", "blob/path" } };
string result = message.FormatReason();
Assert.Equal("New blob detected: blob/path", result);
}
[Fact]
public void FormatReason_ReasonDetailsAlreadySet_ReturnsExpectedReason()
{
FunctionStartedMessage message = new FunctionStartedMessage();
message.ReasonDetails = "The trigger fired!";
string result = message.FormatReason();
Assert.Equal("The trigger fired!", result);
}
}
}

Просмотреть файл

@ -1,32 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Threading;
using WebJobs.Host.Storage.Logging;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
{
internal static class UpdateOutputLogCommandExtensions
{
public static bool TryExecute(this UpdateOutputLogCommand command)
{
if (command == null)
{
throw new ArgumentNullException("command");
}
return command.TryExecuteAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public static void SaveAndClose(this UpdateOutputLogCommand command)
{
if (command == null)
{
throw new ArgumentNullException("command");
}
command.SaveAndCloseAsync(null, CancellationToken.None).GetAwaiter().GetResult();
}
}
}

Просмотреть файл

@ -1,100 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.Storage.Blob;
using Moq;
using WebJobs.Host.Storage.Logging;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
{
public class UpdateOutputLogCommandTests
{
private CloudBlockBlob GetTestBlob()
{
// Need a fake blob that we can read from, see UpdateOutputLogCommand.ReadBlobAsync
var account = new FakeStorage.FakeAccount();
var blobClient = account.CreateCloudBlobClient();
var blob = blobClient.GetContainerReference("container").GetBlockBlobReference("blob");
return blob;
}
[Fact]
public void TestIncrementalWriter()
{
string content = null;
Func<string, CancellationToken, Task> fp = (x, _) =>
{
content = x;
return Task.FromResult(0);
};
var blob = GetTestBlob();
UpdateOutputLogCommand writer = UpdateOutputLogCommand.Create(
blob, fp);
var tw = writer.Output;
tw.Write("1");
// Ensure content not yet written
Assert.Equal(null, content);
writer.TryExecute();
Assert.Equal("1", content);
tw.Write("2");
writer.TryExecute();
Assert.Equal("12", content);
tw.Write("3");
writer.SaveAndClose();
Assert.Equal("123", content);
}
[Fact]
public async Task TestMultipleThreads()
{
// This validates a bug where flushing from one thread while writing from another
// would cause an exception.
var blob = GetTestBlob();
string content = null;
Func<string, CancellationToken, Task> fp = (x, _) => { content = x; return Task.FromResult(0); };
UpdateOutputLogCommand writer = UpdateOutputLogCommand.Create(blob, fp);
var tw = writer.Output;
bool writeDone = false;
// Start a Task to flush
Task flushTask = Task.Run(() =>
{
while (!writeDone)
{
writer.TryExecute();
}
});
// Start a Task to write
Task writeTask = Task.Run(() =>
{
for (int i = 0; i < 10000000; i++)
{
tw.WriteLine(string.Empty);
}
writeDone = true;
});
await flushTask;
await writeTask;
}
}
}

Просмотреть файл

@ -7,9 +7,11 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Specialized;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.Configuration;
@ -91,7 +93,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
var host = CreateHost();
string connectionString = GetStorageConnectionString(host);
var containerClient = GetHostingBlobContainerClient(host);
string hostId = GetHostId(host);
using (host)
@ -104,7 +106,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
await host.StopAsync();
}
await ClearLeaseBlob(connectionString, hostId);
await ClearLeaseBlob(containerClient, hostId);
}
[Fact]
@ -112,13 +114,13 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
var host = CreateHost();
string connectionString = GetStorageConnectionString(host);
var containerClient = GetHostingBlobContainerClient(host);
string hostId = GetHostId(host);
ICloudBlob blob = await GetLockBlobAsync(connectionString, hostId);
BlobClient blobClient = await GetLockBlobAsync(containerClient, hostId);
// Acquire a lease on the host lock blob
string leaseId = await blob.AcquireLeaseAsync(TimeSpan.FromMinutes(1));
string leaseId = (await blobClient.GetBlobLeaseClient().AcquireAsync(TimeSpan.FromMinutes(1))).Value.LeaseId;
using (host)
{
@ -130,7 +132,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
Assert.False(primaryState.IsPrimary);
// Now release it, and we should reclaim it.
await blob.ReleaseLeaseAsync(new AccessCondition { LeaseId = leaseId });
await blobClient.GetBlobLeaseClient(leaseId).ReleaseAsync();
await TestHelpers.Await(() => primaryState.IsPrimary,
userMessageCallback: () => $"{nameof(IPrimaryHostStateProvider.IsPrimary)} was not correctly set to 'true' when lease was acquired.");
@ -138,7 +140,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
await host.StopAsync();
}
await ClearLeaseBlob(connectionString, hostId);
await ClearLeaseBlob(containerClient, hostId);
}
[Fact]
@ -146,12 +148,12 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
var host = CreateHost();
string connectionString = GetStorageConnectionString(host);
var containerClient = GetHostingBlobContainerClient(host);
string hostId = GetHostId(host);
using (host)
{
ICloudBlob blob = await GetLockBlobAsync(connectionString, hostId);
BlobClient blobClient = await GetLockBlobAsync(containerClient, hostId);
var primaryState = host.Services.GetService<IPrimaryHostStateProvider>();
var manager = host.Services.GetServices<IHostedService>().OfType<PrimaryHostCoordinator>().Single();
var lockManager = host.Services.GetService<IDistributedLockManager>();
@ -165,7 +167,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
// Release the manager's lease and acquire one with a different id
await lockManager.ReleaseLockAsync(manager.LockHandle, CancellationToken.None);
tempLeaseId = await blob.AcquireLeaseAsync(TimeSpan.FromSeconds(30), Guid.NewGuid().ToString());
tempLeaseId = (await blobClient.GetBlobLeaseClient(Guid.NewGuid().ToString()).AcquireAsync(TimeSpan.FromSeconds(30))).Value.LeaseId;
await TestHelpers.Await(() => !primaryState.IsPrimary,
userMessageCallback: () => $"{nameof(IPrimaryHostStateProvider.IsPrimary)} was not correctly set to 'false' when lease lost.");
@ -174,14 +176,14 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
if (tempLeaseId != null)
{
await blob.ReleaseLeaseAsync(new AccessCondition { LeaseId = tempLeaseId });
await blobClient.GetBlobLeaseClient(tempLeaseId).ReleaseAsync();
}
}
await host.StopAsync();
}
await ClearLeaseBlob(connectionString, hostId);
await ClearLeaseBlob(containerClient, hostId);
}
[Fact]
@ -189,7 +191,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
var host = CreateHost();
string connectionString = GetStorageConnectionString(host);
var containerClient = GetHostingBlobContainerClient(host);
string hostId = GetHostId(host);
var primaryHostCoordinator = host.Services.GetServices<IHostedService>().OfType<PrimaryHostCoordinator>().Single();
@ -206,23 +208,23 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
// Container disposal is a fire-and-forget so this service disposal could be delayed. This will force it.
primaryHostCoordinator.Dispose();
ICloudBlob blob = await GetLockBlobAsync(connectionString, hostId);
BlobClient blobClient = await GetLockBlobAsync(containerClient, hostId);
string leaseId = null;
try
{
// Acquire a lease on the host lock blob
leaseId = await blob.AcquireLeaseAsync(TimeSpan.FromSeconds(15));
leaseId = (await blobClient.GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15))).Value.LeaseId;
await blob.ReleaseLeaseAsync(new AccessCondition { LeaseId = leaseId });
await blobClient.GetBlobLeaseClient(leaseId).ReleaseAsync();
}
catch (StorageException exc) when (exc.RequestInformation.HttpStatusCode == 409)
catch (RequestFailedException exc) when (exc.Status == 409)
{
}
Assert.False(string.IsNullOrEmpty(leaseId), "Failed to acquire a blob lease. The lease was not properly released.");
await ClearLeaseBlob(connectionString, hostId);
await ClearLeaseBlob(containerClient, hostId);
}
[Fact]
@ -238,7 +240,6 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
s.AddSingleton<IDistributedLockManager>(_ => blobMock.Object);
});
string connectionString = GetStorageConnectionString(host);
string hostId = GetHostId(host);
string instanceId = Microsoft.Azure.WebJobs.Utility.GetInstanceId();
@ -268,7 +269,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
.Returns(() => Task.FromResult<IDistributedLock>(new FakeLock()));
blobMock.Setup(b => b.RenewAsync(It.IsAny<IDistributedLock>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromException<bool>(new StorageException(new RequestResult { HttpStatusCode = 409 }, "test", null)))
.Returns(() => Task.FromException<bool>(new RequestFailedException(409, "test")))
.Callback(() => renewResetEvent.Set());
var host = CreateHost(s =>
@ -309,13 +310,13 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
s.AddSingleton<IHostIdProvider>(_ => new FixedHostIdProvider(hostId1));
});
Assert.True(host1.Services.GetRequiredService<IAzureBlobStorageProvider>().TryCreateHostingBlobContainerClient(out BlobContainerClient containerClient1));
var host2 = CreateHost(s =>
{
s.AddSingleton<IHostIdProvider>(_ => new FixedHostIdProvider(hostId2));
});
string host1ConnectionString = GetStorageConnectionString(host1);
string host2ConnectionString = GetStorageConnectionString(host2);
Assert.True(host2.Services.GetRequiredService<IAzureBlobStorageProvider>().TryCreateHostingBlobContainerClient(out BlobContainerClient containerClient2));
using (host1)
using (host2)
@ -335,41 +336,36 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
await host2.StopAsync();
}
await Task.WhenAll(ClearLeaseBlob(host1ConnectionString, hostId1), ClearLeaseBlob(host2ConnectionString, hostId2));
await Task.WhenAll(ClearLeaseBlob(containerClient1, hostId1), ClearLeaseBlob(containerClient2, hostId2));
}
private static async Task<ICloudBlob> GetLockBlobAsync(string accountConnectionString, string hostId)
private static async Task<BlobClient> GetLockBlobAsync(BlobContainerClient containerClient, string hostId)
{
CloudStorageAccount account = CloudStorageAccount.Parse(accountConnectionString);
CloudBlobClient client = account.CreateCloudBlobClient();
await containerClient.CreateIfNotExistsAsync();
var container = client.GetContainerReference(HostContainerNames.Hosts);
await container.CreateIfNotExistsAsync();
// the StorageDistributedLockManager puts things under the /locks path by default
CloudBlockBlob blob = container.GetBlockBlobReference("locks/" + PrimaryHostCoordinator.GetBlobName(hostId));
if (!await blob.ExistsAsync())
// The BlobLeaseDistributedLockManager puts things under the /locks path by default
var blobClient = containerClient.GetBlobClient("locks/" + PrimaryHostCoordinator.GetBlobName(hostId));
if (!await blobClient.ExistsAsync())
{
await blob.UploadFromStreamAsync(new MemoryStream());
await blobClient.UploadTextAsync("", overwrite: true);
}
return blob;
return blobClient;
}
private async Task ClearLeaseBlob(string connectionString, string hostId)
private async Task ClearLeaseBlob(BlobContainerClient containerClient, string hostId)
{
ICloudBlob blob = await GetLockBlobAsync(connectionString, hostId);
BlobClient blobClient = await GetLockBlobAsync(containerClient, hostId);
try
{
await blob.BreakLeaseAsync(TimeSpan.Zero);
await blobClient.GetBlobLeaseClient().BreakAsync(TimeSpan.Zero);
}
catch
{
}
await blob.DeleteIfExistsAsync();
await blobClient.DeleteIfExistsAsync();
}
private class FakeLock : IDistributedLock
@ -394,16 +390,17 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
}
}
private static string GetStorageConnectionString(IHost host)
{
return host.Services.GetService<IConfiguration>().GetWebJobsConnectionString("Storage");
}
public static string GetHostId(IHost host)
{
return host.Services.GetService<IHostIdProvider>().GetHostIdAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public static BlobContainerClient GetHostingBlobContainerClient(IHost host)
{
host.Services.GetRequiredService<IAzureBlobStorageProvider>().TryCreateHostingBlobContainerClient(out BlobContainerClient containerClient);
return containerClient;
}
public class Program
{
[NoAutomaticTrigger]

Просмотреть файл

@ -21,12 +21,10 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
var expected = new[]
{
"CloudBlobContainerDistributedLockManager",
"DistributedLockManagerContainerProvider",
"JobHostInternalStorageOptions",
"RuntimeStorageWebJobsBuilderExtensions",
"StorageBaseDistributedLockManager",
"StorageServiceCollectionExtensions"
"StorageServiceCollectionExtensions",
"IAzureBlobStorageProvider"
};
TestHelpers.AssertPublicTypes(expected, assembly);

Просмотреть файл

@ -12,7 +12,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
using SingletonLockHandle = Microsoft.Azure.WebJobs.Host.StorageBaseDistributedLockManager.SingletonLockHandle;
using SingletonLockHandle = Microsoft.Azure.WebJobs.Host.BlobLeaseDistributedLockManager.SingletonLockHandle;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
{

Просмотреть файл

@ -5,7 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using Moq;
using Xunit;
using SingletonLockHandle = Microsoft.Azure.WebJobs.Host.StorageBaseDistributedLockManager.SingletonLockHandle;
using SingletonLockHandle = Microsoft.Azure.WebJobs.Host.BlobLeaseDistributedLockManager.SingletonLockHandle;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
{

Просмотреть файл

@ -0,0 +1,372 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
{
public class SingletonManagerStorageIntegrationTests : IAsyncLifetime
{
private const string TestArtifactContainerPrefix = "e2e-singletonmanagertests";
private const string TestArtifactContainerName = TestArtifactContainerPrefix + "-%rnd%";
private const string TestHostId = "testhost";
private const string TestLockId = "testid";
private const string TestInstanceId = "testinstance";
private BlobLeaseDistributedLockManager _core;
private SingletonManager _singletonManager;
private SingletonOptions _singletonConfig;
private Mock<IWebJobsExceptionHandler> _mockExceptionDispatcher;
private readonly BlobContainerClient _testContainerClient;
private TestLoggerProvider _loggerProvider;
private TestNameResolver _nameResolver;
internal class TestBlobLeaseDistributedLockManager : BlobLeaseDistributedLockManager
{
// To set up testing
public BlobContainerClient ContainerClient;
public TestBlobLeaseDistributedLockManager(ILoggerFactory logger, IAzureBlobStorageProvider blobStorageProvider) : base(logger, blobStorageProvider) { }
protected override BlobContainerClient GetContainerClient(string accountName)
{
if (!string.IsNullOrWhiteSpace(accountName))
{
throw new InvalidOperationException("This test does not support multiple accounts.");
}
return ContainerClient;
}
// Testing the base method to verify we can use multiple storage accounts
internal BlobContainerClient BaseGetContainerClient(string accountName)
{
return base.GetContainerClient(accountName);
}
}
// All dependencies of SingletonManager is mocked except Blob storage
public SingletonManagerStorageIntegrationTests()
{
ILoggerFactory loggerFactory = new LoggerFactory();
_loggerProvider = new TestLoggerProvider();
loggerFactory.AddProvider(_loggerProvider);
var testBlobLeaseDistributedLockManager = new TestBlobLeaseDistributedLockManager(loggerFactory, null);
var blobServiceClient = TestHelpers.GetTestBlobServiceClient();
_testContainerClient = blobServiceClient.GetBlobContainerClient(new RandomNameResolver().ResolveInString(TestArtifactContainerName));
testBlobLeaseDistributedLockManager.ContainerClient = _testContainerClient;
_core = testBlobLeaseDistributedLockManager;
_mockExceptionDispatcher = new Mock<IWebJobsExceptionHandler>(MockBehavior.Strict);
_singletonConfig = new SingletonOptions();
// use reflection to bypass the normal validations (so tests can run fast)
TestHelpers.SetField(_singletonConfig, "_lockAcquisitionPollingInterval", TimeSpan.FromMilliseconds(500));
TestHelpers.SetField(_singletonConfig, "_lockPeriod", TimeSpan.FromSeconds(15));
_singletonConfig.LockAcquisitionTimeout = TimeSpan.FromSeconds(16);
_nameResolver = new TestNameResolver();
_singletonManager = new SingletonManager(_core, new OptionsWrapper<SingletonOptions>(_singletonConfig), _mockExceptionDispatcher.Object, loggerFactory, new FixedHostIdProvider(TestHostId), _nameResolver);
_singletonManager.MinimumLeaseRenewalInterval = TimeSpan.FromMilliseconds(250);
}
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
await _testContainerClient.DeleteIfExistsAsync();
}
[Fact]
public async Task TryLockAsync_CreatesBlob_WhenItDoesNotExist()
{
CancellationToken cancellationToken = new CancellationToken();
// Create the container as this is only testing that the blob is created
await _testContainerClient.CreateIfNotExistsAsync();
await foreach (var _ in _testContainerClient.GetBlobsAsync())
{
Assert.False(true, "Blob already exists. This shouldn't happen.");
}
SingletonAttribute attribute = new SingletonAttribute();
RenewableLockHandle lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
Assert.Equal(TestLockId, innerHandle.LockId);
// Shouldn't be able to lease this blob
var exception = await Assert.ThrowsAnyAsync<RequestFailedException>(async () => await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15)));
Assert.Equal(409, exception.Status); // Conflict
// Checks Instance Id in metadata
var metadata = (await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetPropertiesAsync()).Value.Metadata;
Assert.Single(metadata.Keys);
Assert.Equal(TestInstanceId, metadata[BlobLeaseDistributedLockManager.FunctionInstanceMetadataKey]);
Assert.NotNull(lockHandle);
Assert.NotNull(lockHandle.LeaseRenewalTimer);
// Verify the leaseId in the innerHandle is the actual active LeaseId. Otherwise, ChangeAsync will fail.
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient(innerHandle.LeaseId).ChangeAsync(Guid.NewGuid().ToString());
}
[Fact]
public async Task TryLockAsync_CreatesBlobContainer_WhenItDoesNotExist()
{
CancellationToken cancellationToken = new CancellationToken();
Assert.False(_testContainerClient.Exists());
SingletonAttribute attribute = new SingletonAttribute();
RenewableLockHandle lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
Assert.Equal(TestLockId, innerHandle.LockId);
// Shouldn't be able to lease this blob
var exception = await Assert.ThrowsAnyAsync<RequestFailedException>(async () => await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15)));
Assert.Equal(409, exception.Status); // Conflict
// Checks Instance Id in metadata
var metadata = (await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetPropertiesAsync()).Value.Metadata;
Assert.Single(metadata.Keys);
Assert.Equal(TestInstanceId, metadata[BlobLeaseDistributedLockManager.FunctionInstanceMetadataKey]);
Assert.NotNull(lockHandle);
Assert.NotNull(lockHandle.LeaseRenewalTimer);
// Verify the leaseId in the innerHandle is the actual active LeaseId. Otherwise, ChangeAsync will fail.
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient(innerHandle.LeaseId).ChangeAsync(Guid.NewGuid().ToString());
}
[Fact]
public async Task TryLockAsync_CreatesBlobLease_WithAutoRenewal()
{
CancellationToken cancellationToken = new CancellationToken();
SingletonAttribute attribute = new SingletonAttribute();
var lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
Assert.Equal(TestLockId, innerHandle.LockId);
// Shouldn't be able to lease this blob yet
var exception = await Assert.ThrowsAnyAsync<RequestFailedException>(async () => await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15)));
Assert.Equal(409, exception.Status); // Conflict
// Wait for enough time that we expect some lease renewals to occur
var duration = _singletonConfig.LockPeriod.TotalMilliseconds + TimeSpan.FromSeconds(1).TotalMilliseconds;
await Task.Delay((int)duration);
// Still shouldn't be able to lease this blob yet
exception = await Assert.ThrowsAnyAsync<RequestFailedException>(async () => await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15)));
Assert.Equal(409, exception.Status); // Conflict
// Now release the lock and verify no more renewals
await _singletonManager.ReleaseLockAsync(lockHandle, cancellationToken);
// Should be able to lease this blob now in the test context
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15));
// verify the logger
TestLogger logger = _loggerProvider.CreatedLoggers.Single() as TestLogger;
Assert.Equal(LogCategories.Singleton, logger.Category);
var messages = logger.GetLogMessages();
Assert.Equal(2, messages.Count);
Assert.NotNull(messages.Single(m => m.Level == Microsoft.Extensions.Logging.LogLevel.Debug && m.FormattedMessage == "Singleton lock acquired (testid)"));
Assert.NotNull(messages.Single(m => m.Level == Microsoft.Extensions.Logging.LogLevel.Debug && m.FormattedMessage == "Singleton lock released (testid)"));
Assert.NotNull(lockHandle);
Assert.NotNull(lockHandle.LeaseRenewalTimer);
}
[Fact]
public async Task TryLockAsync_WithContention_PollsForLease()
{
CancellationToken cancellationToken = new CancellationToken();
// Setup test so that the blob exists
await _testContainerClient.CreateIfNotExistsAsync();
if (!_testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).Exists())
{
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).UploadTextAsync("", overwrite: true);
}
// Lease this blob in the test context to test polling behavior of the SingletonManager
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15));
// SingletonManager will be trying to poll for the lock and eventually get it after 15 seconds
SingletonAttribute attribute = new SingletonAttribute();
var lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
Assert.Equal(TestLockId, innerHandle.LockId);
// Shouldn't be able to lease this blob since the SingletonManager has the lease
var exception = await Assert.ThrowsAnyAsync<RequestFailedException>(async () => await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15)));
Assert.Equal(409, exception.Status); // Conflict
// Checks Instance Id in metadata
var metadata = (await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetPropertiesAsync()).Value.Metadata;
Assert.Single(metadata.Keys);
Assert.Equal(TestInstanceId, metadata[BlobLeaseDistributedLockManager.FunctionInstanceMetadataKey]);
Assert.NotNull(lockHandle);
Assert.NotNull(lockHandle.LeaseRenewalTimer);
}
[Fact]
public async Task TryLockAsync_WithContention_NoRetry_DoesNotPollForLease()
{
var task = Task.Run(async () =>
{
CancellationToken cancellationToken = new CancellationToken();
// Setup test so that the blob exists
await _testContainerClient.CreateIfNotExistsAsync();
if (!_testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).Exists())
{
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).UploadTextAsync("", overwrite: true);
}
// Lease this blob in the test context
var lease = await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15));
// SingletonManager will not poll for the lease; test should end quickly
SingletonAttribute attribute = new SingletonAttribute();
var lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken, retry: false);
Assert.Null(lockHandle);
});
if (task.Wait(1500))
{
await task;
}
else
{
throw new TimeoutException("Test ran too long. The SingletonManager is likely retrying to get lease");
}
}
[Fact]
public async Task LockAsync_WithContention_AcquisitionTimeoutExpires_Throws()
{
CancellationToken cancellationToken = new CancellationToken();
// Setup test so that the blob exists
await _testContainerClient.CreateIfNotExistsAsync();
if (!_testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).Exists())
{
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).UploadTextAsync("", overwrite: true);
}
// Lease this blob in the test context
var lease = await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15));
// Failure to acquire lock is expected with a custom error message
SingletonAttribute attribute = new SingletonAttribute();
attribute.LockAcquisitionTimeout = 1;
TimeoutException exception = await Assert.ThrowsAsync<TimeoutException>(async () => await _singletonManager.LockAsync(TestLockId, TestInstanceId, attribute, cancellationToken));
Assert.Equal("Unable to acquire singleton lock blob lease for blob 'testid' (timeout of 0:00:01 exceeded).", exception.Message);
}
[Fact]
public async Task ReleaseLockAsync_StopsRenewalTimerAndReleasesLease()
{
CancellationToken cancellationToken = new CancellationToken();
SingletonAttribute attribute = new SingletonAttribute();
var lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
await _singletonManager.ReleaseLockAsync(lockHandle, cancellationToken);
// Timer should've been stopped
await Assert.ThrowsAnyAsync<InvalidOperationException>(async () => await lockHandle.LeaseRenewalTimer.StopAsync(cancellationToken));
// Verify lease has been released by SingletonManager
var lease = await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15));
}
[Fact]
public async Task GetLockOwnerAsync_LeaseLocked_ReturnsOwner()
{
CancellationToken cancellationToken = new CancellationToken();
Assert.False(_testContainerClient.Exists());
// No owner yet and no blob/container
SingletonAttribute attribute = new SingletonAttribute();
string lockOwner = await _singletonManager.GetLockOwnerAsync(attribute, TestLockId, cancellationToken);
Assert.Equal(null, lockOwner);
await _testContainerClient.CreateIfNotExistsAsync();
if (!_testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).Exists())
{
await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).UploadTextAsync("", overwrite: true);
}
var properties = (await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetPropertiesAsync()).Value;
Assert.Equal(LeaseState.Available, properties.LeaseState);
Assert.Equal(LeaseStatus.Unlocked, properties.LeaseStatus);
// No owner but blob/container exists
lockOwner = await _singletonManager.GetLockOwnerAsync(attribute, TestLockId, cancellationToken);
Assert.Equal(null, lockOwner);
// Get a lease
var lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
// There should be an owner
lockOwner = await _singletonManager.GetLockOwnerAsync(attribute, TestLockId, cancellationToken);
Assert.Equal(TestInstanceId, lockOwner);
// Shouldn't be able to lease this blob
await Assert.ThrowsAnyAsync<Exception>(async () => await _testContainerClient.GetBlobClient(string.Format("locks/{0}", TestLockId)).GetBlobLeaseClient().AcquireAsync(TimeSpan.FromSeconds(15)));
}
private class TestNameResolver : INameResolver
{
public TestNameResolver()
{
Names = new Dictionary<string, string>();
}
public Dictionary<string, string> Names { get; private set; }
public string Resolve(string name)
{
if (Names.TryGetValue(name, out string value))
{
return value;
}
throw new NotSupportedException(string.Format("Cannot resolve name: '{0}'", name));
}
}
}
}

Просмотреть файл

@ -3,16 +3,19 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using FakeStorage;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Azure.WebJobs.Logging;
@ -21,7 +24,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
using SingletonLockHandle = Microsoft.Azure.WebJobs.Host.StorageBaseDistributedLockManager.SingletonLockHandle;
using SingletonLockHandle = Microsoft.Azure.WebJobs.Host.BlobLeaseDistributedLockManager.SingletonLockHandle;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
{
@ -54,46 +57,118 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
private readonly IConfiguration _configuration = new ConfigurationBuilder().Build();
private StorageBaseDistributedLockManager _core;
private BlobLeaseDistributedLockManager _core;
private SingletonManager _singletonManager;
private SingletonOptions _singletonConfig;
private CloudBlobDirectory _mockBlobDirectory;
private CloudBlobDirectory _mockSecondaryBlobDirectory;
internal FakeAccount _account1 = new FakeAccount();
internal FakeAccount _account2 = new FakeAccount();
private MockAzureBlobStorageAccount _account = new MockAzureBlobStorageAccount(ConnectionStringNames.Storage);
private Mock<IWebJobsExceptionHandler> _mockExceptionDispatcher;
private Mock<CloudBlockBlob> _mockStorageBlob;
private TestLoggerProvider _loggerProvider;
private readonly Dictionary<string, string> _mockBlobMetadata;
private TestNameResolver _nameResolver;
private class FakeLeaseProvider : StorageBaseDistributedLockManager
private class FakeLeaseProvider : BlobLeaseDistributedLockManager
{
internal FakeAccount _account1 = new FakeAccount();
internal FakeAccount _account2 = new FakeAccount();
private readonly MockAzureBlobStorageAccount _account;
public FakeLeaseProvider(ILoggerFactory logger) : base(logger) { }
protected override CloudBlobContainer GetContainer(string accountName)
public FakeLeaseProvider(ILoggerFactory logger,
IAzureBlobStorageProvider azureBlobStorageProvider,
MockAzureBlobStorageAccount account)
: base(logger, azureBlobStorageProvider)
{
FakeAccount account;
if (string.IsNullOrEmpty(accountName) || accountName == ConnectionStringNames.Storage)
_account = account;
}
protected override BlobContainerClient GetContainerClient(string accountName)
{
return _account.BlobContainerClient.Object;
}
protected override BlobLeaseClient GetBlobLeaseClient(BlobClient blobClient, string proposedLeaseId)
{
return _account.BlobLeaseClient.Object;
}
protected override BlobContainerClient GetParentBlobContainerClient(BlobClient blobClient)
{
return _account.BlobContainerClient.Object;
}
// Testing the base method to verify we can use multiple storage accounts
internal BlobContainerClient BaseGetContainerClient(string accountName)
{
return base.GetContainerClient(accountName);
}
}
/// <summary>
/// This is the mocking layer for Azure Blob storage objects. This private class extensively relies on reflection to construct and edit
/// classes in <see cref="Azure.Storage.Blobs.Models"/> since they have internal constructors and no setters.
/// Relationship is BlobContainerClient <-- has --> BlobClient <-- has --> BlobLeaseClient.
/// The tests in <see cref="SingletonManagerTests"/> use this class as a utility.
/// </summary>
private class MockAzureBlobStorageAccount
{
public MockAzureBlobStorageAccount(string accountName)
{
AccountName = accountName;
BlobClient = new Mock<BlobClient>(MockBehavior.Strict);
BlobContainerClient = new Mock<BlobContainerClient>(MockBehavior.Strict);
BlobContainerClient.Setup(b => b.GetBlobClient(It.IsAny<string>())).Returns(BlobClient.Object);
BlobLeaseClient = new Mock<BlobLeaseClient>(MockBehavior.Strict);
}
public string AccountName { get; private set; }
public Mock<BlobContainerClient> BlobContainerClient { get; private set; }
public Mock<BlobClient> BlobClient { get; private set; }
public Mock<BlobLeaseClient> BlobLeaseClient { get; private set; }
public IDictionary<string, string> LastSetMetadata { get; set; }
public Response<ReleasedObjectInfo> CreateReleasedObjectInfoResponse()
{
var blobInfo = CreateInternalObjectHelper<BlobInfo>();
ConstructorInfo c = typeof(ReleasedObjectInfo).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[1] { typeof(BlobInfo) }, null);
var releasedObjectInfo = (ReleasedObjectInfo)c.Invoke(BindingFlags.NonPublic, null, new object[1] { blobInfo }, null);
return Response.FromValue(releasedObjectInfo, default);
}
public Response<T> CreateInternalAzureBlobResponse<T>(Dictionary<string, object> fields = null)
{
T obj = CreateInternalObjectHelper<T>();
if (fields != null)
{
account = _account1;
var allFields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var field in fields)
{
var f = allFields.Where(f => f.Name.Contains(field.Key)).SingleOrDefault();
f.SetValue(obj, field.Value);
}
}
else if (accountName == Secondary)
return Response.FromValue(obj, default);
}
private static T CreateInternalObjectHelper<T>()
{
ConstructorInfo c = typeof(T).GetConstructor(new Type[0]) ?? typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
return (T)c.Invoke(BindingFlags.NonPublic, null, null, null);
}
public static Dictionary<string, object> AddObjectField(Dictionary<string, object> fields, string fieldName, object fieldValue)
{
fields = fields ?? new Dictionary<string, object>();
if (!fields.ContainsKey(fieldName))
{
account = _account2;
fields[fieldName] = fieldValue;
}
else
{
throw new InvalidOperationException("Unknown account: " + accountName);
}
var container = account.CreateCloudBlobClient().GetContainerReference("azure-webjobs-hosts");
return container;
return fields;
}
}
@ -105,20 +180,10 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
var logger = loggerFactory?.CreateLogger(LogCategories.Singleton);
var leaseProvider = new FakeLeaseProvider(loggerFactory);
_mockBlobDirectory = leaseProvider._account1.CreateCloudBlobClient().GetContainerReference(HostContainerNames.Hosts).GetDirectoryReference(HostDirectoryNames.SingletonLocks);
_mockSecondaryBlobDirectory = leaseProvider._account2.CreateCloudBlobClient().GetContainerReference(HostContainerNames.Hosts).GetDirectoryReference(HostDirectoryNames.SingletonLocks);
var leaseProvider = new FakeLeaseProvider(loggerFactory, null, _account);
_mockExceptionDispatcher = new Mock<IWebJobsExceptionHandler>(MockBehavior.Strict);
_mockStorageBlob = new Mock<CloudBlockBlob>(MockBehavior.Strict,
new Uri("https://fakeaccount.blob.core.windows.net/" + HostContainerNames.Hosts + "/" + HostDirectoryNames.SingletonLocks + "/" + TestLockId));
_mockBlobMetadata = new Dictionary<string, string>();
leaseProvider._account1.SetBlob(HostContainerNames.Hosts, HostDirectoryNames.SingletonLocks + "/" + TestLockId, _mockStorageBlob.Object);
_singletonConfig = new SingletonOptions();
// use reflection to bypass the normal validations (so tests can run fast)
@ -127,90 +192,136 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
_singletonConfig.LockAcquisitionTimeout = TimeSpan.FromMilliseconds(200);
_nameResolver = new TestNameResolver();
_core = leaseProvider;
_singletonManager = new SingletonManager(_core, new OptionsWrapper<SingletonOptions>(_singletonConfig), _mockExceptionDispatcher.Object, loggerFactory, new FixedHostIdProvider(TestHostId), _nameResolver);
_singletonManager.MinimumLeaseRenewalInterval = TimeSpan.FromMilliseconds(250);
}
[Fact]
public void GetLockDirectory_HandlesMultipleAccounts()
public void GetLockPath()
{
var directory = _core.GetLockDirectory(ConnectionStringNames.Storage);
Assert.Equal(_mockBlobDirectory.Uri, directory.Uri);
var path = _core.GetLockPath(ConnectionStringNames.Storage);
var expectedPath = string.Format("{0}/{1}", HostDirectoryNames.SingletonLocks, ConnectionStringNames.Storage);
Assert.Equal(expectedPath, path);
}
directory = _core.GetLockDirectory(null);
Assert.Equal(_mockBlobDirectory.Uri, directory.Uri);
// This test should not make any calls to storage. We are just creating clients.
[Fact]
public void GetContainerClient_SupportsMultipleAccounts()
{
var blobStorageProvider = TestHelpers.GetTestAzureBlobStorageProvider();
var blobLockManager = new FakeLeaseProvider(new LoggerFactory(), blobStorageProvider, null);
directory = _core.GetLockDirectory(Secondary);
Assert.Equal(_mockSecondaryBlobDirectory.Uri, directory.Uri);
// Testing primary account
blobStorageProvider.TryCreateBlobServiceClientFromConnection(ConnectionStringNames.Storage, out BlobServiceClient blobServiceClient);
var accountContainer = blobLockManager.BaseGetContainerClient(ConnectionStringNames.Storage);
Assert.Equal(blobServiceClient.AccountName, accountContainer.AccountName);
// Testing primary account by providing null accountName argument
blobStorageProvider.TryCreateBlobServiceClientFromConnection(ConnectionStringNames.Storage, out blobServiceClient);
accountContainer = blobLockManager.BaseGetContainerClient(null);
Assert.Equal(blobServiceClient.AccountName, accountContainer.AccountName);
// Testing secondary account
blobStorageProvider.TryCreateBlobServiceClientFromConnection(Secondary, out blobServiceClient);
accountContainer = blobLockManager.BaseGetContainerClient(Secondary);
Assert.Equal(blobServiceClient.AccountName, accountContainer.AccountName);
}
[Fact]
public async Task TryLockAsync_CreatesBlob_WhenItDoesNotExist()
{
CancellationToken cancellationToken = new CancellationToken();
RequestResult storageResult = new RequestResult
{
HttpStatusCode = 404
};
StorageException storageException = new StorageException(storageResult, null, null);
RequestFailedException storageException = new RequestFailedException(404, "Not Found Test Error");
var mockAccount = _account;
int count = 0;
MockAcquireLeaseAsync(null, () =>
MockAcquireLeaseAsync(mockAccount, null, () =>
{
if (count++ == 0)
{
throw storageException;
}
return TestLeaseId;
var fields = new Dictionary<string, object>()
{
{ "LeaseId", TestLeaseId },
};
return mockAccount.CreateInternalAzureBlobResponse<BlobLease>(fields);
});
_mockStorageBlob.Setup(p => p.UploadTextAsync(string.Empty, cancellationToken)).Returns(Task.FromResult(true));
//_mockStorageBlob.SetupGet(p => p.Metadata).Returns(_mockBlobMetadata);
_mockStorageBlob.Setup(p => p.SetMetadataAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, cancellationToken)).Returns(Task.FromResult(true));
_mockStorageBlob.Setup(p => p.ReleaseLeaseAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, cancellationToken)).Returns(Task.FromResult(true));
// First call to create the blob should fail because container does not exist
int uploadAsyncCallCount = 0;
mockAccount.BlobContainerClient.Setup(p => p.CreateIfNotExistsAsync(It.IsAny<PublicAccessType>(), It.IsAny<IDictionary<string, string>>(), It.IsAny<BlobContainerEncryptionScopeOptions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobContainerInfo>()));
mockAccount.BlobClient.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
if (uploadAsyncCallCount++ == 0)
{
throw storageException;
}
return Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobContentInfo>());
});
mockAccount.BlobClient.Setup(p => p.GetPropertiesAsync(It.IsAny<BlobRequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobProperties>()));
mockAccount.BlobClient.Setup(p => p.SetMetadataAsync(It.IsAny<IDictionary<string, string>>(), It.Is<BlobRequestConditions>(q => q.LeaseId == TestLeaseId), It.IsAny<CancellationToken>()))
.Callback<IDictionary<string, string>, BlobRequestConditions, CancellationToken>((metadata, conditions, cancellationToken) => mockAccount.LastSetMetadata = metadata)
.Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobInfo>()));
SingletonAttribute attribute = new SingletonAttribute();
RenewableLockHandle lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
Assert.Equal(_mockStorageBlob.Object, innerHandle.Blob);
Assert.Equal(mockAccount.BlobLeaseClient.Object, innerHandle.BlobLeaseClient);
Assert.Equal(TestLeaseId, innerHandle.LeaseId);
Assert.Equal(1, _mockStorageBlob.Object.Metadata.Keys.Count);
Assert.Equal(_mockStorageBlob.Object.Metadata[StorageBaseDistributedLockManager.FunctionInstanceMetadataKey], TestInstanceId);
Assert.Equal(1, mockAccount.LastSetMetadata.Keys.Count);
Assert.Equal(mockAccount.LastSetMetadata[BlobLeaseDistributedLockManager.FunctionInstanceMetadataKey], TestInstanceId);
mockAccount.BlobContainerClient.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
[Fact]
public async Task TryLockAsync_CreatesBlobLease_WithAutoRenewal()
{
CancellationToken cancellationToken = new CancellationToken();
//_mockStorageBlob.SetupGet(p => p.Metadata).Returns(_mockBlobMetadata);
MockAcquireLeaseAsync(null, () => TestLeaseId);
var mockAccount = _account;
MockAcquireLeaseAsync(mockAccount, null, () =>
{
var fields = new Dictionary<string, object>()
{
{ "LeaseId", TestLeaseId },
};
return mockAccount.CreateInternalAzureBlobResponse<BlobLease>(fields);
});
_mockStorageBlob.Setup(p => p.SetMetadataAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, cancellationToken)).Returns(Task.FromResult(true));
_mockStorageBlob.Setup(p => p.ReleaseLeaseAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, cancellationToken)).Returns(Task.FromResult(true));
mockAccount.BlobClient.Setup(p => p.GetPropertiesAsync(It.IsAny<BlobRequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobProperties>()));
mockAccount.BlobClient.Setup(p => p.SetMetadataAsync(It.IsAny<IDictionary<string, string>>(), It.Is<BlobRequestConditions>(q => q.LeaseId == TestLeaseId), It.IsAny<CancellationToken>()))
.Callback<IDictionary<string, string>, BlobRequestConditions, CancellationToken>((metadata, conditions, cancellationToken) => mockAccount.LastSetMetadata = metadata)
.Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobInfo>()));
mockAccount.BlobLeaseClient.Setup(p => p.ReleaseAsync(It.IsAny<RequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateReleasedObjectInfoResponse()));
int renewCount = 0;
_mockStorageBlob.Setup(p => p.RenewLeaseAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, It.IsAny<CancellationToken>()))
.Callback<AccessCondition, BlobRequestOptions, OperationContext, CancellationToken>(
(mockAccessCondition, mockOptions, mockContext, mockCancellationToken) =>
mockAccount.BlobLeaseClient.Setup(p => p.RenewAsync(It.IsAny<RequestConditions>(), It.IsAny<CancellationToken>()))
.Callback<RequestConditions, CancellationToken>((requestCondition, cancellationToken) =>
{
renewCount++;
}).Returns(Task.FromResult(true));
})
.Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobLease>()));
SingletonAttribute attribute = new SingletonAttribute();
var lockHandle = await _singletonManager.TryLockAsync(TestLockId, TestInstanceId, attribute, cancellationToken);
var innerHandle = lockHandle.GetInnerHandle();
Assert.Equal(_mockStorageBlob.Object, innerHandle.Blob);
Assert.Equal(mockAccount.BlobLeaseClient.Object, innerHandle.BlobLeaseClient);
Assert.Equal(TestLeaseId, innerHandle.LeaseId);
Assert.Equal(1, _mockStorageBlob.Object.Metadata.Keys.Count);
Assert.Equal(_mockStorageBlob.Object.Metadata[StorageBaseDistributedLockManager.FunctionInstanceMetadataKey], TestInstanceId);
Assert.Equal(1, mockAccount.LastSetMetadata.Keys.Count);
Assert.Equal(mockAccount.LastSetMetadata[BlobLeaseDistributedLockManager.FunctionInstanceMetadataKey], TestInstanceId);
// wait for enough time that we expect some lease renewals to occur
int duration = 2000;
@ -235,23 +346,37 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
Assert.Equal(0, renewCount);
_mockStorageBlob.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
[Fact]
public async Task TryLockAsync_WithContention_PollsForLease()
{
CancellationToken cancellationToken = new CancellationToken();
// _mockStorageBlob.SetupGet(p => p.Metadata).Returns(_mockBlobMetadata);
_mockStorageBlob.Setup(p => p.SetMetadataAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, cancellationToken)).Returns(Task.FromResult(true));
var mockAccount = _account;
mockAccount.BlobClient.Setup(p => p.GetPropertiesAsync(It.IsAny<BlobRequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobProperties>()));
mockAccount.BlobClient.Setup(p => p.SetMetadataAsync(It.IsAny<IDictionary<string, string>>(), It.Is<BlobRequestConditions>(q => q.LeaseId == TestLeaseId), It.IsAny<CancellationToken>()))
.Callback<IDictionary<string, string>, BlobRequestConditions, CancellationToken>((metadata, conditions, cancellationToken) => mockAccount.LastSetMetadata = metadata)
.Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobInfo>()));
int numRetries = 3;
int count = 0;
MockAcquireLeaseAsync(null, () =>
MockAcquireLeaseAsync(mockAccount, null, () =>
{
count++;
return count > numRetries ? TestLeaseId : null;
if (count > numRetries)
{
var fields = new Dictionary<string, object>()
{
{ "LeaseId", TestLeaseId },
};
return mockAccount.CreateInternalAzureBlobResponse<BlobLease>(fields);
}
throw new RequestFailedException(409, "Failed to get lease in test");
});
SingletonAttribute attribute = new SingletonAttribute();
@ -263,7 +388,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
Assert.Equal(numRetries, count - 1);
Assert.NotNull(lockHandle.LeaseRenewalTimer);
_mockStorageBlob.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
[Fact]
@ -272,11 +398,11 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
CancellationToken cancellationToken = new CancellationToken();
int count = 0;
MockAcquireLeaseAsync(null, () =>
var mockAccount = _account;
MockAcquireLeaseAsync(mockAccount, null, () =>
{
count++;
return null;
throw new RequestFailedException(409, "Failed to get lease in test");
});
SingletonAttribute attribute = new SingletonAttribute();
@ -285,38 +411,21 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
Assert.Null(lockHandle);
Assert.Equal(1, count);
_mockStorageBlob.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
// Helper to setup mock since the signatures are very complex
private void MockAcquireLeaseAsync(Action fpAction, Func<string> returns)
private void MockAcquireLeaseAsync(MockAzureBlobStorageAccount mockAccount, Action fpAction, Func<Response<BlobLease>> returns)
{
_mockStorageBlob.Setup(
p => p.AcquireLeaseAsync(_singletonConfig.LockPeriod, null, It.IsAny<AccessCondition>(), It.IsAny<BlobRequestOptions>(), It.IsAny<OperationContext>(), It.IsAny<CancellationToken>())
)
.Callback<TimeSpan?, string, AccessCondition, BlobRequestOptions, OperationContext, CancellationToken>(
(mockPeriod, mockLeaseId, accessCondition, blobRequest, opCtx, cancelToken) =>
mockAccount.BlobLeaseClient.Setup(p => p.AcquireAsync(_singletonConfig.LockPeriod, It.IsAny<RequestConditions>(), It.IsAny<CancellationToken>()))
.Callback<TimeSpan, RequestConditions, CancellationToken>((timeSpan, requestConditions, cancellationToken) =>
{
fpAction?.Invoke();
}).Returns(() =>
{
var retResult = returns();
return Task.FromResult<string>(retResult);
});
}
private void MockFetchAttributesAsync(Action fpAction)
{
_mockStorageBlob.Setup(
p => p.FetchAttributesAsync(It.IsAny<AccessCondition>(), It.IsAny<BlobRequestOptions>(), It.IsAny<OperationContext>(), It.IsAny<CancellationToken>())
)
.Callback<AccessCondition, BlobRequestOptions, OperationContext, CancellationToken>(
(accessCondition, blobRequest, opCtx, cancelToken) =>
{
fpAction?.Invoke();
}).Returns(() =>
{
return Task.CompletedTask;
return Task.FromResult(retResult);
});
}
@ -326,11 +435,11 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
CancellationToken cancellationToken = new CancellationToken();
int count = 0;
MockAcquireLeaseAsync(() =>
var mockAccount = _account;
MockAcquireLeaseAsync(mockAccount, () =>
{
++count;
}, () => null);
}, () => throw new RequestFailedException(409, "Failed to get lease in test"));
SingletonAttribute attribute = new SingletonAttribute();
TimeoutException exception = await Assert.ThrowsAsync<TimeoutException>(async () => await _singletonManager.LockAsync(TestLockId, TestInstanceId, attribute, cancellationToken));
@ -339,23 +448,25 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
Assert.Equal(expectedRetryCount, count - 1);
Assert.Equal("Unable to acquire singleton lock blob lease for blob 'testid' (timeout of 0:00:00.2 exceeded).", exception.Message);
_mockStorageBlob.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
[Fact]
public async Task ReleaseLockAsync_StopsRenewalTimerAndReleasesLease()
{
CancellationToken cancellationToken = new CancellationToken();
var mockAccount = _account;
Mock<ITaskSeriesTimer> mockRenewalTimer = new Mock<ITaskSeriesTimer>(MockBehavior.Strict);
mockRenewalTimer.Setup(p => p.StopAsync(cancellationToken)).Returns(Task.FromResult(true));
_mockStorageBlob.Setup(p => p.ReleaseLeaseAsync(It.Is<AccessCondition>(q => q.LeaseId == TestLeaseId), null, null, cancellationToken)).Returns(Task.FromResult(true));
mockAccount.BlobLeaseClient.Setup(p => p.ReleaseAsync(It.IsAny<RequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateReleasedObjectInfoResponse()));
var handle = new RenewableLockHandle(
new SingletonLockHandle
{
Blob = _mockStorageBlob.Object,
BlobLeaseClient = mockAccount.BlobLeaseClient.Object,
LeaseId = TestLeaseId
},
mockRenewalTimer.Object
@ -369,34 +480,55 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
[Fact]
public async Task GetLockOwnerAsync_LeaseLocked_ReturnsOwner()
{
MockFetchAttributesAsync(null);
var mockAccount = _account;
var fields = new Dictionary<string, object>()
{
{ "LeaseState", LeaseState.Leased },
};
mockAccount.BlobClient.Setup(p => p.GetPropertiesAsync(It.IsAny<BlobRequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobProperties>(fields)));
_mockStorageBlob.Object.Properties.SetLeaseState(LeaseState.Leased);
SingletonAttribute attribute = new SingletonAttribute();
string lockOwner = await _singletonManager.GetLockOwnerAsync(attribute, TestLockId, CancellationToken.None);
Assert.Equal(null, lockOwner);
_mockStorageBlob.Object.Metadata.Add(StorageBaseDistributedLockManager.FunctionInstanceMetadataKey, TestLockId);
IDictionary<string, string> metadata = new Dictionary<string, string>()
{
{ BlobLeaseDistributedLockManager.FunctionInstanceMetadataKey, TestLockId }
};
fields = new Dictionary<string, object>()
{
{ "LeaseState", LeaseState.Leased },
{ "Metadata", metadata }
};
mockAccount.BlobClient.Setup(p => p.GetPropertiesAsync(It.IsAny<BlobRequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobProperties>(fields)));
lockOwner = await _singletonManager.GetLockOwnerAsync(attribute, TestLockId, CancellationToken.None);
Assert.Equal(TestLockId, lockOwner);
_mockStorageBlob.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
[Fact]
public async Task GetLockOwnerAsync_LeaseAvailable_ReturnsNull()
{
MockFetchAttributesAsync(null);
var mockAccount = _account;
_mockStorageBlob.Object.Properties.SetLeaseState(LeaseState.Available);
_mockStorageBlob.Object.Properties.SetLeaseStatus(LeaseStatus.Unlocked);
var fields = new Dictionary<string, object>()
{
{ "LeaseState", LeaseState.Available },
{ "LeaseStatus", LeaseStatus.Unlocked },
};
mockAccount.BlobClient.Setup(p => p.GetPropertiesAsync(It.IsAny<BlobRequestConditions>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockAccount.CreateInternalAzureBlobResponse<BlobProperties>(fields)));
SingletonAttribute attribute = new SingletonAttribute();
string lockOwner = await _singletonManager.GetLockOwnerAsync(attribute, TestLockId, CancellationToken.None);
Assert.Equal(null, lockOwner);
_mockStorageBlob.VerifyAll();
mockAccount.BlobClient.VerifyAll();
mockAccount.BlobLeaseClient.VerifyAll();
}
[Theory]

Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Xunit;
using static Microsoft.Azure.WebJobs.Host.Storage.BlobServiceClientProvider;
namespace Microsoft.Azure.WebJobs.Host.UnitTests
{
public class StorageServiceUriOptionsTests
{
[Fact]
public void GetBlobServiceUri_BindsProperly()
{
// Value and children in the section
var configValues = new Dictionary<string, string>
{
{ "SomeSection:BLOBServiceUri", "https://account1.blob.core.windows.net" },
{ "SomeSection:ACCountName", "account2" },
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();
var options = config.GetWebJobsConnectionSection("SomeSection").Get<StorageServiceUriOptions>();
Assert.Equal("https://account1.blob.core.windows.net", options.BlobServiceUri);
Assert.Equal("account2", options.AccountName);
Assert.Equal("https://account1.blob.core.windows.net", options.GetBlobServiceUri().ToString().TrimEnd('/'));
}
[Fact]
public void GetBlobServiceUri_ThrowsOnBadUri()
{
// Value and children in the section
var configValues = new Dictionary<string, string>
{
{ "SomeSection:BLOBServiceUri", "account1.blob.core.windows.net" },
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();
var options = config.GetWebJobsConnectionSection("SomeSection").Get<StorageServiceUriOptions>();
Assert.Equal("account1.blob.core.windows.net", options.BlobServiceUri);
Assert.Throws<UriFormatException>(() => options.GetBlobServiceUri().ToString());
}
[Fact]
public void GetBlobServiceUri_UsesAccountNameWithDefaults()
{
// Value and children in the section
var configValues = new Dictionary<string, string>
{
{ "SomeSection:accountName", "account1" },
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();
var options = config.GetWebJobsConnectionSection("SomeSection").Get<StorageServiceUriOptions>();
Assert.Equal("account1", options.AccountName);
Assert.Equal("https://account1.blob.core.windows.net", options.GetBlobServiceUri().ToString().TrimEnd('/'));
}
}
}

Просмотреть файл

@ -1,59 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.FunctionalTests.TestDoubles;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
{
static class Utility
{
public static IHostBuilder ConfigureDefaultTestHost<TProgram>(this IHostBuilder builder, StorageAccount account)
{
return builder.ConfigureDefaultTestHost<TProgram>(b =>
{
b.AddAzureStorage();
})
.ConfigureServices(services => services.AddFakeStorageAccountProvider(account));
}
public static IHostBuilder ConfigureFakeStorageAccount(this IHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services.AddFakeStorageAccountProvider();
});
}
public static IServiceCollection AddFakeStorageAccountProvider(this IServiceCollection services)
{
// return services.AddFakeStorageAccountProvider(new XFakeStorageAccount()); $$$
throw new NotImplementedException();
}
public static IServiceCollection AddNullLoggerProviders(this IServiceCollection services)
{
return services
.AddSingleton<IFunctionOutputLoggerProvider, NullFunctionOutputLoggerProvider>()
.AddSingleton<IFunctionInstanceLoggerProvider, NullFunctionInstanceLoggerProvider>();
}
public static IServiceCollection AddFakeStorageAccountProvider(this IServiceCollection services, StorageAccount account)
{
throw new NotImplementedException();
/*
if (account is XFakeStorageAccount)
{
services.AddNullLoggerProviders();
}
return services.AddSingleton<XStorageAccountProvider>(new FakeStorageAccountProvider(account));
*/
}
}
}

Просмотреть файл

@ -28,17 +28,16 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="Moq" Version="4.7.145" />
<PackageReference Include="Moq" Version="4.16.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.0-beta.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj" />
<ProjectReference Include="..\FakeStorage\FakeAzureStorage.csproj" />
<ProjectReference Include="..\Microsoft.Azure.WebJobs.Host.TestCommon\WebJobs.Host.TestCommon.csproj" />
<ProjectReference Include="..\TestProjects\FSharpFunctions\FSharpFunctions.fsproj" />
</ItemGroup>

Просмотреть файл

@ -1,32 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.Azure.Storage.Queue;
namespace Microsoft.Azure.WebJobs.Host.TestCommon.AzureSdk
{
public static class CloudQueueClientExtensions
{
public async static Task CreateQueueOrClearIfExists(this CloudQueueClient queueClient, string queueName)
{
CloudQueue queue = queueClient.GetQueueReference(queueName);
bool wasCreatedNow = await queue.CreateIfNotExistsAsync();
if (!wasCreatedNow)
{
await queue.ClearAsync();
}
}
public async static Task DeleteQueueIfExists(this CloudQueueClient queueClient, string queueName)
{
CloudQueue queue = queueClient.GetQueueReference(queueName);
if (await queue.ExistsAsync())
{
await queue.DeleteAsync();
}
}
}
}

Просмотреть файл

@ -1,11 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Azure.Storage;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace Microsoft.Azure.WebJobs.Host.TestCommon
{
@ -13,20 +11,10 @@ namespace Microsoft.Azure.WebJobs.Host.TestCommon
{
public static JobHost<TProgram> Create<TProgram>()
{
return Create<TProgram>(CloudStorageAccount.DevelopmentStorageAccount, maxDequeueCount: 5);
return Create<TProgram>(maxDequeueCount: 5);
}
public static JobHost<TProgram> Create<TProgram>(int maxDequeueCount)
{
return Create<TProgram>(CloudStorageAccount.DevelopmentStorageAccount, maxDequeueCount);
}
public static JobHost<TProgram> Create<TProgram>(CloudStorageAccount storageAccount)
{
return Create<TProgram>(storageAccount, maxDequeueCount: 5);
}
public static JobHost<TProgram> Create<TProgram>(CloudStorageAccount storageAccount, int maxDequeueCount)
{
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<TProgram>()
@ -43,7 +31,7 @@ namespace Microsoft.Azure.WebJobs.Host.TestCommon
})
.Build();
return host.GetJobHost<TProgram>();
return host.GetJobHost<TProgram>();
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Azure.Storage.Queues;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Extensions.Azure;
namespace Microsoft.Azure.WebJobs.Host.TestCommon
{
/// <summary>
/// Provider to create QueueServiceClient objects. This is used only for tests and is intended to only honor connection strings.
/// </summary>
internal class QueueServiceClientProvider : StorageClientProvider<QueueServiceClient, QueueClientOptions>
{
public QueueServiceClientProvider(AzureComponentFactory componentFactory, AzureEventSourceLogForwarder logForwarder)
: base(componentFactory, logForwarder) { }
}
}

Просмотреть файл

@ -8,11 +8,15 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Queues;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.FunctionalTests.TestDoubles;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -350,6 +354,30 @@ namespace Microsoft.Azure.WebJobs.Host.TestCommon
FieldInfo elapsedFieldInfo = typeof(Stopwatch).GetField("_elapsed", BindingFlags.NonPublic | BindingFlags.Instance);
elapsedFieldInfo.SetValue(sw, elapsed.Ticks);
}
public static BlobServiceClient GetTestBlobServiceClient(string connection = "Storage")
{
var host = new HostBuilder().ConfigureDefaultTestHost(builder => builder.AddAzureStorageCoreServices()).Build();
var componentFactory = host.Services.GetRequiredService<AzureComponentFactory>();
var logForwarder = host.Services.GetRequiredService<AzureEventSourceLogForwarder>();
var provider = new BlobServiceClientProvider(componentFactory, logForwarder);
return provider.Create(connection, host.Services.GetRequiredService<IConfiguration>());
}
public static QueueServiceClient GetTestQueueServiceClient(string connection = "Storage")
{
var host = new HostBuilder().ConfigureDefaultTestHost(builder => builder.AddAzureStorageCoreServices()).Build();
var componentFactory = host.Services.GetRequiredService<AzureComponentFactory>();
var logForwarder = host.Services.GetRequiredService<AzureEventSourceLogForwarder>();
var provider = new QueueServiceClientProvider(componentFactory, logForwarder);
return provider.Create(connection, host.Services.GetRequiredService<IConfiguration>());
}
public static IAzureBlobStorageProvider GetTestAzureBlobStorageProvider()
{
var host = new HostBuilder().ConfigureDefaultTestHost(builder => builder.AddAzureStorageCoreServices()).Build();
return host.Services.GetRequiredService<IAzureBlobStorageProvider>();
}
}
public class TestProgram

Просмотреть файл

@ -22,8 +22,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Storage.Blob" Version="11.1.7" />
<PackageReference Include="Microsoft.Azure.Storage.Queue" Version="11.1.7" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.9.0" />
<PackageReference Include="Azure.Storage.Queues" Version="12.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
<PrivateAssets>all</PrivateAssets>
@ -34,6 +34,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
</ItemGroup>

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Xunit;
@ -41,5 +42,38 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
Environment.SetEnvironmentVariable(settingName, null);
}
}
[Fact]
public void GetWebJobsConnectionSection_ReturnsExpected()
{
// Value and children in the section
var configValues = new Dictionary<string, string>
{
{ "AzureWebJobsStorage", "connectionString" },
{ "AzureWebJobsStorage:subsection", "test1" },
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();
var section = config.GetWebJobsConnectionSection(ConnectionStringNames.Storage);
Assert.True(section.Exists());
Assert.False(string.IsNullOrEmpty(section.Value));
Assert.Equal("test1", section["subsection"]);
// No value, just children
configValues = new Dictionary<string, string>
{
{ "AzureWebJobsStorage:subsection", "test2" },
};
config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();
section = config.GetWebJobsConnectionSection(ConnectionStringNames.Storage);
Assert.True(section.Exists());
Assert.True(string.IsNullOrEmpty(section.Value));
Assert.Equal("test2", section["subsection"]);
}
}
}