SQL-Migration: enable cross subscription service migration (#22876)

* x subscription migration support

* refresh after cutover

* fix service irregular status load behavior

* queue service status requests, fix typo

* add migationTargetServerName helper method

* save context before api call
This commit is contained in:
brian-harris
2023-04-27 16:28:32 -07:00
committed by GitHub
parent 65f8915b7e
commit fe32180c71
15 changed files with 347 additions and 240 deletions

View File

@@ -656,11 +656,6 @@ export async function deleteMigration(account: azdata.Account, subscription: Sub
} }
} }
export async function getLocationDisplayName(location: string): Promise<string> {
const api = await getAzureCoreAPI();
return api.getRegionDisplayName(location);
}
export async function validateIrSqlDatabaseMigrationSettings( export async function validateIrSqlDatabaseMigrationSettings(
migration: MigrationStateModel, migration: MigrationStateModel,
sourceServerName: string, sourceServerName: string,
@@ -674,7 +669,7 @@ export async function validateIrSqlDatabaseMigrationSettings(
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const account = migration._azureAccount; const account = migration._azureAccount;
const subscription = migration._targetSubscription; const subscription = migration._sqlMigrationServiceSubscription;
const serviceId = migration._sqlMigrationService?.id; const serviceId = migration._sqlMigrationService?.id;
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
const path = encodeURI(`${serviceId}/validateIr?api-version=${DMSV2_API_VERSION}`); const path = encodeURI(`${serviceId}/validateIr?api-version=${DMSV2_API_VERSION}`);
@@ -735,11 +730,12 @@ export async function validateIrDatabaseMigrationSettings(
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const account = migration._azureAccount; const account = migration._azureAccount;
const subscription = migration._targetSubscription; const serviceSubscription = migration._sqlMigrationServiceSubscription;
const targetSubscription = migration._targetSubscription;
const serviceId = migration._sqlMigrationService?.id; const serviceId = migration._sqlMigrationService?.id;
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
const path = encodeURI(`${serviceId}/validateIr?api-version=${DMSV2_API_VERSION}`); const path = encodeURI(`${serviceId}/validateIr?api-version=${DMSV2_API_VERSION}`);
const storage = await getStorageAccountAccessKeys(account, subscription, networkShare.storageAccount); const storage = await getStorageAccountAccessKeys(account, targetSubscription, networkShare.storageAccount);
const requestBody: ValdiateIrDatabaseMigrationRequest = { const requestBody: ValdiateIrDatabaseMigrationRequest = {
sourceDatabaseName: sourceDatabaseName ?? '', sourceDatabaseName: sourceDatabaseName ?? '',
@@ -775,7 +771,7 @@ export async function validateIrDatabaseMigrationSettings(
const response = await api.makeAzureRestRequest<any>( const response = await api.makeAzureRestRequest<any>(
account, account,
subscription, serviceSubscription,
path, path,
azurecore.HttpRequestMethod.POST, azurecore.HttpRequestMethod.POST,
requestBody, requestBody,

View File

@@ -759,6 +759,12 @@ export function AUTH_KEY_REFRESHED(keyName: string): string {
export function SERVICE_NOT_READY(serviceName: string): string { export function SERVICE_NOT_READY(serviceName: string): string {
return localize('sql.migration.service.not.ready', "Azure Database Migration Service is not registered. Azure Database Migration Service '{0}' needs to be registered with self-hosted integration runtime on any node.", serviceName); return localize('sql.migration.service.not.ready', "Azure Database Migration Service is not registered. Azure Database Migration Service '{0}' needs to be registered with self-hosted integration runtime on any node.", serviceName);
} }
export function SERVICE_ERROR_NOT_READY(serviceName: string, error: string): string {
return localize('sql.migration.service.error.not.ready',
"The following error occurred while retrieving registration information for Azure Database Migration Service '{0}'. Please click refresh and try again. Error: '{1}'.",
serviceName,
error);
}
export function SERVICE_READY(serviceName: string, host: string): string { export function SERVICE_READY(serviceName: string, host: string): string {
return localize('sql.migration.service.ready', "Azure Database Migration Service '{0}' is connected to self-hosted integration runtime running on the node - {1}", serviceName, host); return localize('sql.migration.service.ready', "Azure Database Migration Service '{0}' is connected to self-hosted integration runtime running on the node - {1}", serviceName, host);
} }

View File

@@ -776,12 +776,12 @@ export class DashboardTab extends TabBase<DashboardTab> {
this.serviceContextChangedEvent.event( this.serviceContextChangedEvent.event(
async (e) => { async (e) => {
if (e.connectionId === await getSourceConnectionId()) { if (e.connectionId === await getSourceConnectionId()) {
await this.updateServiceContext(this._serviceContextButton); await this.updateServiceButtonContext(this._serviceContextButton);
await this.refresh(); await this.refresh();
} }
} }
)); ));
await this.updateServiceContext(this._serviceContextButton); await this.updateServiceButtonContext(this._serviceContextButton);
return this._serviceContextButton; return this._serviceContextButton;
} }

View File

@@ -147,7 +147,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
await this.refresh(); await this.refresh();
const dialog = new ConfirmCutoverDialog(this.model); const dialog = new ConfirmCutoverDialog(this.model);
await dialog.initialize(); await dialog.initialize();
await this.refresh();
if (this.model.CutoverError) { if (this.model.CutoverError) {
await this.statusBar.showError( await this.statusBar.showError(
loc.MIGRATION_CUTOVER_ERROR, loc.MIGRATION_CUTOVER_ERROR,

View File

@@ -78,7 +78,7 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
} }
} }
public async refresh(): Promise<void> { public async refresh(initialize?: boolean): Promise<void> {
if (this.isRefreshing || if (this.isRefreshing ||
this._refreshLoader === undefined) { this._refreshLoader === undefined) {
@@ -91,6 +91,10 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
await this.statusBar.clearError(); await this.statusBar.clearError();
if (initialize) {
await this.updateServiceButtonContext(this._serviceContextButton);
}
await this._statusTable.updateProperty('data', []); await this._statusTable.updateProperty('data', []);
this._migrations = await getCurrentMigrations(); this._migrations = await getCurrentMigrations();
await this._populateMigrationTable(); await this._populateMigrationTable();
@@ -180,12 +184,11 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
this.serviceContextChangedEvent.event( this.serviceContextChangedEvent.event(
async (e) => { async (e) => {
if (e.connectionId === await getSourceConnectionId()) { if (e.connectionId === await getSourceConnectionId()) {
await this.updateServiceContext(this._serviceContextButton); await this.refresh(true);
await this.refresh();
} }
} }
)); ));
await this.updateServiceContext(this._serviceContextButton); await this.updateServiceButtonContext(this._serviceContextButton);
this._searchBox = this.view.modelBuilder.inputBox() this._searchBox = this.view.modelBuilder.inputBox()
.withProps({ .withProps({

View File

@@ -57,7 +57,7 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
switch (this._selectedTabId) { switch (this._selectedTabId) {
case undefined: case undefined:
case MigrationsListTabId: case MigrationsListTabId:
return this._migrationsListTab.refresh(); return this._migrationsListTab.refresh(true);
default: default:
return this._migrationDetailsViewTab.refresh(); return this._migrationDetailsViewTab.refresh();
} }
@@ -155,11 +155,10 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
return undefined; return undefined;
} }
private async _openTab(tab: azdata.Tab): Promise<void> { private async _openTab(tab: TabBase<any>): Promise<void> {
if (tab.id === this._selectedTabId) { if (tab.id === this._selectedTabId) {
return; return;
} }
await this.statusBar.clearError(); await this.statusBar.clearError();
this._tab.clearItems(); this._tab.clearItems();
this._tab.addItem(tab.content); this._tab.addItem(tab.content);

View File

@@ -82,16 +82,12 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
return new Date(stringDate1) > new Date(stringDate2) ? -sortDir : sortDir; return new Date(stringDate1) > new Date(stringDate2) ? -sortDir : sortDir;
} }
protected async updateServiceContext(button: azdata.ButtonComponent): Promise<void> { protected async updateServiceButtonContext(button: azdata.ButtonComponent): Promise<void> {
const label = await getSelectedServiceStatus(); const label = await getSelectedServiceStatus();
if (button.label !== label || await button.updateProperty('label', '');
button.title !== label) { await button.updateProperty('title', '');
await button.updateProperty('label', label);
button.label = label; await button.updateProperty('title', label);
button.title = label;
await this.refresh();
}
} }
protected createNewLoginMigrationButton(): azdata.ButtonComponent { protected createNewLoginMigrationButton(): azdata.ButtonComponent {

View File

@@ -61,7 +61,7 @@ export class CreateSqlMigrationServiceDialog {
this._dialogObject.okButton.position = 'left'; this._dialogObject.okButton.position = 'left';
this._dialogObject.cancelButton.position = 'left'; this._dialogObject.cancelButton.position = 'left';
let tab = azdata.window.createTab(''); const tab = azdata.window.createTab('');
this._dialogObject.registerCloseValidator(async () => { this._dialogObject.registerCloseValidator(async () => {
return true; return true;
}); });
@@ -73,70 +73,64 @@ export class CreateSqlMigrationServiceDialog {
width: '80px' width: '80px'
}).component(); }).component();
this._disposables.push(this._formSubmitButton.onDidClick(async (e) => { this._disposables.push(
this._dialogObject.message = { this._formSubmitButton.onDidClick(async (e) => {
text: ''
};
this._statusLoadingComponent.loading = true;
this.migrationServiceResourceGroupDropdown.loading = false;
this.setFormEnabledState(false);
const subscription = this._model._targetSubscription;
const resourceGroup = this._selectedResourceGroup;
const location = this._model._targetServerInstance.location;
const serviceName = this.migrationServiceNameText.value;
const formValidationErrors = this.validateCreateServiceForm(subscription, resourceGroup.name, location, serviceName);
if (formValidationErrors.length > 0) {
this.setDialogMessage(formValidationErrors);
this._statusLoadingComponent.loading = false;
this.setFormEnabledState(true);
return;
}
try {
utils.clearDialogMessage(this._dialogObject); utils.clearDialogMessage(this._dialogObject);
this._selectedResourceGroup = resourceGroup;
this._createdMigrationService = await createSqlMigrationService(
this._model._azureAccount,
subscription,
resourceGroup.name,
location,
serviceName!,
this._model._sessionId);
if (this._createdMigrationService.error) { this._statusLoadingComponent.loading = true;
this.setDialogMessage(`${this._createdMigrationService.error.code} : ${this._createdMigrationService.error.message}`); this.migrationServiceResourceGroupDropdown.loading = false;
this._statusLoadingComponent.loading = false; this.setFormEnabledState(false);
const subscription = this._model._sqlMigrationServiceSubscription;
const resourceGroup = this._selectedResourceGroup;
const location = this._model._location.name;
const serviceName = this.migrationServiceNameText.value;
const formValidationErrors = this.validateCreateServiceForm(subscription, resourceGroup.name, location, serviceName);
try {
if (formValidationErrors.length > 0) {
this.setDialogMessage(formValidationErrors);
this.setFormEnabledState(true);
return;
}
utils.clearDialogMessage(this._dialogObject);
this._createdMigrationService = await createSqlMigrationService(
this._model._azureAccount,
subscription,
resourceGroup.name,
location,
serviceName!,
this._model._sessionId);
if (this._createdMigrationService.error) {
this.setDialogMessage(`${this._createdMigrationService.error.code} : ${this._createdMigrationService.error.message}`);
this.setFormEnabledState(true);
return;
}
if (this._isBlobContainerUsed && !this._model.isSqlDbTarget) {
this._dialogObject.okButton.enabled = true;
this._setupContainer.display = 'none';
this._dialogObject.message = {
text: constants.DATA_MIGRATION_SERVICE_CREATED_SUCCESSFULLY,
level: azdata.window.MessageLevel.Information
};
} else {
await this.refreshStatus();
await this.refreshAuthTable();
this._setupContainer.display = 'inline';
this._testConnectionButton.hidden = false;
}
} catch (e) {
console.log(e);
this.setDialogMessage(e.message);
this.setFormEnabledState(true); this.setFormEnabledState(true);
return; } finally {
}
if (this._isBlobContainerUsed && !this._model.isSqlDbTarget) {
this._dialogObject.okButton.enabled = true;
this._statusLoadingComponent.loading = false;
this._setupContainer.display = 'none';
this._dialogObject.message = {
text: constants.DATA_MIGRATION_SERVICE_CREATED_SUCCESSFULLY,
level: azdata.window.MessageLevel.Information
};
} else {
await this.refreshStatus();
await this.refreshAuthTable();
this._setupContainer.display = 'inline';
this._testConnectionButton.hidden = false;
this._statusLoadingComponent.loading = false; this._statusLoadingComponent.loading = false;
} }
} catch (e) { }));
console.log(e);
this.setDialogMessage(e.message);
this._statusLoadingComponent.loading = false;
this.setFormEnabledState(true);
return;
}
}));
this._statusLoadingComponent = view.modelBuilder.loadingComponent().withProps({ this._statusLoadingComponent = view.modelBuilder.loadingComponent().withProps({
loadingText: constants.LOADING_MIGRATION_SERVICES, loadingText: constants.LOADING_MIGRATION_SERVICES,
@@ -289,7 +283,11 @@ export class CreateSqlMigrationServiceDialog {
}).component(); }).component();
this._disposables.push(this._createResourceGroupLink.onDidClick(async e => { this._disposables.push(this._createResourceGroupLink.onDidClick(async e => {
const createResourceGroupDialog = new CreateResourceGroupDialog(this._model._azureAccount, this._model._targetSubscription, this._model._targetServerInstance.location); const createResourceGroupDialog = new CreateResourceGroupDialog(
this._model._azureAccount,
this._model._sqlMigrationServiceSubscription,
this._model._location.name);
const createdResourceGroup = await createResourceGroupDialog.initialize(); const createdResourceGroup = await createResourceGroupDialog.initialize();
if (createdResourceGroup) { if (createdResourceGroup) {
this._resourceGroups.push(createdResourceGroup); this._resourceGroups.push(createdResourceGroup);
@@ -324,7 +322,7 @@ export class CreateSqlMigrationServiceDialog {
this.migrationServiceLocation = this._view.modelBuilder.text().withProps({ this.migrationServiceLocation = this._view.modelBuilder.text().withProps({
enabled: false, enabled: false,
value: await this._model.getLocationDisplayName(this._model._targetServerInstance.location), value: this._model._location.displayName,
CSSStyles: { CSSStyles: {
'margin': '-1em 0 0' 'margin': '-1em 0 0'
} }
@@ -386,7 +384,7 @@ export class CreateSqlMigrationServiceDialog {
private async populateSubscriptions(): Promise<void> { private async populateSubscriptions(): Promise<void> {
this.migrationServiceResourceGroupDropdown.loading = true; this.migrationServiceResourceGroupDropdown.loading = true;
this.migrationServiceSubscription.value = this._model._targetSubscription.name; this.migrationServiceSubscription.value = this._model._sqlMigrationServiceSubscription.name;
await this.populateResourceGroups(); await this.populateResourceGroups();
} }
@@ -395,7 +393,7 @@ export class CreateSqlMigrationServiceDialog {
try { try {
this._resourceGroups = await utils.getAllResourceGroups( this._resourceGroups = await utils.getAllResourceGroups(
this._model._azureAccount, this._model._azureAccount,
this._model._targetSubscription); this._model._sqlMigrationServiceSubscription);
this.migrationServiceResourceGroupDropdown.values = utils.getResourceDropdownValues( this.migrationServiceResourceGroupDropdown.values = utils.getResourceDropdownValues(
this._resourceGroups, this._resourceGroups,
constants.RESOURCE_GROUP_NOT_FOUND); constants.RESOURCE_GROUP_NOT_FOUND);
@@ -510,10 +508,10 @@ export class CreateSqlMigrationServiceDialog {
} }
private async refreshStatus(): Promise<void> { private async refreshStatus(): Promise<void> {
const subscription = this._model._targetSubscription; const subscription = this._model._sqlMigrationServiceSubscription;
const resourceGroupId = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name; const resourceGroupId = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const resourceGroup = getResourceName(resourceGroupId); const resourceGroup = getResourceName(resourceGroupId);
const location = this._model._targetServerInstance.location; const location = this._model._location.name;
const maxRetries = 5; const maxRetries = 5;
let migrationServiceStatus!: SqlMigrationService; let migrationServiceStatus!: SqlMigrationService;
@@ -558,7 +556,6 @@ export class CreateSqlMigrationServiceDialog {
...styles.BODY_CSS ...styles.BODY_CSS
} }
}); });
this._dialogObject.okButton.enabled = true;
} else { } else {
this._connectionStatus.text = constants.SERVICE_NOT_READY(this._createdMigrationService!.name); this._connectionStatus.text = constants.SERVICE_NOT_READY(this._createdMigrationService!.name);
await this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ await this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
@@ -568,16 +565,16 @@ export class CreateSqlMigrationServiceDialog {
...styles.BODY_CSS ...styles.BODY_CSS
} }
}); });
this._dialogObject.okButton.enabled = false;
} }
this._dialogObject.okButton.enabled = true;
} }
} }
private async refreshAuthTable(): Promise<void> { private async refreshAuthTable(): Promise<void> {
const subscription = this._model._targetSubscription; const subscription = this._model._sqlMigrationServiceSubscription;
const resourceGroupId = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name; const resourceGroupId = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const resourceGroup = getResourceName(resourceGroupId); const resourceGroup = getResourceName(resourceGroupId);
const location = this._model._targetServerInstance.location; const location = this._model._location.name;
const keys = await getSqlMigrationServiceAuthKeys( const keys = await getSqlMigrationServiceAuthKeys(
this._model._azureAccount, this._model._azureAccount,
subscription, subscription,

View File

@@ -74,6 +74,8 @@ export class RestartMigrationDialog {
// Integration Runtime // Integration Runtime
sqlMigrationService: serviceContext.migrationService, sqlMigrationService: serviceContext.migrationService,
serviceSubscription: null,
serviceResourceGroup: null
}; };
const getStorageAccountResourceGroup = (storageAccountResourceId: string): azureResource.AzureResourceResourceGroup => { const getStorageAccountResourceGroup = (storageAccountResourceId: string): azureResource.AzureResourceResourceGroup => {

View File

@@ -8,7 +8,7 @@ import * as azurecore from 'azurecore';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as contracts from '../service/contracts'; import * as contracts from '../service/contracts';
import * as features from '../service/features'; import * as features from '../service/features';
import { SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, SqlVMServer, getLocationDisplayName, getSqlManagedInstanceDatabases, AzureSqlDatabaseServer, VirtualMachineInstanceView } from '../api/azure'; import { SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, SqlVMServer, getSqlManagedInstanceDatabases, AzureSqlDatabaseServer, VirtualMachineInstanceView } from '../api/azure';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
@@ -150,6 +150,8 @@ export interface SavedInfo {
blobs: Blob[]; blobs: Blob[];
targetDatabaseNames: string[]; targetDatabaseNames: string[];
sqlMigrationService: SqlMigrationService | undefined; sqlMigrationService: SqlMigrationService | undefined;
serviceSubscription: azurecore.azureResource.AzureResourceSubscription | null;
serviceResourceGroup: azurecore.azureResource.AzureResourceResourceGroup | null;
serverAssessment: ServerAssessment | null; serverAssessment: ServerAssessment | null;
skuRecommendation: SkuRecommendationSavedInfo | null; skuRecommendation: SkuRecommendationSavedInfo | null;
} }
@@ -201,6 +203,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _targetPassword!: string; public _targetPassword!: string;
public _sourceTargetMapping: Map<string, TargetDatabaseInfo | undefined> = new Map(); public _sourceTargetMapping: Map<string, TargetDatabaseInfo | undefined> = new Map();
public _sqlMigrationServiceSubscription!: azurecore.azureResource.AzureResourceSubscription;
public _sqlMigrationServiceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup; public _sqlMigrationServiceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
public _sqlMigrationService!: SqlMigrationService | undefined; public _sqlMigrationService!: SqlMigrationService | undefined;
public _sqlMigrationServices!: SqlMigrationService[]; public _sqlMigrationServices!: SqlMigrationService[];
@@ -342,6 +345,19 @@ export class MigrationStateModel implements Model, vscode.Disposable {
r.state === ValidateIrState.Succeeded) r.state === ValidateIrState.Succeeded)
} }
public get migrationTargetServerName(): string {
switch (this._targetType) {
case MigrationTargetType.SQLMI:
return (this._targetServerInstance as azurecore.azureResource.AzureSqlManagedInstance)?.name;
case MigrationTargetType.SQLVM:
return (this._targetServerInstance as SqlVMServer)?.name;
case MigrationTargetType.SQLDB:
return (this._targetServerInstance as AzureSqlDatabaseServer)?.name;
default:
return '';
}
}
public get isBackupContainerNetworkShare(): boolean { public get isBackupContainerNetworkShare(): boolean {
return this._databaseBackup?.networkContainerType === NetworkContainerType.NETWORK_SHARE; return this._databaseBackup?.networkContainerType === NetworkContainerType.NETWORK_SHARE;
} }
@@ -927,10 +943,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
return this.extensionContext.extensionPath; return this.extensionContext.extensionPath;
} }
public getLocationDisplayName(location: string): Promise<string> {
return getLocationDisplayName(location);
}
public async getManagedDatabases(): Promise<string[]> { public async getManagedDatabases(): Promise<string[]> {
return ( return (
await getSqlManagedInstanceDatabases(this._azureAccount, await getSqlManagedInstanceDatabases(this._azureAccount,
@@ -1107,7 +1119,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
requestBody.properties.sourceDatabaseName = this._databasesForMigration[i]; requestBody.properties.sourceDatabaseName = this._databasesForMigration[i];
const response = await startDatabaseMigration( const response = await startDatabaseMigration(
this._azureAccount, this._azureAccount,
this._targetSubscription, this._sqlMigrationServiceSubscription,
this._sqlMigrationService?.location!, this._sqlMigrationService?.location!,
this._targetServerInstance, this._targetServerInstance,
this._targetDatabaseNames[i], this._targetDatabaseNames[i],
@@ -1131,9 +1143,9 @@ export class MigrationStateModel implements Model, vscode.Disposable {
{ {
'sessionId': this._sessionId, 'sessionId': this._sessionId,
'tenantId': this._azureAccount.properties.tenants[0].id, 'tenantId': this._azureAccount.properties.tenants[0].id,
'subscriptionId': this._targetSubscription?.id, 'subscriptionId': this._sqlMigrationServiceSubscription?.id,
'resourceGroup': this._resourceGroup?.name, 'resourceGroup': this._sqlMigrationServiceResourceGroup?.name,
'location': this._targetServerInstance.location, 'location': this._location.name,
'targetType': this._targetType, 'targetType': this._targetType,
'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name), 'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name),
'hashedDatabaseName': hashString(this._databasesForMigration[i]), 'hashedDatabaseName': hashString(this._databasesForMigration[i]),
@@ -1197,12 +1209,16 @@ export class MigrationStateModel implements Model, vscode.Disposable {
sqlMigrationService: undefined, sqlMigrationService: undefined,
serverAssessment: null, serverAssessment: null,
skuRecommendation: null, skuRecommendation: null,
serviceResourceGroup: null,
serviceSubscription: null,
}; };
switch (currentPage) { switch (currentPage) {
case Page.Summary: case Page.Summary:
case Page.IntegrationRuntime: case Page.IntegrationRuntime:
saveInfo.sqlMigrationService = this._sqlMigrationService; saveInfo.sqlMigrationService = this._sqlMigrationService;
saveInfo.serviceSubscription = this._sqlMigrationServiceSubscription;
saveInfo.serviceResourceGroup = this._sqlMigrationServiceResourceGroup;
saveInfo.migrationMode = this._databaseBackup.migrationMode; saveInfo.migrationMode = this._databaseBackup.migrationMode;
saveInfo.networkContainerType = this._databaseBackup.networkContainerType; saveInfo.networkContainerType = this._databaseBackup.networkContainerType;
@@ -1282,6 +1298,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._databaseBackup.subscription = this.savedInfo.subscription || undefined!; this._databaseBackup.subscription = this.savedInfo.subscription || undefined!;
this._sqlMigrationService = this.savedInfo.sqlMigrationService; this._sqlMigrationService = this.savedInfo.sqlMigrationService;
this._sqlMigrationServiceSubscription = this.savedInfo.serviceSubscription || undefined!;
this._sqlMigrationServiceResourceGroup = this.savedInfo.serviceResourceGroup || undefined!;
const savedAssessmentResults = this.savedInfo.serverAssessment; const savedAssessmentResults = this.savedInfo.serverAssessment;
if (savedAssessmentResults) { if (savedAssessmentResults) {

View File

@@ -228,7 +228,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
description: constants.DATABASE_BACKUP_NETWORK_SHARE_WINDOWS_USER_INFO, description: constants.DATABASE_BACKUP_NETWORK_SHARE_WINDOWS_USER_INFO,
width: WIZARD_INPUT_COMPONENT_WIDTH, width: WIZARD_INPUT_COMPONENT_WIDTH,
requiredIndicator: true, requiredIndicator: true,
CSSStyles: { ...styles.LABEL_CSS } CSSStyles: { ...styles.LABEL_CSS },
}).component(); }).component();
this._windowsUserAccountText = this._view.modelBuilder.inputBox() this._windowsUserAccountText = this._view.modelBuilder.inputBox()
.withProps({ .withProps({
@@ -627,7 +627,13 @@ export class DatabaseBackupPage extends MigrationWizardPage {
// check for storage account connectivity // check for storage account connectivity
if ((this.migrationStateModel.isSqlMiTarget || this.migrationStateModel.isSqlVmTarget)) { if ((this.migrationStateModel.isSqlMiTarget || this.migrationStateModel.isSqlVmTarget)) {
if (!(await canTargetConnectToStorageAccount(this.migrationStateModel._targetType, this.migrationStateModel._targetServerInstance, selectedStorageAccount, this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription))) { if (!(await canTargetConnectToStorageAccount(
this.migrationStateModel._targetType,
this.migrationStateModel._targetServerInstance,
selectedStorageAccount,
this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription))) {
this._inaccessibleStorageAccounts = [selectedStorageAccount.name]; this._inaccessibleStorageAccounts = [selectedStorageAccount.name];
} else { } else {
this._inaccessibleStorageAccounts = []; this._inaccessibleStorageAccounts = [];
@@ -661,7 +667,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
// check for storage account connectivity // check for storage account connectivity
const selectedStorageAccount = this.migrationStateModel._storageAccounts.find(sa => sa.name === (this._networkShareContainerStorageAccountDropdown.value as azdata.CategoryValue).displayName); 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 ((this.migrationStateModel.isSqlMiTarget || this.migrationStateModel.isSqlVmTarget) && selectedStorageAccount) {
if (!(await canTargetConnectToStorageAccount(this.migrationStateModel._targetType, this.migrationStateModel._targetServerInstance, selectedStorageAccount, this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription))) { if (!(await canTargetConnectToStorageAccount(
this.migrationStateModel._targetType,
this.migrationStateModel._targetServerInstance,
selectedStorageAccount, this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription))) {
this._inaccessibleStorageAccounts = [selectedStorageAccount.name]; this._inaccessibleStorageAccounts = [selectedStorageAccount.name];
} else { } else {
this._inaccessibleStorageAccounts = []; this._inaccessibleStorageAccounts = [];
@@ -1129,7 +1139,13 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._inaccessibleStorageAccounts = this._inaccessibleStorageAccounts.filter(storageAccountName => storageAccountName.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))) { 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 = this._inaccessibleStorageAccounts.filter(storageAccountName => storageAccountName.toLowerCase() !== selectedStorageAccount.name.toLowerCase());
this._inaccessibleStorageAccounts.push(selectedStorageAccount.name); this._inaccessibleStorageAccounts.push(selectedStorageAccount.name);
} }
@@ -1452,10 +1468,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async getSubscriptionValues(): Promise<void> { private async getSubscriptionValues(): Promise<void> {
this._networkShareContainerSubscription.value = this.migrationStateModel._targetSubscription.name; this._networkShareContainerSubscription.value = this.migrationStateModel._targetSubscription.name;
this._networkShareContainerLocation.value = await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location); this._networkShareContainerLocation.value = await this.migrationStateModel._location.displayName;
this._blobContainerSubscription.value = this.migrationStateModel._targetSubscription.name; this._blobContainerSubscription.value = this.migrationStateModel._targetSubscription.name;
this._blobContainerLocation.value = await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location); this._blobContainerLocation.value = this.migrationStateModel._location.displayName;
this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel._targetSubscription; this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel._targetSubscription;

View File

@@ -10,7 +10,7 @@ import { MigrationMode, MigrationStateModel, NetworkContainerType, StateChangeEv
import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog'; import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import { getFullResourceGroupFromId, getLocationDisplayName, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlVMServer } from '../api/azure'; import { getFullResourceGroupFromId, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlVMServer } from '../api/azure';
import { IconPathHelper } from '../constants/iconPathHelper'; import { IconPathHelper } from '../constants/iconPathHelper';
import { logError, TelemetryViews } from '../telemetry'; import { logError, TelemetryViews } from '../telemetry';
import * as utils from '../api/utils'; import * as utils from '../api/utils';
@@ -19,7 +19,7 @@ import * as styles from '../constants/styles';
export class IntergrationRuntimePage extends MigrationWizardPage { export class IntergrationRuntimePage extends MigrationWizardPage {
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
private _statusLoadingComponent!: azdata.LoadingComponent; private _statusLoadingComponent!: azdata.LoadingComponent;
private _subscription!: azdata.TextComponent; private _subscriptionDropdown!: azdata.DropDownComponent;
private _location!: azdata.TextComponent; private _location!: azdata.TextComponent;
private _resourceGroupDropdown!: azdata.DropDownComponent; private _resourceGroupDropdown!: azdata.DropDownComponent;
private _dmsDropdown!: azdata.DropDownComponent; private _dmsDropdown!: azdata.DropDownComponent;
@@ -27,7 +27,6 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
private _dmsStatusInfoBox!: azdata.InfoBoxComponent; private _dmsStatusInfoBox!: azdata.InfoBoxComponent;
private _authKeyTable!: azdata.DeclarativeTableComponent; private _authKeyTable!: azdata.DeclarativeTableComponent;
private _refreshButton!: azdata.ButtonComponent; private _refreshButton!: azdata.ButtonComponent;
private _connectionStatusLoader!: azdata.LoadingComponent;
private _copy1!: azdata.ButtonComponent; private _copy1!: azdata.ButtonComponent;
private _copy2!: azdata.ButtonComponent; private _copy2!: azdata.ButtonComponent;
private _refresh1!: azdata.ButtonComponent; private _refresh1!: azdata.ButtonComponent;
@@ -157,8 +156,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this._networkShareButton.onDidChangeCheckedState(async checked => { this._networkShareButton.onDidChangeCheckedState(async checked => {
if (checked) { if (checked) {
this.migrationStateModel._databaseBackup.networkContainerType = NetworkContainerType.NETWORK_SHARE; this.migrationStateModel._databaseBackup.networkContainerType = NetworkContainerType.NETWORK_SHARE;
await utils.updateControlDisplay(this._dmsInfoContainer, true);
this.migrationStateModel.refreshDatabaseBackupPage = true; this.migrationStateModel.refreshDatabaseBackupPage = true;
const hasService = this.migrationStateModel._sqlMigrationService !== undefined;
await utils.updateControlDisplay(this._dmsInfoContainer, hasService);
if (hasService) {
await this.loadStatus();
}
} }
})); }));
@@ -271,15 +275,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this.migrationStateModel._databaseBackup.networkContainerType = NetworkContainerType.BLOB_CONTAINER; this.migrationStateModel._databaseBackup.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
this._blobContainerButton.checked = true; this._blobContainerButton.checked = true;
this._subscription.value = this.migrationStateModel._targetSubscription.name; await this.loadSubscriptionsDropdown();
this._location.value = await getLocationDisplayName(
this.migrationStateModel._targetServerInstance.location); this._location.value = this.migrationStateModel._location.displayName;
await utils.updateControlDisplay( await utils.updateControlDisplay(
this._dmsInfoContainer, this._dmsInfoContainer,
isSqlDbTarget || isNetworkShare); isSqlDbTarget || isNetworkShare);
await this.loadResourceGroupDropdown();
} }
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> { public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
@@ -306,13 +308,32 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
value: constants.SUBSCRIPTION, value: constants.SUBSCRIPTION,
CSSStyles: { ...styles.LABEL_CSS } CSSStyles: { ...styles.LABEL_CSS }
}).component(); }).component();
this._subscription = this._view.modelBuilder.text()
this._subscriptionDropdown = this._view.modelBuilder.dropDown()
.withProps({ .withProps({
enabled: false, ariaLabel: constants.MIGRATION_SERVICE_SELECT_SERVICE_LABEL,
width: WIZARD_INPUT_COMPONENT_WIDTH, width: WIZARD_INPUT_COMPONENT_WIDTH,
editable: true,
required: true,
fireOnTextChange: true,
placeholder: constants.SELECT_A_SERVICE,
CSSStyles: { 'margin': '0' } CSSStyles: { 'margin': '0' }
}).component(); }).component();
this._disposables.push(
this._subscriptionDropdown.onValueChanged(async (value) => {
if (value && value !== 'undefined' && value !== constants.SERVICE_NOT_FOUND) {
const selectedSubscription = this.migrationStateModel._subscriptions.find(
sub => `${sub.name} - ${sub.id}` === value);
this.migrationStateModel._sqlMigrationServiceSubscription = (selectedSubscription)
? selectedSubscription
: undefined!;
} else {
this.migrationStateModel._sqlMigrationServiceSubscription = undefined!;
}
await this.loadResourceGroupDropdown();
}));
const locationLabel = this._view.modelBuilder.text() const locationLabel = this._view.modelBuilder.text()
.withProps({ .withProps({
value: constants.LOCATION, value: constants.LOCATION,
@@ -349,8 +370,11 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this.migrationStateModel._sqlMigrationServiceResourceGroup = (selectedResourceGroup) this.migrationStateModel._sqlMigrationServiceResourceGroup = (selectedResourceGroup)
? selectedResourceGroup ? selectedResourceGroup
: undefined!; : undefined!;
this.populateDms();
} }
else {
this.migrationStateModel._sqlMigrationServiceResourceGroup = undefined!;
}
this.loadDmsDropdown();
})); }));
const migrationServiceDropdownLabel = this._view.modelBuilder.text() const migrationServiceDropdownLabel = this._view.modelBuilder.text()
@@ -379,15 +403,18 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
dms => dms.name === value dms => dms.name === value
&& dms.properties.resourceGroup.toLowerCase() === resourceGroupName); && dms.properties.resourceGroup.toLowerCase() === resourceGroupName);
if (selectedDms) { const showShirStatus = selectedDms !== undefined &&
this.migrationStateModel._sqlMigrationService = selectedDms; (this.migrationStateModel.isSqlDbTarget ||
await this.loadStatus(); this.migrationStateModel.isBackupContainerNetworkShare);
}
this.migrationStateModel._sqlMigrationService = selectedDms;
await utils.updateControlDisplay( await utils.updateControlDisplay(
this._dmsInfoContainer, this._dmsInfoContainer,
this.migrationStateModel.isSqlDbTarget || showShirStatus);
this.migrationStateModel.isBackupContainerNetworkShare);
if (showShirStatus) {
await this.loadStatus();
}
} else { } else {
this.migrationStateModel._sqlMigrationService = undefined; this.migrationStateModel._sqlMigrationService = undefined;
await utils.updateControlDisplay(this._dmsInfoContainer, false); await utils.updateControlDisplay(this._dmsInfoContainer, false);
@@ -414,15 +441,15 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this.migrationStateModel._sqlMigrationServiceResourceGroup = createdDmsResult.resourceGroup; this.migrationStateModel._sqlMigrationServiceResourceGroup = createdDmsResult.resourceGroup;
this.migrationStateModel._sqlMigrationService = createdDmsResult.service; this.migrationStateModel._sqlMigrationService = createdDmsResult.service;
await this.loadResourceGroupDropdown(); await this.loadResourceGroupDropdown();
this.populateDms();
})); }));
return this._view.modelBuilder.flexContainer() return this._view.modelBuilder.flexContainer()
.withItems([ .withItems([
descriptionText, descriptionText,
subscriptionLabel, subscriptionLabel,
this._subscription, this._subscriptionDropdown,
locationLabel, locationLabel,
this._location, this._location,
resourceGroupLabel, resourceGroupLabel,
@@ -534,48 +561,71 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
authenticationKeysLabel, authenticationKeysLabel,
this._authKeyTable]); this._authKeyTable]);
this._connectionStatusLoader = this._view.modelBuilder.loadingComponent()
.withItem(statusContainer)
.withProps({ loading: false })
.component();
container.addItems([ container.addItems([
connectionLabelContainer, connectionLabelContainer,
this._connectionStatusLoader]); statusContainer]);
return container; return container;
} }
public async loadSubscriptionsDropdown(): Promise<void> {
try {
this._subscriptionDropdown.loading = true;
this.migrationStateModel._subscriptions = await utils.getAzureSubscriptions(
this.migrationStateModel._azureAccount);
const sub = this.migrationStateModel._sqlMigrationServiceSubscription
?? this.migrationStateModel._targetSubscription;
this._subscriptionDropdown.values = await utils.getAzureSubscriptionsDropdownValues(
this.migrationStateModel._subscriptions);
utils.selectDefaultDropdownValue(this._subscriptionDropdown, sub?.id, false);
} catch (e) {
logError(TelemetryViews.IntegrationRuntimePage, 'Error loadSubscriptionsDropdown', e);
} finally {
this._subscriptionDropdown.loading = false;
}
}
public async loadResourceGroupDropdown(): Promise<void> { public async loadResourceGroupDropdown(): Promise<void> {
try { try {
this._resourceGroupDropdown.loading = true; this._resourceGroupDropdown.loading = true;
this._dmsDropdown.loading = true; const account = this.migrationStateModel._azureAccount;
const subscription = this.migrationStateModel._sqlMigrationServiceSubscription;
const serviceId = this.migrationStateModel._sqlMigrationService?.id;
const resourceGroup = this.migrationStateModel._sqlMigrationServiceResourceGroup?.name ??
serviceId !== undefined
? getFullResourceGroupFromId(serviceId!)
: undefined;
this.migrationStateModel._sqlMigrationServices = await utils.getAzureSqlMigrationServices( const migrationServices = await utils.getAzureSqlMigrationServices(
this.migrationStateModel._azureAccount, account,
this.migrationStateModel._targetSubscription); subscription);
this.migrationStateModel._resourceGroups = utils.getServiceResourceGroupsByLocation( const resourceGroups = utils.getServiceResourceGroupsByLocation(
this.migrationStateModel._sqlMigrationServices, migrationServices,
this.migrationStateModel._location); this.migrationStateModel._location);
this._resourceGroupDropdown.values = utils.getResourceDropdownValues( this._resourceGroupDropdown.values = utils.getResourceDropdownValues(
this.migrationStateModel._resourceGroups, resourceGroups,
constants.RESOURCE_GROUP_NOT_FOUND); constants.RESOURCE_GROUP_NOT_FOUND);
const resourceGroup = this.migrationStateModel._sqlMigrationService this.migrationStateModel._sqlMigrationServices = migrationServices;
? getFullResourceGroupFromId(this.migrationStateModel._sqlMigrationService?.id) this.migrationStateModel._resourceGroups = resourceGroups;
: undefined;
utils.selectDefaultDropdownValue(this._resourceGroupDropdown, resourceGroup, false); utils.selectDefaultDropdownValue(this._resourceGroupDropdown, resourceGroup, false);
} catch (e) {
logError(TelemetryViews.IntegrationRuntimePage, 'Error loadResourceGroupDropdown', e);
} finally { } finally {
this._dmsDropdown.loading = false;
this._resourceGroupDropdown.loading = false; this._resourceGroupDropdown.loading = false;
} }
} }
public populateDms(): void { public loadDmsDropdown(): void {
try { try {
this._dmsDropdown.loading = true; this._dmsDropdown.loading = true;
const serviceId = this.migrationStateModel._sqlMigrationService?.id;
this._dmsDropdown.values = utils.getAzureResourceDropdownValues( this._dmsDropdown.values = utils.getAzureResourceDropdownValues(
this.migrationStateModel._sqlMigrationServices, this.migrationStateModel._sqlMigrationServices,
this.migrationStateModel._location, this.migrationStateModel._location,
@@ -584,53 +634,77 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
utils.selectDefaultDropdownValue( utils.selectDefaultDropdownValue(
this._dmsDropdown, this._dmsDropdown,
this.migrationStateModel._sqlMigrationService?.id, serviceId,
false); false);
} catch (e) {
logError(TelemetryViews.IntegrationRuntimePage, 'Error loadDmsDropdown', e);
} finally { } finally {
this._dmsDropdown.loading = false; this._dmsDropdown.loading = false;
} }
} }
private _lastIn = 0;
private async loadStatus(): Promise<void> { private async loadStatus(): Promise<void> {
const callSequence = ++this._lastIn;
let serviceName = '';
try { try {
this._statusLoadingComponent.loading = true; if (callSequence === this._lastIn) {
this._statusLoadingComponent.loading = true;
}
const service = this.migrationStateModel._sqlMigrationService;
if (service) {
const account = this.migrationStateModel._azureAccount;
const subscription = this.migrationStateModel._sqlMigrationServiceSubscription;
const resourceGroup = service.properties.resourceGroup;
const location = service.location;
serviceName = service.name;
if (service?.properties?.integrationRuntimeState) {
service.properties.integrationRuntimeState = undefined;
}
if (this.migrationStateModel._sqlMigrationService) {
const migrationService = await getSqlMigrationService( const migrationService = await getSqlMigrationService(
this.migrationStateModel._azureAccount, account,
this.migrationStateModel._targetSubscription, subscription,
this.migrationStateModel._sqlMigrationService.properties.resourceGroup, resourceGroup,
this.migrationStateModel._sqlMigrationService.location, location,
this.migrationStateModel._sqlMigrationService.name); serviceName);
this.migrationStateModel._sqlMigrationService = migrationService;
// exit if new call has started
if (callSequence !== this._lastIn) { return; }
const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData( const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData(
this.migrationStateModel._azureAccount, account,
this.migrationStateModel._targetSubscription, subscription,
this.migrationStateModel._sqlMigrationService.properties.resourceGroup, resourceGroup,
this.migrationStateModel._sqlMigrationService.location, location,
this.migrationStateModel._sqlMigrationService!.name); serviceName);
this.migrationStateModel._nodeNames = migrationServiceMonitoringStatus.nodes.map(
const nodeNames = migrationServiceMonitoringStatus.nodes.map(
node => node.nodeName); node => node.nodeName);
// exit if new call has started
if (callSequence !== this._lastIn) { return; }
const migrationServiceAuthKeys = await getSqlMigrationServiceAuthKeys( const migrationServiceAuthKeys = await getSqlMigrationServiceAuthKeys(
this.migrationStateModel._azureAccount, account,
this.migrationStateModel._targetSubscription, subscription,
this.migrationStateModel._sqlMigrationService.properties.resourceGroup, resourceGroup,
this.migrationStateModel._sqlMigrationService.location, location,
this.migrationStateModel._sqlMigrationService!.name); serviceName);
// exit if new call has started
if (callSequence !== this._lastIn) { return; }
const state = migrationService.properties.integrationRuntimeState; const state = migrationService.properties.integrationRuntimeState;
if (state === 'Online') { if (state === 'Online') {
await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{ await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.SERVICE_READY( text: constants.SERVICE_READY(serviceName, nodeNames.join(', ')),
this.migrationStateModel._sqlMigrationService!.name,
this.migrationStateModel._nodeNames.join(', ')),
style: 'success' style: 'success'
}); });
} else { } else {
await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{ await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.SERVICE_NOT_READY( text: constants.SERVICE_NOT_READY(serviceName),
this.migrationStateModel._sqlMigrationService!.name),
style: 'error' style: 'error'
}); });
} }
@@ -655,12 +729,26 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
} }
]]; ]];
// exit if new call has started
if (callSequence !== this._lastIn) { return; }
await this._authKeyTable.setDataValues(data); await this._authKeyTable.setDataValues(data);
this.migrationStateModel._sqlMigrationService = migrationService;
this.migrationStateModel._sqlMigrationServiceSubscription = subscription;
this.migrationStateModel._nodeNames = nodeNames;
} }
} catch (e) { } catch (e) {
logError(TelemetryViews.IntegrationRuntimePage, 'ErrorLoadingStatus', e); await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.SERVICE_ERROR_NOT_READY(serviceName, e.message),
style: 'error'
});
logError(TelemetryViews.IntegrationRuntimePage, 'Error loadStatus', e);
} finally { } finally {
this._statusLoadingComponent.loading = false; if (callSequence === this._lastIn) {
this._statusLoadingComponent.loading = false;
}
} }
} }
} }

View File

@@ -100,8 +100,7 @@ export class SummaryPage extends MigrationWizardPage {
createInformationRow( createInformationRow(
this._view, this._view,
constants.LOCATION, constants.LOCATION,
await this.migrationStateModel.getLocationDisplayName( this.migrationStateModel._location.displayName),
this.migrationStateModel._targetServerInstance.location)),
createInformationRow( createInformationRow(
this._view, this._view,
constants.RESOURCE_GROUP, constants.RESOURCE_GROUP,
@@ -140,16 +139,15 @@ export class SummaryPage extends MigrationWizardPage {
constants.IR_PAGE_TITLE), constants.IR_PAGE_TITLE),
createInformationRow( createInformationRow(
this._view, constants.SUBSCRIPTION, this._view, constants.SUBSCRIPTION,
this.migrationStateModel._targetSubscription.name), this.migrationStateModel._sqlMigrationServiceSubscription.name),
createInformationRow( createInformationRow(
this._view, this._view,
constants.LOCATION, constants.LOCATION,
await this.migrationStateModel.getLocationDisplayName( this.migrationStateModel._location.displayName),
this.migrationStateModel._sqlMigrationService?.location!)),
createInformationRow( createInformationRow(
this._view, this._view,
constants.RESOURCE_GROUP, constants.RESOURCE_GROUP,
this.migrationStateModel._sqlMigrationService?.properties?.resourceGroup!), this.migrationStateModel._sqlMigrationServiceResourceGroup.name),
createInformationRow( createInformationRow(
this._view, this._view,
constants.IR_PAGE_TITLE, constants.IR_PAGE_TITLE,

View File

@@ -709,7 +709,10 @@ export class TargetSelectionPage extends MigrationWizardPage {
if (selectedVm) { if (selectedVm) {
this.migrationStateModel._targetServerInstance = utils.deepClone(selectedVm)! as SqlVMServer; this.migrationStateModel._targetServerInstance = utils.deepClone(selectedVm)! as SqlVMServer;
this.migrationStateModel._vmInstanceView = await getVMInstanceView(this.migrationStateModel._targetServerInstance, this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription); this.migrationStateModel._vmInstanceView = await getVMInstanceView(
this.migrationStateModel._targetServerInstance,
this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription);
this.wizard.message = { text: '' }; this.wizard.message = { text: '' };
// validate power state from VM instance view // validate power state from VM instance view
@@ -870,45 +873,45 @@ export class TargetSelectionPage extends MigrationWizardPage {
this._azureAccountsDropdown.loading = true; this._azureAccountsDropdown.loading = true;
this.migrationStateModel._azureAccounts = await utils.getAzureAccounts(); this.migrationStateModel._azureAccounts = await utils.getAzureAccounts();
this._azureAccountsDropdown.values = await utils.getAzureAccountsDropdownValues(this.migrationStateModel._azureAccounts);
} finally {
this._azureAccountsDropdown.loading = false;
const accountId = const accountId =
this.migrationStateModel._azureAccount?.displayInfo?.userId ?? this.migrationStateModel._azureAccount?.displayInfo?.userId ??
this._serviceContext?.azureAccount?.displayInfo?.userId; this._serviceContext?.azureAccount?.displayInfo?.userId;
this._azureAccountsDropdown.values = await utils.getAzureAccountsDropdownValues(this.migrationStateModel._azureAccounts);
utils.selectDefaultDropdownValue( utils.selectDefaultDropdownValue(
this._azureAccountsDropdown, this._azureAccountsDropdown,
accountId, accountId,
false); false);
} finally {
this._azureAccountsDropdown.loading = false;
} }
} }
private async populateTenantsDropdown(): Promise<void> { private async populateTenantsDropdown(): Promise<void> {
try { try {
this._accountTenantDropdown.loading = true; this._accountTenantDropdown.loading = true;
if (!utils.isAccountTokenStale(this.migrationStateModel._azureAccount) &&
this.migrationStateModel._azureAccount?.properties?.tenants?.length > 0) {
this.migrationStateModel._accountTenants = utils.getAzureTenants(this.migrationStateModel._azureAccount);
this._accountTenantDropdown.values = utils.getAzureTenantsDropdownValues(this.migrationStateModel._accountTenants);
}
const tenantId = const tenantId =
this.migrationStateModel._azureTenant?.id ?? this.migrationStateModel._azureTenant?.id ??
this._serviceContext?.tenant?.id; this._serviceContext?.tenant?.id;
if (!utils.isAccountTokenStale(this.migrationStateModel._azureAccount) &&
this.migrationStateModel._azureAccount?.properties?.tenants?.length > 0) {
this.migrationStateModel._accountTenants = utils.getAzureTenants(this.migrationStateModel._azureAccount);
this._accountTenantDropdown.values = utils.getAzureTenantsDropdownValues(this.migrationStateModel._accountTenants);
}
utils.selectDefaultDropdownValue( utils.selectDefaultDropdownValue(
this._accountTenantDropdown, this._accountTenantDropdown,
tenantId, tenantId,
true); true);
await this._azureAccountsDropdown.validate();
} finally {
this._accountTenantDropdown.loading = false;
await this._accountTenantFlexContainer.updateCssStyles( await this._accountTenantFlexContainer.updateCssStyles(
this.migrationStateModel._azureAccount?.properties?.tenants?.length > 1 this.migrationStateModel._azureAccount?.properties?.tenants?.length > 1
? { 'display': 'inline' } ? { 'display': 'inline' }
: { 'display': 'none' } : { 'display': 'none' }
); );
await this._azureAccountsDropdown.validate();
} finally {
this._accountTenantDropdown.loading = false;
} }
} }
@@ -916,19 +919,20 @@ export class TargetSelectionPage extends MigrationWizardPage {
try { try {
this._azureSubscriptionDropdown.loading = true; this._azureSubscriptionDropdown.loading = true;
this.migrationStateModel._subscriptions = await utils.getAzureSubscriptions(this.migrationStateModel._azureAccount); this.migrationStateModel._subscriptions = await utils.getAzureSubscriptions(this.migrationStateModel._azureAccount);
this._azureSubscriptionDropdown.values = await utils.getAzureSubscriptionsDropdownValues(this.migrationStateModel._subscriptions);
} catch (e) {
console.log(e);
} finally {
this._azureSubscriptionDropdown.loading = false;
const subscriptionId = const subscriptionId =
this.migrationStateModel._targetSubscription?.id ?? this.migrationStateModel._targetSubscription?.id ??
this._serviceContext?.subscription?.id; this._serviceContext?.subscription?.id;
this._azureSubscriptionDropdown.values = await utils.getAzureSubscriptionsDropdownValues(this.migrationStateModel._subscriptions);
utils.selectDefaultDropdownValue( utils.selectDefaultDropdownValue(
this._azureSubscriptionDropdown, this._azureSubscriptionDropdown,
subscriptionId, subscriptionId,
false); false);
} catch (e) {
console.log(e);
} finally {
this._azureSubscriptionDropdown.loading = false;
} }
} }
@@ -964,19 +968,20 @@ export class TargetSelectionPage extends MigrationWizardPage {
this.migrationStateModel._targetSqlDatabaseServers); this.migrationStateModel._targetSqlDatabaseServers);
break; break;
} }
this._azureLocationDropdown.values = utils.getAzureLocationsDropdownValues(this.migrationStateModel._locations);
} catch (e) {
console.log(e);
} finally {
this._azureLocationDropdown.loading = false;
const location = const location =
this.migrationStateModel._location?.displayName ?? this.migrationStateModel._location?.displayName ??
this._serviceContext?.location?.displayName; this._serviceContext?.location?.displayName;
this._azureLocationDropdown.values = utils.getAzureLocationsDropdownValues(this.migrationStateModel._locations);
utils.selectDefaultDropdownValue( utils.selectDefaultDropdownValue(
this._azureLocationDropdown, this._azureLocationDropdown,
location, location,
true); true);
} catch (e) {
console.log(e);
} finally {
this._azureLocationDropdown.loading = false;
} }
} }
@@ -1000,24 +1005,29 @@ export class TargetSelectionPage extends MigrationWizardPage {
this.migrationStateModel._location); this.migrationStateModel._location);
break; break;
} }
const resourceGroupId = this.migrationStateModel._resourceGroup?.id;
this._azureResourceGroupDropdown.values = utils.getResourceDropdownValues( this._azureResourceGroupDropdown.values = utils.getResourceDropdownValues(
this.migrationStateModel._resourceGroups, this.migrationStateModel._resourceGroups,
constants.RESOURCE_GROUP_NOT_FOUND); constants.RESOURCE_GROUP_NOT_FOUND);
utils.selectDefaultDropdownValue(
this._azureResourceGroupDropdown,
resourceGroupId,
false);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} finally { } finally {
this._azureResourceGroupDropdown.loading = false; this._azureResourceGroupDropdown.loading = false;
utils.selectDefaultDropdownValue(
this._azureResourceGroupDropdown,
this.migrationStateModel._resourceGroup?.id,
false);
} }
} }
private async populateResourceInstanceDropdown(): Promise<void> { private async populateResourceInstanceDropdown(): Promise<void> {
try { try {
this._azureResourceDropdown.loading = true; this._azureResourceDropdown.loading = true;
const targetName = this.migrationStateModel.migrationTargetServerName;
switch (this.migrationStateModel._targetType) { switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI: case MigrationTargetType.SQLMI:
this._azureResourceDropdown.values = await utils.getManagedInstancesDropdownValues( this._azureResourceDropdown.values = await utils.getManagedInstancesDropdownValues(
@@ -1043,31 +1053,19 @@ export class TargetSelectionPage extends MigrationWizardPage {
constants.NO_SQL_DATABASE_SERVER_FOUND); constants.NO_SQL_DATABASE_SERVER_FOUND);
break; break;
} }
} finally {
this._azureResourceDropdown.loading = false;
let targetName = '';
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI:
targetName = (this.migrationStateModel._targetServerInstance as azureResource.AzureSqlManagedInstance)?.name;
break;
case MigrationTargetType.SQLVM:
targetName = (this.migrationStateModel._targetServerInstance as SqlVMServer)?.name;
break;
case MigrationTargetType.SQLDB:
targetName = (this.migrationStateModel._targetServerInstance as AzureSqlDatabaseServer)?.name;
break;
}
utils.selectDefaultDropdownValue( utils.selectDefaultDropdownValue(
this._azureResourceDropdown, this._azureResourceDropdown,
targetName, targetName,
true); true);
} finally {
this._azureResourceDropdown.loading = false;
} }
} }
private _updateTdeMigrationButtonStatus() { private _updateTdeMigrationButtonStatus() {
this.wizard.customButtons[TDE_MIGRATION_BUTTON_INDEX].enabled =
this.wizard.customButtons[TDE_MIGRATION_BUTTON_INDEX].enabled = this.migrationStateModel.tdeMigrationConfig.shouldAdsMigrateCertificates() && this.migrationStateModel.tdeMigrationConfig.shouldAdsMigrateCertificates() &&
this.migrationStateModel._targetManagedInstances.length > 0; this.migrationStateModel._targetManagedInstances.length > 0;
} }

View File

@@ -272,10 +272,7 @@ export class WizardController {
stateModel: MigrationStateModel, stateModel: MigrationStateModel,
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>): Promise<void> { serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>): Promise<void> {
const resourceGroup = this._getResourceGroupByName( const resourceGroup = stateModel._sqlMigrationServiceResourceGroup;
stateModel._resourceGroups,
stateModel._sqlMigrationService?.properties.resourceGroup);
const subscription = this._getSubscriptionFromResourceId( const subscription = this._getSubscriptionFromResourceId(
stateModel._subscriptions, stateModel._subscriptions,
resourceGroup?.id); resourceGroup?.id);
@@ -296,13 +293,6 @@ export class WizardController {
serviceContextChangedEvent); serviceContextChangedEvent);
} }
private _getResourceGroupByName(
resourceGroups: azureResource.AzureResourceResourceGroup[],
displayName?: string): azureResource.AzureResourceResourceGroup | undefined {
return resourceGroups.find(rg => rg.name === displayName);
}
private _getLocationByValue( private _getLocationByValue(
locations: azureResource.AzureLocation[], locations: azureResource.AzureLocation[],
name?: string): azureResource.AzureLocation | undefined { name?: string): azureResource.AzureLocation | undefined {