[SQL Migration] Add storage/MI connectivity validation (#22410)

* wip

* Add SQL VM POC

* Undo azurecore changes

* Add warning banner instead of blocking on next

* Fix warning banner behavior

* Add private endpoint support

* Fix navigation issue

* Add offline scenario

* Address PR comments

* Fix merge conflicts
This commit is contained in:
Raymond Truong
2023-03-29 12:48:22 -07:00
committed by GitHub
parent e70865ff20
commit 4867a3747c
4 changed files with 164 additions and 11 deletions

View File

@@ -18,7 +18,7 @@ import { logError, TelemetryViews } from '../telemetry';
import * as styles from '../constants/styles';
import { TableMigrationSelectionDialog } from '../dialog/tableMigrationSelection/tableMigrationSelectionDialog';
import { ValidateIrDialog } from '../dialog/validationResults/validateIrDialog';
import { getSourceConnectionCredentials, getSourceConnectionProfile, getSourceConnectionQueryProvider, getSourceConnectionUri } from '../api/sqlUtils';
import { canTargetConnectToStorageAccount, getSourceConnectionCredentials, getSourceConnectionProfile, getSourceConnectionQueryProvider, getSourceConnectionUri } from '../api/sqlUtils';
const WIZARD_TABLE_COLUMN_WIDTH = '200px';
const WIZARD_TABLE_COLUMN_WIDTH_SMALL = '170px';
@@ -71,6 +71,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private _existingDatabases: string[] = [];
private _nonPageBlobErrors: string[] = [];
private _inaccessibleStorageAccounts: string[] = [];
private _disposables: vscode.Disposable[] = [];
// SQL DB table selection
@@ -605,7 +606,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
fireOnTextChange: true,
}).component();
this._disposables.push(
this._networkShareContainerStorageAccountDropdown.onValueChanged((value) => {
this._networkShareContainerStorageAccountDropdown.onValueChanged(async (value) => {
if (value && value !== 'undefined') {
const selectedStorageAccount = this.migrationStateModel._storageAccounts.find(sa => sa.name === value);
if (selectedStorageAccount) {
@@ -613,6 +614,22 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this.migrationStateModel._databaseBackup.networkShares[i].storageAccount = selectedStorageAccount;
}
this.migrationStateModel.resetIrValidationResults();
// check for storage account connectivity
if ((this.migrationStateModel.isSqlMiTarget || this.migrationStateModel.isSqlVmTarget)) {
if (!(await canTargetConnectToStorageAccount(this.migrationStateModel._targetType, this.migrationStateModel._targetServerInstance, selectedStorageAccount, this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription))) {
this._inaccessibleStorageAccounts = [selectedStorageAccount.name];
} else {
this._inaccessibleStorageAccounts = [];
}
this.wizard.message = {
text: this._inaccessibleStorageAccounts.length > 0
? constants.STORAGE_ACCOUNT_CONNECTIVITY_WARNING(this.migrationStateModel._targetServerInstance.name, this._inaccessibleStorageAccounts)
: '',
level: azdata.window.MessageLevel.Warning
}
}
}
}
}));
@@ -628,7 +645,26 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._disposables.push(
this._networkShareContainerStorageAccountRefreshButton.onDidClick(
value => this.loadNetworkShareStorageDropdown()));
async () => {
this.loadNetworkShareStorageDropdown();
// check for storage account connectivity
const selectedStorageAccount = this.migrationStateModel._storageAccounts.find(sa => sa.name === (this._networkShareContainerStorageAccountDropdown.value as azdata.CategoryValue).displayName);
if ((this.migrationStateModel.isSqlMiTarget || this.migrationStateModel.isSqlVmTarget) && selectedStorageAccount) {
if (!(await canTargetConnectToStorageAccount(this.migrationStateModel._targetType, this.migrationStateModel._targetServerInstance, selectedStorageAccount, this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription))) {
this._inaccessibleStorageAccounts = [selectedStorageAccount.name];
} else {
this._inaccessibleStorageAccounts = [];
}
this.wizard.message = {
text: this._inaccessibleStorageAccounts.length > 0
? constants.STORAGE_ACCOUNT_CONNECTIVITY_WARNING(this.migrationStateModel._targetServerInstance.name, this._inaccessibleStorageAccounts)
: '',
level: azdata.window.MessageLevel.Warning
}
}
}));
const storageAccountContainer = this._view.modelBuilder.flexContainer()
.withProps({ CSSStyles: { 'margin-top': '-1em' } })
@@ -825,10 +861,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
this._blobContainerTargetDatabaseNamesTable.columns[folderColumnIndex].hidden = folderColumnNewHidden;
const connectionProfile = await getSourceConnectionProfile();
const queryProvider = await getSourceConnectionQueryProvider();
let username = '';
@@ -871,6 +903,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerDropdowns = [];
this._blobContainerLastBackupFileDropdowns = [];
this._blobContainerFolderDropdowns = [];
this._inaccessibleStorageAccounts = [];
if (this.migrationStateModel.isSqlMiTarget) {
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
@@ -1073,7 +1106,28 @@ export class DatabaseBackupPage extends MigrationWizardPage {
if (value && value !== 'undefined') {
const selectedStorageAccount = this.migrationStateModel._storageAccounts.find(sa => sa.name === value);
if (selectedStorageAccount && !blobStorageAccountErrorStrings.includes(value)) {
const oldSelectedStorageAccount = this.migrationStateModel._databaseBackup.blobs[index].storageAccount ? this.migrationStateModel._databaseBackup.blobs[index].storageAccount.name : '';
this.migrationStateModel._databaseBackup.blobs[index].storageAccount = selectedStorageAccount;
// check for storage account connectivity
if ((this.migrationStateModel.isSqlMiTarget || this.migrationStateModel.isSqlVmTarget)) {
if (this.migrationStateModel._databaseBackup.blobs.filter((e, i) => i !== index).every(blob => blob.storageAccount && blob.storageAccount.name.toLowerCase() !== oldSelectedStorageAccount.toLowerCase())) {
this._inaccessibleStorageAccounts = this._inaccessibleStorageAccounts.filter(storageAccountName => storageAccountName.toLowerCase() !== oldSelectedStorageAccount.toLowerCase());
}
if (!(await canTargetConnectToStorageAccount(this.migrationStateModel._targetType, this.migrationStateModel._targetServerInstance, selectedStorageAccount, this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription))) {
this._inaccessibleStorageAccounts = this._inaccessibleStorageAccounts.filter(storageAccountName => storageAccountName.toLowerCase() !== selectedStorageAccount.name.toLowerCase());
this._inaccessibleStorageAccounts.push(selectedStorageAccount.name);
}
this.wizard.message = {
text: this._inaccessibleStorageAccounts.length > 0
? constants.STORAGE_ACCOUNT_CONNECTIVITY_WARNING(this.migrationStateModel._targetServerInstance.name, this._inaccessibleStorageAccounts)
: '',
level: azdata.window.MessageLevel.Warning
}
}
await this.loadBlobContainerDropdown(index);
await blobContainerDropdown.updateProperties({ enabled: true });
} else {
@@ -1090,6 +1144,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
if (selectedBlobContainer && !blobContainerErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].blobContainer = selectedBlobContainer;
// check for block blobs for SQL VM <= 2014
if (this.migrationStateModel.isSqlVmTarget && utils.isTargetSqlVm2014OrBelow(this.migrationStateModel._targetServerInstance as SqlVMServer)) {
const backups = await utils.getBlobLastBackupFileNames(
this.migrationStateModel._azureAccount,
@@ -1137,7 +1192,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._disposables.push(
blobContainerFolderDropdown.onValueChanged(value => {
if (value && value !== 'undefined') {
if (this.migrationStateModel._blobContainerFolders.includes(value) && !blobFolderErrorStrings.includes(value)) {
if (this.migrationStateModel._blobContainerFolders && this.migrationStateModel._blobContainerFolders.includes(value) && !blobFolderErrorStrings.includes(value)) {
const selectedFolder = value;
this.migrationStateModel._databaseBackup.blobs[index].folderName = selectedFolder;
}