remove EventGridFaultyEvent, remove publisher, add unit test project

This commit is contained in:
ShunXian Cai 2017-07-31 11:34:19 -07:00
Родитель 4eb98649de
Коммит 77ad9271bd
21 изменённых файлов: 382 добавлений и 473 удалений

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

@ -1,15 +1,15 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.6 VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D0455F87-5E51-4AFD-ACF1-2F50A352F3D5}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D0455F87-5E51-4AFD-ACF1-2F50A352F3D5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridExtension", "src\EventGridExtension\EventGridExtension.csproj", "{D04DD2A7-EE6B-44C2-B615-DC380D8D2569}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventGridExtension", "src\EventGridExtension\EventGridExtension.csproj", "{D04DD2A7-EE6B-44C2-B615-DC380D8D2569}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B40A0AB6-1971-4DE9-954A-DD92AF1694CC}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B40A0AB6-1971-4DE9-954A-DD92AF1694CC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridBinding", "test\localTests\EventGridBinding.csproj", "{828D7696-5D52-4AEB-8973-4382E9760094}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extension.tests", "test\Extension.tests\Extension.tests.csproj", "{694416E6-094C-4F52-8B0F-D6B8197E5660}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -21,16 +21,19 @@ Global
{D04DD2A7-EE6B-44C2-B615-DC380D8D2569}.Debug|Any CPU.Build.0 = Debug|Any CPU {D04DD2A7-EE6B-44C2-B615-DC380D8D2569}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D04DD2A7-EE6B-44C2-B615-DC380D8D2569}.Release|Any CPU.ActiveCfg = Release|Any CPU {D04DD2A7-EE6B-44C2-B615-DC380D8D2569}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D04DD2A7-EE6B-44C2-B615-DC380D8D2569}.Release|Any CPU.Build.0 = Release|Any CPU {D04DD2A7-EE6B-44C2-B615-DC380D8D2569}.Release|Any CPU.Build.0 = Release|Any CPU
{828D7696-5D52-4AEB-8973-4382E9760094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {694416E6-094C-4F52-8B0F-D6B8197E5660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{828D7696-5D52-4AEB-8973-4382E9760094}.Debug|Any CPU.Build.0 = Debug|Any CPU {694416E6-094C-4F52-8B0F-D6B8197E5660}.Debug|Any CPU.Build.0 = Debug|Any CPU
{828D7696-5D52-4AEB-8973-4382E9760094}.Release|Any CPU.ActiveCfg = Release|Any CPU {694416E6-094C-4F52-8B0F-D6B8197E5660}.Release|Any CPU.ActiveCfg = Release|Any CPU
{828D7696-5D52-4AEB-8973-4382E9760094}.Release|Any CPU.Build.0 = Release|Any CPU {694416E6-094C-4F52-8B0F-D6B8197E5660}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{D04DD2A7-EE6B-44C2-B615-DC380D8D2569} = {D0455F87-5E51-4AFD-ACF1-2F50A352F3D5} {D04DD2A7-EE6B-44C2-B615-DC380D8D2569} = {D0455F87-5E51-4AFD-ACF1-2F50A352F3D5}
{828D7696-5D52-4AEB-8973-4382E9760094} = {B40A0AB6-1971-4DE9-954A-DD92AF1694CC} {694416E6-094C-4F52-8B0F-D6B8197E5660} = {B40A0AB6-1971-4DE9-954A-DD92AF1694CC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7059D7BF-670B-4F26-9A7A-E73F95BEBF57}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

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

@ -36,7 +36,9 @@ build_script:
dotnet build -v q dotnet build -v q
dotnet pack src\EventGridExtension\EventGridExtension.csproj -o ..\..\buildoutput --no-build --version-suffix "-$env:APPVEYOR_BUILD_NUMBER" dotnet pack src\EventGridExtension\EventGridExtension.csproj -o ..\..\buildoutput --no-build --version-suffix "-$env:APPVEYOR_BUILD_NUMBER"
test_script:
- ps: >-
dotnet test .\test\Extension.tests\ -v q --no-build
artifacts: artifacts:
- path: buildoutput\*.nupkg - path: buildoutput\*.nupkg
name: Binaries name: Binaries

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

@ -1,58 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{
public class DefaultPublisher : IPublisher
{
public const string Name = "DefaultPublisher";
public string PublisherName
{
get { return Name; }
}
public List<IDisposable> Recycles
{
get { return null; }
}
public Dictionary<string, Type> ExtractBindingContract(Type t)
{
if (t == typeof(EventGridEvent) || t == typeof(string))
{
var contract = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
// for javascript, 1st attempt is to return JSON string of EventGridEvent
contract.Add("EventGridTrigger", t);
return contract;
}
else
{
return null;
}
}
public Task<Dictionary<string, object>> ExtractBindingData(EventGridEvent e, Type t)
{
var bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
if (t == typeof(EventGridEvent))
{
bindingData.Add("EventGridTrigger", e);
}
else if (t == typeof(string))
{
bindingData.Add("EventGridTrigger", JsonConvert.SerializeObject(e, Formatting.Indented));
}
return Task.FromResult<Dictionary<string, object>>(bindingData);
}
public object GetArgument(Dictionary<string, object> bindingData)
{
return bindingData["EventGridTrigger"];
}
}
}

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

@ -11,13 +11,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="$(TargetFramework)=='net46'"> <ItemGroup Condition="$(TargetFramework)=='net46'">
<PackageReference Include="Microsoft.Azure.WebJobs" Version="2.1.0-beta1" /> <PackageReference Include="Microsoft.Azure.WebJobs" Version="2.1.0-beta1-10976" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="2.1.0-beta1" /> <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="2.1.0-beta1-10489" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$(TargetFramework)=='netstandard2.0'"> <ItemGroup Condition="$(TargetFramework)=='netstandard2.0'">
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.0-beta1-10941" /> <PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.0-beta1-10941" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.0-beta1-10001" /> <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.0-beta1-10001" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -1,4 +1,5 @@
using Microsoft.Azure.WebJobs.Host.Config; using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Host.Executors;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
@ -15,12 +16,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
public class EventGridExtensionConfig : IExtensionConfigProvider, public class EventGridExtensionConfig : IExtensionConfigProvider,
IAsyncConverter<HttpRequestMessage, HttpResponseMessage> IAsyncConverter<HttpRequestMessage, HttpResponseMessage>
{ {
private bool _isTest = false; private TraceWriter _tracer = null;
public bool IsTest
{
get { return _isTest; }
set { _isTest = value; }
}
public void Initialize(ExtensionConfigContext context) public void Initialize(ExtensionConfigContext context)
{ {
@ -28,6 +24,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{ {
throw new ArgumentNullException("context"); throw new ArgumentNullException("context");
} }
else if (context.Trace == null)
{
throw new ArgumentNullException("context.Trace");
}
_tracer = context.Trace;
Uri url = context.GetWebhookHandler(); Uri url = context.GetWebhookHandler();
@ -50,6 +51,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
private async Task<HttpResponseMessage> ProcessAsync(HttpRequestMessage req) private async Task<HttpResponseMessage> ProcessAsync(HttpRequestMessage req)
{ {
// webjobs.script uses req.GetQueryNameValuePairs();
// which requires webapi.core...but this does not work for .netframework2.0
// TODO change this once webjobs.script is migrated
var functionName = HttpUtility.ParseQueryString(req.RequestUri.Query)["functionName"]; var functionName = HttpUtility.ParseQueryString(req.RequestUri.Query)["functionName"];
if (String.IsNullOrEmpty(functionName) || !_listeners.ContainsKey(functionName)) if (String.IsNullOrEmpty(functionName) || !_listeners.ContainsKey(functionName))
{ {
@ -67,20 +71,13 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{ {
string jsonArray = await req.Content.ReadAsStringAsync(); string jsonArray = await req.Content.ReadAsStringAsync();
SubscriptionValidationEvent validationEvent = null; SubscriptionValidationEvent validationEvent = null;
try List<EventGridEvent> events = JsonConvert.DeserializeObject<List<EventGridEvent>>(jsonArray);
{ validationEvent = events[0].Data.ToObject<SubscriptionValidationEvent>();
List<EventGridEvent> events = JsonConvert.DeserializeObject<List<EventGridEvent>>(jsonArray);
validationEvent = events[0].Data.ToObject<SubscriptionValidationEvent>();
}
catch (JsonException)
{
// TODO remove once validation use JObject
List<EventGridFaultyEvent> events = JsonConvert.DeserializeObject<List<EventGridFaultyEvent>>(jsonArray);
validationEvent = JsonConvert.DeserializeObject<SubscriptionValidationEvent>(events[0].Data);
}
SubscriptionValidationResponse validationResponse = new SubscriptionValidationResponse { ValidationResponse = validationEvent.ValidationCode }; SubscriptionValidationResponse validationResponse = new SubscriptionValidationResponse { ValidationResponse = validationEvent.ValidationCode };
var returnMessage = new HttpResponseMessage(HttpStatusCode.OK); var returnMessage = new HttpResponseMessage(HttpStatusCode.OK);
returnMessage.Content = new StringContent(JsonConvert.SerializeObject(validationResponse)); returnMessage.Content = new StringContent(JsonConvert.SerializeObject(validationResponse));
_tracer.Trace(new TraceEvent(System.Diagnostics.TraceLevel.Info,
$"perform handshake with eventGrid for endpoint: {req.RequestUri}"));
return returnMessage; return returnMessage;
} }
else if (String.Equals(eventTypeHeader, "Notification", StringComparison.OrdinalIgnoreCase)) else if (String.Equals(eventTypeHeader, "Notification", StringComparison.OrdinalIgnoreCase))

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

@ -16,7 +16,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
// Register our extension configuration provider // Register our extension configuration provider
// done by the function runtime // done by the function runtime
config.RegisterExtensionConfigProvider(new EventGridExtensionConfig() { IsTest = true }); config.RegisterExtensionConfigProvider(new EventGridExtensionConfig());
} }
} }
} }

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

@ -1,7 +1,4 @@
using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Host.Executors;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;

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

@ -14,12 +14,5 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{ {
} }
public EventGridTriggerAttribute(string publisher)
{
Publisher = publisher;
}
public string Connection { get; set; } = null;
public string Publisher { get; }
} }
} }

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

@ -6,6 +6,8 @@ using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Listeners; using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Protocols; using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Triggers; using Microsoft.Azure.WebJobs.Host.Triggers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -38,53 +40,41 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
return Task.FromResult<ITriggerBinding>(null); return Task.FromResult<ITriggerBinding>(null);
} }
// depends on the publisher, we could have different expectation for paramter if (!isSupportBindingType(parameter.ParameterType))
// TODO javascript, you cannot sepcify parameterType?
string publisherName = attribute.Publisher;
IPublisher publisher = null;
// factory pattern
if (String.IsNullOrEmpty(publisherName))
{
publisher = new DefaultPublisher();
}
else if (String.Equals(publisherName, EventHubCapturePublisher.Name, StringComparison.OrdinalIgnoreCase))
{
// if publisher is EventHubCapture, Connection String is going to be required
publisher = new EventHubCapturePublisher(attribute.Connection);
}
var contract = publisher?.ExtractBindingContract(parameter.ParameterType);
if (contract == null)
{ {
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
"Can't bind EventGridTriggerAttribute with publisher '{0}' to type '{1}'.", publisherName, parameter.ParameterType)); "Can't bind EventGridTriggerAttribute to type '{0}'.", parameter.ParameterType));
} }
// unsupported publisher is caught in attribute constrcutor
return Task.FromResult<ITriggerBinding>(new EventGridTriggerBinding(context.Parameter, _extensionConfigProvider, context.Parameter.Member.Name, publisher, contract)); return Task.FromResult<ITriggerBinding>(new EventGridTriggerBinding(context.Parameter, _extensionConfigProvider, context.Parameter.Member.Name));
} }
public bool isSupportBindingType(Type t)
{
return (t == typeof(EventGridEvent) || t == typeof(string));
}
private class EventGridTriggerBinding : ITriggerBinding private class EventGridTriggerBinding : ITriggerBinding
{ {
private readonly ParameterInfo _parameter; private readonly ParameterInfo _parameter;
private readonly IReadOnlyDictionary<string, Type> _bindingContract; private readonly Dictionary<string, Type> _bindingContract;
private EventGridExtensionConfig _listenersStore; private EventGridExtensionConfig _listenersStore;
private readonly string _functionName; private readonly string _functionName;
private readonly IPublisher _publisher;
public EventGridTriggerBinding(ParameterInfo parameter, EventGridExtensionConfig listenersStore, string functionName, IPublisher publisher, Dictionary<string, Type> contract) public EventGridTriggerBinding(ParameterInfo parameter, EventGridExtensionConfig listenersStore, string functionName)
{ {
_publisher = publisher;
_listenersStore = listenersStore; _listenersStore = listenersStore;
_parameter = parameter; _parameter = parameter;
_functionName = functionName; _functionName = functionName;
_bindingContract = contract; _bindingContract = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{"data",typeof(JObject) }
};
} }
public IReadOnlyDictionary<string, Type> BindingDataContract public IReadOnlyDictionary<string, Type> BindingDataContract
{ {
// TODO? not per parameter?
get { return _bindingContract; } get { return _bindingContract; }
} }
@ -93,21 +83,30 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
get { return typeof(EventGridEvent); } get { return typeof(EventGridEvent); }
} }
public async Task<ITriggerData> BindAsync(object value, ValueBindingContext context) public Task<ITriggerData> BindAsync(object value, ValueBindingContext context)
{ {
EventGridEvent triggerValue = value as EventGridEvent; EventGridEvent triggerValue = value as EventGridEvent;
var bindingData = await _publisher.ExtractBindingData(triggerValue, _parameter.ParameterType); var bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
IValueBinder valueBinder = new EventGridValueBinder(_parameter, _publisher.GetArgument(bindingData), _publisher.Recycles); {
return new TriggerData(valueBinder, bindingData); {"data", triggerValue.Data}
};
object argument;
if (_parameter.ParameterType == typeof(string))
{
argument = JsonConvert.SerializeObject(triggerValue, Formatting.Indented);
}
else
{
argument = triggerValue;
}
IValueBinder valueBinder = new EventGridValueBinder(_parameter, argument);
return Task.FromResult<ITriggerData>(new TriggerData(valueBinder, bindingData));
} }
public Task<IListener> CreateListenerAsync(ListenerFactoryContext context) public Task<IListener> CreateListenerAsync(ListenerFactoryContext context)
{ {
// listenersStore is of Type "EventGridExtensionConfig"
if (_listenersStore.IsTest)
{
return Task.FromResult<IListener>(new TestListener(context.Executor));
}
return Task.FromResult<IListener>(new EventGridListener(context.Executor, _listenersStore, _functionName)); return Task.FromResult<IListener>(new EventGridListener(context.Executor, _listenersStore, _functionName));
} }
@ -141,7 +140,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
private readonly object _value; private readonly object _value;
private List<IDisposable> _disposables = null; private List<IDisposable> _disposables = null;
public EventGridValueBinder(ParameterInfo parameter, object value, List<IDisposable> disposables) public EventGridValueBinder(ParameterInfo parameter, object value, List<IDisposable> disposables = null)
: base(parameter.ParameterType) : base(parameter.ParameterType)
{ {
_value = value; _value = value;

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

@ -16,85 +16,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
public string ValidationCode { get; set; } public string ValidationCode { get; set; }
} }
public class StorageBlob
{
/*
{
"fileUrl": "https://shunsouthcentralus.blob.core.windows.net/archivecontainershun/canaryeh/test/1/2017/07/14/23/09/27.avro",
"fileType": "AzureBlockBlob",
"partitionId": "1",
"sizeInBytes": 0,
"eventCount": 0,
"firstSequenceNumber": -1,
"lastSequenceNumber": -1,
"firstEnqueueTime": "0001-01-01T00:00:00",
"lastEnqueueTime": "0001-01-01T00:00:00"
}
*/
[JsonProperty(PropertyName = "fileUrl")]
public Uri FileUrl { get; set; }
[JsonProperty(PropertyName = "fileType")]
public string FileType { get; set; }
[JsonProperty(PropertyName = "partitionId")]
public int PartitionId { get; set; }
[JsonProperty(PropertyName = "sizeInBytes")]
public int SizeInBytes { get; set; }
[JsonProperty(PropertyName = "eventCount")]
public int EventCount { get; set; }
[JsonProperty(PropertyName = "firstSequenceNumber")]
public int FirstSequenceNumber { get; set; }
[JsonProperty(PropertyName = "lastSequenceNumber")]
public int LastSequenceNumber { get; set; }
[JsonProperty(PropertyName = "firstEnqueueTime")]
public DateTime FirstEnqueueTime { get; set; }
[JsonProperty(PropertyName = "lastEnqueueTime")]
public DateTime LastEnqueueTime { get; set; }
}
public class EventGridFaultyEvent
{
/*
{
'id': 'eac180e8-92e0-436d-8699-a0324e2a5fef',
'topic': '/subscriptions/5b4b650e-28b9-4790-b3ab-ddbd88d727c4/resourceGroups/canaryeh/providers/microsoft.eventhub/namespaces/canaryeh',
'subject': 'eventhubs/test',
'data': '{\""validationCode\"":\""85fe9560-f63f-469b-b40a-5a6327db05e6\""}', <-- String instead of JObject
'eventType': 'Microsoft.EventGrid/SubscriptionValidationEvent',
'eventTime': '2017-07-28T00:43:28.6153503Z'
}
*/
[JsonProperty(PropertyName = "topic")]
public string Topic { get; set; }
[JsonProperty(PropertyName = "subject")]
public string Subject { get; set; }
[JsonProperty(PropertyName = "data")]
public string Data { get; set; }
[JsonProperty(PropertyName = "eventType")]
public string EventType { get; set; }
[JsonProperty(PropertyName = "publishTime")]
public DateTime PublishTime { get; set; }
[JsonProperty(PropertyName = "eventTime")]
public DateTime EventTime { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
}
public class EventGridEvent public class EventGridEvent
{ {
/* /*
@ -115,7 +36,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
[JsonProperty(PropertyName = "subject")] [JsonProperty(PropertyName = "subject")]
public string Subject { get; set; } public string Subject { get; set; }
// the content of this depends on the publisher
[JsonProperty(PropertyName = "data")] [JsonProperty(PropertyName = "data")]
public JObject Data { get; set; } public JObject Data { get; set; }

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

@ -1,129 +0,0 @@
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{
public class EventHubCapturePublisher : IPublisher
{
public const string Name = "eventHubCapture";
private List<IDisposable> _recycles = null;
private StorageCredentials _credentials;
public EventHubCapturePublisher(string connectionStringName)
{
if (String.IsNullOrEmpty(connectionStringName))
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
"Can't bind EventGridTriggerAttribute with publisher '{0}': missing ConnectionString for Storageblob.", Name));
}
var connectionString = AmbientConnectionStringProvider.Instance.GetConnectionString(connectionStringName);
_credentials = CloudStorageAccount.Parse(connectionString).Credentials;
}
public string PublisherName
{
get { return Name; }
}
public List<IDisposable> Recycles
{
get { return _recycles; }
}
public Dictionary<string, Type> ExtractBindingContract(Type t)
{
var contract = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
// TODO we can determine the ACTION in this function, so that when calling ExtractBindingData, we don't have to do the comparison again
if (t == typeof(EventGridEvent))
{
contract.Add("EventGridTrigger", t);
}
else if (t == typeof(Stream) || t == typeof(string) || t == typeof(CloudBlob) || t == typeof(byte[]))
{
contract.Add("EventGridTrigger", t);
contract.Add("BlobTrigger", typeof(string));
contract.Add("Uri", typeof(Uri));
contract.Add("Properties", typeof(BlobProperties));
contract.Add("Metadata", typeof(IDictionary<string, string>));
}
else
{
// fail
return null;
}
return contract;
}
public async Task<Dictionary<string, object>> ExtractBindingData(EventGridEvent e, Type t)
{
var bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
if (t == typeof(EventGridEvent))
{
bindingData.Add("EventGridTrigger", e);
}
else
{
StorageBlob data = e.Data.ToObject<StorageBlob>();
var blob = new CloudBlob(data.FileUrl, _credentials);
// set metadata based on https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/azure-functions/functions-bindings-storage-blob.md#trigger-metadata
//BlobTrigger.Type string.The triggering blob path
bindingData.Add("BlobTrigger", blob.Container.Name + "/" + blob.Name);
//Uri.Type System.Uri.The blob's URI for the primary location.
bindingData.Add("Uri", blob.Uri);
//Properties.Type Microsoft.WindowsAzure.Storage.Blob.BlobProperties.The blob's system properties.
bindingData.Add("Properties", blob.Properties);
//Metadata.Type IDictionary<string, string>.The user - defined metadata for the blob
bindingData.Add("Metadata", blob.Metadata);
// [Blob("output/copy-{name}")] out string output, does not apply here
// bindingData.Add("name", blob.Name);
if (t == typeof(CloudBlob))
{
bindingData.Add("EventGridTrigger", blob);
}
else
{
// convert from stream
var blobStream = await blob.OpenReadAsync();
if (t == typeof(Stream))
{
_recycles = new List<IDisposable>();
_recycles.Add(blobStream); // close after function call
bindingData.Add("EventGridTrigger", blobStream);
}
// copy to memory => use case javascript
else if (t == typeof(Byte[]))
{
using (MemoryStream ms = new MemoryStream())
{
await blobStream.CopyToAsync(ms);
bindingData.Add("EventGridTrigger", ms.ToArray());
}
blobStream.Close(); // close before the function call
}
else if (t == typeof(string))
{
using (StreamReader responseStream = new StreamReader(blobStream))
{
string blobData = await responseStream.ReadToEndAsync();
bindingData.Add("EventGridTrigger", blobData);
}
blobStream.Close(); // close before the function call
}
}
}
return bindingData;
}
public object GetArgument(Dictionary<string, object> bindingData)
{
return bindingData["EventGridTrigger"];
}
}
}

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

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{
public interface IPublisher
{
string PublisherName { get; }
List<IDisposable> Recycles { get; }
// this method needs to filter invalid datatype
// return null
Dictionary<string, Type> ExtractBindingContract(Type t);
Task<Dictionary<string, object>> ExtractBindingData(EventGridEvent e, Type t);
object GetArgument(Dictionary<string, object> bindingData);
}
}

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

@ -1,89 +0,0 @@
using Microsoft.Azure.WebJobs.Host.Executors;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Azure.WebJobs.Extensions.EventGrid
{
public class TestListener : Microsoft.Azure.WebJobs.Host.Listeners.IListener
{
public ITriggeredFunctionExecutor Executor { private set; get; }
private System.Timers.Timer _timer;
public TestListener(ITriggeredFunctionExecutor executor)
{
Executor = executor;
// For this sample, we're using a timer to generate
// trigger events. You'll replace this with your event source.
_timer = new System.Timers.Timer(5 * 1000)
{
AutoReset = false
};
_timer.Elapsed += OnTimer;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer.Start();
return Task.FromResult(true);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer.Stop();
return Task.FromResult(true);
}
public void Dispose()
{
_timer.Dispose();
}
public void Cancel()
{
// TODO: cancel any outstanding tasks initiated by this listener
}
private void OnTimer(object sender, System.Timers.ElapsedEventArgs e)
{
// invoke the function executor
// this is similar to code in ProcessAsync()
string stringJson = @"[{
'topic': '/subscriptions/5b4b650e-28b9-4790-b3ab-ddbd88d727c4/resourcegroups/canaryeh/providers/Microsoft.EventHub/namespaces/canaryeh',
'subject': 'eventhubs/test',
'eventType': 'captureFileCreated',
'eventTime': '2017-07-14T23:10:27.7689666Z',
'id': '7b11c4ce-1c34-4416-848b-1730e766f126',
'data': {
'fileUrl': 'https://shunsouthcentralus.blob.core.windows.net/debugging/shunBlob.txt',
'fileType': 'AzureBlockBlob',
'partitionId': '1',
'sizeInBytes': 0,
'eventCount': 0,
'firstSequenceNumber': -1,
'lastSequenceNumber': -1,
'firstEnqueueTime': '0001-01-01T00:00:00',
'lastEnqueueTime': '0001-01-01T00:00:00'
},
'publishTime': '2017-07-14T23:10:29.5004788Z'
}]";
List<EventGridEvent> events = JsonConvert.DeserializeObject<List<EventGridEvent>>(stringJson);
foreach (var param in events)
{
TriggeredFunctionData input = new TriggeredFunctionData
{
TriggerValue = param
};
Executor.TryExecuteAsync(input, CancellationToken.None).Wait();
}
}
}
}

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

@ -0,0 +1,15 @@
using Microsoft.Azure.WebJobs;
using System;
using System.Collections.Generic;
using System.Text;
namespace Extension.tests
{
public class FakeTypeLocator<T> : ITypeLocator
{
public IReadOnlyList<Type> GetTypes()
{
return new Type[] { typeof(T) };
}
}
}

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

@ -0,0 +1,28 @@
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Host.Config;
using System;
namespace Extension.tests
{
[Binding]
public class BindingDataAttribute : Attribute
{
public BindingDataAttribute(string toBeAutoResolve)
{
ToBeAutoResolve = toBeAutoResolve;
}
[AutoResolve]
public string ToBeAutoResolve { get; set; }
}
public class TestExtensionConfig : IExtensionConfigProvider
{
public void Initialize(ExtensionConfigContext context)
{
context.AddBindingRule<BindingDataAttribute>().
BindToInput<string>(attr => attr.ToBeAutoResolve);
}
}
}

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

@ -0,0 +1,24 @@
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using System;
using System.Collections.Generic;
using System.Text;
namespace Extension.tests
{
public class TestHelpers
{
public static JobHost NewHost<T>(EventGridExtensionConfig ext = null)
{
JobHostConfiguration config = new JobHostConfiguration();
config.HostId = Guid.NewGuid().ToString("n");
config.StorageConnectionString = null;
config.DashboardConnectionString = null;
config.TypeLocator = new FakeTypeLocator<T>();
config.AddExtension(ext ?? new EventGridExtensionConfig());
config.AddExtension(new TestExtensionConfig());
var host = new JobHost(config);
return host;
}
}
}

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

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
<PackageReference Include="xunit" Version="2.3.0-beta2-build3683" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta3-build3705" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\EventGridExtension\EventGridExtension.csproj" />
</ItemGroup>
</Project>

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

@ -0,0 +1,93 @@
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using Newtonsoft.Json;
using System.Collections.Generic;
using Xunit;
using System.IO;
using System;
using System.Threading.Tasks;
namespace Extension.tests
{
public class JobhostEndToEnd
{
const string singleEvent = @"{
'topic': '/subscriptions/5b4b650e-28b9-4790-b3ab-ddbd88d727c4/resourcegroups/canaryeh/providers/Microsoft.EventHub/namespaces/canaryeh',
'subject': 'eventhubs/test',
'eventType': 'captureFileCreated',
'eventTime': '2017-07-14T23:10:27.7689666Z',
'id': '7b11c4ce-1c34-4416-848b-1730e766f126',
'data': {
'fileUrl': 'https://shunsouthcentralus.blob.core.windows.net/debugging/shunBlob.txt',
'fileType': 'AzureBlockBlob',
'partitionId': '1',
'sizeInBytes': 0,
'eventCount': 0,
'firstSequenceNumber': -1,
'lastSequenceNumber': -1,
'firstEnqueueTime': '0001-01-01T00:00:00',
'lastEnqueueTime': '0001-01-01T00:00:00'
},
'publishTime': '2017-07-14T23:10:29.5004788Z'
}";
static private string functionOut = null;
[Fact]
public async Task ConsumeEventGridEventTest()
{
EventGridEvent eve = JsonConvert.DeserializeObject<EventGridEvent>(singleEvent);
var args = new Dictionary<string, object>{
{ "value", eve }
};
var host = TestHelpers.NewHost<MyProg1>();
await host.CallAsync("MyProg1.TestEventGrid", args);
Assert.Equal(functionOut, eve.Subject);
functionOut = null;
await host.CallAsync("MyProg1.TestEventGridToString", args);
Assert.Equal(functionOut, eve.Subject);
functionOut = null;
}
[Fact]
public async Task UseInputBlobBinding()
{
EventGridEvent eve = JsonConvert.DeserializeObject<EventGridEvent>(singleEvent);
var args = new Dictionary<string, object>{
{ "value", eve }
};
var host = TestHelpers.NewHost<MyProg3>();
await host.CallAsync("MyProg3.TestBlobStream", args);
Assert.Equal(@"https://shunsouthcentralus.blob.core.windows.net/debugging/shunBlob.txt", functionOut);
functionOut = null;
}
public class MyProg1
{
public void TestEventGrid([EventGridTrigger] EventGridEvent value)
{
functionOut = value.Subject;
}
public void TestEventGridToString([EventGridTrigger] string value)
{
functionOut = JsonConvert.DeserializeObject<EventGridEvent>(value).Subject;
}
}
public class MyProg3
{
public void TestBlobStream(
[EventGridTrigger] EventGridEvent value,
[BindingData("{data.fileUrl}")] string autoResolve)
{
functionOut = autoResolve;
}
}
}
}

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

@ -0,0 +1,127 @@
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using Newtonsoft.Json;
using System.Collections.Generic;
using Xunit;
using System.IO;
using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Config;
using System.Net.Http;
using System.Threading;
using System.Net;
using System.Text;
using Newtonsoft.Json.Linq;
namespace Extension.tests
{
public class TestListener : IWebHookProvider
{
Uri IWebHookProvider.GetUrl(IExtensionConfigProvider extension)
{
// Called by configuration registration. URI here doesn't matter.
return new Uri("http://localhost");
}
static private StringBuilder _log = new StringBuilder();
public TestListener()
{
_log.Clear();
}
// Unsubscribe gives a 202.
[Fact]
public async Task TestUnsubscribe()
{
var ext = new EventGridExtensionConfig();
var host = TestHelpers.NewHost<MyProg1>(ext);
await host.StartAsync(); // add listener
var request = CreateUnsubscribeRequest("TestEventGrid");
IAsyncConverter <HttpRequestMessage, HttpResponseMessage> handler = ext;
var response = await handler.ConvertAsync(request, CancellationToken.None);
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
}
// Test that an event payload with multiple events causes multiple dispatches,
/// and that each instance has correct binding data .
// This is the fundamental difference between a regular HTTP trigger and a EventGrid trigger.
[Fact]
public async Task TestDispatch()
{
var ext = new EventGridExtensionConfig();
var host = TestHelpers.NewHost<MyProg1>(ext);
await host.StartAsync(); // add listener
var request = CreateDispatchRequest("TestEventGrid", new EventGridEvent
{
Subject = "One",
Data = JObject.FromObject(new FakePayload
{
Prop = "alpha"
})
},
new EventGridEvent
{
Subject = "Two",
Data = JObject.FromObject(new FakePayload
{
Prop = "beta"
})
});
IAsyncConverter<HttpRequestMessage, HttpResponseMessage> handler = ext;
var response = await handler.ConvertAsync(request, CancellationToken.None);
// Verify that the user function was dispatched twice, in order.
// Also verifies each instance gets its own proper binding data (from FakePayload.Prop)
Assert.Equal("[Dispatch:One,alpha][Dispatch:Two,beta]", _log.ToString());
// TODO - Verify that we return from webhook before the dispatch is finished
// https://github.com/Azure/azure-functions-eventing-extension/issues/10
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
}
static HttpRequestMessage CreateUnsubscribeRequest(string funcName)
{
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/?functionName=" + funcName);
request.Headers.Add("aeg-event-type", "Unsubscribe");
return request;
}
static HttpRequestMessage CreateDispatchRequest(string funcName, params EventGridEvent[] items)
{
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/?functionName=" + funcName);
request.Headers.Add("aeg-event-type", "Notification");
request.Content = new StringContent(
JsonConvert.SerializeObject(items),
Encoding.UTF8,
"application/json");
return request;
}
public class FakePayload
{
public string Prop { get; set; }
}
public class MyProg1
{
public void TestEventGrid(
[EventGridTrigger] EventGridEvent value,
[BindingData("{data.prop}")] string prop)
{
_log.Append("[Dispatch:" + value.Subject + "," + prop + "]");
}
}
}
}

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

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net46</TargetFrameworks> <TargetFrameworks>net46;netcoreapp2.0</TargetFrameworks>
<PackageId>Microsoft.Azure.WebJobs.Extensions.EventGrid.Test</PackageId> <PackageId>Microsoft.Azure.WebJobs.Extensions.EventGrid.Test</PackageId>
<Description></Description> <Description></Description>
<ApplicationIcon /> <ApplicationIcon />

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

@ -1,4 +1,5 @@
using Microsoft.Azure.WebJobs.Extensions.EventGrid; using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using System; using System;
using System.IO; using System.IO;
@ -9,28 +10,19 @@ namespace EventGridBinding
{ {
// TODO test fields // TODO test fields
public void TestEventGrid([EventGridTrigger] EventGridEvent value) public void TestEventGrid([EventGridTrigger] EventGridEvent value)
{
Console.WriteLine(value.ToString());
}
public void TestEventGridToString([EventGridTrigger] string value)
{ {
Console.WriteLine(value); Console.WriteLine(value);
} }
public void TestInputStream([EventGridTrigger("eventhubcapture", Connection = "ShunTestConnectionString")] Stream myBlob, string blobTrigger) public void TestBlobStream([EventGridTrigger] EventGridEvent value, [Blob("{data.container}/{data.blob}", FileAccess.Read, Connection = "ShunTestConnectionString")]Stream myBlob)
{ {
Console.WriteLine($"file name {blobTrigger}");
var reader = new StreamReader(myBlob); var reader = new StreamReader(myBlob);
Console.WriteLine(reader.ReadToEnd()); Console.WriteLine(reader.ReadToEnd());
} }
public void TestByteArray([EventGridTrigger("eventhubcapture", Connection = "ShunTestConnectionString")] byte[] myBlob)
{
foreach (var b in myBlob)
{
Console.Write(b);
}
}
public void TestString([EventGridTrigger("eventhubcapture", Connection = "ShunTestConnectionString")] string myBlob)
{
Console.WriteLine(myBlob);
}
} }
} }