Example #1
0
        public async Task<IHttpActionResult> UpdateConfiguration(Configuration newConfig)
        {
            return await DoActionAsync("ConfigurationController.UpdateConfiguration", async (serviceClient) =>
            {
                UpdateConfigStatus.ConfigUpdate operationStatus = null;
                try
                {
                    // Do some validation checks first
                    var operationStatusTask = UpdateConfigStatus.GetActiveStatus();
                    var serviceConfigTask = serviceClient.GetDeploymentConfiguration();
                    await Task.WhenAll(operationStatusTask, serviceConfigTask);
                    if (operationStatusTask.Result != null)
                    {
                        return BadRequest(String.Format("Operation: {0} is already underway. Wait for this operation to complete before attempting further updates.", operationStatusTask.Result.OperationId));
                    }
                    var serviceSettings = AzureServiceConfiguration.GetSettingsProjected(serviceConfigTask.Result);
                    if (!CompareAccountName(serviceSettings, newConfig.AccountSettings, DashConfiguration.KeyNamespaceAccount))
                    {
                        return BadRequest("Cannot change the namespace account name once is has been set.");
                    }
                    int accountIndex = 0;
                    foreach (var currentAccount in serviceSettings
                                                        .Where(AzureServiceConfiguration.SettingPredicateScaleoutStorage))
                    {
                        if (!CompareAccountName(currentAccount, newConfig.ScaleAccounts.Accounts.ElementAtOrDefault(accountIndex++)))
                        {
                            var removedAccount = ParseConnectionString(currentAccount.Item2);
                            return BadRequest(String.Format("Data account [{0]} cannot be removed.", removedAccount.AccountName));
                        }
                    }

                    // Do some preamble work here synchronously (begin creation of any new storage accounts - we need to return the keys),
                    // but then enqueue a message on our async worker queue to complete the full update in a reliable manner without having
                    // the client wait forever for a response. The response will be an operationid which can be checked on by calling
                    // the /operations/operationid endpoint.
                    var operationId = DashTrace.CorrelationId.ToString();
                    // Reconcile storage accounts - any new accounts (indicated by a blank key) we will create immediately & include the key in the returned config
                    var newAccounts = new[] { 
                            ParseConnectionString(newConfig.AccountSettings, DashConfiguration.KeyNamespaceAccount, true),
                            ParseConnectionString(newConfig.AccountSettings, DashConfiguration.KeyDiagnosticsAccount, true)
                        }
                        .Concat(newConfig.ScaleAccounts.Accounts
                            .Select(account => new StorageAccountCreationInfo
                            {
                                AccountInfo = account,
                            }))
                        .Where(account => account != null && account.AccountInfo != null && String.IsNullOrWhiteSpace(account.AccountInfo.AccountKey))
                        .ToList();
                    // Start our operation log - use the namespace account specified in the configuration
                    var namespaceAccount = DashConfiguration.NamespaceAccount;
                    // See if they've specified an account in the new config
                    var toBeCreatedNamespace = newAccounts
                        .FirstOrDefault(account => String.Equals(account.ConfigKey, DashConfiguration.KeyNamespaceAccount, StringComparison.OrdinalIgnoreCase));
                    if (toBeCreatedNamespace == null)
                    {
                        CloudStorageAccount.TryParse(newConfig.AccountSettings[DashConfiguration.KeyNamespaceAccount], out namespaceAccount);
                    }
                    operationStatus = await UpdateConfigStatus.GetConfigUpdateStatus(operationId, namespaceAccount);
                    await operationStatus.UpdateStatus(UpdateConfigStatus.States.NotStarted, "Begin service update process. OperationId: [{0}]", operationId);
                    operationStatus.AccountsToBeCreated = newAccounts
                        .Select(account => account.AccountInfo.AccountName)
                        .ToList();
                    if (newAccounts.Any())
                    {
                        await operationStatus.UpdateStatus(UpdateConfigStatus.States.CreatingAccounts, "Creating new storage accounts: [{0}]", String.Join(", ", operationStatus.AccountsToBeCreated));
                        var newAccountTasks = await CreateStorageAccounts(serviceClient, newConfig, newAccounts);
                        await Task.WhenAll(newAccountTasks);
                        // If we just created a new account for the namespace, wire it up now
                        if (CloudStorageAccount.TryParse(newConfig.AccountSettings[DashConfiguration.KeyNamespaceAccount], out namespaceAccount))
                        {
                            operationStatus.StatusHandler.UpdateCloudStorageAccount(namespaceAccount);
                        }
                        // Switch AccountsToBeCreated to the request ids that the async task can verify are completed
                        operationStatus.AccountsToBeCreated = newAccountTasks
                            .Select(newAccoutTask => newAccoutTask.Result)
                            .Where(requestId => !String.IsNullOrWhiteSpace(requestId))
                            .ToList();
                        await operationStatus.UpdateStatus(UpdateConfigStatus.States.CreatingAccounts, "Creation of new storage accounts initiated.");
                    }
                    // TODO: When we enable storage analytics (or at least a utilization report), we will need to turn on metrics for
                    // every storage account here.
                    string asyncQueueName = newConfig.GeneralSettings[DashConfiguration.KeyWorkerQueueName];
                    // Work out the list of accounts to import
                    var newConfigSettings = newConfig.AccountSettings
                        .Select(setting => Tuple.Create(setting.Key, setting.Value))
                        .Concat(newConfig.GeneralSettings
                            .Select(setting => Tuple.Create(setting.Key, setting.Value)))
                        .Concat(newConfig.ScaleAccounts.Accounts
                            .Select((account, index) => Tuple.Create(String.Format("{0}{1}", DashConfiguration.KeyScaleoutAccountPrefix, index), GenerateConnectionString(account))))
                        .ToDictionary(setting => setting.Item1, setting => setting.Item2, StringComparer.OrdinalIgnoreCase);
                    var scaleoutAccounts = serviceSettings
                        .Where(AzureServiceConfiguration.SettingPredicateScaleoutStorage)
                        .Select(account => ParseConnectionString(account.Item2))
                        .Where(account => account != null)
                        .ToDictionary(account => account.AccountName, StringComparer.OrdinalIgnoreCase);
                    var importAccounts = newConfig.ScaleAccounts.Accounts
                        .Where(newAccount => !scaleoutAccounts.ContainsKey(newAccount.AccountName))
                        .ToList();
                    // Prepare the new accounts, import accounts & service configuration information into a message for the async worker.
                    // Despite kicking the async worker off directly here, we need the message durably enqueued so that any downstream
                    // failures will be retried.
                    var rdfeAccessToken = await GetRdfeRefreshToken();
                    operationStatus.AccountsToBeImported = importAccounts
                        .Select(account => account.AccountName)
                        .ToList();
                    var message = new QueueMessage(MessageTypes.UpdateService, 
                        new Dictionary<string, string>
                        {
                            { UpdateServicePayload.OperationId, operationId },
                            { UpdateServicePayload.SubscriptionId, serviceClient.SubscriptionId },
                            { UpdateServicePayload.ServiceName, serviceClient.ServiceName },
                            { UpdateServicePayload.RefreshToken, rdfeAccessToken },
                        },
                        DashTrace.CorrelationId);
                    var messageWrapper = new UpdateServicePayload(message);
                    messageWrapper.CreateAccountRequestIds = operationStatus.AccountsToBeCreated;
                    messageWrapper.ImportAccounts = importAccounts
                        .Select(account => GenerateConnectionString(account));
                    messageWrapper.Settings = newConfigSettings;
                    // Post message to the new namespace account (it may have changed) as that is where the async workers will read from after update
                    await new AzureMessageQueue(namespaceAccount, asyncQueueName).EnqueueAsync(message, 0);
                    // Manually fire up the async worker (so that we can supply it with the new namespace account)
                    var queueTask = ProcessOperationMessageLoop(operationId, namespaceAccount, GenerateConnectionString(namespaceAccount), GetMessageDelay(), asyncQueueName);
                    newConfig.OperationId = operationId;
                    newConfig.ScaleAccounts.MaxAccounts = DashConfiguration.MaxDataAccounts; 
                    return Content(HttpStatusCode.Accepted, newConfig);
                }
                catch (Exception ex)
                {
                    if (operationStatus != null)
                    {
                        var task = operationStatus.UpdateStatus(UpdateConfigStatus.States.Failed, ex.ToString());
                    }
                    throw;
                }
            });
        }
Example #2
0
 static bool DoUpdateServiceJob(QueueMessage message, int? invisibilityTimeout = null)
 {
     // Extend the timeout on this message (5 mins for account creation, 1 min for account import & 30 secs to upgrade service)
     message.UpdateInvisibility(invisibilityTimeout ?? 390);
     var payload = new UpdateServicePayload(message);
     string operationId = ServiceUpdater.ImportAccountsAndUpdateService(
         message.Payload[ServiceOperationPayload.SubscriptionId],
         message.Payload[ServiceOperationPayload.ServiceName],
         message.Payload[ServiceOperationPayload.OperationId],
         message.Payload[ServiceOperationPayload.RefreshToken],
         payload.CreateAccountRequestIds,
         payload.ImportAccounts,
         payload.Settings,
         message.AbandonOperation).Result;
     if (!String.IsNullOrWhiteSpace(operationId) && !message.AbandonOperation)
     {
         // Enqueue a follow-up message to check the progress of this operation
         EnqueueServiceOperationUpdate(message, invisibilityTimeout);
         return true;
     }
     return false;
 }