using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Ionic.Zip; using Microsoft.Azure; using Microsoft.Azure.Management.AppService.Fluent; using Microsoft.Azure.Management.AppService.Fluent.Models; using Microsoft.Azure.Management.Fluent; using Microsoft.Azure.Management.Monitor.Fluent; using Microsoft.Azure.Management.RecoveryServices.Backup; using Microsoft.Azure.Management.Sql.Fluent.Models; using Microsoft.WindowsAzure.Storage; using Tango.AzureUtils.ActiveDirectory; using Tango.AzureUtils.Database; using Tango.AzureUtils.Deployment; using Tango.AzureUtils.FTP; using Tango.AzureUtils.Storage; using Tango.BL; using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.DB; using Tango.PMR.FirmwareUpgrade; namespace Tango.AzureUtils.Environment { public class EnvironmentManager : AzureUtilsComponentBase { private ActiveDirectoryManager _adManager; private DeploymentManager _deploymentManager; private DatabaseManager _databaseManager; private StorageManager _storageManager; private FtpManager _ftpManager; public EnvironmentManager(IAzure azure) : base(azure) { _adManager = CreateManager(); _deploymentManager = CreateManager(); _databaseManager = CreateManager(); _storageManager = CreateManager(); _ftpManager = CreateManager(); } #region Creation public async Task CreateEnvironment(IWebApp app, IDeploymentSlot sourceSlot, String name, CreateEnvironmentConfiguration config) { OnProgress(AzureUtilsStage.Environment, $"Retrieving '{sourceSlot.Name}' settings..."); var sourceSettings = await sourceSlot.GetMachineServiceSettingsAsync(); var targetSettings = EnvironmentSettings.FromSlotName(app.Name, name); if (!config.IgnoreExistingSlot) { var deploymentSlots = await app.DeploymentSlots.ListAsync(); if (deploymentSlots.ToList().Exists(x => x.Name.ToLower() == targetSettings.SLOT_NAME.ToLower())) { throw new InvalidOperationException($"Deployment slot '{targetSettings.SLOT_NAME}' already exists."); } } //Add Environment Group if (config.CreateEnvironmentGroup) { await _adManager.Authenticate(config.Email, config.Password); if (!await _adManager.IsGroupExists(targetSettings.ENVIRONMENT_GROUP)) { await _adManager.AddGroup(targetSettings.ENVIRONMENT_GROUP); if (config.AddEnvironmentGroupAdminUser) { await _adManager.AddUserToGroup(targetSettings.ENVIRONMENT_GROUP, config.Email); } } else { await RequestConfirmation($"Environment group '{targetSettings.ENVIRONMENT_GROUP}' already exists. Do you wish to continue?"); } } //Add Deployment Slot var slot = await _deploymentManager.GetDeploymentSlot(app, targetSettings.SLOT_NAME); if (config.CreateDeploymentSlot) { if (slot == null) { slot = await _deploymentManager.CreateDeploymentSlot(app, sourceSlot, name); } else { await RequestConfirmation($"Deployment slot '{targetSettings.SLOT_NAME}' already exists. Do you wish to continue."); } } //Add Database if (config.CreateDatabase) { if (!await _databaseManager.IsDatabaseExists("twine", targetSettings.DB_CATALOG)) { await _databaseManager.AddDatabase("twine", targetSettings.DB_CATALOG); } else { await RequestConfirmation($"Database '{targetSettings.DB_CATALOG}' already exists. Do you wish to continue?"); } } //Add permissions for environment group on database if (config.AddDatabasePermissionsForEnvironmentGroup) { try { await _databaseManager.AddEnvironmentGroupPermissions(sourceSettings.DB_ADDRESS, targetSettings.DB_CATALOG, targetSettings.ENVIRONMENT_GROUP, config.Email, config.Password); } catch (Exception ex) { await RequestConfirmation($"Error creating/adding permissions for environment group '{targetSettings.ENVIRONMENT_GROUP}' on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); } } //Create backup user if (config.CreateDatabaseBackupUser) { try { await _databaseManager.AddBackupUser(sourceSettings.DB_ADDRESS, targetSettings.DB_CATALOG, sourceSettings.DB_USER_NAME, sourceSettings.DB_PASSWORD); } catch (Exception ex) { await RequestConfirmation($"Error creating/adding permissions for BackupUser on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); } } //Create Tango web application user if (config.CreateDatabaseTangoWebApplicationUser) { try { await _databaseManager.AddTangoWebAppUser(sourceSettings.DB_ADDRESS, targetSettings.DB_CATALOG, sourceSettings.DB_USER_NAME, sourceSettings.DB_PASSWORD); } catch (Exception ex) { await RequestConfirmation($"Error creating/adding permissions for Tango web application on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); } } //Synchronize Schema if (config.SynchronizeDatabaseSchema) { await _databaseManager.OpenSQLExaminerSchema(sourceSlot, slot); await RequestConfirmation($"Please synchronize the database schema between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); OnProgress(AzureUtilsStage.Environment, $"Waiting for database schema synchronization..."); } //Synchronize Data if (config.SynchronizeDatabaseData) { await _databaseManager.OpenSQLExaminerData(sourceSlot, slot, true); await RequestConfirmation($"Please synchronize the database static collections between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); OnProgress(AzureUtilsStage.Environment, $"Waiting for database static collections synchronization..."); } await _storageManager.Connect(sourceSettings.STORAGE_ACCOUNT); //Add Storage Containers if (config.CreateStorageContainers) { await _storageManager.CreateContainer(targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); await _storageManager.CreateContainer(targetSettings.TANGO_VERSIONS_CONTAINER); } if (config.CopyStorageBlobs) { //Add Machine Studio storage versions. try { await _storageManager.ValidateMachineStudioStorageUpgrade(sourceSlot, slot); await _storageManager.UpgradeMachineStudioStorage(sourceSlot, slot); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading machine studio storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } //Add PPC storage versions. try { await _storageManager.ValidateTangoStorageUpgrade(sourceSlot, slot, MachineTypes.TS1800, null); await _storageManager.UpgradeTangoStorage(sourceSlot, slot, MachineTypes.TS1800, null); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading PPC storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Copy Website files. if (config.CopyMachineServiceFiles) { await _ftpManager.CopyAppFiles(sourceSlot, slot); } //Configure Diagnostics logging. if (config.ConfigureMachineServiceLogging) { OnProgress(AzureUtilsStage.Environment, "Configuring diagnostics logging..."); var diagnosticsContainer = await _storageManager.CreateContainer(targetSettings.MACHINE_SERVICE_LOGS_CONTAINER); String diagnosticsContainerSasUrl = diagnosticsContainer.GetSharedAccessUrl(); await Azure.WebApps.Inner.UpdateDiagnosticLogsConfigSlotAsync(app.ResourceGroupName, app.Name, new Microsoft.Azure.Management.AppService.Fluent.Models.SiteLogsConfigInner() { ApplicationLogs = new Microsoft.Azure.Management.AppService.Fluent.Models.ApplicationLogsConfig() { AzureBlobStorage = new Microsoft.Azure.Management.AppService.Fluent.Models.AzureBlobStorageApplicationLogsConfig() { Level = sourceSlot.DiagnosticLogsConfig.ApplicationLoggingStorageBlobLogLevel, RetentionInDays = sourceSlot.DiagnosticLogsConfig.ApplicationLoggingStorageBlobRetentionDays, SasUrl = diagnosticsContainerSasUrl, } }, DetailedErrorMessages = new Microsoft.Azure.Management.AppService.Fluent.Models.EnabledConfig(true), }, slot.Name); } //Configure Cloud Backup. if (config.ConfigureCloudBackup) { OnProgress(AzureUtilsStage.Environment, "Configuring cloud backup..."); var sourceConfig = await Azure.WebApps.Inner.GetBackupConfigurationSlotAsync(app.ResourceGroupName, app.Name, sourceSlot.Name); var backupContainer = await _storageManager.CreateContainer(targetSettings.MACHINE_SERVICE_BACKUPS_CONTAINER); var connectionStrings = await slot.GetConnectionStringsAsync(); var databaseConnectionString = connectionStrings.SingleOrDefault(x => x.Value.Name == targetSettings.DB_CATALOG); await Azure.WebApps.Inner.UpdateBackupConfigurationSlotAsync(app.ResourceGroupName, app.Name, new Microsoft.Azure.Management.AppService.Fluent.Models.BackupRequestInner() { BackupSchedule = sourceConfig.BackupSchedule, Enabled = sourceConfig.Enabled, StorageAccountUrl = backupContainer.GetSharedAccessUrl(), Databases = new List() { new DatabaseBackupSetting() { DatabaseType = DatabaseType.SqlAzure, ConnectionStringName = targetSettings.DB_CATALOG, ConnectionString = databaseConnectionString.Value.Value, } } }, slot.Name); } //Restart slot. OnProgress(AzureUtilsStage.Environment, "Restarting deployment slot..."); await slot.RestartAsync(); OnCompleted("Environment created successfully."); return slot; } #endregion #region Upgrade public async Task UpgradeEnvironment(IWebAppBase sourceApp, IWebAppBase targetApp, UpgradeEnvironmentConfiguration config) { await ValidateEnvironmentUpgrade(sourceApp, targetApp, config); OnProgress(AzureUtilsStage.Environment, $"Retrieving '{sourceApp.Name}' settings..."); var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); //Synchronize Schema if (config.SynchronizeDatabaseSchema) { await _databaseManager.OpenSQLExaminerSchema(sourceApp, targetApp); await RequestConfirmation($"Please synchronize the database schema between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); OnProgress(AzureUtilsStage.Environment, $"Waiting for database schema synchronization..."); } //Synchronize Data if (config.SynchronizeDatabaseData) { await _databaseManager.OpenSQLExaminerData(sourceApp, targetApp); await RequestConfirmation($"Please synchronize the database static collections between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); OnProgress(AzureUtilsStage.Environment, $"Waiting for database static collections synchronization..."); } //Add Machine Studio storage versions. if (config.CopyMachineStudioStorageBlobs) { try { await _storageManager.ValidateMachineStudioStorageUpgrade(sourceApp, targetApp); await _storageManager.UpgradeMachineStudioStorage(sourceApp, targetApp); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading machine studio storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Add PPC storage versions. if (config.CopyPPCStorageBlobs) { try { await _storageManager.ValidateTangoStorageUpgrade(sourceApp, targetApp, MachineTypes.TS1800, config.PPCVersionTag); await _storageManager.UpgradeTangoStorage(sourceApp, targetApp, MachineTypes.TS1800, config.PPCVersionTag); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading PPC storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Add Eureka storage versions. if (config.CopyEurekaStorageBlobs) { try { await _storageManager.ValidateTangoStorageUpgrade(sourceApp, targetApp, MachineTypes.Eureka, config.EurekaVersionTag); await _storageManager.UpgradeTangoStorage(sourceApp, targetApp, MachineTypes.Eureka, config.EurekaVersionTag); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Twine X4 storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Add X1 storage versions. if (config.CopyX1StorageBlobs) { try { await _storageManager.ValidateTangoStorageUpgrade(sourceApp, targetApp, MachineTypes.X1, config.X1VersionTag); await _storageManager.UpgradeTangoStorage(sourceApp, targetApp, MachineTypes.X1, config.X1VersionTag); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Twine X1 storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Add FSE storage versions. if (config.CopyFSEStorageBlobs) { try { await _storageManager.ValidateFSEStorageUpgrade(sourceApp, targetApp, FSEBuildVariants.FSE); await _storageManager.UpgradeFSEStorage(sourceApp, targetApp, FSEBuildVariants.FSE); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading FSE storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Add Twine RSM storage versions. if (config.CopyTwineRSMStorageBlobs) { try { await _storageManager.ValidateFSEStorageUpgrade(sourceApp, targetApp, FSEBuildVariants.TwineRSM); await _storageManager.UpgradeFSEStorage(sourceApp, targetApp, FSEBuildVariants.TwineRSM); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Twine RSM storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Upgrade machine studio database version. if (config.UpgradeMachineStudioDatabaseVersion) { try { await _databaseManager.ValidateMachineStudioDatabaseUpgrade(sourceApp, targetApp); await _databaseManager.UpgradeMachineStudioVersion(sourceApp, targetApp); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Machine Studio database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Upgrade PPC database version. if (config.UpgradePPCDatabaseVersion) { try { await _databaseManager.ValidateTangoDatabaseUpgrade(sourceApp, targetApp, MachineTypes.TS1800, config.PPCVersionTag); await _databaseManager.UpgradeTangoVersion(sourceApp, targetApp, MachineTypes.TS1800, config.PPCVersionTag); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading PPC database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Upgrade Eureka database version. if (config.UpgradeEurekaDatabaseVersion) { try { await _databaseManager.ValidateTangoDatabaseUpgrade(sourceApp, targetApp, MachineTypes.Eureka, config.EurekaVersionTag); await _databaseManager.UpgradeTangoVersion(sourceApp, targetApp, MachineTypes.Eureka, config.EurekaVersionTag); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Twine X4 database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Upgrade X1 database version. if (config.UpgradeX1DatabaseVersion) { try { await _databaseManager.ValidateTangoDatabaseUpgrade(sourceApp, targetApp, MachineTypes.X1, config.X1VersionTag); await _databaseManager.UpgradeTangoVersion(sourceApp, targetApp, MachineTypes.X1, config.X1VersionTag); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Twine X1 database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Upgrade FSE database version. if (config.UpgradeFSEDatabaseVersion) { try { await _databaseManager.ValidateFSEDatabaseUpgrade(sourceApp, targetApp, FSEBuildVariants.FSE); await _databaseManager.UpgradeFSEVersion(sourceApp, targetApp, FSEBuildVariants.FSE); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading FSE database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Upgrade FSE database version. if (config.UpgradeTwineRSMDatabaseVersion) { try { await _databaseManager.ValidateFSEDatabaseUpgrade(sourceApp, targetApp, FSEBuildVariants.TwineRSM); await _databaseManager.UpgradeFSEVersion(sourceApp, targetApp, FSEBuildVariants.TwineRSM); } catch (Exception ex) { await RequestConfirmation($"Issues encountered with upgrading Twine RSM database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } //Copy Website files. if (config.CopyMachineServiceFiles) { await _ftpManager.CopyAppFiles(sourceApp, targetApp); //Restart slot. OnProgress(AzureUtilsStage.Environment, "Restarting target deployment slot..."); await targetApp.RestartAsync(); } OnCompleted("Environment upgraded successfully."); } public async Task ValidateEnvironmentUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp, UpgradeEnvironmentConfiguration config) { if (sourceApp == targetApp || sourceApp.Name == targetApp.Name) { throw new InvalidOperationException("Source slot and target slot are identical."); } //Add Machine Studio storage versions. if (config.CopyMachineStudioStorageBlobs) { await _storageManager.ValidateMachineStudioStorageUpgrade(sourceApp, targetApp); } //Add PPC storage versions. if (config.CopyPPCStorageBlobs) { await _storageManager.ValidateTangoStorageUpgrade(sourceApp, targetApp, MachineTypes.TS1800, config.PPCVersionTag); } //Add Eureka storage versions. if (config.CopyEurekaStorageBlobs) { await _storageManager.ValidateTangoStorageUpgrade(sourceApp, targetApp, MachineTypes.Eureka, config.EurekaVersionTag); } //Add X1 storage versions. if (config.CopyX1StorageBlobs) { await _storageManager.ValidateTangoStorageUpgrade(sourceApp, targetApp, MachineTypes.X1, config.X1VersionTag); } //Add FSE storage versions. if (config.CopyFSEStorageBlobs) { await _storageManager.ValidateFSEStorageUpgrade(sourceApp, targetApp, FSEBuildVariants.FSE); } //Add Twine RSM storage versions. if (config.CopyTwineRSMStorageBlobs) { await _storageManager.ValidateFSEStorageUpgrade(sourceApp, targetApp, FSEBuildVariants.TwineRSM); } //Upgrade machine studio database version. if (config.UpgradeMachineStudioDatabaseVersion) { await _databaseManager.ValidateMachineStudioDatabaseUpgrade(sourceApp, targetApp); } //Upgrade PPC database version. if (config.UpgradePPCDatabaseVersion) { await _databaseManager.ValidateTangoDatabaseUpgrade(sourceApp, targetApp, MachineTypes.TS1800, config.PPCVersionTag); } //Upgrade Eureka database version. if (config.UpgradeEurekaDatabaseVersion) { await _databaseManager.ValidateTangoDatabaseUpgrade(sourceApp, targetApp, MachineTypes.Eureka, config.EurekaVersionTag); } //Upgrade X1 database version. if (config.UpgradeX1DatabaseVersion) { await _databaseManager.ValidateTangoDatabaseUpgrade(sourceApp, targetApp, MachineTypes.X1, config.X1VersionTag); } //Upgrade FSE database version. if (config.UpgradeFSEDatabaseVersion) { await _databaseManager.ValidateFSEDatabaseUpgrade(sourceApp, targetApp, FSEBuildVariants.FSE); } //Upgrade Twine RSM database version. if (config.UpgradeTwineRSMDatabaseVersion) { await _databaseManager.ValidateFSEDatabaseUpgrade(sourceApp, targetApp, FSEBuildVariants.TwineRSM); } } #endregion #region Removal public async Task RemoveEnvironment(IDeploymentSlot slot, String slotName, RemoveEnvironmentConfiguration config) { OnProgress(AzureUtilsStage.Environment, $"Retrieving '{slot.Name}' settings..."); var settings = await slot.GetMachineServiceSettingsAsync(); if (settings.DEPLOYMENT_SLOT != slotName) { throw new ValidationException("The specified slot name confirmation does not match the specified slot instance."); } var envSettings = EnvironmentSettings.FromSlotName(slot.Parent.Name, settings.DEPLOYMENT_SLOT); //Remove Environment Group if (config.RemoveEnvironmentGroup) { await _adManager.Authenticate(config.Email, config.Password); if (await _adManager.IsGroupExists(settings.ENVIRONMENT_GROUP)) { await _adManager.RemoveGroup(settings.ENVIRONMENT_GROUP); } else { await RequestConfirmation($"Environment group '{settings.ENVIRONMENT_GROUP}' does not exist. Do you wish to continue?"); } } //Remove Deployment Slot if (config.RemoveDeploymentSlot) { try { await _deploymentManager.RemoveDeploymentSlot(slot); } catch (Exception ex) { await RequestConfirmation($"Encountered an error while trying to remove deployment slot.\n{ex.FlattenMessage()}\nDo you wish to continue."); } } await _storageManager.Connect(settings.STORAGE_ACCOUNT); //Remove Storage Containers if (config.RemoveStorageContainers) { await _storageManager.RemoveContainer(settings.MACHINE_STUDIO_VERSIONS_CONTAINER); await _storageManager.RemoveContainer(settings.TANGO_VERSIONS_CONTAINER); await _storageManager.RemoveContainer(envSettings.MACHINE_SERVICE_LOGS_CONTAINER); await _storageManager.RemoveContainer(envSettings.MACHINE_SERVICE_BACKUPS_CONTAINER); } //Remove Database if (config.RemoveDatabase) { await _databaseManager.RemoveDatabase("twine", settings.DB_CATALOG); } OnCompleted("Environment removed successfully."); } #endregion #region Rollback public async Task RollbackEnvironment(IWebAppBase app, RollbackEnvironmentConfiguration config) { OnProgress(AzureUtilsStage.Environment, $"Retrieving settings for '{app.Name}'..."); var settings = await app.GetMachineServiceSettingsAsync(); await _storageManager.Connect(settings.STORAGE_ACCOUNT); if (config.RollbackMachineStudio) { try { await _storageManager.DowngradeMachineStudioStorage(app); } catch (Exception ex) { await RequestConfirmation($"Error occurred while trying to remove machine studio storage blobs.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } try { await _databaseManager.DowngradeMachineStudioVersion(app); } catch (Exception ex) { await RequestConfirmation($"Error occurred while trying to remove machine studio database version.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } if (config.RollbackPPC) { try { await _storageManager.DowngradeTangoStorage(app, MachineTypes.TS1800, config.PPCVersionTag); } catch (Exception ex) { await RequestConfirmation($"Error occurred while trying to remove PPC storage blobs.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } try { await _databaseManager.DowngradeTangoVersion(app, MachineTypes.TS1800, config.PPCVersionTag); } catch (Exception ex) { await RequestConfirmation($"Error occurred while trying to remove PPC database version.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } OnCompleted("Environment rollback completed successfully."); } #endregion } }