コード例 #1
0
        public ExportFileManagerTests()
        {
            _exportDestinationClient = Substitute.For <IExportDestinationClient>();
            _exportDestinationClient
            .CreateFileAsync(Arg.Any <string>(), Arg.Any <CancellationToken>())
            .Returns <Uri>(callInfo => new Uri("https://localhost/" + callInfo.ArgAt <string>(0)));

            _exportJobConfigurationFormat = $"{ExportFormatTags.ResourceName}";
            _exportJobRecord = new ExportJobRecord(
                new Uri("https://localhost/$export"),
                ExportJobType.All,
                _exportJobConfigurationFormat,
                resourceType: null,
                filters: null,
                "hash",
                rollingFileSizeInMB: 1);

            _exportFileManager = new ExportFileManager(_exportJobRecord, _exportDestinationClient);
        }
コード例 #2
0
        public async Task GivenStorageAccountUriChanged_WhenExecuted_ThenRecordsAreSentToOldStorageAccount()
        {
            var exportJobRecordWithChangedConnection = new ExportJobRecord(
                new Uri("https://localhost/ExportJob/"),
                "Patient",
                "hash",
                since: PartialDateTime.MinValue,
                storageAccountConnectionHash: Microsoft.Health.Core.Extensions.StringExtensions.ComputeHash(_exportJobConfiguration.StorageAccountConnection),
                storageAccountUri: "origionalUri");

            SetupExportJobRecordAndOperationDataStore(exportJobRecordWithChangedConnection);

            ExportJobConfiguration configurationWithUri = new ExportJobConfiguration();

            configurationWithUri.StorageAccountUri = "newUri";

            IExportDestinationClient mockDestinationClient = Substitute.For <IExportDestinationClient>();
            ExportJobConfiguration   capturedConfiguration = null;

            mockDestinationClient.ConnectAsync(
                Arg.Do <ExportJobConfiguration>(arg => capturedConfiguration = arg),
                Arg.Any <CancellationToken>(),
                Arg.Any <string>())
            .Returns(x =>
            {
                return(Task.CompletedTask);
            });

            var exportJobTask = new ExportJobTask(
                () => _fhirOperationDataStore.CreateMockScope(),
                Options.Create(_exportJobConfiguration),
                () => _searchService.CreateMockScope(),
                _resourceToByteArraySerializer,
                mockDestinationClient,
                NullLogger <ExportJobTask> .Instance);

            await exportJobTask.ExecuteAsync(_exportJobRecord, _weakETag, _cancellationToken);

            Assert.Equal(exportJobRecordWithChangedConnection.StorageAccountUri, capturedConfiguration.StorageAccountUri);
        }
コード例 #3
0
        public ExportJobTask(
            Func <IScoped <IFhirOperationDataStore> > fhirOperationDataStoreFactory,
            IOptions <ExportJobConfiguration> exportJobConfiguration,
            Func <IScoped <ISearchService> > searchServiceFactory,
            IResourceToByteArraySerializer resourceToByteArraySerializer,
            IExportDestinationClient exportDestinationClient,
            ILogger <ExportJobTask> logger)
        {
            EnsureArg.IsNotNull(fhirOperationDataStoreFactory, nameof(fhirOperationDataStoreFactory));
            EnsureArg.IsNotNull(exportJobConfiguration?.Value, nameof(exportJobConfiguration));
            EnsureArg.IsNotNull(searchServiceFactory, nameof(searchServiceFactory));
            EnsureArg.IsNotNull(resourceToByteArraySerializer, nameof(resourceToByteArraySerializer));
            EnsureArg.IsNotNull(exportDestinationClient, nameof(exportDestinationClient));
            EnsureArg.IsNotNull(logger, nameof(logger));

            _fhirOperationDataStoreFactory = fhirOperationDataStoreFactory;
            _exportJobConfiguration        = exportJobConfiguration.Value;
            _searchServiceFactory          = searchServiceFactory;
            _resourceToByteArraySerializer = resourceToByteArraySerializer;
            _exportDestinationClient       = exportDestinationClient;
            _logger = logger;
        }
コード例 #4
0
        public async Task GivenConnectingToDestinationFails_WhenExecuted_ThenJobStatusShouldBeUpdatedToFailed()
        {
            // Setup export destination client.
            string connectionFailure = "failedToConnectToDestination";
            IExportDestinationClient mockExportDestinationClient = Substitute.For <IExportDestinationClient>();

            mockExportDestinationClient.ConnectAsync(Arg.Any <CancellationToken>(), Arg.Any <string>())
            .Returns <Task>(x => throw new DestinationConnectionException(connectionFailure, HttpStatusCode.BadRequest));

            var exportJobTask = new ExportJobTask(
                () => _fhirOperationDataStore.CreateMockScope(),
                Options.Create(_exportJobConfiguration),
                () => _searchService.CreateMockScope(),
                _resourceToByteArraySerializer,
                mockExportDestinationClient,
                NullLogger <ExportJobTask> .Instance);

            await exportJobTask.ExecuteAsync(_exportJobRecord, _weakETag, _cancellationToken);

            Assert.NotNull(_lastExportJobOutcome);
            Assert.Equal(OperationStatus.Failed, _lastExportJobOutcome.JobRecord.Status);
            Assert.Equal(connectionFailure, _lastExportJobOutcome.JobRecord.FailureDetails.FailureReason);
            Assert.Equal(HttpStatusCode.BadRequest, _lastExportJobOutcome.JobRecord.FailureDetails.FailureStatusCode);
        }
コード例 #5
0
        public ExportJobTask(
            Func <IScoped <IFhirOperationDataStore> > fhirOperationDataStoreFactory,
            IOptions <ExportJobConfiguration> exportJobConfiguration,
            Func <IScoped <ISearchService> > searchServiceFactory,
            IGroupMemberExtractor groupMemberExtractor,
            IResourceToByteArraySerializer resourceToByteArraySerializer,
            IExportDestinationClient exportDestinationClient,
            IResourceDeserializer resourceDeserializer,
            IScoped <IAnonymizerFactory> anonymizerFactory,
            IMediator mediator,
            IFhirRequestContextAccessor contextAccessor,
            ILogger <ExportJobTask> logger)
        {
            EnsureArg.IsNotNull(fhirOperationDataStoreFactory, nameof(fhirOperationDataStoreFactory));
            EnsureArg.IsNotNull(exportJobConfiguration?.Value, nameof(exportJobConfiguration));
            EnsureArg.IsNotNull(searchServiceFactory, nameof(searchServiceFactory));
            EnsureArg.IsNotNull(groupMemberExtractor, nameof(groupMemberExtractor));
            EnsureArg.IsNotNull(resourceToByteArraySerializer, nameof(resourceToByteArraySerializer));
            EnsureArg.IsNotNull(exportDestinationClient, nameof(exportDestinationClient));
            EnsureArg.IsNotNull(resourceDeserializer, nameof(resourceDeserializer));
            EnsureArg.IsNotNull(mediator, nameof(mediator));
            EnsureArg.IsNotNull(contextAccessor, nameof(contextAccessor));
            EnsureArg.IsNotNull(logger, nameof(logger));

            _fhirOperationDataStoreFactory = fhirOperationDataStoreFactory;
            _exportJobConfiguration        = exportJobConfiguration.Value;
            _searchServiceFactory          = searchServiceFactory;
            _groupMemberExtractor          = groupMemberExtractor;
            _resourceToByteArraySerializer = resourceToByteArraySerializer;
            _resourceDeserializer          = resourceDeserializer;
            _exportDestinationClient       = exportDestinationClient;
            _anonymizerFactory             = anonymizerFactory;
            _mediator        = mediator;
            _contextAccessor = contextAccessor;
            _logger          = logger;
        }
コード例 #6
0
ファイル: ExportJobTask.cs プロジェクト: jbuiss0n/fhir-server
        /// <inheritdoc />
        public async Task ExecuteAsync(ExportJobRecord exportJobRecord, WeakETag weakETag, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(exportJobRecord, nameof(exportJobRecord));

            _exportJobRecord = exportJobRecord;
            _weakETag        = weakETag;

            try
            {
                // Get destination type from secret store.
                DestinationInfo destinationInfo = await GetDestinationInfo(cancellationToken);

                // Connect to the destination using appropriate client.
                _exportDestinationClient = _exportDestinationClientFactory.Create(destinationInfo.DestinationType);

                await _exportDestinationClient.ConnectAsync(destinationInfo.DestinationConnectionString, cancellationToken, _exportJobRecord.Id);

                // TODO: For now, always restart from the beginning. We will support resume in another work item.
                _exportJobRecord.Progress = new ExportJobProgress(continuationToken: null, page: 0);

                ExportJobProgress progress = _exportJobRecord.Progress;

                // Current page will be used to organize a set of search results into a group so that they can be committed together.
                uint currentBatchId = progress.Page;

                // The first item is placeholder for continuation token so that it can be updated efficiently later.
                var queryParameters = new Tuple <string, string>[]
                {
                    null,
                    Tuple.Create(KnownQueryParameterNames.Count, _exportJobConfiguration.MaximumNumberOfResourcesPerQuery.ToString(CultureInfo.InvariantCulture)),
                    Tuple.Create(KnownQueryParameterNames.LastUpdated, $"le{_exportJobRecord.QueuedTime.ToString("o", CultureInfo.InvariantCulture)}"),
                };

                // Process the export if:
                // 1. There is continuation token, which means there is more resource to be exported.
                // 2. There is no continuation token but the page is 0, which means it's the initial export.
                while (progress.ContinuationToken != null || progress.Page == 0)
                {
                    // Commit the changes if necessary.
                    if (progress.Page != 0 && progress.Page % _exportJobConfiguration.NumberOfPagesPerCommit == 0)
                    {
                        await _exportDestinationClient.CommitAsync(cancellationToken);

                        // Update the job record.
                        await UpdateJobRecord(_exportJobRecord, cancellationToken);

                        currentBatchId = progress.Page;
                    }

                    // Set the continuation token.
                    queryParameters[0] = Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken);

                    SearchResult searchResult = await _searchService.SearchAsync(_exportJobRecord.ResourceType, queryParameters, cancellationToken);

                    foreach (ResourceWrapper resourceWrapper in searchResult.Results)
                    {
                        await ProcessResourceWrapperAsync(resourceWrapper, currentBatchId, cancellationToken);
                    }

                    if (searchResult.ContinuationToken == null)
                    {
                        // No more continuation token, we are done.
                        break;
                    }

                    // Update the job record.
                    progress.UpdateContinuationToken(searchResult.ContinuationToken);
                }

                // Commit one last time for any pending changes.
                await _exportDestinationClient.CommitAsync(cancellationToken);

                _exportJobRecord.Output.AddRange(_resourceTypeToFileInfoMapping.Values);

                _logger.LogTrace("Successfully completed the job.");

                await UpdateJobStatus(OperationStatus.Completed, updateEndTimestamp : true, cancellationToken);

                try
                {
                    // Best effort to delete the secret. If it fails to delete, then move on.
                    await _secretStore.DeleteSecretAsync(_exportJobRecord.SecretName, cancellationToken);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to delete the secret.");
                }
            }
            catch (JobConflictException)
            {
                // The job was updated by another process.
                _logger.LogWarning("The job was updated by another process.");

                // TODO: We will want to get the latest and merge the results without updating the status.
                return;
            }
            catch (Exception ex)
            {
                // The job has encountered an error it cannot recover from.
                // Try to update the job to failed state.
                _logger.LogError(ex, "Encountered an unhandled exception. The job will be marked as failed.");

                await UpdateJobStatus(OperationStatus.Failed, updateEndTimestamp : true, cancellationToken);
            }
        }