private Task <TileMetadata> CanGenerateDxfTiles(string status) { //Set up DataOcean stuff var expectedTopFolderResult = new DataOceanDirectory { Id = Guid.NewGuid(), Name = topLevelFolderName }; var expectedDcFileResult = new DataOceanFile { Id = Guid.NewGuid(), Name = dcFileName, ParentId = expectedTopFolderResult.Id }; var expectedDxfFileResult = new DataOceanFile { Id = Guid.NewGuid(), Name = dxfFileName, ParentId = expectedTopFolderResult.Id }; var subFolderPath = new DataOceanFileUtil(dxfFullName).GeneratedTilesFolder; var parts = subFolderPath.Split(DataOceanUtil.PathSeparator); var subFolderName = parts[parts.Length - 1]; var dataOceanMock = new Mock <IDataOceanClient>(); dataOceanMock.Setup(d => d.GetFileId(dcFullName, null)).ReturnsAsync(expectedDcFileResult.Id); dataOceanMock.Setup(d => d.GetFileId(dxfFullName, null)).ReturnsAsync(expectedDxfFileResult.Id); dataOceanMock.Setup(d => d.MakeFolder(subFolderPath, null)).ReturnsAsync(true); dataOceanMock.Setup(d => d.GetFolderId($"{DataOceanUtil.PathSeparator}{topLevelFolderName}", null)).ReturnsAsync(expectedTopFolderResult.Id); //Set up Pegasus stuff var units = DxfUnitsType.UsSurveyFeet.ToString(); var expectedExecution = NewDxfPegasusExecution(expectedDcFileResult, expectedDxfFileResult, subFolderName, units, status); var expectedExecutionResult = new PegasusExecutionResult { Execution = expectedExecution }; var expectedExecutionAttemptResult = new PegasusExecutionAttemptResult { ExecutionAttempt = new PegasusExecutionAttempt { Id = Guid.NewGuid(), Status = ExecutionStatus.EXECUTING } }; var config = serviceProvider.GetRequiredService <Common.Abstractions.Configuration.IConfigurationStore>(); var pegasusBaseUrl = config.GetValueString("PEGASUS_URL"); var baseRoute = "/api/executions"; var createExecutionUrl = $"{pegasusBaseUrl}{baseRoute}"; var startExecutionUrl = $"{pegasusBaseUrl}{baseRoute}/{expectedExecution.Id}/start"; var executionStatusUrl = $"{pegasusBaseUrl}{baseRoute}/{expectedExecution.Id}"; var gracefulMock = new Mock <IWebRequest>(); gracefulMock .Setup(g => g.ExecuteRequest <PegasusExecutionResult>(createExecutionUrl, It.IsAny <MemoryStream>(), null, HttpMethod.Post, null, 0, false)).ReturnsAsync(expectedExecutionResult); gracefulMock .Setup(g => g.ExecuteRequest <PegasusExecutionAttemptResult>(startExecutionUrl, null, null, HttpMethod.Post, null, 0, false)).ReturnsAsync(expectedExecutionAttemptResult); gracefulMock .Setup(g => g.ExecuteRequest <PegasusExecutionResult>(executionStatusUrl, null, null, HttpMethod.Get, null, 0, false)).ReturnsAsync(expectedExecutionResult); return(ProcessWithSuccess(gracefulMock, dataOceanMock, subFolderPath, true)); }
public async Task CanGenerateGeoTiffTilesFailedToStartExecution() { //Set up DataOcean stuff var expectedTopFolderResult = new DataOceanDirectory { Id = Guid.NewGuid(), Name = topLevelFolderName }; var expectedFileResult = new DataOceanFile { Id = Guid.NewGuid(), Name = geoTiffFileName, ParentId = expectedTopFolderResult.Id }; var subFolderPath = new DataOceanFileUtil(geoTiffFullName).GeneratedTilesFolder; var parts = subFolderPath.Split(DataOceanUtil.PathSeparator); var subFolderName = parts[parts.Length - 1]; var dataOceanMock = new Mock <IDataOceanClient>(); dataOceanMock.Setup(d => d.GetFileId(geoTiffFullName, null)).ReturnsAsync(expectedFileResult.Id); dataOceanMock.Setup(d => d.MakeFolder(subFolderPath, null)).ReturnsAsync(true); dataOceanMock.Setup(d => d.GetFolderId($"{DataOceanUtil.PathSeparator}{topLevelFolderName}", null)).ReturnsAsync(expectedTopFolderResult.Id); //Set up Pegasus stuff var expectedExecution = NewGeoTiffPegasusExecution(expectedFileResult, subFolderName, ExecutionStatus.NOT_READY); var expectedExecutionResult = new PegasusExecutionResult { Execution = expectedExecution }; _ = new PegasusExecutionAttemptResult { ExecutionAttempt = new PegasusExecutionAttempt { Id = Guid.NewGuid(), Status = ExecutionStatus.EXECUTING } }; var config = serviceProvider.GetRequiredService <Common.Abstractions.Configuration.IConfigurationStore>(); var pegasusBaseUrl = config.GetValueString("PEGASUS_URL"); var baseRoute = "/api/executions"; var createExecutionUrl = $"{pegasusBaseUrl}{baseRoute}"; var startExecutionUrl = $"{pegasusBaseUrl}{baseRoute}/{expectedExecution.Id}/start"; var gracefulMock = new Mock <IWebRequest>(); gracefulMock .Setup(g => g.ExecuteRequest <PegasusExecutionResult>(createExecutionUrl, It.IsAny <MemoryStream>(), null, HttpMethod.Post, null, 0, false)).ReturnsAsync(expectedExecutionResult); gracefulMock .Setup(g => g.ExecuteRequest <PegasusExecutionAttemptResult>(startExecutionUrl, null, null, HttpMethod.Post, null, 0, false)).ReturnsAsync((PegasusExecutionAttemptResult)null); await ProcessWithFailure(gracefulMock, dataOceanMock, $"Failed to start execution for {geoTiffFullName}", false); }
/// <summary> /// Generates raster tiles using the Pegasus API and stores them in the data ocean. /// The source is either a DXF file or a GeoTIFF file. /// </summary> /// <param name="fileName">The path and file name of the source file</param> /// <param name="createExecutionMessage">The details of tile generation for Pegasus</param> /// <param name="customHeaders"></param> /// <returns>Metadata for the generated tiles including the zoom range</returns> private async Task <TileMetadata> GenerateTiles(string fileName, CreateExecutionMessage createExecutionMessage, IHeaderDictionary customHeaders, Action <IHeaderDictionary> setJobIdAction) { Log.LogDebug($"Pegasus execution: {JsonConvert.SerializeObject(createExecutionMessage)}"); TileMetadata metadata = null; //Delete any old tiles. To avoid 2 traversals just try the delete anyway without checking for existence. await DeleteTiles(fileName, customHeaders); //In DataOcean this is actually a multifile not a folder string tileFolderFullName = new DataOceanFileUtil(fileName).GeneratedTilesFolder; //Get the parent folder id var parts = tileFolderFullName.Split(DataOceanUtil.PathSeparator); var tileFolderName = parts[parts.Length - 1]; var parentPath = tileFolderFullName.Substring(0, tileFolderFullName.Length - tileFolderName.Length - 1); var parentId = await dataOceanClient.GetFolderId(parentPath, customHeaders); //Set common parameters createExecutionMessage.Execution.Parameters.ParentId = parentId; createExecutionMessage.Execution.Parameters.Name = tileFolderName; createExecutionMessage.Execution.Parameters.TileOrder = TILE_ORDER; createExecutionMessage.Execution.Parameters.MultiFile = "true"; createExecutionMessage.Execution.Parameters.Public = "false"; const string baseRoute = "/api/executions"; var payload = JsonConvert.SerializeObject(createExecutionMessage); PegasusExecutionResult executionResult; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(payload))) { executionResult = await gracefulClient.ExecuteRequest <PegasusExecutionResult>($"{pegasusBaseUrl}{baseRoute}", ms, customHeaders, HttpMethod.Post); } if (executionResult == null) { throw new ServiceException(HttpStatusCode.InternalServerError, new ContractExecutionResult(ContractExecutionStatesEnum.InternalProcessingError, $"Failed to create execution for {fileName}")); } setJobIdAction?.Invoke(new HeaderDictionary { { PEGASUS_LOG_JOBID_KEY, executionResult.Execution.Id.ToString() } }); //2. Start the execution Log.LogDebug($"Starting execution for {fileName}"); var executionRoute = $"{baseRoute}/{executionResult.Execution.Id}"; var startExecutionRoute = $"{executionRoute}/start"; var startResult = await gracefulClient.ExecuteRequest <PegasusExecutionAttemptResult>($"{pegasusBaseUrl}{startExecutionRoute}", null, customHeaders, HttpMethod.Post); if (startResult == null) { throw new ServiceException(HttpStatusCode.InternalServerError, new ContractExecutionResult(ContractExecutionStatesEnum.InternalProcessingError, $"Failed to start execution for {fileName}")); } //3. Monitor status of execution until done Log.LogDebug($"Monitoring execution status for {fileName}"); var endJob = DateTime.Now + TimeSpan.FromMinutes(executionTimeout); var done = false; var success = true; while (!done && DateTime.Now <= endJob) { if (executionWaitInterval > 0) { await Task.Delay(executionWaitInterval); } var policyResult = await Policy .Handle <Exception>() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(1000), (exception, calculatedWaitDuration) => { Log.LogError(exception, $"PollyAsync: Failed attempt to query Pegasus. Jobid {executionResult.Execution.Id.ToString()}"); }) .ExecuteAndCaptureAsync(async() => { Log.LogDebug($"Executing monitoring request for {fileName} and jobid {executionResult.Execution.Id.ToString()}"); executionResult = await gracefulClient.ExecuteRequest <PegasusExecutionResult>($"{pegasusBaseUrl}{executionRoute}", null, customHeaders, HttpMethod.Get); var status = executionResult.Execution.ExecutionStatus; success = string.Compare(status, ExecutionStatus.FINISHED, StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(status, ExecutionStatus.SUCCEEDED, StringComparison.OrdinalIgnoreCase) == 0; if (string.Compare(status, ExecutionStatus.FAILED, StringComparison.OrdinalIgnoreCase) == 0) { //Try to retrieve why it failed var jobEventsStream = await gracefulClient.ExecuteRequestAsStreamContent($"{pegasusBaseUrl}{executionRoute}/events", HttpMethod.Get, customHeaders); if (jobEventsStream != null) { var jobEvents = await jobEventsStream.ReadAsStringAsync(); Log.LogError($"Pegasus job {executionResult.Execution.Id} failed to execute with the events: {jobEvents}"); setJobIdAction?.Invoke(new HeaderDictionary { { PEGASUS_LOG_EVENTS_KEY, jobEvents } }); } else { Log.LogDebug($"Unable to resolve jobEventsStream for execution id {executionResult.Execution.Id}"); } } done = success || string.Compare(status, ExecutionStatus.FAILED, StringComparison.OrdinalIgnoreCase) == 0; setJobIdAction?.Invoke(new HeaderDictionary { { PEGASUS_LOG_RESULT_KEY, status } }); Log.LogDebug($"Execution status {status} for {fileName} and jobid {executionResult.Execution.Id.ToString()}"); }); if (policyResult.FinalException != null) { Log.LogCritical(policyResult.FinalException, $"TileGeneration PollyAsync: {GetType().FullName} failed with exception for jobid {executionResult.Execution.Id.ToString()}: "); throw policyResult.FinalException; } } if (!done) { Log.LogInformation($"{nameof(GenerateTiles)} timed out: {fileName}"); } else if (!success) { Log.LogInformation($"{nameof(GenerateTiles)} failed: {fileName}"); throw new ServiceException(HttpStatusCode.InternalServerError, new ContractExecutionResult(ContractExecutionStatesEnum.InternalProcessingError, $"Failed to generate tiles for {fileName}")); } if (success) { /* * Can't delete as not mutable * * //4. Delete the execution * Log.LogDebug($"Deleting execution for {dxfFileName}"); * await gracefulClient.ExecuteRequest($"{pegasusBaseUrl}{executionRoute}", null, customHeaders, HttpMethod.Delete, null, 0, false); */ //5. Get the zoom range from the tile metadata file var metadataFileName = new DataOceanFileUtil(fileName).TilesMetadataFileName; Log.LogDebug($"Getting tiles metadata for {metadataFileName}"); var stream = await dataOceanClient.GetFile(metadataFileName, customHeaders); using (var sr = new StreamReader(stream)) using (var jtr = new JsonTextReader(sr)) { metadata = new JsonSerializer().Deserialize <TileMetadata>(jtr); } } Log.LogInformation($"{nameof(GenerateTiles)}: returning {(metadata == null ? "null" : JsonConvert.SerializeObject(metadata))}"); return(metadata); }