Add IR Migration configuration Validation to SQL Migration extension (#21386)

* re-factor and consolidate wizard pages

* validation WIP 11/10

* validate ir dialog

* navigation fixes

* bump version to 1.2.0

* add resource strings and fix navigatin issue

* map validation state to resource string clean up

* address review comments

* fix typos, address review comments

* address review feedback, readability

* fix res string, validation check, col width

* bug fixes, nav, sqldb migration

* fix nav/refresh/visibility issues

* fix nav issues, cancel pending validation items

* update error text / position

* fix localization bug
This commit is contained in:
brian-harris
2022-12-16 14:52:24 -08:00
committed by GitHub
parent 754d70d654
commit 2e240729af
29 changed files with 1993 additions and 692 deletions

View File

@@ -15,8 +15,22 @@ import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError
import { hashString, deepClone } from '../api/utils';
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
import { excludeDatabases, TargetDatabaseInfo } from '../api/sqlUtils';
const localize = nls.loadMessageBundle();
export enum ValidateIrState {
Pending = 'Pending',
Running = 'Running',
Succeeded = 'Succeeded',
Failed = 'Failed',
Canceled = 'Canceled',
}
export interface ValidationResult {
errors: string[];
state: ValidateIrState;
}
export enum State {
INIT,
COLLECTING_SOURCE_INFO,
@@ -72,9 +86,8 @@ export enum Page {
DatabaseSelector,
SKURecommendation,
TargetSelection,
MigrationMode,
DatabaseBackup,
IntegrationRuntime,
DatabaseBackup,
Summary
}
@@ -212,6 +225,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _sqldbDbs: string[] = [];
public _targetType!: MigrationTargetType;
public _validateIrSqlDb: ValidationResult[] = [];
public _validateIrSqlMi: ValidationResult[] = [];
public _validateIrSqlVm: ValidationResult[] = [];
public _skuRecommendationResults!: SkuRecommendation;
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
@@ -268,6 +285,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._databaseBackup = {} as DatabaseBackupModel;
this._databaseBackup.networkShares = [];
this._databaseBackup.blobs = [];
this._databaseBackup.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
this._targetDatabaseNames = [];
this._assessmentReportFilePath = '';
this._skuRecommendationReportFilePaths = [];
@@ -279,6 +297,60 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._skuEnableElastic = false;
}
public get validationTargetResults(): ValidationResult[] {
switch (this._targetType) {
case MigrationTargetType.SQLDB:
return this._validateIrSqlDb;
case MigrationTargetType.SQLMI:
return this._validateIrSqlMi;
case MigrationTargetType.SQLVM:
return this._validateIrSqlVm;
default:
return [];
}
}
public resetIrValidationResults(): void {
if (this.isIrMigration) {
this._validateIrSqlDb = [];
this._validateIrSqlMi = [];
this._validateIrSqlVm = [];
}
}
public get isSqlVmTarget(): boolean {
return this._targetType === MigrationTargetType.SQLVM;
}
public get isSqlMiTarget(): boolean {
return this._targetType === MigrationTargetType.SQLMI;
}
public get isSqlDbTarget(): boolean {
return this._targetType === MigrationTargetType.SQLDB;
}
public get isIrTargetValidated(): boolean {
const results = this.validationTargetResults ?? [];
return results.length > 1
&& results.every(r =>
r.errors.length === 0 &&
r.state === ValidateIrState.Succeeded)
}
public get isBackupContainerNetworkShare(): boolean {
return this._databaseBackup?.networkContainerType === NetworkContainerType.NETWORK_SHARE;
}
public get isBackupContainerBlobContainer(): boolean {
return this._databaseBackup?.networkContainerType === NetworkContainerType.BLOB_CONTAINER;
}
public get isIrMigration(): boolean {
return this.isSqlDbTarget
|| this.isBackupContainerNetworkShare;
}
public get sourceConnectionId(): string {
return this._sourceConnectionId;
}
@@ -877,7 +949,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
value => value.connectionId === this.sourceConnectionId);
const isOfflineMigration = this._databaseBackup.migrationMode === MigrationMode.OFFLINE;
const isSqlDbTarget = this._targetType === MigrationTargetType.SQLDB;
const isSqlDbTarget = this.isSqlDbTarget;
const requestBody: StartDatabaseMigrationRequest = {
location: this._sqlMigrationService?.location!,
@@ -901,93 +973,93 @@ export class MigrationStateModel implements Model, vscode.Disposable {
for (let i = 0; i < this._databasesForMigration.length; i++) {
try {
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
requestBody.properties.backupConfiguration = {
targetLocation: undefined!,
sourceLocation: {
fileStorageType: 'AzureBlob',
azureBlob: {
storageAccountResourceId: this._databaseBackup.blobs[i].storageAccount.id,
accountKey: this._databaseBackup.blobs[i].storageKey,
blobContainerName: this._databaseBackup.blobs[i].blobContainer.name
}
}
};
if (isSqlDbTarget) {
const sourceDatabaseName = this._databasesForMigration[i];
const targetDatabaseInfo = this._sourceTargetMapping.get(sourceDatabaseName);
const totalTables = targetDatabaseInfo?.sourceTables.size ?? 0;
// skip databases that don't have tables
if (totalTables === 0) {
continue;
}
if (isOfflineMigration) {
requestBody.properties.offlineConfiguration = {
offline: isOfflineMigration,
lastBackupName: this._databaseBackup.blobs[i]?.lastBackupFile
};
const sourceTables: string[] = [];
let selectedTables = 0;
targetDatabaseInfo?.sourceTables.forEach(sourceTableInfo => {
if (sourceTableInfo.selectedForMigration) {
selectedTables++;
sourceTables.push(sourceTableInfo.tableName);
}
break;
case NetworkContainerType.NETWORK_SHARE:
requestBody.properties.backupConfiguration = {
targetLocation: {
storageAccountResourceId: this._databaseBackup.networkShares[i].storageAccount.id,
accountKey: this._databaseBackup.networkShares[i].storageKey,
},
sourceLocation: {
fileStorageType: 'FileShare',
fileShare: {
path: this._databaseBackup.networkShares[i].networkShareLocation,
username: this._databaseBackup.networkShares[i].windowsUser,
password: this._databaseBackup.networkShares[i].password,
});
// skip databases that don't have tables selected
if (selectedTables === 0) {
continue;
}
const sqlDbTarget = this._targetServerInstance as AzureSqlDatabaseServer;
requestBody.properties.offlineConfiguration = undefined;
requestBody.properties.sourceSqlConnection = {
dataSource: currentConnection?.serverName!,
authentication: this._authenticationType,
userName: this._sqlServerUsername,
password: this._sqlServerPassword,
encryptConnection: true,
trustServerCertificate: currentConnection?.options.trustServerCertificate ?? false,
};
requestBody.properties.targetSqlConnection = {
dataSource: sqlDbTarget.properties.fullyQualifiedDomainName,
authentication: MigrationSourceAuthenticationType.Sql,
userName: this._targetUserName,
password: this._targetPassword,
encryptConnection: true,
trustServerCertificate: false,
};
// send an empty array when 'all' tables are selected for migration
requestBody.properties.tableList = selectedTables === totalTables
? []
: sourceTables;
} else {
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
requestBody.properties.backupConfiguration = {
targetLocation: undefined!,
sourceLocation: {
fileStorageType: FileStorageType.AzureBlob,
azureBlob: {
storageAccountResourceId: this._databaseBackup.blobs[i].storageAccount.id,
accountKey: this._databaseBackup.blobs[i].storageKey,
blobContainerName: this._databaseBackup.blobs[i].blobContainer.name
}
}
}
};
break;
default:
if (isSqlDbTarget) {
const sourceDatabaseName = this._databasesForMigration[i];
const targetDatabaseInfo = this._sourceTargetMapping.get(sourceDatabaseName);
const totalTables = targetDatabaseInfo?.sourceTables.size ?? 0;
// skip databases that don't have tables
if (totalTables === 0) {
continue;
}
const sourceTables: string[] = [];
let selectedTables = 0;
targetDatabaseInfo?.sourceTables.forEach(sourceTableInfo => {
if (sourceTableInfo.selectedForMigration) {
selectedTables++;
sourceTables.push(sourceTableInfo.tableName);
}
});
// skip databases that don't have tables selected
if (selectedTables === 0) {
continue;
}
const sqlDbTarget = this._targetServerInstance as AzureSqlDatabaseServer;
requestBody.properties.offlineConfiguration = undefined;
requestBody.properties.sourceSqlConnection = {
dataSource: currentConnection?.serverName!,
authentication: this._authenticationType,
userName: this._sqlServerUsername,
password: this._sqlServerPassword,
encryptConnection: true,
trustServerCertificate: currentConnection?.options.trustServerCertificate ?? false,
};
requestBody.properties.targetSqlConnection = {
dataSource: sqlDbTarget.properties.fullyQualifiedDomainName,
authentication: MigrationSourceAuthenticationType.Sql,
userName: this._targetUserName,
password: this._targetPassword,
encryptConnection: true,
trustServerCertificate: false,
};
// send an empty array when 'all' tables are selected for migration
requestBody.properties.tableList = selectedTables === totalTables
? []
: sourceTables;
}
break;
if (isOfflineMigration) {
requestBody.properties.offlineConfiguration = {
offline: isOfflineMigration,
lastBackupName: this._databaseBackup.blobs[i]?.lastBackupFile
};
}
break;
case NetworkContainerType.NETWORK_SHARE:
requestBody.properties.backupConfiguration = {
targetLocation: {
storageAccountResourceId: this._databaseBackup.networkShares[i].storageAccount.id,
accountKey: this._databaseBackup.networkShares[i].storageKey,
},
sourceLocation: {
fileStorageType: FileStorageType.FileShare,
fileShare: {
path: this._databaseBackup.networkShares[i].networkShareLocation,
username: this._databaseBackup.networkShares[i].windowsUser,
password: this._databaseBackup.networkShares[i].password,
}
}
};
break;
}
}
requestBody.properties.sourceDatabaseName = this._databasesForMigration[i];
const response = await startDatabaseMigration(
this._azureAccount,
@@ -1086,16 +1158,14 @@ export class MigrationStateModel implements Model, vscode.Disposable {
case Page.IntegrationRuntime:
saveInfo.sqlMigrationService = this._sqlMigrationService;
saveInfo.migrationMode = this._databaseBackup.migrationMode;
saveInfo.networkContainerType = this._databaseBackup.networkContainerType;
case Page.DatabaseBackup:
saveInfo.networkContainerType = this._databaseBackup.networkContainerType;
saveInfo.networkShares = this._databaseBackup.networkShares;
saveInfo.blobs = this._databaseBackup.blobs;
saveInfo.targetDatabaseNames = this._targetDatabaseNames;
case Page.MigrationMode:
saveInfo.migrationMode = this._databaseBackup.migrationMode;
case Page.TargetSelection:
saveInfo.azureAccount = deepClone(this._azureAccount);
saveInfo.azureTenant = deepClone(this._azureTenant);
@@ -1162,7 +1232,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._sourceDatabaseNames = this._databasesForMigration;
this._targetDatabaseNames = this.savedInfo.targetDatabaseNames;
this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType || undefined!;
this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType ?? NetworkContainerType.BLOB_CONTAINER;
this._databaseBackup.networkShares = this.savedInfo.networkShares;
this._databaseBackup.blobs = this.savedInfo.blobs;
this._databaseBackup.subscription = this.savedInfo.subscription || undefined!;