public static bool ProgressBlobReplication(string sourceAccount, string destAccount, string container, string blobName, string copyId, int?waitDelay = null) { bool retval = false; ICloudBlob destBlob = null; try { var destContainer = DashConfiguration.GetDataAccountByAccountName(destAccount).CreateCloudBlobClient().GetContainerReference(container); destBlob = destContainer.GetBlobReferenceFromServer(blobName); retval = ProcessBlobCopyStatus(destBlob, sourceAccount, copyId, waitDelay); } catch (StorageException ex) { // Classify the errors into retryable & non-retryable retval = !RecoverReplicationError(ex, destBlob, String.Format("Storage error checking replication progress for blob [{0}][{1}] to account [{2}]. Details: {3}", sourceAccount, PathUtils.CombineContainerAndBlob(container, blobName), destAccount, ex)); } catch (Exception ex) { DashTrace.TraceWarning("Error checking replication progress for blob [{0}][{1}] to account [{2}]. Details: {3}", sourceAccount, PathUtils.CombineContainerAndBlob(container, blobName), destAccount, ex); } return(retval); }
/// <summary> /// Generic function to redirect a put request for properties of a blob /// </summary> public static async Task <HandlerResult> BasicBlobAsync(IHttpRequestWrapper requestWrapper, string container, string blob, bool servePrimaryOnly, bool operationCanReplicateBlob) { return(await WebOperationRunner.DoHandlerAsync("BlobHandler.BasicBlobAsync", async() => { var namespaceBlob = await NamespaceHandler.FetchNamespaceBlobAsync(container, blob); if (!await namespaceBlob.ExistsAsync()) { return new HandlerResult { StatusCode = HttpStatusCode.NotFound, }; } string accountName = namespaceBlob.SelectDataAccount(servePrimaryOnly); if (operationCanReplicateBlob) { if (namespaceBlob.IsReplicated || BlobReplicationHandler.ShouldReplicateBlob(requestWrapper.Headers, container, blob)) { accountName = namespaceBlob.PrimaryAccountName; await BlobReplicationHandler.EnqueueBlobReplicationAsync(namespaceBlob, false); } } Uri redirect = ControllerOperations.GetRedirectUri(HttpContextFactory.Current.Request, DashConfiguration.GetDataAccountByAccountName(accountName), namespaceBlob.Container, namespaceBlob.BlobName, false); return HandlerResult.Redirect(requestWrapper, redirect); })); }
public static async Task <HandlerResult> AbortCopyBlobAsync(IHttpRequestWrapper requestWrapper, string destContainer, string destBlob, string copyId) { return(await WebOperationRunner.DoHandlerAsync("BlobHandler.AbortCopyBlobAsync", async() => { var destNamespaceBlob = await NamespaceHandler.FetchNamespaceBlobAsync(destContainer, destBlob); var destCloudBlob = NamespaceHandler.GetBlobByName( DashConfiguration.GetDataAccountByAccountName(destNamespaceBlob.PrimaryAccountName), destContainer, destBlob); await destCloudBlob.AbortCopyAsync(copyId); return new HandlerResult { StatusCode = HttpStatusCode.NoContent, }; })); }
public static async Task <HandlerResult> PutBlobAsync(IHttpRequestWrapper requestWrapper, string container, string blob, bool operationCanReplicateBlob) { return(await WebOperationRunner.DoHandlerAsync("BlobHandler.PutBlobAsync", async() => { var namespaceBlob = await NamespaceHandler.CreateNamespaceBlobAsync(container, blob); if (operationCanReplicateBlob) { if (BlobReplicationHandler.ShouldReplicateBlob(requestWrapper.Headers, container, blob)) { await BlobReplicationHandler.EnqueueBlobReplicationAsync(namespaceBlob, false); } } Uri redirect = ControllerOperations.GetRedirectUri(HttpContextFactory.Current.Request, DashConfiguration.GetDataAccountByAccountName(namespaceBlob.PrimaryAccountName), container, blob, false); return HandlerResult.Redirect(requestWrapper, redirect); })); }
public static bool DeleteReplica(string accountName, string container, string blobName, string eTag) { bool retval = false; ICloudBlob replicaBlob = null; try { DashTrace.TraceInformation("Deleting replica blob [{0}] from account [{1}]", PathUtils.CombineContainerAndBlob(container, blobName), accountName); var blobContainer = DashConfiguration.GetDataAccountByAccountName(accountName).CreateCloudBlobClient().GetContainerReference(container); replicaBlob = blobContainer.GetBlobReferenceFromServer(blobName); AccessCondition accessCondition = null; if (!String.IsNullOrWhiteSpace(eTag)) { accessCondition = AccessCondition.GenerateIfMatchCondition(eTag); } replicaBlob.Delete(DeleteSnapshotsOption.IncludeSnapshots, accessCondition); FinalizeBlobReplication(accountName, container, blobName, true); retval = true; } catch (StorageException ex) { // Classify the errors into retryable & non-retryable retval = !RecoverReplicationError(ex, replicaBlob, String.Format("Storage error deleting replica blob [{0}] from account [{1}]. Details: {2}", PathUtils.CombineContainerAndBlob(container, blobName), accountName, ex)); } catch (Exception ex) { DashTrace.TraceWarning("Error deleting replica blob [{0}] from account [{1}]. Details: {2}", PathUtils.CombineContainerAndBlob(container, blobName), accountName, ex); } return(retval); }
public static async Task ImportAccountAsync(string accountName) { await OperationRunner.DoActionAsync(String.Format("Importing data account: {0}", accountName), async() => { // This method will only import the blobs into the namespace. A future task may be // to redistribute the blobs to balance the entire virtual account. var account = DashConfiguration.GetDataAccountByAccountName(accountName); if (account == null) { DashTrace.TraceWarning("Failure importing storage account: {0}. The storage account has not been configured as part of this virtual account", accountName); return; } // Check if we've already imported this account var accountClient = account.CreateCloudBlobClient(); var namespaceClient = DashConfiguration.NamespaceAccount.CreateCloudBlobClient(); var accountContainers = await ListContainersAsync(accountClient); var status = await AccountStatus.GetAccountStatus(accountName); await status.UpdateStatusInformation(AccountStatus.States.Healthy, "Importing storage account: {0} into virtual account", accountName); bool alreadyImported = false; await GetAccountBlobs(accountClient, async(blobItem) => { var blob = (ICloudBlob)blobItem; var namespaceBlob = await NamespaceBlob.FetchForBlobAsync( (CloudBlockBlob)NamespaceHandler.GetBlobByName(DashConfiguration.NamespaceAccount, blob.Container.Name, blob.Name, blob.IsSnapshot ? blob.SnapshotTime.ToString() : String.Empty)); alreadyImported = await namespaceBlob.ExistsAsync(true) && namespaceBlob.DataAccounts.Contains(accountName, StringComparer.OrdinalIgnoreCase); return(false); }); if (alreadyImported) { await status.UpdateStatusWarning("Importing storage account: {0} has already been imported. This account cannot be imported again.", accountName); return; } // Sync the container structure first - add containers in the imported account to the virtual account await status.UpdateStatusInformation("Importing storage account: {0}. Synchronizing container structure", accountName); int containersAddedCount = 0, containersWarningCount = 0; var namespaceContainers = await ListContainersAsync(namespaceClient); await ProcessContainerDifferencesAsync(accountContainers, namespaceContainers, async(newContainerName, accountContainer) => { var createContainerResult = await ContainerHandler.DoForAllContainersAsync(newContainerName, HttpStatusCode.Created, async newContainer => await CopyContainer(accountContainer, newContainer), true, new[] { account }); if (createContainerResult.StatusCode < HttpStatusCode.OK || createContainerResult.StatusCode >= HttpStatusCode.Ambiguous) { await status.UpdateStatusWarning("Importing storage account: {0}. Failed to create container: {1} in virtual account. Details: {2}, {3}", accountName, newContainerName, createContainerResult.StatusCode.ToString(), createContainerResult.ReasonPhrase); containersWarningCount++; } else { containersAddedCount++; } }, (newContainerName, ex) => { status.UpdateStatusWarning("Importing storage account: {0}. Error processing container {1}. Details: {2}", accountName, newContainerName, ex.ToString()).Wait(); containersWarningCount++; }); // Sync the other way await ProcessContainerDifferencesAsync(namespaceContainers, accountContainers, async(newContainerName, namespaceContainer) => { await CopyContainer(namespaceContainer, accountClient.GetContainerReference(newContainerName)); }, (newContainerName, ex) => { status.UpdateStatusWarning("Importing storage account: {0}. Error replicating container {1} to imported account. Details: {2}", accountName, newContainerName, ex.ToString()).Wait(); containersWarningCount++; }); DashTrace.TraceInformation("Importing storage account: {0}. Synchronized containers structure. {1} containers added to virtual account. {2} failures/warnings.", accountName, containersAddedCount, containersWarningCount); // Start importing namespace entries await status.UpdateStatusInformation("Importing storage account: {0}. Adding blob entries to namespace", accountName); int blobsAddedCount = 0, warningCount = 0, duplicateCount = 0; await GetAccountBlobs(accountClient, async(blobItem) => { var blob = (ICloudBlob)blobItem; try { var namespaceBlob = await NamespaceBlob.FetchForBlobAsync( (CloudBlockBlob)NamespaceHandler.GetBlobByName(DashConfiguration.NamespaceAccount, blob.Container.Name, blob.Name, blob.IsSnapshot ? blob.SnapshotTime.ToString() : String.Empty)); if (await namespaceBlob.ExistsAsync()) { if (!String.Equals(namespaceBlob.PrimaryAccountName, accountName, StringComparison.OrdinalIgnoreCase)) { await status.UpdateStatusWarning("Importing storage account: {0}. Adding blob: {1}/{2} would result in a duplicate blob entry. This blob will NOT be imported into the virtual account. Manually add the contents of this blob to the virtual account.", accountName, blob.Container.Name, blob.Name); duplicateCount++; } } else { namespaceBlob.PrimaryAccountName = accountName; namespaceBlob.Container = blob.Container.Name; namespaceBlob.BlobName = blob.Name; namespaceBlob.IsMarkedForDeletion = false; await namespaceBlob.SaveAsync(); blobsAddedCount++; } } catch (StorageException ex) { status.UpdateStatusWarning("Importing storage account: {0}. Error importing blob: {0}/{1} into virtual namespace. Details: {3}", accountName, blob.Container.Name, blob.Name, ex.ToString()).Wait(); warningCount++; } return(true); }); if (status.State < AccountStatus.States.Warning) { await status.UpdateStatus(String.Empty, AccountStatus.States.Unknown, TraceLevel.Off); } DashTrace.TraceInformation("Successfully imported the contents of storage account: '{0}' into the virtual namespace. Blobs added: {1}, duplicates detected: {2}, errors encountered: {3}", accountName, blobsAddedCount, duplicateCount, warningCount); }, ex => { var status = AccountStatus.GetAccountStatus(accountName).Result; status.UpdateStatusWarning("Error importing storage account: {0} into virtual account. Details: {1}", accountName, ex.ToString()).Wait(); }, false, true); }
public static async Task <HandlerResult> CopyBlobAsync(IHttpRequestWrapper requestWrapper, string destContainer, string destBlob, string source) { return(await WebOperationRunner.DoHandlerAsync(String.Format("BlobHandler.CopyBlobAsync: {0}/{1} from {2}", destContainer, destBlob, source), async() => { // source is a naked URI supplied by client Uri sourceUri; if (Uri.TryCreate(source, UriKind.RelativeOrAbsolute, out sourceUri)) { string sourceContainer = String.Empty; string sourceBlobName = String.Empty; string sourceQuery = String.Empty; BlobType sourceBlobType = BlobType.BlockBlob; var requestVersion = requestWrapper.Headers.Value("x-ms-version", StorageServiceVersions.Version_2009_09_19); bool processRelativeSource = false; if (!sourceUri.IsAbsoluteUri) { if (requestVersion >= StorageServiceVersions.Version_2012_02_12) { // 2012-02-12 onwards doesn't accept relative URIs return new HandlerResult { StatusCode = HttpStatusCode.BadRequest, }; } // Make sourceUri absolute here because a bunch of Uri functionality fails for relative URIs sourceUri = new Uri(new Uri("http://dummyhost"), sourceUri); processRelativeSource = true; } if (processRelativeSource || (String.Equals(sourceUri.Host, requestWrapper.Url.Host, StringComparison.OrdinalIgnoreCase) && ((sourceUri.IsDefaultPort && requestWrapper.Url.IsDefaultPort) || (sourceUri.Port == requestWrapper.Url.Port)))) { var segments = PathUtils.GetPathSegments(sourceUri.AbsolutePath); if (processRelativeSource) { // Blob in named container: /accountName/containerName/blobName // Snapshot in named container: /accountName/containerName/blobName?snapshot=<DateTime> // Blob in root container: /accountName/blobName // Snapshot in root container: /accountName/blobName?snapshot=<DateTime> if (!String.Equals(segments.FirstOrDefault(), DashConfiguration.AccountName)) { return new HandlerResult { StatusCode = HttpStatusCode.BadRequest, ErrorInformation = new DashErrorInformation { ErrorCode = "CopyAcrossAccountsNotSupported", ErrorMessage = "The copy source account and destination account must be the same.", }, }; } if (segments.Count() == 2) { sourceContainer = "root"; sourceBlobName = segments[1]; } else if (segments.Count() > 2) { sourceContainer = segments[1]; sourceBlobName = PathUtils.CombinePathSegments(segments.Skip(2)); } } else { sourceContainer = segments.FirstOrDefault(); sourceBlobName = PathUtils.CombinePathSegments(segments.Skip(1)); } } var destNamespaceBlob = await NamespaceHandler.FetchNamespaceBlobAsync(destContainer, destBlob); string destAccount = String.Empty; if (!String.IsNullOrEmpty(sourceContainer) && !String.IsNullOrEmpty(sourceBlobName)) { var sourceQueryParams = HttpUtility.ParseQueryString(sourceUri.Query); var sourceNamespaceBlob = await NamespaceHandler.FetchNamespaceBlobAsync(sourceContainer, sourceBlobName, sourceQueryParams["snapshot"]); if (!await sourceNamespaceBlob.ExistsAsync()) { // This isn't actually documented (what happens when the source doesn't exist), but by obervation the service emits 404 return new HandlerResult { StatusCode = HttpStatusCode.NotFound, }; } var sourceCloudContainer = NamespaceHandler.GetContainerByName( DashConfiguration.GetDataAccountByAccountName(sourceNamespaceBlob.PrimaryAccountName), sourceContainer); sourceBlobType = sourceCloudContainer.GetBlobReferenceFromServer(sourceBlobName).BlobType; // This is effectively an intra-account copy which is expected to be atomic. Therefore, even if the destination already // exists, we need to place the destination in the same data account as the source. // If the destination blob already exists, we delete it below to prevent an orphaned data blob destAccount = sourceNamespaceBlob.PrimaryAccountName; var sourceUriBuilder = ControllerOperations.GetRedirectUriBuilder("GET", requestWrapper.Url.Scheme, DashConfiguration.GetDataAccountByAccountName(destAccount), sourceContainer, sourceBlobName, false, String.Empty); sourceUri = sourceUriBuilder.Uri; } else if (await destNamespaceBlob.ExistsAsync()) { destAccount = destNamespaceBlob.PrimaryAccountName; } else { destAccount = NamespaceHandler.GetDataStorageAccountForBlob(destBlob).Credentials.AccountName; } bool replicateDestination = false; if (await destNamespaceBlob.ExistsAsync() && destNamespaceBlob.PrimaryAccountName != destAccount) { // Delete the existing blob to prevent orphaning it replicateDestination = destNamespaceBlob.IsReplicated; var dataBlob = NamespaceHandler.GetBlobByName( DashConfiguration.GetDataAccountByAccountName(destNamespaceBlob.PrimaryAccountName), destContainer, destBlob); await dataBlob.DeleteIfExistsAsync(); } destNamespaceBlob.PrimaryAccountName = destAccount; destNamespaceBlob.Container = destContainer; destNamespaceBlob.BlobName = destBlob; destNamespaceBlob.IsMarkedForDeletion = false; await destNamespaceBlob.SaveAsync(); // Now that we've got the metadata tucked away - do the actual copy var destCloudContainer = NamespaceHandler.GetContainerByName(DashConfiguration.GetDataAccountByAccountName(destAccount), destContainer); ICloudBlob destCloudBlob = null; if (sourceBlobType == BlobType.PageBlob) { destCloudBlob = destCloudContainer.GetPageBlobReference(destBlob); } else { destCloudBlob = destCloudContainer.GetBlockBlobReference(destBlob); } // Storage client will retry failed copy. Let our clients decide that. var copyId = await destCloudBlob.StartCopyFromBlobAsync(sourceUri, AccessCondition.GenerateEmptyCondition(), AccessCondition.GenerateEmptyCondition(), new BlobRequestOptions { RetryPolicy = new NoRetry(), }, new OperationContext()); // Check if we should replicate the copied destination blob if (replicateDestination || BlobReplicationHandler.ShouldReplicateBlob(requestWrapper.Headers, destContainer, destBlob)) { await BlobReplicationHandler.EnqueueBlobReplicationAsync(destNamespaceBlob, false); } return new HandlerResult { StatusCode = requestVersion >= StorageServiceVersions.Version_2012_02_12 ? HttpStatusCode.Accepted : HttpStatusCode.Created, Headers = new ResponseHeaders(new[] { new KeyValuePair <string, string>("x-ms-copy-id", copyId), new KeyValuePair <string, string>("x-ms-copy-status", destCloudBlob.CopyState.Status == CopyStatus.Success ? "success" : "pending"), }) }; } return new HandlerResult { StatusCode = HttpStatusCode.BadRequest, }; })); }
public static void SetConfiguration(string filePath, DashConfiguration config) { File.WriteAllText(filePath, JsonConvert.SerializeObject(config, Formatting.Indented)); }
private async Task <HttpResponseMessage> ForwardRequestHandler(NamespaceBlob namespaceBlob, StorageOperationTypes operation) { // Clone the inbound request var sourceRequest = this.Request; // We always target the primary data account for forwarded messages. If the operation invalidates the replicas, then // separate logic will enqueue the new blob to be replicated. var clonedRequest = new HttpRequestMessage(sourceRequest.Method, ControllerOperations.GetRedirectUri(sourceRequest.RequestUri, sourceRequest.Method.Method, DashConfiguration.GetDataAccountByAccountName(namespaceBlob.PrimaryAccountName), namespaceBlob.Container, namespaceBlob.BlobName, false)); clonedRequest.Version = sourceRequest.Version; foreach (var property in sourceRequest.Properties) { clonedRequest.Properties.Add(property); } foreach (var header in sourceRequest.Headers) { if (!_noCopyHeaders.Contains(header.Key)) { clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); } } // Depending on the operation, we have to do some fixup to unwind HttpRequestMessage a bit - we also have to fixup some responses switch (operation) { case StorageOperationTypes.GetBlob: case StorageOperationTypes.GetBlobMetadata: case StorageOperationTypes.GetBlobProperties: case StorageOperationTypes.GetBlockList: case StorageOperationTypes.GetPageRanges: case StorageOperationTypes.LeaseBlob: case StorageOperationTypes.SetBlobMetadata: case StorageOperationTypes.SetBlobProperties: case StorageOperationTypes.SnapshotBlob: // Push any headers that are assigned to Content onto the request itself as these operations do not have any body if (sourceRequest.Content != null) { foreach (var header in sourceRequest.Content.Headers) { clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); } } break; default: clonedRequest.Content = sourceRequest.Content; break; } var client = new HttpClient(); var response = await client.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead); // Fixup response for HEAD requests switch (operation) { case StorageOperationTypes.GetBlobProperties: var content = response.Content; if (response.IsSuccessStatusCode && content != null) { string mediaType = null; string dummyContent = String.Empty; if (content.Headers.ContentType != null) { mediaType = content.Headers.ContentType.MediaType; } // For some reason, a HEAD request requires some content otherwise the Content-Length is set to 0 dummyContent = "A"; response.Content = new StringContent(dummyContent, null, mediaType); foreach (var header in content.Headers) { response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } response.Content.Headers.ContentLength = content.Headers.ContentLength; content.Dispose(); } break; } return(response); }
public static bool BeginBlobReplication(string sourceAccount, string destAccount, string container, string blobName, int?waitDelay = null) { bool retval = false; ICloudBlob destBlob = null; try { // Process is: // - start the copy // - wait around for a little while to see if it finishes - if so, we're done - update the namespace // - if the copy is still in progress, enqueue a ReplicateProgress message to revisit the progress & update the namespace // Attempt to acquire a reference to the specified destination first, because if the source no longer exists we must cleanup this orphaned replica var destContainer = DashConfiguration.GetDataAccountByAccountName(destAccount).CreateCloudBlobClient().GetContainerReference(container); try { destBlob = destContainer.GetBlobReferenceFromServer(blobName); } catch { destBlob = null; } var sourceClient = DashConfiguration.GetDataAccountByAccountName(sourceAccount).CreateCloudBlobClient(); var sourceBlob = sourceClient.GetContainerReference(container).GetBlobReferenceFromServer(blobName); if (destBlob == null) { if (sourceBlob.BlobType == BlobType.PageBlob) { destBlob = destContainer.GetPageBlobReference(blobName); } else { destBlob = destContainer.GetBlockBlobReference(blobName); } } DashTrace.TraceInformation("Replicating blob [{0}] to account [{1}]", sourceBlob.Uri, destAccount); // If the source is still being copied to (we kicked off the replication as the result of a copy operation), then just recycle // this message (don't return false as that may ultimately cause us to give up replicating). if (sourceBlob.CopyState != null && sourceBlob.CopyState.Status == CopyStatus.Pending) { DashTrace.TraceInformation("Waiting for replication source [{0}] to complete copy.", sourceBlob.Uri); new AzureMessageQueue().Enqueue( BlobReplicationHandler.ConstructReplicationMessage( false, sourceAccount, destAccount, container, blobName, String.Empty)); return(true); } var sasUri = new UriBuilder(sourceBlob.Uri); sasUri.Query = sourceBlob.GetSharedAccessSignature(new SharedAccessBlobPolicy { SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-5), SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(60), Permissions = SharedAccessBlobPermissions.Read, }).TrimStart('?'); string copyId = destBlob.StartCopyFromBlob(sasUri.Uri); DateTime waitForCompleteGiveUp = DateTime.UtcNow.AddSeconds(waitDelay ?? (DashConfiguration.AsyncWorkerTimeout / 2)); while (DateTime.UtcNow < waitForCompleteGiveUp) { destBlob.FetchAttributes(); if (destBlob.CopyState.CopyId != copyId || destBlob.CopyState.Status != CopyStatus.Pending) { break; } Thread.Sleep(1000); } retval = ProcessBlobCopyStatus(destBlob, sourceAccount, copyId, waitDelay); } catch (StorageException ex) { // Classify the errors into retryable & non-retryable retval = !RecoverReplicationError(ex, destBlob, String.Format("Storage error initiating replication for blob [{0}][{1}] to account [{2}]. Details: {3}", sourceAccount, PathUtils.CombineContainerAndBlob(container, blobName), destAccount, ex)); } catch (Exception ex) { DashTrace.TraceWarning("Error initiating replication for blob [{0}][{1}] to account [{2}]. Details: {3}", sourceAccount, PathUtils.CombineContainerAndBlob(container, blobName), destAccount, ex); } return(retval); }