Migraiton enhancements v: 0.1.1 (#15570)

* adding filters and cards for failed migrations

* Added card and filter for completing cutover

* Fixing blob container support and some ux enhancements

* Enabling eastus2 and canada central regions

* Increasing height of container to accomodate newer cards and cleaning up database backup page

* vbump migration

* Removing unused code
This commit is contained in:
Aasim Khan
2021-05-25 18:00:30 -07:00
committed by GitHub
parent 730367494b
commit e49dd12951
16 changed files with 561 additions and 309 deletions

View File

@@ -32,7 +32,12 @@ export async function getLocations(account: azdata.Account, subscription: Subscr
throw new Error(response.errors.toString());
}
sortResourceArrayByName(response.locations);
const supportedLocations = ['eastus2', 'eastus2euap'];
const supportedLocations = [
'eastus2',
'eastus2euap',
'eastus',
'canadacentral'
];
const filteredLocations = response.locations.filter(loc => {
return supportedLocations.includes(loc.name);
});
@@ -377,8 +382,8 @@ export interface DatabaseMigration {
}
export interface DatabaseMigrationProperties {
scope: string;
provisioningState: string;
migrationStatus: string;
provisioningState: 'Succeeded' | 'Failed' | 'Creating';
migrationStatus: 'InProgress' | 'Failed' | 'Succeeded' | 'Creating' | 'Completing' | 'Cancelling';
migrationStatusDetails?: MigrationStatusDetails;
startedOn: string;
endedOn: string;

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { DAYS, HRS, MINUTE, SEC } from '../constants/strings';
import { AdsMigrationStatus } from '../dialog/migrationStatus/migrationStatusDialogModel';
import { MigrationContext } from '../models/migrationLocalStorage';
export function deepClone<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
@@ -83,3 +85,38 @@ export function convertTimeDifferenceToDuration(startTime: Date, endTime: Date):
return DAYS(parseFloat(days));
}
}
export function filterMigrations(databaseMigrations: MigrationContext[], statusFilter: string, databaseNameFilter?: string): MigrationContext[] {
let filteredMigration: MigrationContext[] = [];
if (statusFilter === AdsMigrationStatus.ALL) {
filteredMigration = databaseMigrations;
} else if (statusFilter === AdsMigrationStatus.ONGOING) {
filteredMigration = databaseMigrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
const provisioning = value.migrationContext.properties.provisioningState;
return status === 'InProgress' || status === 'Creating' || provisioning === 'Creating';
});
} else if (statusFilter === AdsMigrationStatus.SUCCEEDED) {
filteredMigration = databaseMigrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
return status === 'Succeeded';
});
} else if (statusFilter === AdsMigrationStatus.FAILED) {
filteredMigration = databaseMigrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
const provisioning = value.migrationContext.properties.provisioningState;
return status === 'Failed' || provisioning === 'Failed';
});
} else if (statusFilter === AdsMigrationStatus.COMPLETING) {
filteredMigration = databaseMigrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
return status === 'Completing';
});
}
if (databaseNameFilter) {
filteredMigration = filteredMigration.filter((value) => {
return value.migrationContext.name.toLowerCase().includes(databaseNameFilter.toLowerCase());
});
}
return filteredMigration;
}

View File

@@ -30,6 +30,8 @@ export class IconPathHelper {
public static cancel: IconPath;
public static warning: IconPath;
public static info: IconPath;
public static error: IconPath;
public static completingCutover: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.copy = {
@@ -108,5 +110,13 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/info.svg'),
dark: context.asAbsolutePath('images/infoBox.svg')
};
IconPathHelper.error = {
light: context.asAbsolutePath('images/error.svg'),
dark: context.asAbsolutePath('images/error.svg')
};
IconPathHelper.completingCutover = {
light: context.asAbsolutePath('images/completingCutover.svg'),
dark: context.asAbsolutePath('images/completingCutover.svg')
};
}
}

View File

@@ -5,6 +5,7 @@
import { AzureAccount } from 'azurecore';
import * as nls from 'vscode-nls';
import { MigrationSourceAuthenticationType } from '../models/stateMachine';
const localize = nls.loadMessageBundle();
@@ -144,6 +145,15 @@ export const ENTER_NETWORK_SHARE_INFORMATION = localize('sql.migration.enter.net
export const ENTER_BLOB_CONTAINER_INFORMATION = localize('sql.migration.blob.container.information', "Enter the target name and select the blob container location for selected databases");
export const ENTER_FILE_SHARE_INFORMATION = localize('sql.migration.enter.file.share.information', "Enter the target name and select the file share location of selected databases");
export const INVALID_TARGET_NAME_ERROR = localize('sql.migration.invalid.target.name.error', "Please enter a valid name for the target database.");
export const PROVIDE_UNIQUE_CONTAINERS = localize('sql.migration.provide.unique.containers', "Please provide unique containers for target databases. Databases affected: ");
export function SQL_SOURCE_DETAILS(authMethod: MigrationSourceAuthenticationType, serverName: string): string {
switch (authMethod) {
case MigrationSourceAuthenticationType.Integrated:
return localize('sql.migration.source.details.windowAuth', "Enter the Windows Authentication credential used for connecting to SQL Server Instance {0}. This credential will be used to for connecting to SQL Server instance and identifying valid backup file(s)", serverName);
case MigrationSourceAuthenticationType.Sql:
return localize('sql.migration.source.details.sqlAuth', "Enter the SQL Authentication credential used for connecting to SQL Server Instance {0}. This credential will be used to for connecting to SQL Server instance and identifying valid backup file(s)", serverName);
}
}
// integration runtime page
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
@@ -261,8 +271,10 @@ export const PRE_REQ_1 = localize('sql.migration.pre.req.1', "Azure account deta
export const PRE_REQ_2 = localize('sql.migration.pre.req.2', "Azure SQL Managed Instance or SQL Server on Azure Virtual Machine");
export const PRE_REQ_3 = localize('sql.migration.pre.req.3', "Backup location details");
export const MIGRATION_IN_PROGRESS = localize('sql.migration.migration.in.progress', "Database migration in progress");
export const MIGRATION_FAILED = localize('sql.migration.failed', "Migration failed");
export const LOG_SHIPPING_IN_PROGRESS = localize('sql.migration.log.shipping.in.progress', "Log shipping in progress");
export const MIGRATION_COMPLETED = localize('sql.migration.migration.completed', "Database migration completed");
export const MIGRATION_COMPLETED = localize('sql.migration.migration.completed', "Migration completed");
export const MIGRATION_CUTOVER_CARD = localize('sql.migration.cutover.card', "Completing cutover");
export const SUCCESSFULLY_MIGRATED_TO_AZURE_SQL = localize('sql.migration.successfully.migrated.to.azure.sql', "Successfully migrated to Azure SQL");
export const MIGRATION_NOT_STARTED = localize('sql.migration.migration.not.started', "Migration not started");
export const CHOOSE_TO_MIGRATE_TO_AZURE_SQL = localize('sql.migration.choose.to.migrate.to.azure.sql', "Choose to migrate to Azure SQL");

View File

@@ -9,7 +9,8 @@ import { MigrationContext, MigrationLocalStorage } from '../models/migrationLoca
import * as loc from '../constants/strings';
import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { MigrationStatusDialog } from '../dialog/migrationStatus/migrationStatusDialog';
import { MigrationCategory } from '../dialog/migrationStatus/migrationStatusDialogModel';
import { AdsMigrationStatus } from '../dialog/migrationStatus/migrationStatusDialogModel';
import { filterMigrations } from '../api/utils';
interface IActionMetadata {
title?: string,
@@ -39,6 +40,8 @@ export class DashboardWidget {
private _inProgressMigrationButton!: StatusCard;
private _inProgressWarningMigrationButton!: StatusCard;
private _successfulMigrationButton!: StatusCard;
private _failedMigrationButton!: StatusCard;
private _completingMigrationButton!: StatusCard;
private _notStartedMigrationCard!: StatusCard;
private _migrationStatusMap: Map<string, MigrationContext[]> = new Map();
private _viewAllMigrationsButton!: azdata.ButtonComponent;
@@ -233,15 +236,9 @@ export class DashboardWidget {
this._migrationStatusCardLoadingContainer.loading = true;
try {
this.setCurrentMigrations(await this.getMigrations());
const migrationStatus = await this.getCurrentMigrations();
const inProgressMigrations = migrationStatus.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
const provisioning = value.migrationContext.properties.provisioningState;
return status === 'InProgress' || status === 'Creating' || status === 'Completing' || provisioning === 'Creating';
});
const migrations = await this.getCurrentMigrations();
const inProgressMigrations = filterMigrations(migrations, AdsMigrationStatus.ONGOING);
let warningCount = 0;
for (let i = 0; i < inProgressMigrations.length; i++) {
if (
inProgressMigrations[i].asyncOperationResult?.error?.message ||
@@ -252,7 +249,6 @@ export class DashboardWidget {
warningCount += 1;
}
}
if (warningCount > 0) {
this._inProgressWarningMigrationButton.warningText!.value = loc.MIGRATION_INPROGRESS_WARNING(warningCount);
this._inProgressMigrationButton.container.display = 'none';
@@ -261,22 +257,32 @@ export class DashboardWidget {
this._inProgressMigrationButton.container.display = 'inline';
this._inProgressWarningMigrationButton.container.display = 'none';
}
this._inProgressMigrationButton.count.value = inProgressMigrations.length.toString();
this._inProgressWarningMigrationButton.count.value = inProgressMigrations.length.toString();
const successfulMigration = migrationStatus.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
return status === 'Succeeded';
});
const successfulMigration = filterMigrations(migrations, AdsMigrationStatus.SUCCEEDED);
this._successfulMigrationButton.count.value = successfulMigration.length.toString();
const currentConnection = (await azdata.connection.getCurrentConnection());
const migrationDatabases = new Set(
migrationStatus.map((value) => {
return value.migrationContext.properties.sourceDatabaseName;
}));
const serverDatabases = await azdata.connection.listDatabases(currentConnection.connectionId);
this._notStartedMigrationCard.count.value = (serverDatabases.length - migrationDatabases.size).toString();
const failedMigrations = filterMigrations(migrations, AdsMigrationStatus.FAILED);
const failedCount = failedMigrations.length;
if (failedCount > 0) {
this._failedMigrationButton.container.display = 'inline';
this._failedMigrationButton.count.value = failedMigrations.length.toString();
} else {
this._failedMigrationButton.container.display = 'none';
}
const completingCutoverMigrations = filterMigrations(migrations, AdsMigrationStatus.COMPLETING);
const cutoverCount = completingCutoverMigrations.length;
if (cutoverCount > 0) {
this._completingMigrationButton.container.display = 'inline';
this._completingMigrationButton.count.value = cutoverCount.toString();
} else {
this._completingMigrationButton.container.display = 'none';
}
} catch (error) {
console.log(error);
} finally {
@@ -498,7 +504,7 @@ export class DashboardWidget {
const statusContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
width: '400px',
height: '280px',
height: '350px',
justifyContent: 'flex-start',
}).withProps({
CSSStyles: {
@@ -527,7 +533,7 @@ export class DashboardWidget {
this._viewAllMigrationsButton.onDidClick(async (e) => {
const migrationStatus = await this.getCurrentMigrations();
new MigrationStatusDialog(migrationStatus ? migrationStatus : await this.getMigrations(), MigrationCategory.ALL).initialize();
new MigrationStatusDialog(migrationStatus ? migrationStatus : await this.getMigrations(), AdsMigrationStatus.ALL).initialize();
});
const refreshButton = view.modelBuilder.hyperlink().withProps({
@@ -581,7 +587,7 @@ export class DashboardWidget {
loc.MIGRATION_IN_PROGRESS
);
this._inProgressMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), MigrationCategory.ONGOING);
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.ONGOING);
dialog.initialize();
});
@@ -595,7 +601,7 @@ export class DashboardWidget {
''
);
this._inProgressWarningMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), MigrationCategory.ONGOING);
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.ONGOING);
dialog.initialize();
});
@@ -608,13 +614,38 @@ export class DashboardWidget {
loc.MIGRATION_COMPLETED
);
this._successfulMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), MigrationCategory.SUCCEEDED);
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.SUCCEEDED);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(
this._successfulMigrationButton.container
);
this._completingMigrationButton = this.createStatusCard(
IconPathHelper.completingCutover,
loc.MIGRATION_CUTOVER_CARD
);
this._completingMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.COMPLETING);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(
this._completingMigrationButton.container
);
this._failedMigrationButton = this.createStatusCard(
IconPathHelper.error,
loc.MIGRATION_FAILED
);
this._failedMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.FAILED);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(
this._failedMigrationButton.container
);
this._notStartedMigrationCard = this.createStatusCard(
IconPathHelper.notStartedMigration,
loc.MIGRATION_NOT_STARTED
@@ -650,7 +681,7 @@ export class DashboardWidget {
const linksContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
width: '400px',
height: '280px',
height: '350px',
justifyContent: 'flex-start',
}).withProps({
CSSStyles: {

View File

@@ -72,7 +72,8 @@ export class ConfirmCutoverDialog {
}
}).component();
const pendingBackupCount = this.migrationCutoverModel.migrationStatus.properties.migrationStatusDetails?.activeBackupSets.filter(f => f.listOfBackupFiles[0].status !== 'Restored' && f.listOfBackupFiles[0].status !== 'Ignored').length;
const pendingBackupCount = this.migrationCutoverModel.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.filter(f => f.listOfBackupFiles[0].status !== 'Restored' && f.listOfBackupFiles[0].status !== 'Ignored').length ?? 0;
const pendingText = this._view.modelBuilder.text().withProps({
CSSStyles: {
'font-size': '13px',

View File

@@ -517,9 +517,14 @@ export class MigrationCutoverDialog {
this._fullBackupFile.value = fullBackupFileName! ?? '-';
let backupLocation;
const isBlobMigration = this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob !== undefined;
// Displaying storage accounts and blob container for azure blob backups.
if (this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob) {
backupLocation = `${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation.azureBlob.storageAccountResourceId.split('/').pop()} - ${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation.azureBlob.blobContainerName}`;
if (isBlobMigration) {
backupLocation = `${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.storageAccountResourceId.split('/').pop()} - ${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.blobContainerName}`;
this._fileCount.display = 'none';
this.fileTable.updateCssStyles({
'display': 'none'
});
} else {
backupLocation = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.fileShare?.path! ?? '-';
}
@@ -547,7 +552,7 @@ export class MigrationCutoverDialog {
if (migrationStatusTextValue === MigrationStatus.InProgress) {
const restoredCount = (this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.filter(a => a.listOfBackupFiles[0].status === 'Restored'))?.length ?? 0;
if (restoredCount > 0) {
if (restoredCount > 0 || isBlobMigration) {
this._cutoverButton.enabled = true;
}
this._cancelButton.enabled = true;

View File

@@ -8,9 +8,9 @@ import * as vscode from 'vscode';
import { IconPathHelper } from '../../constants/iconPathHelper';
import { MigrationContext, MigrationLocalStorage } from '../../models/migrationLocalStorage';
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
import { MigrationCategory, MigrationStatusDialogModel } from './migrationStatusDialogModel';
import { AdsMigrationStatus, MigrationStatusDialogModel } from './migrationStatusDialogModel';
import * as loc from '../../constants/strings';
import { convertTimeDifferenceToDuration } from '../../api/utils';
import { convertTimeDifferenceToDuration, filterMigrations } from '../../api/utils';
export class MigrationStatusDialog {
private _model: MigrationStatusDialogModel;
private _dialogObject!: azdata.window.Dialog;
@@ -21,7 +21,7 @@ export class MigrationStatusDialog {
private _statusTable!: azdata.DeclarativeTableComponent;
private _refreshLoader!: azdata.LoadingComponent;
constructor(migrations: MigrationContext[], private _filter: MigrationCategory) {
constructor(migrations: MigrationContext[], private _filter: AdsMigrationStatus) {
this._model = new MigrationStatusDialogModel(migrations);
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
}
@@ -40,7 +40,11 @@ export class MigrationStatusDialog {
this.populateMigrationTable();
});
this._statusDropdown.value = this._statusDropdown.values![this._filter];
if (this._filter) {
this._statusDropdown.value = (<azdata.CategoryValue[]>this._statusDropdown.values).find((value) => {
return value.name === this._filter;
});
}
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[
@@ -124,10 +128,7 @@ export class MigrationStatusDialog {
private populateMigrationTable(): void {
try {
const migrations = this._model.filterMigration(
this._searchBox.value!,
(<azdata.CategoryValue>this._statusDropdown.value).name
);
const migrations = filterMigrations(this._model._migrations, (<azdata.CategoryValue>this._statusDropdown.value).name, this._searchBox.value!);
const data: azdata.DeclarativeTableCellValue[][] = [];

View File

@@ -10,47 +10,34 @@ export class MigrationStatusDialogModel {
public statusDropdownValues: azdata.CategoryValue[] = [
{
displayName: 'Status: All',
name: 'All',
name: AdsMigrationStatus.ALL,
}, {
displayName: 'Status: Ongoing',
name: 'Ongoing',
name: AdsMigrationStatus.ONGOING,
}, {
displayName: 'Status: Completing',
name: AdsMigrationStatus.COMPLETING
}, {
displayName: 'Status: Succeeded',
name: 'Succeeded',
name: AdsMigrationStatus.SUCCEEDED,
}, {
displayName: 'Status: Failed',
name: AdsMigrationStatus.FAILED
}
];
constructor(public _migrations: MigrationContext[]) {
}
public filterMigration(databaseName: string, category: string): MigrationContext[] {
let filteredMigration: MigrationContext[] = [];
if (category === 'All') {
filteredMigration = this._migrations;
} else if (category === 'Ongoing') {
filteredMigration = this._migrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
const provisioning = value.migrationContext.properties.provisioningState;
return status === 'InProgress' || status === 'Creating' || status === 'Completing' || provisioning === 'Creating';
});
} else if (category === 'Succeeded') {
filteredMigration = this._migrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
return status === 'Succeeded';
});
}
if (databaseName) {
filteredMigration = filteredMigration.filter((value) => {
return value.migrationContext.name.toLowerCase().includes(databaseName.toLowerCase());
});
}
return filteredMigration;
}
}
export enum MigrationCategory {
ALL,
ONGOING,
SUCCEEDED
/**
* This enum is used to categorize migrations internally in ADS. A migration has 2 statuses: Provisioning Status and Migration Status. The values from both the statuses are mapped to different values in this enum
*/
export enum AdsMigrationStatus {
ALL = 'all',
ONGOING = 'ongoing',
SUCCEEDED = 'succeeded',
FAILED = 'failed',
COMPLETING = 'completing'
}

View File

@@ -58,10 +58,9 @@ export enum NetworkContainerType {
export interface DatabaseBackupModel {
migrationMode: MigrationMode;
networkContainerType: NetworkContainerType;
storageKey: string;
networkShare: NetworkShare;
subscription: azureResource.AzureResourceSubscription;
blob: Blob;
blobs: Blob[];
}
export interface NetworkShare {
@@ -70,12 +69,14 @@ export interface NetworkShare {
password: string;
resourceGroup: azureResource.AzureResourceResourceGroup;
storageAccount: StorageAccount;
storageKey: string;
}
export interface Blob {
resourceGroup: azureResource.AzureResourceResourceGroup;
storageAccount: StorageAccount;
blobContainer: azureResource.BlobContainer;
storageKey: string;
}
export interface Model {
@@ -144,7 +145,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._currentState = State.INIT;
this._databaseBackup = {} as DatabaseBackupModel;
this._databaseBackup.networkShare = {} as NetworkShare;
this._databaseBackup.blob = {} as Blob;
this._databaseBackup.blobs = [];
}
public get sourceConnectionId(): string {
@@ -667,38 +668,38 @@ export class MigrationStateModel implements Model, vscode.Disposable {
scope: this._targetServerInstance.id
}
};
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
requestBody.properties.backupConfiguration = {
targetLocation: undefined!,
sourceLocation: {
azureBlob: {
storageAccountResourceId: this._databaseBackup.blob.storageAccount.id,
accountKey: this._databaseBackup.storageKey,
blobContainerName: this._databaseBackup.blob.blobContainer.name
}
}
};
break;
case NetworkContainerType.NETWORK_SHARE:
requestBody.properties.backupConfiguration = {
targetLocation: {
storageAccountResourceId: this._databaseBackup.networkShare.storageAccount.id,
accountKey: this._databaseBackup.storageKey,
},
sourceLocation: {
fileShare: {
path: this._databaseBackup.networkShare.networkShareLocation,
username: this._databaseBackup.networkShare.windowsUser,
password: this._databaseBackup.networkShare.password,
}
}
};
break;
}
for (let i = 0; i < this._migrationDbs.length; i++) {
try {
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
requestBody.properties.backupConfiguration = {
targetLocation: undefined!,
sourceLocation: {
azureBlob: {
storageAccountResourceId: this._databaseBackup.blobs[i].storageAccount.id,
accountKey: this._databaseBackup.blobs[i].storageKey,
blobContainerName: this._databaseBackup.blobs[i].blobContainer.name
}
}
};
break;
case NetworkContainerType.NETWORK_SHARE:
requestBody.properties.backupConfiguration = {
targetLocation: {
storageAccountResourceId: this._databaseBackup.networkShare.storageAccount.id,
accountKey: this._databaseBackup.networkShare.storageKey,
},
sourceLocation: {
fileShare: {
path: this._databaseBackup.networkShare.networkShareLocation,
username: this._databaseBackup.networkShare.windowsUser,
password: this._databaseBackup.networkShare.password,
}
}
};
break;
}
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
const response = await startDatabaseMigration(
this._azureAccount,

View File

@@ -7,9 +7,8 @@ import * as azdata from 'azdata';
import { EOL } from 'os';
import { getStorageAccountAccessKeys } from '../api/azure';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import { Blob, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import * as vscode from 'vscode';
import { IconPathHelper } from '../constants/iconPathHelper';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
export class DatabaseBackupPage extends MigrationWizardPage {
@@ -19,17 +18,16 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private _windowsUserAccountText!: azdata.InputBoxComponent;
private _passwordText!: azdata.InputBoxComponent;
private _networkSharePath!: azdata.InputBoxComponent;
private _sourceHelpText!: azdata.TextComponent;
private _sqlSourceUsernameInput!: azdata.InputBoxComponent;
private _sqlSourcepassword!: azdata.InputBoxComponent;
private _blobContainer!: azdata.FlexContainer;
private _blobContainerSubscription!: azdata.InputBoxComponent;
private _blobContainerLocation!: azdata.InputBoxComponent;
private _blobContainerResourceGroup!: azdata.DropDownComponent;
private _blobContainerStorageAccountDropdown!: azdata.DropDownComponent;
private _blobContainerDropdown!: azdata.DropDownComponent;
private _fileShareContainer!: azdata.FlexContainer;
private _fileShareSubscription!: azdata.InputBoxComponent;
private _fileShareStorageAccountDropdown!: azdata.DropDownComponent;
private _blobContainerResourceGroupDropdowns!: azdata.DropDownComponent[];
private _blobContainerStorageAccountDropdowns!: azdata.DropDownComponent[];
private _blobContainerDropdowns!: azdata.DropDownComponent[];
private _networkShareStorageAccountDetails!: azdata.FlexContainer;
private _networkShareContainerSubscription!: azdata.InputBoxComponent;
@@ -39,8 +37,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private _networkShareContainerStorageAccountRefreshButton!: azdata.ButtonComponent;
private _targetDatabaseContainer!: azdata.FlexContainer;
private _targetDatabaseNamesTable!: azdata.DeclarativeTableComponent;
private _targetDatabaseNames: azdata.InputBoxComponent[] = [];
private _newtworkShareTargetDatabaseNamesTable!: azdata.DeclarativeTableComponent;
private _blobContainerTargetDatabaseNamesTable!: azdata.DeclarativeTableComponent;
private _networkTableContainer!: azdata.FlexContainer;
private _blobTableContainer!: azdata.FlexContainer;
private _networkShareTargetDatabaseNames: azdata.InputBoxComponent[] = [];
private _blobContainerTargetDatabaseNames: azdata.InputBoxComponent[] = [];
private _existingDatabases: string[] = [];
@@ -107,7 +109,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
networkShareButton.onDidChangeCheckedState((e) => {
if (e) {
this.toggleNetworkContainerFields(NetworkContainerType.NETWORK_SHARE);
this.switchNetworkContainerFields(NetworkContainerType.NETWORK_SHARE);
}
});
@@ -122,25 +124,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
blobContainerButton.onDidChangeCheckedState((e) => {
if (e) {
this.toggleNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
}
});
const fileShareButton = this._view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
label: constants.DATABASE_BACKUP_NC_FILE_SHARE_RADIO_LABEL,
enabled: false,
CSSStyles: {
'font-size': '13px'
}
}).component();
fileShareButton.onDidChangeCheckedState((e) => {
if (e) {
vscode.window.showInformationMessage('Feature coming soon');
networkShareButton.checked = true;
//this.toggleNetworkContainerFields(NetworkContainerType.FILE_SHARE);
this.switchNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
}
});
@@ -148,8 +132,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
[
selectLocationText,
networkShareButton,
blobContainerButton,
fileShareButton
blobContainerButton
]
).withLayout({
flexFlow: 'column'
@@ -161,19 +144,69 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private createNetworkDetailsContainer(): azdata.FlexContainer {
this._networkShareContainer = this.createNetworkShareContainer();
this._blobContainer = this.createBlobContainer();
this._fileShareContainer = this.createFileShareContainer();
const networkContainer = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).withItems([
this._networkShareContainer,
this._blobContainer,
this._fileShareContainer
]).component();
return networkContainer;
}
private createNetworkShareContainer(): azdata.FlexContainer {
const sqlSourceHeader = this._view.modelBuilder.text().withProps({
value: constants.SOURCE_CREDENTIALS,
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '14px',
'font-weight': 'bold'
}
}).component();
this._sourceHelpText = this._view.modelBuilder.text().withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '13px',
}
}).component();
const usernameLable = this._view.modelBuilder.text().withProps({
value: constants.USERNAME,
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold',
}
}).component();
this._sqlSourceUsernameInput = this._view.modelBuilder.inputBox().withProps({
required: true,
enabled: false,
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._sqlSourceUsernameInput.onTextChanged(value => {
this.migrationStateModel._sqlServerUsername = value;
});
const sqlPasswordLabel = this._view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_NETWORK_SHARE_PASSWORD_LABEL,
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold',
}
}).component();
this._sqlSourcepassword = this._view.modelBuilder.inputBox().withProps({
required: true,
inputType: 'password',
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._sqlSourcepassword.onTextChanged(value => {
this.migrationStateModel._sqlServerPassword = value;
});
const networkShareHeading = this._view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_NETWORK_SHARE_HEADER_TEXT,
width: WIZARD_INPUT_COMPONENT_WIDTH,
@@ -282,8 +315,17 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this.migrationStateModel._databaseBackup.networkShare.password = value;
});
const flexContainer = this._view.modelBuilder.flexContainer().withItems(
[
sqlSourceHeader,
this._sourceHelpText,
usernameLable,
this._sqlSourceUsernameInput,
sqlPasswordLabel,
this._sqlSourcepassword,
networkShareHeading,
networkShareHelpText,
networkLocationInputBoxLabel,
@@ -303,39 +345,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return flexContainer;
}
private createFileShareContainer(): azdata.FlexContainer {
const subscriptionLabel = this._view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_FILE_SHARE_SUBSCRIPTION_LABEL,
requiredIndicator: true,
}).component();
this._fileShareSubscription = this._view.modelBuilder.inputBox().withProps({
enabled: false
}).component();
const storageAccountLabel = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_FILE_SHARE_STORAGE_ACCOUNT_LABEL,
}).component();
this._fileShareStorageAccountDropdown = this._view.modelBuilder.dropDown().component();
const flexContainer = this._view.modelBuilder.flexContainer()
.withItems(
[
subscriptionLabel,
this._fileShareSubscription,
storageAccountLabel,
this._fileShareStorageAccountDropdown
]
).withLayout({
flexFlow: 'column'
}).withProps({
display: 'none'
}).component();
return flexContainer;
}
private createBlobContainer(): azdata.FlexContainer {
const subscriptionLabel = this._view.modelBuilder.text()
@@ -364,66 +373,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
enabled: false
}).component();
const resourceGroupLabel = this._view.modelBuilder.text()
.withProps({
value: constants.RESOURCE_GROUP,
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
}
}).component();
this._blobContainerResourceGroup = this._view.modelBuilder.dropDown().withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._blobContainerResourceGroup.onValueChanged(e => {
if (e.selected && e.selected !== constants.RESOURCE_GROUP_NOT_FOUND) {
this.migrationStateModel._databaseBackup.blob.resourceGroup = this.migrationStateModel.getAzureResourceGroup(e.index);
}
this.loadblobStorageDropdown();
});
const storageAccountLabel = this._view.modelBuilder.text()
.withProps({
value: constants.STORAGE_ACCOUNT,
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
}
}).component();
this._blobContainerStorageAccountDropdown = this._view.modelBuilder.dropDown()
.withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
if (value.selected && value.selected !== constants.NO_STORAGE_ACCOUNT_FOUND) {
this.migrationStateModel._databaseBackup.blob.storageAccount = this.migrationStateModel.getStorageAccount(value.index);
}
await this.loadBlobContainerDropdown();
});
const blobContainerLabel = this._view.modelBuilder.text()
.withProps({
value: constants.BLOB_CONTAINER,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
}
}).component();
this._blobContainerDropdown = this._view.modelBuilder.dropDown()
.withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._blobContainerDropdown.onValueChanged(async (value) => {
if (value.selected && value.selected !== constants.NO_BLOBCONTAINERS_FOUND) {
this.migrationStateModel._databaseBackup.blob.blobContainer = this.migrationStateModel.getBlobContainer(value.index);
}
});
const flexContainer = this._view.modelBuilder.flexContainer()
.withItems(
[
@@ -431,12 +380,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerSubscription,
locationLabel,
this._blobContainerLocation,
resourceGroupLabel,
this._blobContainerResourceGroup,
storageAccountLabel,
this._blobContainerStorageAccountDropdown,
blobContainerLabel,
this._blobContainerDropdown,
]
).withLayout({
flexFlow: 'column'
@@ -449,12 +392,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private createTargetDatabaseContainer(): azdata.FlexContainer {
const rowCssStyle: azdata.CssStyles = {
'border': 'none',
'font-size': '13px',
'border-bottom': '1px solid',
};
const headerCssStyles: azdata.CssStyles = {
'border': 'none',
'font-size': '13px',
@@ -462,8 +399,13 @@ export class DatabaseBackupPage extends MigrationWizardPage {
'text-align': 'left',
'border-bottom': '1px solid',
};
const rowCssStyle: azdata.CssStyles = {
'border': 'none',
'font-size': '13px',
'border-bottom': '1px solid',
};
this._targetDatabaseNamesTable = this._view.modelBuilder.declarativeTable().withProps({
this._newtworkShareTargetDatabaseNamesTable = this._view.modelBuilder.declarativeTable().withProps({
columns: [
{
displayName: constants.SOURCE_DATABASE,
@@ -483,11 +425,64 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
]
}).component();
this._blobContainerTargetDatabaseNamesTable = this._view.modelBuilder.declarativeTable().withProps({
columns: [
{
displayName: constants.SOURCE_DATABASE,
valueType: azdata.DeclarativeDataType.string,
rowCssStyles: rowCssStyle,
headerCssStyles: headerCssStyles,
isReadOnly: true,
width: '200px'
},
{
displayName: constants.TARGET_DATABASE_NAME,
valueType: azdata.DeclarativeDataType.component,
rowCssStyles: rowCssStyle,
headerCssStyles: headerCssStyles,
isReadOnly: true,
width: '200px'
},
{
displayName: constants.RESOURCE_GROUP,
valueType: azdata.DeclarativeDataType.component,
rowCssStyles: rowCssStyle,
headerCssStyles: headerCssStyles,
isReadOnly: true,
width: '200px'
},
{
displayName: constants.STORAGE_ACCOUNT,
valueType: azdata.DeclarativeDataType.component,
rowCssStyles: rowCssStyle,
headerCssStyles: headerCssStyles,
isReadOnly: true,
width: '200px'
},
{
displayName: constants.BLOB_CONTAINER,
valueType: azdata.DeclarativeDataType.component,
rowCssStyles: rowCssStyle,
headerCssStyles: headerCssStyles,
isReadOnly: true,
width: '200px'
}
]
}).component();
this._networkTableContainer = this._view.modelBuilder.flexContainer().withItems([
this._newtworkShareTargetDatabaseNamesTable
]).component();
this._blobTableContainer = this._view.modelBuilder.flexContainer().withItems([
this._blobContainerTargetDatabaseNamesTable
]).component();
const container = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).withItems([
this._targetDatabaseNamesTable
this._networkTableContainer,
this._blobTableContainer
]).withProps({
display: 'none'
}).component();
@@ -631,23 +626,39 @@ export class DatabaseBackupPage extends MigrationWizardPage {
public async onPageEnter(): Promise<void> {
if (this.migrationStateModel.refreshDatabaseBackupPage) {
this._targetDatabaseNames = [];
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
const username = results.rows[0][0].displayValue;
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin' ? MigrationSourceAuthenticationType.Sql : connectionProfile.authenticationType === 'Integrated' ? MigrationSourceAuthenticationType.Integrated : undefined!;
this._sourceHelpText.value = constants.SQL_SOURCE_DETAILS(this.migrationStateModel._authenticationType, connectionProfile.serverName);
this._sqlSourceUsernameInput.value = username;
this._sqlSourcepassword.value = (await azdata.connection.getCredentials(this.migrationStateModel.sourceConnectionId)).password;
this._networkShareTargetDatabaseNames = [];
this._blobContainerTargetDatabaseNames = [];
this._blobContainerResourceGroupDropdowns = [];
this._blobContainerStorageAccountDropdowns = [];
this._blobContainerDropdowns = [];
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
}
this.migrationStateModel._targetDatabaseNames = [];
const tableRows: azdata.DeclarativeTableCellValue[][] = [];
this.migrationStateModel._databaseBackup.blobs = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
this.migrationStateModel._targetDatabaseNames.push('');
this.migrationStateModel._databaseBackup.blobs.push(<Blob>{});
const targetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
width: '280px'
width: '200px'
}).withValidation(c => {
if (this._targetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
if (this._networkShareTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR;
return false;
}
@@ -664,18 +675,105 @@ export class DatabaseBackupPage extends MigrationWizardPage {
targetDatabaseInput.onTextChanged((value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
});
this._targetDatabaseNames.push(targetDatabaseInput);
this._networkShareTargetDatabaseNames.push(targetDatabaseInput);
const blobtargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
width: '200px'
}).withValidation(c => {
if (this._blobContainerTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR;
return false;
}
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) { // Making sure if database with same name is not present on the target Azure SQL
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, this.migrationStateModel._targetServerInstance.name);
return false;
}
if (c.value!.length < 1 || c.value!.length > 128 || !/[^<>*%&:\\\/?]/.test(c.value!)) {
c.validationErrorMessage = constants.INVALID_TARGET_NAME_ERROR;
return false;
}
return true;
}).component();
blobtargetDatabaseInput.onTextChanged((value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
});
this._blobContainerTargetDatabaseNames.push(blobtargetDatabaseInput);
const blobContainerResourceDropdown = this._view.modelBuilder.dropDown().withProps({
width: '200px'
}).component();
blobContainerResourceDropdown.onValueChanged(e => {
if (e.selected && e.selected !== constants.RESOURCE_GROUP_NOT_FOUND) {
this.migrationStateModel._databaseBackup.blobs[index].resourceGroup = this.migrationStateModel.getAzureResourceGroup(e.index);
}
this.loadblobStorageDropdown(index);
});
this._blobContainerResourceGroupDropdowns.push(blobContainerResourceDropdown);
const blobContainerStorageAccountDropdown = this._view.modelBuilder.dropDown()
.withProps({
width: '200px'
}).component();
blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
if (value.selected && value.selected !== constants.NO_STORAGE_ACCOUNT_FOUND) {
this.migrationStateModel._databaseBackup.blobs[index].storageAccount = this.migrationStateModel.getStorageAccount(value.index);
}
await this.loadBlobContainerDropdown(index);
});
this._blobContainerStorageAccountDropdowns.push(blobContainerStorageAccountDropdown);
const blobContainerDropdown = this._view.modelBuilder.dropDown()
.withProps({
width: '200px'
}).component();
blobContainerDropdown.onValueChanged(async (value) => {
if (value.selected && value.selected !== constants.NO_BLOBCONTAINERS_FOUND) {
this.migrationStateModel._databaseBackup.blobs[index].blobContainer = this.migrationStateModel.getBlobContainer(value.index);
}
});
this._blobContainerDropdowns.push(blobContainerDropdown);
});
let data: azdata.DeclarativeTableCellValue[][] = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
});
targetRow.push({
value: targetDatabaseInput
value: this._networkShareTargetDatabaseNames[index]
});
tableRows.push(targetRow);
data.push(targetRow);
});
this._newtworkShareTargetDatabaseNamesTable.dataValues = data;
data = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
});
targetRow.push({
value: this._blobContainerTargetDatabaseNames[index]
});
targetRow.push({
value: this._blobContainerResourceGroupDropdowns[index]
});
targetRow.push({
value: this._blobContainerStorageAccountDropdowns[index]
});
targetRow.push({
value: this._blobContainerDropdowns[index]
});
data.push(targetRow);
});
this._blobContainerTargetDatabaseNamesTable.dataValues = data;
this._targetDatabaseNamesTable.dataValues = tableRows;
this.migrationStateModel.refreshDatabaseBackupPage = false;
}
await this.getSubscriptionValues();
@@ -696,15 +794,39 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
break;
case NetworkContainerType.BLOB_CONTAINER:
if ((<azdata.CategoryValue>this._blobContainerResourceGroup.value).displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
}
if ((<azdata.CategoryValue>this._blobContainerStorageAccountDropdown.value).displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
if ((<azdata.CategoryValue>this._blobContainerDropdown.value).displayName === constants.NO_BLOBCONTAINERS_FOUND) {
errors.push(constants.INVALID_BLOBCONTAINER_ERROR);
this._blobContainerResourceGroupDropdowns.forEach(v => {
if ((<azdata.CategoryValue>v.value).displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
}
});
this._blobContainerStorageAccountDropdowns.forEach(v => {
if ((<azdata.CategoryValue>v.value).displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
});
this._blobContainerDropdowns.forEach(v => {
if ((<azdata.CategoryValue>v.value).displayName === constants.NO_BLOBCONTAINERS_FOUND) {
errors.push(constants.INVALID_BLOBCONTAINER_ERROR);
}
});
const duplicates: Map<string, number[]> = new Map();
for (let i = 0; i < this.migrationStateModel._targetDatabaseNames.length; i++) {
const blobContainerId = this.migrationStateModel._databaseBackup.blobs[i].blobContainer.id;
if (duplicates.has(blobContainerId)) {
duplicates.get(blobContainerId)?.push(i);
} else {
duplicates.set(blobContainerId, [i]);
}
}
duplicates.forEach((d) => {
if (d.length > 1) {
const dupString = `${d.map(index => this.migrationStateModel._migrationDbs[index]).join(', ')}`;
errors.push(constants.PROVIDE_UNIQUE_CONTAINERS + dupString);
}
});
break;
}
@@ -727,13 +849,25 @@ export class DatabaseBackupPage extends MigrationWizardPage {
public async onPageLeave(): Promise<void> {
try {
const storageAccount = (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) ?
this.migrationStateModel._databaseBackup.blob.storageAccount : this.migrationStateModel._databaseBackup.networkShare.storageAccount;
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
for (let i = 0; i < this.migrationStateModel._databaseBackup.blobs.length; i++) {
const storageAccount = this.migrationStateModel._databaseBackup.blobs[i].storageAccount;
this.migrationStateModel._databaseBackup.blobs[i].storageKey = (await getStorageAccountAccessKeys(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
storageAccount)).keyName1;
}
break;
case NetworkContainerType.NETWORK_SHARE:
const storageAccount = this.migrationStateModel._databaseBackup.networkShare.storageAccount;
this.migrationStateModel._databaseBackup.storageKey = (await getStorageAccountAccessKeys(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
storageAccount)).keyName1;
this.migrationStateModel._databaseBackup.networkShare.storageKey = (await getStorageAccountAccessKeys(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
storageAccount)).keyName1;
break;
}
} finally {
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
@@ -744,7 +878,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
private toggleNetworkContainerFields(containerType: NetworkContainerType): void {
private switchNetworkContainerFields(containerType: NetworkContainerType): void {
this.wizard.message = {
text: '',
level: azdata.window.MessageLevel.Error
@@ -752,11 +886,18 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this.wizard.nextButton.enabled = true;
this.migrationStateModel._databaseBackup.networkContainerType = containerType;
this._fileShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.FILE_SHARE) ? 'inline' : 'none' });
this._blobContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none' });
this._networkShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' });
this._networkShareStorageAccountDetails.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' });
this._targetDatabaseContainer.updateCssStyles({ 'display': 'inline' });
this._networkTableContainer.display = (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none';
this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none';
//Preserving the database Names between the 2 tables.
this.migrationStateModel._targetDatabaseNames.forEach((v, index) => {
this._networkShareTargetDatabaseNames[index].value = v;
this._blobContainerTargetDatabaseNames[index].value = v;
});
this._windowsUserAccountText.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
@@ -764,11 +905,19 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._passwordText.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
});
this._sqlSourceUsernameInput.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
});
this._sqlSourcepassword.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
});
this.validateFields();
}
private async validateFields(): Promise<void> {
await this._sqlSourceUsernameInput.validate();
await this._sqlSourcepassword.validate();
await this._networkSharePath.validate();
await this._windowsUserAccountText.validate();
await this._passwordText.validate();
@@ -776,12 +925,13 @@ export class DatabaseBackupPage extends MigrationWizardPage {
await this._networkShareStorageAccountResourceGroupDropdown.validate();
await this._networkShareContainerStorageAccountDropdown.validate();
await this._blobContainerSubscription.validate();
await this._blobContainerResourceGroup.validate();
await this._blobContainerStorageAccountDropdown.validate();
await this._blobContainerDropdown.validate();
await this._targetDatabaseNames.forEach((inputBox) => {
inputBox.validate();
});
for (let i = 0; i < this._networkShareTargetDatabaseNames.length; i++) {
await this._networkShareTargetDatabaseNames[i].validate();
await this._blobContainerTargetDatabaseNames[i].validate();
await this._blobContainerResourceGroupDropdowns[i].validate();
await this._blobContainerStorageAccountDropdowns[i].validate();
await this._blobContainerDropdowns[i].validate();
}
}
private async getSubscriptionValues(): Promise<void> {
@@ -823,36 +973,37 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
private async loadblobResourceGroup(): Promise<void> {
this._blobContainerResourceGroup.loading = true;
this._blobContainerResourceGroupDropdowns.forEach(v => v.loading = true);
try {
this._blobContainerResourceGroup.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
this._blobContainerResourceGroupDropdowns.forEach(v => v.values = resourceGroupValues);
} catch (error) {
console.log(error);
} finally {
this._blobContainerResourceGroup.loading = false;
this._blobContainerResourceGroupDropdowns.forEach(v => v.loading = false);
}
}
private async loadblobStorageDropdown(): Promise<void> {
this._blobContainerStorageAccountDropdown.loading = true;
private async loadblobStorageDropdown(index: number): Promise<void> {
this._blobContainerStorageAccountDropdowns[index].loading = true;
try {
this._blobContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blob.resourceGroup);
this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].resourceGroup);
} catch (error) {
console.log(error);
} finally {
this._blobContainerStorageAccountDropdown.loading = false;
this._blobContainerStorageAccountDropdowns[index].loading = false;
}
}
private async loadBlobContainerDropdown(): Promise<void> {
this._blobContainerDropdown.loading = true;
private async loadBlobContainerDropdown(index: number): Promise<void> {
this._blobContainerDropdowns[index].loading = true;
try {
const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blob.storageAccount);
this._blobContainerDropdown.values = blobContainerValues;
const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].storageAccount);
this._blobContainerDropdowns[index].values = blobContainerValues;
} catch (error) {
console.log(error);
} finally {
this._blobContainerDropdown.loading = false;
this._blobContainerDropdowns[index].loading = false;
}
}

View File

@@ -96,29 +96,25 @@ export class SummaryPage extends MigrationWizardPage {
]
);
break;
case NetworkContainerType.FILE_SHARE:
flexContainer.addItems(
[
createInformationRow(this._view, constants.TYPE, constants.FILE_SHARE),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
]
);
break;
case NetworkContainerType.BLOB_CONTAINER:
flexContainer.addItems(
[
createInformationRow(this._view, constants.TYPE, constants.BLOB_CONTAINER),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.blob.storageAccount.location),
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.blob.storageAccount.resourceGroup!),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.blob.storageAccount.name),
createInformationRow(this._view, constants.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.blob.blobContainer.name)
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name)
]
);
}
flexContainer.addItem(createHeadingTextComponent(this._view, constants.TARGET_NAME));
this.migrationStateModel._migrationDbs.forEach((db, index) => {
flexContainer.addItem(createInformationRow(this._view, db, this.migrationStateModel._targetDatabaseNames[index]));
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
flexContainer.addItems([
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.blobs[index].storageAccount.location),
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.blobs[index].storageAccount.resourceGroup!),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.blobs[index].storageAccount.name),
createInformationRow(this._view, constants.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.blobs[index].blobContainer.name)
]);
}
});
return flexContainer;
}

View File

@@ -15,7 +15,6 @@ import { AccountsSelectionPage } from './accountsSelectionPage';
import { IntergrationRuntimePage } from './integrationRuntimePage';
import { SummaryPage } from './summaryPage';
import { MigrationModePage } from './migrationModePage';
import { SqlSourceConfigurationPage } from './sqlSourceConfigurationPage';
export const WIZARD_INPUT_COMPONENT_WIDTH = '600px';
export class WizardController {
@@ -40,14 +39,12 @@ export class WizardController {
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);
const migrationModePage = new MigrationModePage(wizard, stateModel);
const azureAccountsPage = new AccountsSelectionPage(wizard, stateModel);
const sourceConfigurationPage = new SqlSourceConfigurationPage(wizard, stateModel);
const databaseBackupPage = new DatabaseBackupPage(wizard, stateModel);
const integrationRuntimePage = new IntergrationRuntimePage(wizard, stateModel);
const summaryPage = new SummaryPage(wizard, stateModel);
const pages: MigrationWizardPage[] = [
azureAccountsPage,
sourceConfigurationPage,
skuRecommendationPage,
migrationModePage,
databaseBackupPage,