private static async Task RaiseEventIfFileExists(DurableOrchestrationClient starter, TraceWriter log, string instanceId, string folderName, string batchId, string filename) { var concatenatedFilename = $"{batchId}_{filename}"; if (File.Exists(Path.Combine(folderName, concatenatedFilename))) { log.Info($"*** TRIGGER: file {concatenatedFilename} for batch {batchId} - found"); await starter.RaiseEventAsync(instanceId, EventNames.NewFile(filename), null); } else { log.Info($"*** TRIGGER: file {concatenatedFilename} for batch {batchId} - missing"); } }
public static async Task <BatchResponse> RunOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log) { var batchContext = context.GetInput <BatchContext>(); log.Info($"*** ORCHESTRATOR Starting: {batchContext.BatchId} (folder: {batchContext.FolderName})"); using (var cts = new CancellationTokenSource()) { // Wait for events for all required files var requiredFileTasks = batchContext.RequiredFiles .Select(f => context.WaitForExternalEvent <object>(EventNames.NewFile(f))) .ToArray(); var gotFilesTask = Task.WhenAll(requiredFileTasks); var timeoutTask = context.CreateTimer(context.CurrentUtcDateTime.Add(WaitForFilesTimeout), cts.Token); var firedTask = await Task.WhenAny(gotFilesTask, timeoutTask); if (firedTask == timeoutTask) { log.Info($"*** ORCHESTRATOR Timeout waiting for batch files for batch {batchContext.BatchId}"); // TODO take whatever action is required here (e.g. escalate for human intervention) return(new BatchResponse { BatchId = batchContext.BatchId, Success = false }); } cts.Cancel(); // cancel the timeout and continue processing... } // Currently process all files in a single activity function // If each file can be processed independently then could split into multiple activity invocations log.Info($"** ORCHESTRATOR calling Process files"); await context.CallActivityAsync("ProcessFiles", batchContext); log.Info($"*** ORCHESTRATOR Done: {batchContext.BatchId} (folder: {batchContext.FolderName})"); return(new BatchResponse { BatchId = batchContext.BatchId, Success = true }); }
public static async Task <HttpResponseMessage> HttpStart( // TODO - replace HttpTrigger with eventgrid trigger for blobs [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req, [OrchestrationClient] DurableOrchestrationClient starter, TraceWriter log) { // Determine path var query = req.RequestUri.ParseQueryString(); var path = query["path"]; if (string.IsNullOrEmpty(path)) { log.Error("*** path querystring value missing"); return(req.CreateResponse(HttpStatusCode.BadRequest, "path querystring value missing", new JsonMediaTypeFormatter())); } // Get context (required files, instance id, ...) var batchContext = GetBatchContextFromPath(path); var instanceId = $"instance-{batchContext.BatchId}"; // Determin if the path is for a file that we care about var filename = Path.GetFileName(path); if (batchContext.RequiredFiles.Any(f => $"{batchContext.BatchId}_{f}" == filename)) { log.Info($"*** Batch {batchContext.BatchId} - notification for {filename}"); } else { log.Info($"*** Ignoring path: {path}"); return(req.CreateResponse(HttpStatusCode.NoContent, "Path ignored", new JsonMediaTypeFormatter())); } // Find or start an orchestration instance log.Info($"*** TRIGGER: Looking up instance: {instanceId}"); var status = await starter.GetStatusAsync(instanceId); if (status == null) { log.Info($"*** TRIGGER: no instance found - {instanceId} - starting..."); await starter.StartNewAsync("StorageBatches", instanceId, batchContext); log.Info($"*** TRIGGER: Started orchestration with ID = '{instanceId}'."); // workaround for https://github.com/Azure/azure-functions-durable-extension/issues/101 log.Info($"*** TRIGGER: Checking for orchestration with ID {instanceId}..."); while (null == await starter.GetStatusAsync(instanceId)) { System.Threading.Thread.Sleep(500); log.Info($"*** TRIGGER: Checking for orchestration with ID {instanceId}..."); } log.Info($"*** TRIGGER: Checking for orchestration with ID {instanceId}... found it!"); } else { log.Info($"*** TRIGGER: Got existing instance for {instanceId} (name {status.Name}). status {status.RuntimeStatus})"); } // Raise event for the file that triggered us log.Info($"*** TRIGGER: {instanceId}: Raising event for file {filename}"); await starter.RaiseEventAsync(instanceId, EventNames.NewFile(filename), null); return(starter.CreateCheckStatusResponse(req, instanceId)); }