Skip to content

Commit d644a83

Browse files
Merge pull request #322 from microsoft/dev
merge dev into main
2 parents d0b6e8b + 0bf357e commit d644a83

37 files changed

+314
-19277
lines changed

src/TeamCloud.Data.Expanders/ComponentTaskExpander.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public async Task ExpandAsync(ComponentTask document)
7373

7474
private async Task<string> GetEventsAsync(ComponentTask document)
7575
{
76+
var output = default(string);
77+
7678
if (document.TaskState.IsActive() && AzureResourceIdentifier.TryParse(document.ResourceId, out var resourceId))
7779
{
7880
try
@@ -83,8 +85,8 @@ private async Task<string> GetEventsAsync(ComponentTask document)
8385

8486
if (containerGroup is not null)
8587
{
86-
return await containerGroup
87-
.GetEventContentAsync("runner")
88+
output = await containerGroup
89+
.GetEventContentAsync(document.Id)
8890
.ConfigureAwait(false);
8991
}
9092
}
@@ -94,7 +96,7 @@ private async Task<string> GetEventsAsync(ComponentTask document)
9496
}
9597
}
9698

97-
return default;
99+
return output;
98100
}
99101

100102
private async Task<string> GetOutputAsync(ComponentTask document)

src/TeamCloud.Data/IDocumentSubscription.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,36 @@
44
*/
55

66
using System;
7+
using System.Collections.Concurrent;
8+
using System.Reflection;
79
using System.Threading.Tasks;
810
using TeamCloud.Model.Data.Core;
911

1012
namespace TeamCloud.Data;
1113

1214
public abstract class DocumentSubscription : IDocumentSubscription
1315
{
16+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, MethodInfo>> HandleMethodCache = new ConcurrentDictionary<Type,ConcurrentDictionary<Type, MethodInfo>>();
17+
18+
private MethodInfo GetHandleMethod(IContainerDocument containerDocument) => HandleMethodCache
19+
.GetOrAdd(GetType(), _ => new ConcurrentDictionary<Type, MethodInfo>())
20+
.GetOrAdd(containerDocument.GetType(), containerDocumentType =>
21+
{
22+
var subscriberInterface = typeof(IDocumentSubscription<>)
23+
.MakeGenericType(containerDocument.GetType());
24+
25+
if (subscriberInterface.IsAssignableFrom(GetType()))
26+
return subscriberInterface.GetMethod(nameof(HandleAsync), new Type[] { containerDocument.GetType(), typeof(DocumentSubscriptionEvent) });
27+
28+
return null;
29+
});
30+
1431
public virtual bool CanHandle(IContainerDocument containerDocument)
1532
{
1633
if (containerDocument is null)
1734
throw new ArgumentNullException(nameof(containerDocument));
1835

19-
return typeof(IDocumentSubscription<>).MakeGenericType(containerDocument.GetType()).IsAssignableFrom(GetType());
36+
return GetHandleMethod(containerDocument) is not null;
2037
}
2138

2239
public virtual Task HandleAsync(IContainerDocument containerDocument, DocumentSubscriptionEvent subscriptionEvent)
@@ -25,12 +42,7 @@ public virtual Task HandleAsync(IContainerDocument containerDocument, DocumentSu
2542
throw new ArgumentNullException(nameof(containerDocument));
2643

2744
if (CanHandle(containerDocument))
28-
{
29-
return (Task)typeof(IDocumentExpander<>)
30-
.MakeGenericType(containerDocument.GetType())
31-
.GetMethod(nameof(HandleAsync), new Type[] { containerDocument.GetType(), typeof(DocumentSubscriptionEvent) })
32-
.Invoke(this, new object[] { containerDocument, subscriptionEvent });
33-
}
45+
return (Task)GetHandleMethod(containerDocument).Invoke(this, new object[] { containerDocument, subscriptionEvent });
3446

3547
throw new NotImplementedException($"Missing document subscription implementation IDocumentSubscription<{containerDocument.GetType().Name}> at {GetType()}");
3648
}

src/TeamCloud.Orchestrator/API/CommandTrigger.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ await commandAuditWriter
268268
else
269269
{
270270
commandResult = await commandHandler
271-
.HandleAsync(command, commandCollector, durableClient, null, log ?? NullLogger.Instance)
271+
.HandleAsync(command, commandCollector, null, log ?? NullLogger.Instance)
272272
.ConfigureAwait(false);
273273

274274
if (!commandResult.RuntimeStatus.IsFinal())

src/TeamCloud.Orchestrator/Command/Activities/CommandCollectActivity.cs renamed to src/TeamCloud.Orchestrator/Command/Activities/CommandEnqueueActivity.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
namespace TeamCloud.Orchestrator.Command.Activities;
1515

16-
public sealed class CommandCollectActivity
16+
public sealed class CommandEnqueueActivity
1717
{
18-
[FunctionName(nameof(CommandCollectActivity))]
18+
[FunctionName(nameof(CommandEnqueueActivity))]
1919
public async Task RunActivity(
2020
[ActivityTrigger] IDurableActivityContext activityContext,
2121
[Queue(CommandHandler.ProcessorQueue)] IAsyncCollector<ICommand> commandCollector,
@@ -40,7 +40,7 @@ await commandCollector
4040
}
4141
catch (Exception exc)
4242
{
43-
log.LogError(exc, $"Failed to collect command: {exc.Message}");
43+
log.LogError(exc, $"Failed to enqeueu command: {exc.Message}");
4444

4545
throw exc.AsSerializable();
4646
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
using System;
7+
using System.Threading.Tasks;
8+
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
9+
using Microsoft.Azure.WebJobs;
10+
using Microsoft.Extensions.Logging;
11+
using TeamCloud.Serialization;
12+
13+
namespace TeamCloud.Orchestrator.Command.Activities;
14+
15+
public sealed class CommandStatusActivity
16+
{
17+
[FunctionName(nameof(CommandStatusActivity))]
18+
public Task<DurableOrchestrationStatus> RunActivity(
19+
[ActivityTrigger] IDurableActivityContext activityContext,
20+
[DurableClient] IDurableClient orchestrationClient,
21+
ILogger log)
22+
{
23+
if (activityContext is null)
24+
throw new ArgumentNullException(nameof(activityContext));
25+
26+
if (orchestrationClient is null)
27+
throw new ArgumentNullException(nameof(orchestrationClient));
28+
29+
if (log is null)
30+
throw new ArgumentNullException(nameof(log));
31+
32+
try
33+
{
34+
var input = activityContext.GetInput<Input>();
35+
36+
return orchestrationClient
37+
.GetStatusAsync(input.CommandId.ToString(), showHistory: input.ShowHistory, showHistoryOutput: input.ShowHistoryOutput, showInput: input.ShowInput);
38+
}
39+
catch (Exception exc)
40+
{
41+
log.LogError(exc, $"Failed to enqeueu command: {exc.Message}");
42+
43+
throw exc.AsSerializable();
44+
}
45+
}
46+
47+
internal struct Input
48+
{
49+
public Guid CommandId { get; set; }
50+
51+
public bool ShowHistory { get; set; }
52+
53+
public bool ShowHistoryOutput { get; set; }
54+
55+
public bool ShowInput { get; set; }
56+
}
57+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
using Microsoft.Azure.WebJobs;
7+
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
8+
using Microsoft.Extensions.Logging;
9+
using System;
10+
using System.Threading.Tasks;
11+
using TeamCloud.Serialization;
12+
13+
namespace TeamCloud.Orchestrator.Command.Activities;
14+
15+
public sealed class CommandTerminateActivity
16+
{
17+
[FunctionName(nameof(CommandTerminateActivity))]
18+
public async Task RunActivity(
19+
[ActivityTrigger] IDurableActivityContext activityContext,
20+
[DurableClient] IDurableClient orchestrationClient,
21+
ILogger log)
22+
{
23+
if (activityContext is null)
24+
throw new ArgumentNullException(nameof(activityContext));
25+
26+
if (orchestrationClient is null)
27+
throw new ArgumentNullException(nameof(orchestrationClient));
28+
29+
if (log is null)
30+
throw new ArgumentNullException(nameof(log));
31+
32+
try
33+
{
34+
var input = activityContext.GetInput<Input>();
35+
36+
await orchestrationClient
37+
.TerminateAsync(input.CommandId.ToString(), input.Reason ?? string.Empty)
38+
.ConfigureAwait(false);
39+
}
40+
catch (Exception exc)
41+
{
42+
log.LogError(exc, $"Failed to enqeueu command: {exc.Message}");
43+
44+
throw exc.AsSerializable();
45+
}
46+
}
47+
48+
internal struct Input
49+
{
50+
public Guid CommandId { get; set; }
51+
52+
public string Reason { get; set; }
53+
}
54+
}

src/TeamCloud.Orchestrator/Command/CommandCollector.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ public sealed class CommandCollector : IAsyncCollector<ICommand>
1919
private readonly ICommand commandContext;
2020
private readonly IDurableOrchestrationContext orchestrationContext;
2121

22-
public CommandCollector(IAsyncCollector<ICommand> collector, ICommand commandContext = null, IDurableOrchestrationContext orchestrationContext = null)
22+
public CommandCollector(IAsyncCollector<ICommand> collector, ICommand commandContext = null)
2323
{
2424
this.collector = collector ?? throw new ArgumentNullException(nameof(collector));
2525
this.commandContext = commandContext;
26-
this.orchestrationContext = orchestrationContext;
26+
}
27+
public CommandCollector(IDurableOrchestrationContext orchestrationContext, ICommand commandContext = null)
28+
{
29+
this.orchestrationContext = orchestrationContext ?? throw new ArgumentNullException(nameof(orchestrationContext));
30+
this.commandContext = commandContext;
2731
}
2832

2933
public async Task AddAsync(ICommand item, CancellationToken cancellationToken = default)
@@ -33,18 +37,22 @@ public async Task AddAsync(ICommand item, CancellationToken cancellationToken =
3337

3438
item.ParentId = commandContext?.CommandId ?? Guid.Empty;
3539

36-
if (orchestrationContext is null)
40+
if (collector is not null)
3741
{
3842
await collector
3943
.AddAsync(item, cancellationToken)
4044
.ConfigureAwait(false);
4145
}
42-
else
46+
else if (orchestrationContext is not null)
4347
{
4448
await orchestrationContext
45-
.CallActivityAsync(nameof(CommandCollectActivity), new CommandCollectActivity.Input() { Command = item })
49+
.CallActivityAsync(nameof(CommandEnqueueActivity), new CommandEnqueueActivity.Input() { Command = item })
4650
.ConfigureAwait(true);
4751
}
52+
else
53+
{
54+
throw new NotSupportedException();
55+
}
4856
}
4957

5058
public Task FlushAsync(CancellationToken cancellationToken = default)

src/TeamCloud.Orchestrator/Command/CommandExtensions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,32 @@
77
using System;
88
using System.Threading.Tasks;
99
using TeamCloud.Model.Commands.Core;
10+
using TeamCloud.Model.Data;
11+
using TeamCloud.Orchestrator.Command.Activities;
1012
using TeamCloud.Serialization;
1113

1214
namespace TeamCloud.Orchestrator.Command;
1315

1416
internal static class CommandExtensions
1517
{
18+
internal static Task TerminateCommandAsync(this IDurableOrchestrationContext orchestrationContext, ComponentTask componentTask, string reason = null)
19+
=> orchestrationContext.TerminateCommandAsync(Guid.Parse(componentTask.Id), reason);
20+
21+
internal static Task TerminateCommandAsync(this IDurableOrchestrationContext orchestrationContext, ICommand command, string reason = null)
22+
=> orchestrationContext.TerminateCommandAsync(command.CommandId, reason);
23+
24+
internal static Task TerminateCommandAsync(this IDurableOrchestrationContext orchestrationContext, Guid commandId, string reason = null)
25+
=> orchestrationContext.CallActivityAsync(nameof(CommandTerminateActivity), new CommandTerminateActivity.Input() { CommandId = commandId, Reason = reason });
26+
27+
internal static Task<DurableOrchestrationStatus> GetCommandStatusAsync(this IDurableOrchestrationContext orchestrationContext, ComponentTask componentTask, bool showHistory = false, bool showHistoryOutput = false, bool showInput = true)
28+
=> orchestrationContext.GetCommandStatusAsync(Guid.Parse(componentTask.Id), showHistory, showHistoryOutput, showInput);
29+
30+
internal static Task<DurableOrchestrationStatus> GetCommandStatusAsync(this IDurableOrchestrationContext orchestrationContext, ICommand command, bool showHistory = false, bool showHistoryOutput = false, bool showInput = true)
31+
=> orchestrationContext.GetCommandStatusAsync(command.CommandId, showHistory, showHistoryOutput, showInput);
32+
33+
internal static Task<DurableOrchestrationStatus> GetCommandStatusAsync(this IDurableOrchestrationContext orchestrationContext, Guid commandId, bool showHistory = false, bool showHistoryOutput = false, bool showInput = true)
34+
=> orchestrationContext.CallActivityAsync<DurableOrchestrationStatus>(nameof(CommandStatusActivity), new CommandStatusActivity.Input() { CommandId = commandId, ShowHistory = showHistory, ShowHistoryOutput = showHistoryOutput, ShowInput = showInput });
35+
1636
internal static async Task<ICommand> GetCommandAsync(this IDurableClient durableClient, Guid commandId)
1737
{
1838
if (durableClient is null)

src/TeamCloud.Orchestrator/Command/CommandHandler.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,41 @@
44
*/
55

66
using System;
7+
using System.Collections.Concurrent;
8+
using System.Reflection;
79
using System.Threading.Tasks;
10+
using Jose;
811
using Microsoft.Azure.WebJobs;
912
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
1013
using Microsoft.Extensions.Logging;
14+
using Microsoft.VisualStudio.Services.Common;
1115
using TeamCloud.Model.Commands.Core;
1216

1317
namespace TeamCloud.Orchestrator.Command;
1418

1519
public abstract class CommandHandler<TCommand> : CommandHandler, ICommandHandler<TCommand>
1620
where TCommand : class, ICommand
1721
{
18-
public abstract Task<ICommandResult> HandleAsync(TCommand command, IAsyncCollector<ICommand> commandQueue, IDurableClient orchestrationClient, IDurableOrchestrationContext orchestrationContext, ILogger log);
22+
public abstract Task<ICommandResult> HandleAsync(TCommand command, IAsyncCollector<ICommand> commandQueue, IDurableOrchestrationContext orchestrationContext, ILogger log);
1923
}
2024

2125
public abstract class CommandHandler : ICommandHandler
2226
{
27+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, MethodInfo>> HandleMethodCache = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, MethodInfo>>();
28+
29+
private MethodInfo GetHandleMethod(ICommand command) => HandleMethodCache
30+
.GetOrAdd(GetType(), _ => new ConcurrentDictionary<Type, MethodInfo>())
31+
.GetOrAdd(command.GetType(), commandType =>
32+
{
33+
var handlerInterface = typeof(ICommandHandler<>)
34+
.MakeGenericType(commandType);
35+
36+
if (handlerInterface.IsAssignableFrom(GetType()))
37+
return handlerInterface.GetMethod(nameof(HandleAsync), new Type[] { command.GetType(), typeof(IAsyncCollector<ICommand>), typeof(IDurableOrchestrationContext), typeof(ILogger) });
38+
39+
return null;
40+
});
41+
2342
public const string ProcessorQueue = "command-processor";
2443
public const string MonitorQueue = "command-monitor";
2544

@@ -30,25 +49,16 @@ public virtual bool CanHandle(ICommand command)
3049
if (command is null)
3150
throw new ArgumentNullException(nameof(command));
3251

33-
return typeof(ICommandHandler<>)
34-
.MakeGenericType(command.GetType())
35-
.IsAssignableFrom(GetType());
52+
return GetHandleMethod(command) is not null;
3653
}
3754

38-
public virtual Task<ICommandResult> HandleAsync(ICommand command, IAsyncCollector<ICommand> commandQueue, IDurableClient orchestrationClient, IDurableOrchestrationContext orchestrationContext, ILogger log)
55+
public virtual Task<ICommandResult> HandleAsync(ICommand command, IAsyncCollector<ICommand> commandQueue, IDurableOrchestrationContext orchestrationContext, ILogger log)
3956
{
4057
if (command is null)
4158
throw new ArgumentNullException(nameof(command));
4259

4360
if (CanHandle(command))
44-
{
45-
var handleMethod = typeof(ICommandHandler<>)
46-
.MakeGenericType(command.GetType())
47-
.GetMethod(nameof(HandleAsync), new Type[] { command.GetType(), typeof(IAsyncCollector<ICommand>), typeof(IDurableClient), typeof(IDurableOrchestrationContext), typeof(ILogger) });
48-
49-
return (Task<ICommandResult>)handleMethod
50-
.Invoke(this, new object[] { command, commandQueue, orchestrationClient, orchestrationContext, log });
51-
}
61+
return (Task<ICommandResult>)GetHandleMethod(command).Invoke(this, new object[] { command, commandQueue, orchestrationContext, log });
5262

5363
throw new NotImplementedException($"Missing orchestrator command handler implementation ICommandHandler<{command.GetTypeName(prettyPrint: true)}> at {GetType()}");
5464
}

src/TeamCloud.Orchestrator/Command/CommandOrchestration.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,8 @@ public CommandOrchestration(ICommandHandler[] commandHandlers)
2727
[FunctionName(nameof(CommandOrchestration))]
2828
public async Task Execute(
2929
[OrchestrationTrigger] IDurableOrchestrationContext orchestratorContext,
30-
[DurableClient] IDurableClient orchestratorClient,
31-
[Queue(CommandHandler.ProcessorQueue)] IAsyncCollector<ICommand> commandQueue,
3230
ILogger log)
3331
{
34-
if (orchestratorClient is null)
35-
throw new ArgumentNullException(nameof(orchestratorClient));
36-
3732
if (orchestratorContext is null)
3833
throw new ArgumentNullException(nameof(orchestratorContext));
3934

@@ -60,7 +55,7 @@ await orchestratorContext
6055
.ConfigureAwait(true);
6156

6257
commandResult = await commandHandler
63-
.HandleAsync(command, new CommandCollector(commandQueue, command, orchestratorContext), orchestratorClient, orchestratorContext, log)
58+
.HandleAsync(command, new CommandCollector(orchestratorContext, command), orchestratorContext, log)
6459
.ConfigureAwait(true);
6560

6661
if (commandResult is null)

0 commit comments

Comments
 (0)