public async Task UpdateStatus(string message, States newState, TraceLevel traceLevel) { this.Message = message; if (newState != States.Unknown) { this.State = newState; } await AccountStatus.UpdateAccountStatus(this); switch (traceLevel) { case TraceLevel.Error: DashTrace.TraceError(message); break; case TraceLevel.Warning: DashTrace.TraceWarning(message); break; case TraceLevel.Info: case TraceLevel.Verbose: DashTrace.TraceInformation(message); break; } }
public async Task <HttpResponseMessage> OptionsCallAsync() { return(await DoHandlerAsync("AccountController.OptionsCallAsync", async() => { var request = HttpContextFactory.Current.Request; HttpResponseMessage response; if (request.Headers["Origin"] != null) { DashTrace.TraceInformation("Forwarding real CORS OPTIONS request"); Uri forwardUri = ControllerOperations.ForwardUriToNamespace(request); var forwardRequest = new HttpRequestMessage(HttpMethod.Options, forwardUri); foreach (string key in _corsOptionsHeaders) { forwardRequest.Headers.TryAddWithoutValidation(key, request.Headers[key]); } HttpClient client = new HttpClient(); response = await client.SendAsync(forwardRequest); DashTrace.TraceInformation("CORS OPTIONS response: {0}, {1}", response.StatusCode, response.ReasonPhrase); } else { response = this.Request.CreateResponse(HttpStatusCode.OK); } response.Headers.Add("x-ms-dash-client", "true"); return response; })); }
public static string GenerateSignature(Func <string> stringToSignFactory, byte[] accountKey) { string stringToSign = stringToSignFactory(); DashTrace.TraceInformation("Authentication signing string: {0}", stringToSign); var bytesToSign = Encoding.UTF8.GetBytes(stringToSign); using (var hmac = new HMACSHA256(accountKey)) { return(Convert.ToBase64String(hmac.ComputeHash(bytesToSign))); } }
protected void Application_Start() { AzureUtils.AddAzureDiagnosticsListener(); DashTrace.TraceInformation("Starting application instance"); GlobalConfiguration.Configure(WebApiConfig.Register); // Import any statically configured accounts var importAccounts = DashConfiguration.ImportAccounts; if (importAccounts.Any()) { Task.Factory.StartNew(() => AccountManager.ImportAccounts(importAccounts)); } }
static void Main(string[] args) { if (!Trace.Listeners.OfType <ConsoleTraceListener>().Any()) { Trace.Listeners.Add(new ConsoleTraceListener()); } AzureUtils.AddAzureDiagnosticsListener(); DashTrace.TraceInformation("DashAsync (version: {0}): Asynchronous worker starting up.", Assembly.GetEntryAssembly().GetName().Version); int msgProcessed = 0, msgErrors = 0; MessageProcessor.ProcessMessageLoop(ref msgProcessed, ref msgErrors); DashTrace.TraceInformation("DashAsync completed. Messages processed [{0}], messages unprocessed [{1}]", msgProcessed, msgErrors); }
public static async Task EnqueueBlobReplicationAsync(NamespaceBlob namespaceBlob, bool deleteReplica, bool saveNamespaceEntry = true) { if (!await namespaceBlob.ExistsAsync()) { return; } // Trim down the namespace replication list to the first 'master' item. This is sufficient to ensure that the // orphaned blobs are not effectively in the account. The master blob will be replicated over the top of the // orphaned blobs. string primaryAccount = namespaceBlob.PrimaryAccountName; if (namespaceBlob.IsReplicated) { namespaceBlob.PrimaryAccountName = primaryAccount; if (saveNamespaceEntry) { await namespaceBlob.SaveAsync(); } } // This rest of this method does not block. Enqueueing the replication is a completely async process var task = Task.Factory.StartNew(() => { var queue = new AzureMessageQueue(); var tasks = DashConfiguration.DataAccounts .Where(dataAccount => !dataAccount.Credentials.AccountName.Equals(primaryAccount, StringComparison.OrdinalIgnoreCase)) .Select(async dataAccount => await queue.EnqueueAsync(ConstructReplicationMessage(deleteReplica, primaryAccount, dataAccount.Credentials.AccountName, namespaceBlob.Container, namespaceBlob.BlobName, deleteReplica ? await GetBlobETagAsync(dataAccount, namespaceBlob.Container, namespaceBlob.BlobName) : null))); Task.WhenAll(tasks) .ContinueWith(antecedent => { if (antecedent.Exception != null) { DashTrace.TraceWarning("Error queueing replication message for blob: {0}. Details: {1}", PathUtils.CombineContainerAndBlob(namespaceBlob.Container, namespaceBlob.BlobName), antecedent.Exception.Flatten()); } else { DashTrace.TraceInformation("Blob: {0} has been enqueued for replication.", PathUtils.CombineContainerAndBlob(namespaceBlob.Container, namespaceBlob.BlobName)); } }); }); }
static bool RecoverReplicationError(StorageException ex, ICloudBlob destBlob, string exceptionMessage) { bool retval = true; switch ((HttpStatusCode)ex.RequestInformation.HttpStatusCode) { case HttpStatusCode.Conflict: case HttpStatusCode.PreconditionFailed: // Destination has been modified - no retry if (destBlob != null) { DashTrace.TraceInformation("A pre-condition was not met attempting to replicate or cleanup blob [{0}] in account [{1}]. This operation will be aborted", destBlob.Name, destBlob.ServiceClient.Credentials.AccountName); } else { DashTrace.TraceWarning("A pre-condition was not met attempting to replicate or cleanup an unknown blob. This operation will be aborted."); } retval = false; break; case HttpStatusCode.NotFound: // The source blob could not be found - delete the target to prevent orphaning if (destBlob != null) { DashTrace.TraceInformation("Replication of blob [{0}] to account [{1}] cannot be completed because the source blob does not exist.", destBlob.Name, destBlob.ServiceClient.Credentials.AccountName); CleanupAbortedBlobReplication(destBlob); } else { DashTrace.TraceWarning("Replication of unknown blob cannot be completed because the source blob does not exist."); } retval = false; break; default: // Unexpected exceptions are warnings DashTrace.TraceWarning(exceptionMessage); break; } return(retval); }
static bool FinalizeBlobReplication(string dataAccount, string container, string blobName, bool deleteReplica, ICloudBlob destBlob = null) { return(NamespaceHandler.PerformNamespaceOperation(container, blobName, async(namespaceBlob) => { bool exists = await namespaceBlob.ExistsAsync(); if (!exists || namespaceBlob.IsMarkedForDeletion) { // It's ok for a deleted replica not to have a corresponding namespace blob if (!deleteReplica) { DashTrace.TraceWarning("Replication of blob [{0}] to account [{1}] cannot be completed because the namespace blob either does not exist or is marked for deletion.", PathUtils.CombineContainerAndBlob(container, blobName), dataAccount); // Attempt to not leave the replica orphaned if (CleanupAbortedBlobReplication(namespaceBlob, destBlob)) { await namespaceBlob.SaveAsync(); } } // Do not attempt retry in this state return true; } string message = "replicated to"; bool nsDirty = false; if (deleteReplica) { nsDirty = namespaceBlob.RemoveDataAccount(dataAccount); message = "dereplicated from"; } else { nsDirty = namespaceBlob.AddDataAccount(dataAccount); } if (nsDirty) { await namespaceBlob.SaveAsync(); DashTrace.TraceInformation("Blob [{0}] has been successfully {1} account [{2}].", PathUtils.CombineContainerAndBlob(container, blobName), message, dataAccount); } return true; }).Result); }
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); }
static bool ProcessBlobCopyStatus(ICloudBlob destBlob, string sourceAccount, string copyId, int?waitDelay = null) { bool retval = false; string destAccount = destBlob.ServiceClient.Credentials.AccountName; Uri sourceUri = destBlob.CopyState.Source; // If the blob has moved on to another copy, just assume that it overrode our copy if (destBlob.CopyState.CopyId == copyId) { switch (destBlob.CopyState.Status) { case CopyStatus.Aborted: // Copy has been abandoned - we don't automatically come back from here DashTrace.TraceWarning("Replicating blob [{0}] to account [{1}]. Copy Id [{3}] has been aborted.", sourceUri, destAccount, copyId); // Make sure we don't orphan the replica CleanupAbortedBlobReplication(destBlob); retval = true; break; case CopyStatus.Failed: case CopyStatus.Invalid: // Possibly temporaral issues - allow the message to retry after a period DashTrace.TraceWarning("Replicating blob [{0}] to account [{1}]. Copy Id [{3}] has been failed or is invalid.", sourceUri, destAccount, copyId); // Make sure we don't orphan the replica CleanupAbortedBlobReplication(destBlob); retval = false; break; case CopyStatus.Pending: // Enqueue a new message to check on the copy status DashTrace.TraceInformation("Replicating blob [{0}] to account [{1}]. Copy Id [{2}] is pending. Copied [{3}]/[{4}] bytes. Enqueing progress message.", sourceUri, destBlob.ServiceClient.Credentials.AccountName, copyId, destBlob.CopyState.BytesCopied, destBlob.CopyState.TotalBytes); new AzureMessageQueue().Enqueue(new QueueMessage(MessageTypes.ReplicateProgress, new Dictionary <string, string> { { ReplicateProgressPayload.Source, sourceAccount }, { ReplicateProgressPayload.Destination, destAccount }, { ReplicateProgressPayload.Container, destBlob.Container.Name }, { ReplicateProgressPayload.BlobName, destBlob.Name }, { ReplicateProgressPayload.CopyID, copyId }, }, DashTrace.CorrelationId), waitDelay ?? (DashConfiguration.WorkerQueueInitialDelay + 10)); retval = true; break; case CopyStatus.Success: retval = FinalizeBlobReplication(destAccount, destBlob.Container.Name, destBlob.Name, false, destBlob); break; } } else { DashTrace.TraceInformation("Replication of blob [{0}] to account [{1}] has been aborted as the destination blob has a different copy id. Expected [{2}], actual [{3}]", sourceUri, destAccount, copyId, destBlob.CopyState.CopyId); // Return true to indicate that this operation shouldn't be retried retval = true; } return(retval); }
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); }
public static async Task <HandlerResult> HandlePrePipelineOperationAsync(IHttpRequestWrapper requestWrapper) { string containerName = requestWrapper.UriParts.Container; string blobName = requestWrapper.UriParts.BlobName; StorageOperationTypes requestOperation = StorageOperations.GetBlobOperation(requestWrapper); DashClientCapabilities client = DashClientDetector.DetectClient(requestWrapper); HandlerResult result = null; bool servePrimaryOnly = false; switch (requestOperation) { case StorageOperationTypes.GetBlobProperties: case StorageOperationTypes.SetBlobProperties: case StorageOperationTypes.GetBlobMetadata: case StorageOperationTypes.SetBlobMetadata: case StorageOperationTypes.LeaseBlob: case StorageOperationTypes.GetBlockList: servePrimaryOnly = true; // Fall through goto case StorageOperationTypes.GetBlob; case StorageOperationTypes.GetBlob: case StorageOperationTypes.SnapshotBlob: case StorageOperationTypes.GetPageRanges: if (client.HasFlag(DashClientCapabilities.FollowRedirects)) { // If the client has specified a concurrent operation, then we always serve the primary account // as that is where the concurrency controls exist if (!servePrimaryOnly && requestWrapper.Headers.IsConcurrentRequest()) { servePrimaryOnly = true; } result = await BlobHandler.BasicBlobAsync(requestWrapper, containerName, blobName, servePrimaryOnly, BlobReplicationOperations.DoesOperationTriggerReplication(requestOperation)); } break; case StorageOperationTypes.PutPage: if (client.HasFlag(DashClientCapabilities.NoPayloadToDash)) { result = await BlobHandler.BasicBlobAsync(requestWrapper, containerName, blobName, true, BlobReplicationOperations.DoesOperationTriggerReplication(requestOperation)); } break; case StorageOperationTypes.PutBlob: case StorageOperationTypes.PutBlock: case StorageOperationTypes.PutBlockList: if (client.HasFlag(DashClientCapabilities.NoPayloadToDash)) { result = await BlobHandler.PutBlobAsync(requestWrapper, containerName, blobName, BlobReplicationOperations.DoesOperationTriggerReplication(requestOperation)); } break; default: // All other operations flow through to the controller action break; } DashTrace.TraceInformation("Operation: {0}, client capability: {1}, action: {2}", requestOperation, client, result == null ? "Forward" : "Redirect"); return(result); }