mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
SQL-Migration: add retry migration prompt (#22555)
* add retry migration prompt * updating review comments * update context menu postion to match toolbar
This commit is contained in:
@@ -11,6 +11,7 @@ import { getSessionIdHeader } from './utils';
|
|||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { MigrationSourceAuthenticationType, MigrationStateModel, NetworkShare } from '../models/stateMachine';
|
import { MigrationSourceAuthenticationType, MigrationStateModel, NetworkShare } from '../models/stateMachine';
|
||||||
import { NetworkInterface } from './dataModels/azure/networkInterfaceModel';
|
import { NetworkInterface } from './dataModels/azure/networkInterfaceModel';
|
||||||
|
import { EOL } from 'os';
|
||||||
|
|
||||||
const ARM_MGMT_API_VERSION = '2021-04-01';
|
const ARM_MGMT_API_VERSION = '2021-04-01';
|
||||||
const SQL_VM_API_VERSION = '2021-11-01-preview';
|
const SQL_VM_API_VERSION = '2021-11-01-preview';
|
||||||
@@ -628,6 +629,20 @@ export async function stopMigration(account: azdata.Account, subscription: Subsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function retryMigration(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise<void> {
|
||||||
|
const api = await getAzureCoreAPI();
|
||||||
|
const path = encodeURI(`${migration.id}/retry?api-version=${DMSV2_API_VERSION}`);
|
||||||
|
const requestBody = { migrationOperationId: migration.properties.migrationOperationId };
|
||||||
|
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||||
|
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true, host);
|
||||||
|
if (response.errors.length > 0) {
|
||||||
|
const message = response.errors
|
||||||
|
.map(err => err.message)
|
||||||
|
.join(', ');
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteMigration(account: azdata.Account, subscription: Subscription, migrationId: string): Promise<void> {
|
export async function deleteMigration(account: azdata.Account, subscription: Subscription, migrationId: string): Promise<void> {
|
||||||
const api = await getAzureCoreAPI();
|
const api = await getAzureCoreAPI();
|
||||||
const path = encodeURI(`${migrationId}?api-version=${DMSV2_API_VERSION}`);
|
const path = encodeURI(`${migrationId}?api-version=${DMSV2_API_VERSION}`);
|
||||||
@@ -817,6 +832,27 @@ export function getBlobContainerId(resourceGroupId: string, storageAccountName:
|
|||||||
return `${resourceGroupId}/providers/Microsoft.Storage/storageAccounts/${storageAccountName}/blobServices/default/containers/${blobContainerName}`;
|
return `${resourceGroupId}/providers/Microsoft.Storage/storageAccounts/${storageAccountName}/blobServices/default/containers/${blobContainerName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMigrationErrors(migration: DatabaseMigration): string {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (migration?.properties) {
|
||||||
|
errors.push(migration.properties.provisioningError);
|
||||||
|
errors.push(migration.properties.migrationFailureError?.message);
|
||||||
|
errors.push(migration.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
|
||||||
|
errors.push(migration.properties.migrationStatusDetails?.restoreBlockingReason);
|
||||||
|
errors.push(migration.properties.migrationStatusDetails?.sqlDataCopyErrors);
|
||||||
|
errors.push(...migration.properties.migrationStatusDetails?.invalidFiles ?? []);
|
||||||
|
errors.push(migration.properties.migrationStatusWarnings?.completeRestoreErrorMessage);
|
||||||
|
errors.push(migration.properties.migrationStatusWarnings?.restoreBlockingReason);
|
||||||
|
errors.push(...migration.properties.migrationStatusDetails?.listOfCopyProgressDetails?.flatMap(cp => cp.errors) ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove undefined and duplicate error entries
|
||||||
|
return errors
|
||||||
|
.filter((e, i, arr) => e !== undefined && i === arr.indexOf(e))
|
||||||
|
.join(EOL);
|
||||||
|
}
|
||||||
|
|
||||||
export interface SqlMigrationServiceProperties {
|
export interface SqlMigrationServiceProperties {
|
||||||
name: string;
|
name: string;
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const MenuCommands = {
|
|||||||
CancelMigration: 'sqlmigration.cancel.migration',
|
CancelMigration: 'sqlmigration.cancel.migration',
|
||||||
DeleteMigration: 'sqlmigration.delete.migration',
|
DeleteMigration: 'sqlmigration.delete.migration',
|
||||||
RetryMigration: 'sqlmigration.retry.migration',
|
RetryMigration: 'sqlmigration.retry.migration',
|
||||||
|
RestartMigration: 'sqlmigration.restart.migration',
|
||||||
StartMigration: 'sqlmigration.start',
|
StartMigration: 'sqlmigration.start',
|
||||||
StartLoginMigration: 'sqlmigration.login.start',
|
StartLoginMigration: 'sqlmigration.login.start',
|
||||||
IssueReporter: 'workbench.action.openIssueReporter',
|
IssueReporter: 'workbench.action.openIssueReporter',
|
||||||
|
|||||||
@@ -264,6 +264,11 @@ export function canDeleteMigration(migration: DatabaseMigration | undefined): bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function canRetryMigration(migration: DatabaseMigration | undefined): boolean {
|
export function canRetryMigration(migration: DatabaseMigration | undefined): boolean {
|
||||||
|
const status = getMigrationStatus(migration);
|
||||||
|
return status === loc.MigrationState.Retriable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canRestartMigrationWizard(migration: DatabaseMigration | undefined): boolean {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
return status === loc.MigrationState.Canceled
|
return status === loc.MigrationState.Canceled
|
||||||
|| status === loc.MigrationState.Retriable
|
|| status === loc.MigrationState.Retriable
|
||||||
|
|||||||
@@ -1042,6 +1042,10 @@ export const DETAILS_COPIED = localize('sql.migration.details.copied', "Details
|
|||||||
export const CANCEL_MIGRATION_CONFIRMATION = localize('sql.cancel.migration.confirmation', "Are you sure you want to cancel this migration?");
|
export const CANCEL_MIGRATION_CONFIRMATION = localize('sql.cancel.migration.confirmation', "Are you sure you want to cancel this migration?");
|
||||||
export const DELETE_MIGRATION_CONFIRMATION = localize('sql.delete.migration.confirmation', "Are you sure you want to delete this migration?");
|
export const DELETE_MIGRATION_CONFIRMATION = localize('sql.delete.migration.confirmation', "Are you sure you want to delete this migration?");
|
||||||
|
|
||||||
|
export const RETRY_MIGRATION_TITLE = localize('sql.retry.migration.title', "The migration failed with the following errors:");
|
||||||
|
export const RETRY_MIGRATION_SUMMARY = localize('sql.retry.migration.summary', "Please resolve any errors before retrying the migration.");
|
||||||
|
export const RETRY_MIGRATION_PROMPT = localize('sql.retry.migration.prompt', "Do you want to retry the failed table migrations?");
|
||||||
|
|
||||||
export const YES = localize('sql.migration.yes', "Yes");
|
export const YES = localize('sql.migration.yes', "Yes");
|
||||||
export const NO = localize('sql.migration.no', "No");
|
export const NO = localize('sql.migration.no', "No");
|
||||||
export const NA = localize('sql.migration.na', "N/A");
|
export const NA = localize('sql.migration.na', "N/A");
|
||||||
@@ -1361,6 +1365,11 @@ export const MIGRATION_CANNOT_RETRY = localize('sql.migration.cannot.retry', 'Mi
|
|||||||
export const RETRY_MIGRATION = localize('sql.migration.retry.migration', "Retry migration");
|
export const RETRY_MIGRATION = localize('sql.migration.retry.migration', "Retry migration");
|
||||||
export const MIGRATION_RETRY_ERROR = localize('sql.migration.retry.migration.error', 'An error occurred while retrying the migration.');
|
export const MIGRATION_RETRY_ERROR = localize('sql.migration.retry.migration.error', 'An error occurred while retrying the migration.');
|
||||||
|
|
||||||
|
// Restart Migration
|
||||||
|
export const MIGRATION_CANNOT_RESTART = localize('sql.migration.cannot.retry', 'Migration cannot be restarted.');
|
||||||
|
export const RESTART_MIGRATION_WIZARD = localize('sql.migration.restart.migration.wizard', "Restart migration wizard");
|
||||||
|
export const MIGRATION_RESTART_ERROR = localize('sql.migration.retry.migration.error', 'An error occurred while restarting the migration.');
|
||||||
|
|
||||||
export const INVALID_OWNER_URI = localize('sql.migration.invalid.owner.uri.error', 'Cannot connect to the database due to invalid OwnerUri (Parameter \'OwnerUri\')');
|
export const INVALID_OWNER_URI = localize('sql.migration.invalid.owner.uri.error', 'Cannot connect to the database due to invalid OwnerUri (Parameter \'OwnerUri\')');
|
||||||
export const DATABASE_BACKUP_PAGE_LOAD_ERROR = localize('sql.migration.database.backup.load.error', 'An error occurred while accessing database details.');
|
export const DATABASE_BACKUP_PAGE_LOAD_ERROR = localize('sql.migration.database.backup.load.error', 'An error occurred while accessing database details.');
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as loc from '../constants/strings';
|
|||||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getMigrationStatusImage } from '../api/utils';
|
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getMigrationStatusImage } from '../api/utils';
|
||||||
import { logError, TelemetryViews } from '../telemetry';
|
import { logError, TelemetryViews } from '../telemetry';
|
||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, isShirMigration } from '../constants/helper';
|
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRestartMigrationWizard, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, isShirMigration } from '../constants/helper';
|
||||||
import { AzureResourceKind, DatabaseMigration, getResourceName } from '../api/azure';
|
import { AzureResourceKind, DatabaseMigration, getResourceName } from '../api/azure';
|
||||||
import * as utils from '../api/utils';
|
import * as utils from '../api/utils';
|
||||||
import * as helper from '../constants/helper';
|
import * as helper from '../constants/helper';
|
||||||
@@ -291,7 +291,7 @@ export class MigrationDetailsTab extends MigrationDetailsTabBase<MigrationDetail
|
|||||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||||
this.cancelButton.enabled = canCancelMigration(migration);
|
this.cancelButton.enabled = canCancelMigration(migration);
|
||||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||||
this.retryButton.enabled = canRetryMigration(migration);
|
this.restartButton.enabled = canRestartMigrationWizard(migration);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.statusBar.showError(
|
await this.statusBar.showError(
|
||||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
@@ -485,6 +485,8 @@ export class MigrationDetailsTab extends MigrationDetailsTabBase<MigrationDetail
|
|||||||
.withProps({ width: '100%' })
|
.withProps({ width: '100%' })
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
|
await utils.updateControlDisplay(this.retryButton, false);
|
||||||
|
|
||||||
this.content = container;
|
this.content = container;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ import * as vscode from 'vscode';
|
|||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { MigrationServiceContext } from '../models/migrationLocalStorage';
|
import { MigrationServiceContext } from '../models/migrationLocalStorage';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { DatabaseMigration, deleteMigration } from '../api/azure';
|
import { DatabaseMigration, deleteMigration, getMigrationErrors, retryMigration } from '../api/azure';
|
||||||
import { TabBase } from './tabBase';
|
import { TabBase } from './tabBase';
|
||||||
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
||||||
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
||||||
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
import { RestartMigrationDialog } from '../dialog/restartMigration/restartMigrationDialog';
|
||||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
import { canDeleteMigration } from '../constants/helper';
|
import { canDeleteMigration, canRetryMigration } from '../constants/helper';
|
||||||
import { logError, TelemetryViews } from '../telemetry';
|
import { logError, TelemetryViews } from '../telemetry';
|
||||||
import { MenuCommands, MigrationTargetType } from '../api/utils';
|
import { MenuCommands, MigrationTargetType } from '../api/utils';
|
||||||
|
import { openRetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
||||||
|
|
||||||
export const infoFieldLgWidth: string = '330px';
|
export const infoFieldLgWidth: string = '330px';
|
||||||
export const infoFieldWidth: string = '250px';
|
export const infoFieldWidth: string = '250px';
|
||||||
@@ -48,6 +49,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
protected copyDatabaseMigrationDetails!: azdata.ButtonComponent;
|
protected copyDatabaseMigrationDetails!: azdata.ButtonComponent;
|
||||||
protected newSupportRequest!: azdata.ButtonComponent;
|
protected newSupportRequest!: azdata.ButtonComponent;
|
||||||
protected retryButton!: azdata.ButtonComponent;
|
protected retryButton!: azdata.ButtonComponent;
|
||||||
|
protected restartButton!: azdata.ButtonComponent;
|
||||||
protected summaryTextComponent: azdata.TextComponent[] = [];
|
protected summaryTextComponent: azdata.TextComponent[] = [];
|
||||||
|
|
||||||
public abstract create(
|
public abstract create(
|
||||||
@@ -242,13 +244,53 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.retryButton.onDidClick(
|
this.retryButton.onDidClick(
|
||||||
async (e) => {
|
async (e) => {
|
||||||
|
await this.statusBar.clearError();
|
||||||
|
if (canRetryMigration(this.model.migration)) {
|
||||||
|
const errorMessage = getMigrationErrors(this.model.migration);
|
||||||
|
await openRetryMigrationDialog(
|
||||||
|
errorMessage,
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
await retryMigration(
|
||||||
|
this.serviceContext.azureAccount!,
|
||||||
|
this.serviceContext.subscription!,
|
||||||
|
this.model.migration);
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
const retryMigrationDialog = new RetryMigrationDialog(
|
} catch (e) {
|
||||||
|
await this.statusBar.showError(
|
||||||
|
loc.MIGRATION_RETRY_ERROR,
|
||||||
|
loc.MIGRATION_RETRY_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationDetailsTab, MenuCommands.RetryMigration, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RETRY);
|
||||||
|
logError(TelemetryViews.MigrationDetailsTab, MenuCommands.RetryMigration, "cannot retry migration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
this.restartButton = this.view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
|
label: loc.RESTART_MIGRATION_WIZARD,
|
||||||
|
iconPath: IconPathHelper.retry,
|
||||||
|
enabled: false,
|
||||||
|
iconHeight: '16px',
|
||||||
|
iconWidth: '16px',
|
||||||
|
height: buttonHeight,
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.restartButton.onDidClick(
|
||||||
|
async (e) => {
|
||||||
|
await this.refresh();
|
||||||
|
const restartMigrationDialog = new RestartMigrationDialog(
|
||||||
this.context,
|
this.context,
|
||||||
this.serviceContext,
|
this.serviceContext,
|
||||||
this.model.migration,
|
this.model.migration,
|
||||||
this.serviceContextChangedEvent);
|
this.serviceContextChangedEvent);
|
||||||
await retryMigrationDialog.openDialog();
|
await restartMigrationDialog.openDialog();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -310,6 +352,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
<azdata.ToolbarComponent>{ component: this.cancelButton },
|
<azdata.ToolbarComponent>{ component: this.cancelButton },
|
||||||
<azdata.ToolbarComponent>{ component: this.deleteButton },
|
<azdata.ToolbarComponent>{ component: this.deleteButton },
|
||||||
<azdata.ToolbarComponent>{ component: this.retryButton },
|
<azdata.ToolbarComponent>{ component: this.retryButton },
|
||||||
|
<azdata.ToolbarComponent>{ component: this.restartButton },
|
||||||
<azdata.ToolbarComponent>{ component: this.copyDatabaseMigrationDetails, toolbarSeparatorAfter: true },
|
<azdata.ToolbarComponent>{ component: this.copyDatabaseMigrationDetails, toolbarSeparatorAfter: true },
|
||||||
<azdata.ToolbarComponent>{ component: this.newSupportRequest, toolbarSeparatorAfter: true },
|
<azdata.ToolbarComponent>{ component: this.newSupportRequest, toolbarSeparatorAfter: true },
|
||||||
<azdata.ToolbarComponent>{ component: this.refreshLoader },
|
<azdata.ToolbarComponent>{ component: this.refreshLoader },
|
||||||
@@ -493,7 +536,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async showMigrationErrors(migration: DatabaseMigration): Promise<void> {
|
protected async showMigrationErrors(migration: DatabaseMigration): Promise<void> {
|
||||||
const errorMessage = this.getMigrationErrors(migration);
|
const errorMessage = getMigrationErrors(migration);
|
||||||
if (errorMessage?.length > 0) {
|
if (errorMessage?.length > 0) {
|
||||||
await this.statusBar.showError(
|
await this.statusBar.showError(
|
||||||
loc.MIGRATION_ERROR_DETAILS_TITLE,
|
loc.MIGRATION_ERROR_DETAILS_TITLE,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
|
|||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { getMigrationStatusImage, getPipelineStatusImage } from '../api/utils';
|
import { getMigrationStatusImage, getPipelineStatusImage } from '../api/utils';
|
||||||
import { logError, TelemetryViews } from '../telemetry';
|
import { logError, TelemetryViews } from '../telemetry';
|
||||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration, formatDateTimeString, formatNumber, formatSizeBytes, formatSizeKb, formatTime, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, PipelineStatusCodes } from '../constants/helper';
|
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRestartMigrationWizard, canRetryMigration, formatDateTimeString, formatNumber, formatSizeBytes, formatSizeKb, formatTime, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, PipelineStatusCodes } from '../constants/helper';
|
||||||
import { CopyProgressDetail, getResourceName } from '../api/azure';
|
import { CopyProgressDetail, getResourceName } from '../api/azure';
|
||||||
import { InfoFieldSchema, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
import { InfoFieldSchema, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
@@ -328,6 +328,7 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
this.cancelButton.enabled = canCancelMigration(migration);
|
this.cancelButton.enabled = canCancelMigration(migration);
|
||||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||||
this.retryButton.enabled = canRetryMigration(migration);
|
this.retryButton.enabled = canRetryMigration(migration);
|
||||||
|
this.restartButton.enabled = canRestartMigrationWizard(migration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _populateTableData(hashSet: loc.LookupTable<number> = {}): Promise<void> {
|
private async _populateTableData(hashSet: loc.LookupTable<number> = {}): Promise<void> {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { IconPathHelper } from '../constants/iconPathHelper';
|
|||||||
import { getCurrentMigrations, getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
import { getCurrentMigrations, getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { filterMigrations, getMigrationDuration, getMigrationStatusImage, getMigrationStatusWithErrors, getMigrationTime, MenuCommands } from '../api/utils';
|
import { filterMigrations, getMigrationDuration, getMigrationStatusImage, getMigrationStatusWithErrors, getMigrationTime, MenuCommands } from '../api/utils';
|
||||||
import { getMigrationTargetType, getMigrationMode, canCancelMigration, canCutoverMigration, canDeleteMigration } from '../constants/helper';
|
import { getMigrationTargetType, getMigrationMode, canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration } from '../constants/helper';
|
||||||
import { DatabaseMigration, getResourceName } from '../api/azure';
|
import { DatabaseMigration, getMigrationErrors, getResourceName } from '../api/azure';
|
||||||
import { logError, TelemetryViews } from '../telemetry';
|
import { logError, TelemetryViews } from '../telemetry';
|
||||||
import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog';
|
import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog';
|
||||||
import { AdsMigrationStatus, EmptySettingValue, ServiceContextChangeEvent, TabBase } from './tabBase';
|
import { AdsMigrationStatus, EmptySettingValue, ServiceContextChangeEvent, TabBase } from './tabBase';
|
||||||
@@ -565,7 +565,7 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
// "Migration status" column
|
// "Migration status" column
|
||||||
case 2:
|
case 2:
|
||||||
const statusMessage = loc.DATABASE_MIGRATION_STATUS_LABEL(getMigrationStatusWithErrors(migration));
|
const statusMessage = loc.DATABASE_MIGRATION_STATUS_LABEL(getMigrationStatusWithErrors(migration));
|
||||||
const errors = this.getMigrationErrors(migration!);
|
const errors = getMigrationErrors(migration!);
|
||||||
|
|
||||||
this.showDialogMessage(
|
this.showDialogMessage(
|
||||||
loc.DATABASE_MIGRATION_STATUS_TITLE,
|
loc.DATABASE_MIGRATION_STATUS_TITLE,
|
||||||
@@ -592,8 +592,7 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
menuCommands.push(...[
|
menuCommands.push(...[
|
||||||
MenuCommands.ViewDatabase,
|
MenuCommands.ViewDatabase,
|
||||||
MenuCommands.ViewTarget,
|
MenuCommands.ViewTarget,
|
||||||
MenuCommands.ViewService,
|
MenuCommands.ViewService]);
|
||||||
MenuCommands.CopyMigration]);
|
|
||||||
|
|
||||||
if (canCancelMigration(migration)) {
|
if (canCancelMigration(migration)) {
|
||||||
menuCommands.push(MenuCommands.CancelMigration);
|
menuCommands.push(MenuCommands.CancelMigration);
|
||||||
@@ -603,6 +602,12 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
menuCommands.push(MenuCommands.DeleteMigration);
|
menuCommands.push(MenuCommands.DeleteMigration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canRetryMigration(migration)) {
|
||||||
|
menuCommands.push(MenuCommands.RetryMigration);
|
||||||
|
}
|
||||||
|
|
||||||
|
menuCommands.push(MenuCommands.CopyMigration);
|
||||||
|
|
||||||
return menuCommands;
|
return menuCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { DatabaseMigration, deleteMigration, getMigrationDetails } from '../api/azure';
|
import { DatabaseMigration, deleteMigration, getMigrationDetails, getMigrationErrors, retryMigration } from '../api/azure';
|
||||||
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
|
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
|
||||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration } from '../constants/helper';
|
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRestartMigrationWizard, canRetryMigration } from '../constants/helper';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { MigrationNotebookInfo, NotebookPathHelper } from '../constants/notebookPathHelper';
|
import { MigrationNotebookInfo, NotebookPathHelper } from '../constants/notebookPathHelper';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { SavedAssessmentDialog } from '../dialog/assessmentResults/savedAssessmentDialog';
|
import { SavedAssessmentDialog } from '../dialog/assessmentResults/savedAssessmentDialog';
|
||||||
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
||||||
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
||||||
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
import { RestartMigrationDialog } from '../dialog/restartMigration/restartMigrationDialog';
|
||||||
import { SqlMigrationServiceDetailsDialog } from '../dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
import { SqlMigrationServiceDetailsDialog } from '../dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
||||||
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||||
import { MigrationStateModel, SavedInfo } from '../models/stateMachine';
|
import { MigrationStateModel, SavedInfo } from '../models/stateMachine';
|
||||||
@@ -28,6 +28,7 @@ import { AdsMigrationStatus, MigrationDetailsEvent, ServiceContextChangeEvent }
|
|||||||
import { migrationServiceProvider } from '../service/provider';
|
import { migrationServiceProvider } from '../service/provider';
|
||||||
import { ApiType, SqlMigrationService } from '../service/features';
|
import { ApiType, SqlMigrationService } from '../service/features';
|
||||||
import { getSourceConnectionId, getSourceConnectionProfile } from '../api/sqlUtils';
|
import { getSourceConnectionId, getSourceConnectionProfile } from '../api/sqlUtils';
|
||||||
|
import { openRetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
||||||
|
|
||||||
export interface MenuCommandArgs {
|
export interface MenuCommandArgs {
|
||||||
connectionId: string,
|
connectionId: string,
|
||||||
@@ -355,20 +356,20 @@ export class DashboardWidget {
|
|||||||
vscode.commands.registerCommand(
|
vscode.commands.registerCommand(
|
||||||
MenuCommands.RetryMigration,
|
MenuCommands.RetryMigration,
|
||||||
async (args: MenuCommandArgs) => {
|
async (args: MenuCommandArgs) => {
|
||||||
try {
|
|
||||||
await this.clearError(args.connectionId);
|
await this.clearError(args.connectionId);
|
||||||
|
const service = await MigrationLocalStorage.getMigrationServiceContext();
|
||||||
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
if (migration && canRetryMigration(migration)) {
|
if (service && migration && canRetryMigration(migration)) {
|
||||||
const retryMigrationDialog = new RetryMigrationDialog(
|
const errorMessage = getMigrationErrors(migration);
|
||||||
this._context,
|
await openRetryMigrationDialog(
|
||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
errorMessage,
|
||||||
migration,
|
async () => {
|
||||||
this._onServiceContextChanged);
|
try {
|
||||||
await retryMigrationDialog.openDialog();
|
await retryMigration(
|
||||||
}
|
service.azureAccount!,
|
||||||
else {
|
service.subscription!,
|
||||||
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RETRY);
|
migration);
|
||||||
}
|
await this._migrationsTab.refresh();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.showError(
|
await this.showError(
|
||||||
args.connectionId,
|
args.connectionId,
|
||||||
@@ -377,6 +378,40 @@ export class DashboardWidget {
|
|||||||
e.message);
|
e.message);
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.RetryMigration, e);
|
logError(TelemetryViews.MigrationsTab, MenuCommands.RetryMigration, e);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RETRY);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.RetryMigration, "cannot retry migration");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.RestartMigration,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
try {
|
||||||
|
await this.clearError(args.connectionId);
|
||||||
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
|
if (migration && canRestartMigrationWizard(migration)) {
|
||||||
|
const restartMigrationDialog = new RestartMigrationDialog(
|
||||||
|
this._context,
|
||||||
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
|
migration,
|
||||||
|
this._onServiceContextChanged);
|
||||||
|
await restartMigrationDialog.openDialog();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RESTART);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.MIGRATION_RESTART_ERROR,
|
||||||
|
loc.MIGRATION_RESTART_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.RestartMigration, e);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._context.subscriptions.push(
|
this._context.subscriptions.push(
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import * as azdata from 'azdata';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { EOL } from 'os';
|
|
||||||
import { DatabaseMigration } from '../api/azure';
|
|
||||||
import { getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
import { getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
||||||
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
|
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
|
||||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
@@ -184,20 +182,6 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
|
|||||||
return feedbackButton;
|
return feedbackButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getMigrationErrors(migration: DatabaseMigration): string {
|
|
||||||
const errors = [];
|
|
||||||
errors.push(migration.properties.provisioningError);
|
|
||||||
errors.push(migration.properties.migrationFailureError?.message);
|
|
||||||
errors.push(migration.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
|
|
||||||
errors.push(migration.properties.migrationStatusDetails?.restoreBlockingReason);
|
|
||||||
errors.push(migration.properties.migrationStatusDetails?.sqlDataCopyErrors);
|
|
||||||
|
|
||||||
// remove undefined and duplicate error entries
|
|
||||||
return errors
|
|
||||||
.filter((e, i, arr) => e !== undefined && i === arr.indexOf(e))
|
|
||||||
.join(EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected showDialogMessage(
|
protected showDialogMessage(
|
||||||
title: string,
|
title: string,
|
||||||
statusMessage: string,
|
statusMessage: string,
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as features from '../../service/features';
|
||||||
|
import { azureResource } from 'azurecore';
|
||||||
|
import { getLocations, getResourceGroupFromId, getBlobContainerId, getFullResourceGroupFromId, getResourceName, DatabaseMigration, getMigrationTargetInstance } from '../../api/azure';
|
||||||
|
import { MigrationMode, MigrationStateModel, NetworkContainerType, SavedInfo } from '../../models/stateMachine';
|
||||||
|
import { MigrationServiceContext } from '../../models/migrationLocalStorage';
|
||||||
|
import { WizardController } from '../../wizard/wizardController';
|
||||||
|
import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper';
|
||||||
|
import * as constants from '../../constants/strings';
|
||||||
|
import { ServiceContextChangeEvent } from '../../dashboard/tabBase';
|
||||||
|
import { migrationServiceProvider } from '../../service/provider';
|
||||||
|
import { getSourceConnectionProfile } from '../../api/sqlUtils';
|
||||||
|
|
||||||
|
export class RestartMigrationDialog {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _context: vscode.ExtensionContext,
|
||||||
|
private readonly _serviceContext: MigrationServiceContext,
|
||||||
|
private readonly _migration: DatabaseMigration,
|
||||||
|
private readonly _serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createMigrationStateModel(
|
||||||
|
serviceContext: MigrationServiceContext,
|
||||||
|
migration: DatabaseMigration,
|
||||||
|
serverName: string,
|
||||||
|
migrationService: features.SqlMigrationService,
|
||||||
|
location: azureResource.AzureLocation): Promise<MigrationStateModel> {
|
||||||
|
|
||||||
|
const stateModel = new MigrationStateModel(this._context, migrationService);
|
||||||
|
const sourceDatabaseName = migration.properties.sourceDatabaseName;
|
||||||
|
const savedInfo: SavedInfo = {
|
||||||
|
closedPage: 0,
|
||||||
|
|
||||||
|
// DatabaseSelector
|
||||||
|
databaseAssessment: [sourceDatabaseName],
|
||||||
|
|
||||||
|
// SKURecommendation
|
||||||
|
databaseList: [sourceDatabaseName],
|
||||||
|
databaseInfoList: [],
|
||||||
|
serverAssessment: null,
|
||||||
|
skuRecommendation: null,
|
||||||
|
migrationTargetType: getMigrationTargetTypeEnum(migration)!,
|
||||||
|
|
||||||
|
// TargetSelection
|
||||||
|
azureAccount: serviceContext.azureAccount!,
|
||||||
|
azureTenant: serviceContext.azureAccount!.properties.tenants[0]!,
|
||||||
|
subscription: serviceContext.subscription!,
|
||||||
|
location: location,
|
||||||
|
resourceGroup: {
|
||||||
|
id: getFullResourceGroupFromId(migration.id),
|
||||||
|
name: getResourceGroupFromId(migration.id),
|
||||||
|
subscription: serviceContext.subscription!,
|
||||||
|
},
|
||||||
|
targetServerInstance: await getMigrationTargetInstance(
|
||||||
|
serviceContext.azureAccount!,
|
||||||
|
serviceContext.subscription!,
|
||||||
|
migration),
|
||||||
|
|
||||||
|
// MigrationMode
|
||||||
|
migrationMode: getMigrationModeEnum(migration),
|
||||||
|
|
||||||
|
// DatabaseBackup
|
||||||
|
targetDatabaseNames: [migration.name],
|
||||||
|
networkContainerType: null,
|
||||||
|
networkShares: [],
|
||||||
|
blobs: [],
|
||||||
|
|
||||||
|
// Integration Runtime
|
||||||
|
sqlMigrationService: serviceContext.migrationService,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStorageAccountResourceGroup = (storageAccountResourceId: string): azureResource.AzureResourceResourceGroup => {
|
||||||
|
return {
|
||||||
|
id: getFullResourceGroupFromId(storageAccountResourceId!),
|
||||||
|
name: getResourceGroupFromId(storageAccountResourceId!),
|
||||||
|
subscription: this._serviceContext.subscription!
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const getStorageAccount = (storageAccountResourceId: string): azureResource.AzureGraphResource => {
|
||||||
|
const storageAccountName = getResourceName(storageAccountResourceId);
|
||||||
|
return {
|
||||||
|
type: 'microsoft.storage/storageaccounts',
|
||||||
|
id: storageAccountResourceId!,
|
||||||
|
tenantId: savedInfo.azureTenant?.id!,
|
||||||
|
subscriptionId: this._serviceContext.subscription?.id!,
|
||||||
|
name: storageAccountName,
|
||||||
|
location: savedInfo.location!.name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceLocation = migration.properties.backupConfiguration?.sourceLocation;
|
||||||
|
if (sourceLocation?.fileShare) {
|
||||||
|
savedInfo.networkContainerType = NetworkContainerType.NETWORK_SHARE;
|
||||||
|
const storageAccountResourceId = migration.properties.backupConfiguration?.targetLocation?.storageAccountResourceId!;
|
||||||
|
savedInfo.networkShares = [
|
||||||
|
{
|
||||||
|
password: '',
|
||||||
|
networkShareLocation: sourceLocation?.fileShare?.path!,
|
||||||
|
windowsUser: sourceLocation?.fileShare?.username!,
|
||||||
|
storageAccount: getStorageAccount(storageAccountResourceId!),
|
||||||
|
resourceGroup: getStorageAccountResourceGroup(storageAccountResourceId!),
|
||||||
|
storageKey: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else if (sourceLocation?.azureBlob) {
|
||||||
|
savedInfo.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
|
||||||
|
const storageAccountResourceId = sourceLocation?.azureBlob?.storageAccountResourceId!;
|
||||||
|
savedInfo.blobs = [
|
||||||
|
{
|
||||||
|
blobContainer: {
|
||||||
|
id: getBlobContainerId(getFullResourceGroupFromId(storageAccountResourceId!), getResourceName(storageAccountResourceId!), sourceLocation?.azureBlob.blobContainerName),
|
||||||
|
name: sourceLocation?.azureBlob.blobContainerName,
|
||||||
|
subscription: this._serviceContext.subscription!
|
||||||
|
},
|
||||||
|
lastBackupFile: getMigrationModeEnum(migration) === MigrationMode.OFFLINE ? migration.properties.offlineConfiguration?.lastBackupName! : undefined,
|
||||||
|
storageAccount: getStorageAccount(storageAccountResourceId!),
|
||||||
|
resourceGroup: getStorageAccountResourceGroup(storageAccountResourceId!),
|
||||||
|
storageKey: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
stateModel.restartMigration = true;
|
||||||
|
stateModel.savedInfo = savedInfo;
|
||||||
|
stateModel.serverName = serverName;
|
||||||
|
return stateModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async openDialog(dialogName?: string) {
|
||||||
|
const locations = await getLocations(
|
||||||
|
this._serviceContext.azureAccount!,
|
||||||
|
this._serviceContext.subscription!);
|
||||||
|
|
||||||
|
const targetInstance = await getMigrationTargetInstance(
|
||||||
|
this._serviceContext.azureAccount!,
|
||||||
|
this._serviceContext.subscription!,
|
||||||
|
this._migration);
|
||||||
|
|
||||||
|
let location: azureResource.AzureLocation;
|
||||||
|
locations.forEach(azureLocation => {
|
||||||
|
if (azureLocation.name === targetInstance.location) {
|
||||||
|
location = azureLocation;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeConnection = await getSourceConnectionProfile();
|
||||||
|
let serverName: string = '';
|
||||||
|
if (!activeConnection) {
|
||||||
|
const connection = await azdata.connection.openConnectionDialog();
|
||||||
|
if (connection) {
|
||||||
|
serverName = connection.options.server;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverName = activeConnection.serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrationService = <features.SqlMigrationService>await migrationServiceProvider.getService(features.ApiType.SqlMigrationProvider)!;
|
||||||
|
const stateModel = await this.createMigrationStateModel(this._serviceContext, this._migration, serverName, migrationService, location!);
|
||||||
|
|
||||||
|
if (await stateModel.loadSavedInfo()) {
|
||||||
|
const wizardController = new WizardController(
|
||||||
|
this._context,
|
||||||
|
stateModel,
|
||||||
|
this._serviceContextChangedEvent);
|
||||||
|
await wizardController.openWizard();
|
||||||
|
} else {
|
||||||
|
void vscode.window.showInformationMessage(constants.MIGRATION_CANNOT_RESTART);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,173 +5,82 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as features from '../../service/features';
|
|
||||||
import { azureResource } from 'azurecore';
|
|
||||||
import { getLocations, getResourceGroupFromId, getBlobContainerId, getFullResourceGroupFromId, getResourceName, DatabaseMigration, getMigrationTargetInstance } from '../../api/azure';
|
|
||||||
import { MigrationMode, MigrationStateModel, NetworkContainerType, SavedInfo } from '../../models/stateMachine';
|
|
||||||
import { MigrationServiceContext } from '../../models/migrationLocalStorage';
|
|
||||||
import { WizardController } from '../../wizard/wizardController';
|
|
||||||
import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper';
|
|
||||||
import * as constants from '../../constants/strings';
|
import * as constants from '../../constants/strings';
|
||||||
import { ServiceContextChangeEvent } from '../../dashboard/tabBase';
|
|
||||||
import { migrationServiceProvider } from '../../service/provider';
|
|
||||||
import { getSourceConnectionProfile } from '../../api/sqlUtils';
|
|
||||||
|
|
||||||
export class RetryMigrationDialog {
|
export function openRetryMigrationDialog(
|
||||||
|
errorMessage: string,
|
||||||
|
onAcceptCallback: () => Promise<void>): void {
|
||||||
|
|
||||||
constructor(
|
const disposables: vscode.Disposable[] = [];
|
||||||
private readonly _context: vscode.ExtensionContext,
|
const tab = azdata.window.createTab('');
|
||||||
private readonly _serviceContext: MigrationServiceContext,
|
|
||||||
private readonly _migration: DatabaseMigration,
|
|
||||||
private readonly _serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createMigrationStateModel(
|
tab.registerContent(async (view) => {
|
||||||
serviceContext: MigrationServiceContext,
|
disposables.push(
|
||||||
migration: DatabaseMigration,
|
view.onClosed(e =>
|
||||||
serverName: string,
|
disposables.forEach(
|
||||||
migrationService: features.SqlMigrationService,
|
d => { try { d.dispose(); } catch { } })));
|
||||||
location: azureResource.AzureLocation): Promise<MigrationStateModel> {
|
|
||||||
|
|
||||||
const stateModel = new MigrationStateModel(this._context, migrationService);
|
const flex = view.modelBuilder.flexContainer()
|
||||||
const sourceDatabaseName = migration.properties.sourceDatabaseName;
|
.withItems([
|
||||||
const savedInfo: SavedInfo = {
|
view.modelBuilder.text()
|
||||||
closedPage: 0,
|
.withProps({
|
||||||
|
value: constants.RETRY_MIGRATION_TITLE,
|
||||||
|
title: constants.RETRY_MIGRATION_TITLE,
|
||||||
|
CSSStyles: { 'font-weight': '600', 'margin': '10px 0px 5px 0px' },
|
||||||
|
})
|
||||||
|
.component(),
|
||||||
|
view.modelBuilder.inputBox()
|
||||||
|
.withProps({
|
||||||
|
value: errorMessage,
|
||||||
|
title: errorMessage,
|
||||||
|
readOnly: true,
|
||||||
|
multiline: true,
|
||||||
|
inputType: 'text',
|
||||||
|
rows: 10,
|
||||||
|
CSSStyles: { 'overflow': 'hidden auto', 'margin': '0px 0px 10px 0px' },
|
||||||
|
})
|
||||||
|
.component(),
|
||||||
|
view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
|
value: constants.RETRY_MIGRATION_SUMMARY,
|
||||||
|
title: constants.RETRY_MIGRATION_SUMMARY,
|
||||||
|
CSSStyles: { 'margin': '0px 0px 10px 0px' },
|
||||||
|
})
|
||||||
|
.component(),
|
||||||
|
view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
|
value: constants.RETRY_MIGRATION_PROMPT,
|
||||||
|
title: constants.RETRY_MIGRATION_PROMPT,
|
||||||
|
CSSStyles: { 'margin': '0px 0px 5px 0px' },
|
||||||
|
})
|
||||||
|
.component(),
|
||||||
|
])
|
||||||
|
.withLayout({ flexFlow: 'column', })
|
||||||
|
.withProps({ CSSStyles: { 'margin': '0 15px 0 15px' } })
|
||||||
|
.component();
|
||||||
|
|
||||||
// DatabaseSelector
|
await view.initializeModel(flex);
|
||||||
databaseAssessment: [sourceDatabaseName],
|
|
||||||
|
|
||||||
// SKURecommendation
|
|
||||||
databaseList: [sourceDatabaseName],
|
|
||||||
databaseInfoList: [],
|
|
||||||
serverAssessment: null,
|
|
||||||
skuRecommendation: null,
|
|
||||||
migrationTargetType: getMigrationTargetTypeEnum(migration)!,
|
|
||||||
|
|
||||||
// TargetSelection
|
|
||||||
azureAccount: serviceContext.azureAccount!,
|
|
||||||
azureTenant: serviceContext.azureAccount!.properties.tenants[0]!,
|
|
||||||
subscription: serviceContext.subscription!,
|
|
||||||
location: location,
|
|
||||||
resourceGroup: {
|
|
||||||
id: getFullResourceGroupFromId(migration.id),
|
|
||||||
name: getResourceGroupFromId(migration.id),
|
|
||||||
subscription: serviceContext.subscription!,
|
|
||||||
},
|
|
||||||
targetServerInstance: await getMigrationTargetInstance(
|
|
||||||
serviceContext.azureAccount!,
|
|
||||||
serviceContext.subscription!,
|
|
||||||
migration),
|
|
||||||
|
|
||||||
// MigrationMode
|
|
||||||
migrationMode: getMigrationModeEnum(migration),
|
|
||||||
|
|
||||||
// DatabaseBackup
|
|
||||||
targetDatabaseNames: [migration.name],
|
|
||||||
networkContainerType: null,
|
|
||||||
networkShares: [],
|
|
||||||
blobs: [],
|
|
||||||
|
|
||||||
// Integration Runtime
|
|
||||||
sqlMigrationService: serviceContext.migrationService,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStorageAccountResourceGroup = (storageAccountResourceId: string): azureResource.AzureResourceResourceGroup => {
|
|
||||||
return {
|
|
||||||
id: getFullResourceGroupFromId(storageAccountResourceId!),
|
|
||||||
name: getResourceGroupFromId(storageAccountResourceId!),
|
|
||||||
subscription: this._serviceContext.subscription!
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const getStorageAccount = (storageAccountResourceId: string): azureResource.AzureGraphResource => {
|
|
||||||
const storageAccountName = getResourceName(storageAccountResourceId);
|
|
||||||
return {
|
|
||||||
type: 'microsoft.storage/storageaccounts',
|
|
||||||
id: storageAccountResourceId!,
|
|
||||||
tenantId: savedInfo.azureTenant?.id!,
|
|
||||||
subscriptionId: this._serviceContext.subscription?.id!,
|
|
||||||
name: storageAccountName,
|
|
||||||
location: savedInfo.location!.name,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const sourceLocation = migration.properties.backupConfiguration?.sourceLocation;
|
|
||||||
if (sourceLocation?.fileShare) {
|
|
||||||
savedInfo.networkContainerType = NetworkContainerType.NETWORK_SHARE;
|
|
||||||
const storageAccountResourceId = migration.properties.backupConfiguration?.targetLocation?.storageAccountResourceId!;
|
|
||||||
savedInfo.networkShares = [
|
|
||||||
{
|
|
||||||
password: '',
|
|
||||||
networkShareLocation: sourceLocation?.fileShare?.path!,
|
|
||||||
windowsUser: sourceLocation?.fileShare?.username!,
|
|
||||||
storageAccount: getStorageAccount(storageAccountResourceId!),
|
|
||||||
resourceGroup: getStorageAccountResourceGroup(storageAccountResourceId!),
|
|
||||||
storageKey: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
} else if (sourceLocation?.azureBlob) {
|
|
||||||
savedInfo.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
|
|
||||||
const storageAccountResourceId = sourceLocation?.azureBlob?.storageAccountResourceId!;
|
|
||||||
savedInfo.blobs = [
|
|
||||||
{
|
|
||||||
blobContainer: {
|
|
||||||
id: getBlobContainerId(getFullResourceGroupFromId(storageAccountResourceId!), getResourceName(storageAccountResourceId!), sourceLocation?.azureBlob.blobContainerName),
|
|
||||||
name: sourceLocation?.azureBlob.blobContainerName,
|
|
||||||
subscription: this._serviceContext.subscription!
|
|
||||||
},
|
|
||||||
lastBackupFile: getMigrationModeEnum(migration) === MigrationMode.OFFLINE ? migration.properties.offlineConfiguration?.lastBackupName! : undefined,
|
|
||||||
storageAccount: getStorageAccount(storageAccountResourceId!),
|
|
||||||
resourceGroup: getStorageAccountResourceGroup(storageAccountResourceId!),
|
|
||||||
storageKey: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
stateModel.retryMigration = true;
|
|
||||||
stateModel.savedInfo = savedInfo;
|
|
||||||
stateModel.serverName = serverName;
|
|
||||||
return stateModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async openDialog(dialogName?: string) {
|
|
||||||
const locations = await getLocations(
|
|
||||||
this._serviceContext.azureAccount!,
|
|
||||||
this._serviceContext.subscription!);
|
|
||||||
|
|
||||||
const targetInstance = await getMigrationTargetInstance(
|
|
||||||
this._serviceContext.azureAccount!,
|
|
||||||
this._serviceContext.subscription!,
|
|
||||||
this._migration);
|
|
||||||
|
|
||||||
let location: azureResource.AzureLocation;
|
|
||||||
locations.forEach(azureLocation => {
|
|
||||||
if (azureLocation.name === targetInstance.location) {
|
|
||||||
location = azureLocation;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeConnection = await getSourceConnectionProfile();
|
const dialog = azdata.window.createModelViewDialog(
|
||||||
let serverName: string = '';
|
'',
|
||||||
if (!activeConnection) {
|
'retryMigrationDialog',
|
||||||
const connection = await azdata.connection.openConnectionDialog();
|
500,
|
||||||
if (connection) {
|
'normal',
|
||||||
serverName = connection.options.server;
|
undefined,
|
||||||
}
|
false);
|
||||||
} else {
|
dialog.content = [tab];
|
||||||
serverName = activeConnection.serverName;
|
dialog.okButton.label = constants.YES;
|
||||||
}
|
dialog.okButton.position = 'left';
|
||||||
|
dialog.cancelButton.label = constants.NO;
|
||||||
|
dialog.cancelButton.position = 'left';
|
||||||
|
dialog.cancelButton.focused = true;
|
||||||
|
|
||||||
const migrationService = <features.SqlMigrationService>await migrationServiceProvider.getService(features.ApiType.SqlMigrationProvider)!;
|
|
||||||
const stateModel = await this.createMigrationStateModel(this._serviceContext, this._migration, serverName, migrationService, location!);
|
|
||||||
|
|
||||||
if (await stateModel.loadSavedInfo()) {
|
|
||||||
const wizardController = new WizardController(
|
disposables.push(
|
||||||
this._context,
|
dialog.okButton.onClick(
|
||||||
stateModel,
|
async e => await onAcceptCallback()));
|
||||||
this._serviceContextChangedEvent);
|
|
||||||
await wizardController.openWizard();
|
azdata.window.openDialog(dialog);
|
||||||
} else {
|
|
||||||
void vscode.window.showInformationMessage(constants.MIGRATION_CANNOT_RETRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export enum Page {
|
|||||||
export enum WizardEntryPoint {
|
export enum WizardEntryPoint {
|
||||||
Default = 'Default',
|
Default = 'Default',
|
||||||
SaveAndClose = 'SaveAndClose',
|
SaveAndClose = 'SaveAndClose',
|
||||||
RetryMigration = 'RetryMigration',
|
RestartMigration = 'RestartMigration',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PerformanceDataSourceOptions {
|
export enum PerformanceDataSourceOptions {
|
||||||
@@ -260,7 +260,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public _skuEnableElastic!: boolean;
|
public _skuEnableElastic!: boolean;
|
||||||
|
|
||||||
public refreshDatabaseBackupPage!: boolean;
|
public refreshDatabaseBackupPage!: boolean;
|
||||||
public retryMigration!: boolean;
|
public restartMigration!: boolean;
|
||||||
public resumeAssessment!: boolean;
|
public resumeAssessment!: boolean;
|
||||||
public savedInfo!: SavedInfo;
|
public savedInfo!: SavedInfo;
|
||||||
public closedPage!: number;
|
public closedPage!: number;
|
||||||
@@ -1121,8 +1121,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
let wizardEntryPoint = WizardEntryPoint.Default;
|
let wizardEntryPoint = WizardEntryPoint.Default;
|
||||||
if (this.resumeAssessment) {
|
if (this.resumeAssessment) {
|
||||||
wizardEntryPoint = WizardEntryPoint.SaveAndClose;
|
wizardEntryPoint = WizardEntryPoint.SaveAndClose;
|
||||||
} else if (this.retryMigration) {
|
} else if (this.restartMigration) {
|
||||||
wizardEntryPoint = WizardEntryPoint.RetryMigration;
|
wizardEntryPoint = WizardEntryPoint.RestartMigration;
|
||||||
}
|
}
|
||||||
if (response.status === 201 || response.status === 200) {
|
if (response.status === 201 || response.status === 200) {
|
||||||
sendSqlMigrationActionEvent(
|
sendSqlMigrationActionEvent(
|
||||||
@@ -1167,7 +1167,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
finally {
|
finally {
|
||||||
// kill existing data collection if user start migration
|
// kill existing data collection if user start migration
|
||||||
await this.refreshPerfDataCollection();
|
await this.refreshPerfDataCollection();
|
||||||
if ((!this.resumeAssessment || this.retryMigration) && this._perfDataCollectionIsCollecting) {
|
if ((!this.resumeAssessment || this.restartMigration) && this._perfDataCollectionIsCollecting) {
|
||||||
void this.stopPerfDataCollection();
|
void this.stopPerfDataCollection();
|
||||||
void vscode.window.showInformationMessage(
|
void vscode.window.showInformationMessage(
|
||||||
constants.AZURE_RECOMMENDATION_STOP_POPUP);
|
constants.AZURE_RECOMMENDATION_STOP_POPUP);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export class WizardController {
|
|||||||
|
|
||||||
// kill existing data collection if user relaunches the wizard via new migration or retry existing migration
|
// kill existing data collection if user relaunches the wizard via new migration or retry existing migration
|
||||||
await this._model.refreshPerfDataCollection();
|
await this._model.refreshPerfDataCollection();
|
||||||
if ((!this._model.resumeAssessment || this._model.retryMigration) && this._model._perfDataCollectionIsCollecting) {
|
if ((!this._model.resumeAssessment || this._model.restartMigration) && this._model._perfDataCollectionIsCollecting) {
|
||||||
void this._model.stopPerfDataCollection();
|
void this._model.stopPerfDataCollection();
|
||||||
void vscode.window.showInformationMessage(loc.AZURE_RECOMMENDATION_STOP_POPUP);
|
void vscode.window.showInformationMessage(loc.AZURE_RECOMMENDATION_STOP_POPUP);
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ export class WizardController {
|
|||||||
const wizardSetupPromises: Thenable<void>[] = [];
|
const wizardSetupPromises: Thenable<void>[] = [];
|
||||||
wizardSetupPromises.push(...pages.map(p => p.registerWizardContent()));
|
wizardSetupPromises.push(...pages.map(p => p.registerWizardContent()));
|
||||||
wizardSetupPromises.push(this._wizardObject.open());
|
wizardSetupPromises.push(this._wizardObject.open());
|
||||||
if (this._model.retryMigration || this._model.resumeAssessment) {
|
if (this._model.resumeAssessment || this._model.restartMigration) {
|
||||||
if (this._model.savedInfo.closedPage >= Page.IntegrationRuntime) {
|
if (this._model.savedInfo.closedPage >= Page.IntegrationRuntime) {
|
||||||
this._model.refreshDatabaseBackupPage = true;
|
this._model.refreshDatabaseBackupPage = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user