[SQL Migration] Consume more detailed migration states (#20490)

* WIP

* Add new states

* Missed a few spots

* Update logic

* Update DatabaseMigrationProperties

* Test: add mocks

* Update a few more references

* Fix warnings

* Remove mocks

* Update cutover logic

* Address PR feedback
This commit is contained in:
Raymond Truong
2022-10-28 17:14:12 -07:00
committed by GitHub
parent f4711d3c9c
commit 0dc8e18230
6 changed files with 81 additions and 53 deletions

View File

@@ -655,7 +655,7 @@ export interface DatabaseMigrationProperties {
scope: string; scope: string;
provisioningState: 'Succeeded' | 'Failed' | 'Creating'; provisioningState: 'Succeeded' | 'Failed' | 'Creating';
provisioningError: string; provisioningError: string;
migrationStatus: 'InProgress' | 'Failed' | 'Succeeded' | 'Creating' | 'Completing' | 'Canceling'; migrationStatus: 'Canceled' | 'Canceling' | 'Completing' | 'Creating' | 'Failed' | 'InProgress' | 'ReadyForCutover' | 'Restoring' | 'Retriable' | 'Succeeded' | 'UploadingFullBackup' | 'UploadingLogBackup';
migrationStatusDetails?: MigrationStatusDetails; migrationStatusDetails?: MigrationStatusDetails;
migrationStatusWarnings?: MigrationStatusWarnings; migrationStatusWarnings?: MigrationStatusWarnings;
startedOn: string; startedOn: string;

View File

@@ -162,19 +162,23 @@ export function filterMigrations(databaseMigrations: azure.DatabaseMigration[],
return filteredMigration.filter( return filteredMigration.filter(
value => { value => {
const status = getMigrationStatus(value); const status = getMigrationStatus(value);
return status === constants.MigrationStatus.InProgress return status === constants.MigrationState.InProgress
|| status === constants.MigrationStatus.Retriable || status === constants.MigrationState.ReadyForCutover
|| status === constants.MigrationStatus.Creating; || status === constants.MigrationState.UploadingFullBackup
|| status === constants.MigrationState.UploadingLogBackup
|| status === constants.MigrationState.Restoring
|| status === constants.MigrationState.Retriable
|| status === constants.MigrationState.Creating;
}); });
case AdsMigrationStatus.SUCCEEDED: case AdsMigrationStatus.SUCCEEDED:
return filteredMigration.filter( return filteredMigration.filter(
value => getMigrationStatus(value) === constants.MigrationStatus.Succeeded); value => getMigrationStatus(value) === constants.MigrationState.Succeeded);
case AdsMigrationStatus.FAILED: case AdsMigrationStatus.FAILED:
return filteredMigration.filter( return filteredMigration.filter(
value => getMigrationStatus(value) === constants.MigrationStatus.Failed); value => getMigrationStatus(value) === constants.MigrationState.Failed);
case AdsMigrationStatus.COMPLETING: case AdsMigrationStatus.COMPLETING:
return filteredMigration.filter( return filteredMigration.filter(
value => getMigrationStatus(value) === constants.MigrationStatus.Completing); value => getMigrationStatus(value) === constants.MigrationState.Completing);
} }
return filteredMigration; return filteredMigration;
} }
@@ -323,20 +327,25 @@ export function getPipelineStatusImage(status: string | undefined): IconPath {
export function getMigrationStatusImage(migration: azure.DatabaseMigration): IconPath { export function getMigrationStatusImage(migration: azure.DatabaseMigration): IconPath {
const status = getMigrationStatus(migration); const status = getMigrationStatus(migration);
switch (status) { switch (status) {
case constants.MigrationStatus.InProgress: case constants.MigrationState.InProgress:
case constants.MigrationState.UploadingFullBackup:
case constants.MigrationState.UploadingLogBackup:
case constants.MigrationState.Restoring:
return IconPathHelper.inProgressMigration; return IconPathHelper.inProgressMigration;
case constants.MigrationStatus.Succeeded: case constants.MigrationState.ReadyForCutover:
return IconPathHelper.cutover;
case constants.MigrationState.Succeeded:
return IconPathHelper.completedMigration; return IconPathHelper.completedMigration;
case constants.MigrationStatus.Creating: case constants.MigrationState.Creating:
return IconPathHelper.notStartedMigration; return IconPathHelper.notStartedMigration;
case constants.MigrationStatus.Completing: case constants.MigrationState.Completing:
return IconPathHelper.completingCutover; return IconPathHelper.completingCutover;
case constants.MigrationStatus.Retriable: case constants.MigrationState.Retriable:
return IconPathHelper.retry; return IconPathHelper.retry;
case constants.MigrationStatus.Canceling: case constants.MigrationState.Canceling:
case constants.MigrationStatus.Canceled: case constants.MigrationState.Canceled:
return IconPathHelper.cancel; return IconPathHelper.cancel;
case constants.MigrationStatus.Failed: case constants.MigrationState.Failed:
default: default:
return IconPathHelper.error; return IconPathHelper.error;
} }

View File

@@ -158,41 +158,49 @@ export function hasMigrationOperationId(migration: DatabaseMigration | undefined
export function canCancelMigration(migration: DatabaseMigration | undefined): boolean { export function canCancelMigration(migration: DatabaseMigration | undefined): boolean {
const status = getMigrationStatus(migration); const status = getMigrationStatus(migration);
return hasMigrationOperationId(migration) return hasMigrationOperationId(migration)
&& (status === loc.MigrationStatus.InProgress || && (status === loc.MigrationState.InProgress
status === loc.MigrationStatus.Retriable || || status === loc.MigrationState.Retriable
status === loc.MigrationStatus.Creating); || status === loc.MigrationState.Creating
|| status === loc.MigrationState.ReadyForCutover
|| status === loc.MigrationState.UploadingFullBackup
|| status === loc.MigrationState.UploadingLogBackup
|| status === loc.MigrationState.Restoring);
} }
export function canDeleteMigration(migration: DatabaseMigration | undefined): boolean { export function canDeleteMigration(migration: DatabaseMigration | undefined): boolean {
const status = getMigrationStatus(migration); const status = getMigrationStatus(migration);
return status === loc.MigrationStatus.Canceled return status === loc.MigrationState.Canceled
|| status === loc.MigrationStatus.Failed || status === loc.MigrationState.Failed
|| status === loc.MigrationStatus.Retriable || status === loc.MigrationState.Retriable
|| status === loc.MigrationStatus.Succeeded; || status === loc.MigrationState.Succeeded;
} }
export function canRetryMigration(migration: DatabaseMigration | undefined): boolean { export function canRetryMigration(migration: DatabaseMigration | undefined): boolean {
const status = getMigrationStatus(migration); const status = getMigrationStatus(migration);
return status === loc.MigrationStatus.Canceled return status === loc.MigrationState.Canceled
|| status === loc.MigrationStatus.Retriable || status === loc.MigrationState.Retriable
|| status === loc.MigrationStatus.Failed || status === loc.MigrationState.Failed
|| status === loc.MigrationStatus.Succeeded; || status === loc.MigrationState.Succeeded;
} }
export function canCutoverMigration(migration: DatabaseMigration | undefined): boolean { export function canCutoverMigration(migration: DatabaseMigration | undefined): boolean {
const status = getMigrationStatus(migration); const status = getMigrationStatus(migration);
return hasMigrationOperationId(migration) return hasMigrationOperationId(migration)
&& status === loc.MigrationStatus.InProgress
&& isOnlineMigration(migration) && isOnlineMigration(migration)
&& (status === loc.MigrationState.ReadyForCutover || status === loc.MigrationState.InProgress) // TODO: InProgress condition can be eventually deprecated
&& isFullBackupRestored(migration); && isFullBackupRestored(migration);
} }
export function isActiveMigration(migration: DatabaseMigration | undefined): boolean { export function isActiveMigration(migration: DatabaseMigration | undefined): boolean {
const status = getMigrationStatus(migration); const status = getMigrationStatus(migration);
return status === loc.MigrationStatus.Completing return status === loc.MigrationState.Completing
|| status === loc.MigrationStatus.Retriable || status === loc.MigrationState.Retriable
|| status === loc.MigrationStatus.Creating || status === loc.MigrationState.Creating
|| status === loc.MigrationStatus.InProgress; || status === loc.MigrationState.InProgress
|| status === loc.MigrationState.ReadyForCutover
|| status === loc.MigrationState.UploadingFullBackup
|| status === loc.MigrationState.UploadingLogBackup
|| status === loc.MigrationState.Restoring;
} }
export function isFullBackupRestored(migration: DatabaseMigration | undefined): boolean { export function isFullBackupRestored(migration: DatabaseMigration | undefined): boolean {

View File

@@ -10,15 +10,20 @@ import { MigrationSourceAuthenticationType } from '../models/stateMachine';
import { formatNumber, ParallelCopyTypeCodes, PipelineStatusCodes } from './helper'; import { formatNumber, ParallelCopyTypeCodes, PipelineStatusCodes } from './helper';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export enum MigrationStatus { // mirrors MigrationState as defined in RP
Failed = 'Failed', export enum MigrationState {
Succeeded = 'Succeeded',
InProgress = 'InProgress',
Canceled = 'Canceled', Canceled = 'Canceled',
Canceling = 'Canceling',
Completing = 'Completing', Completing = 'Completing',
Creating = 'Creating', Creating = 'Creating',
Canceling = 'Canceling', Failed = 'Failed',
InProgress = 'InProgress',
ReadyForCutover = 'ReadyForCutover',
Restoring = 'Restoring',
Retriable = 'Retriable', Retriable = 'Retriable',
Succeeded = 'Succeeded',
UploadingFullBackup = 'UploadingFullBackup',
UploadingLogBackup = 'UploadingLogBackup',
} }
export enum ProvisioningState { export enum ProvisioningState {
@@ -824,14 +829,18 @@ export interface LookupTable<T> {
} }
export const StatusLookup: LookupTable<string | undefined> = { export const StatusLookup: LookupTable<string | undefined> = {
[MigrationStatus.InProgress]: localize('sql.migration.status.inprogress', 'In progress'), [MigrationState.Canceled]: localize('sql.migration.status.canceled', 'Canceled'),
[MigrationStatus.Succeeded]: localize('sql.migration.status.succeeded', 'Succeeded'), [MigrationState.Canceling]: localize('sql.migration.status.canceling', 'Canceling'),
[MigrationStatus.Creating]: localize('sql.migration.status.creating', 'Creating'), [MigrationState.Completing]: localize('sql.migration.status.completing', 'Completing'),
[MigrationStatus.Completing]: localize('sql.migration.status.completing', 'Completing'), [MigrationState.Creating]: localize('sql.migration.status.creating', 'Creating'),
[MigrationStatus.Retriable]: localize('sql.migration.status.retriable', 'Retriable'), [MigrationState.Failed]: localize('sql.migration.status.failed', 'Failed'),
[MigrationStatus.Canceling]: localize('sql.migration.status.canceling', 'Canceling'), [MigrationState.InProgress]: localize('sql.migration.status.inprogress', 'In progress'),
[MigrationStatus.Canceled]: localize('sql.migration.status.canceled', 'Canceled'), [MigrationState.ReadyForCutover]: localize('sql.migration.status.readyforcutover', 'Ready for cutover'),
[MigrationStatus.Failed]: localize('sql.migration.status.failed', 'Failed'), [MigrationState.Restoring]: localize('sql.migration.status.restoring', 'Restoring'),
[MigrationState.Retriable]: localize('sql.migration.status.retriable', 'Retriable'),
[MigrationState.Succeeded]: localize('sql.migration.status.succeeded', 'Succeeded'),
[MigrationState.UploadingFullBackup]: localize('sql.migration.status.uploadingfullbackup', 'Uploading full backup'),
[MigrationState.UploadingLogBackup]: localize('sql.migration.status.uploadinglogbackup', 'Uploading log backup(s)'),
default: undefined default: undefined
}; };
@@ -858,9 +867,13 @@ export const ParallelCopyType: LookupTable<string | undefined> = {
}; };
export function STATUS_WARNING_COUNT(status: string, count: number): string | undefined { export function STATUS_WARNING_COUNT(status: string, count: number): string | undefined {
if (status === MigrationStatus.InProgress || if (status === MigrationState.InProgress ||
status === MigrationStatus.Creating || status === MigrationState.ReadyForCutover ||
status === MigrationStatus.Completing) { status === MigrationState.UploadingFullBackup ||
status === MigrationState.UploadingLogBackup ||
status === MigrationState.Restoring ||
status === MigrationState.Creating ||
status === MigrationState.Completing) {
switch (count) { switch (count) {
case 0: case 0:
return undefined; return undefined;

View File

@@ -9,12 +9,11 @@ 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, getMigrationModeEnum, canCancelMigration, canCutoverMigration } from '../constants/helper'; import { getMigrationTargetType, getMigrationMode, canCancelMigration, canCutoverMigration } from '../constants/helper';
import { DatabaseMigration, getResourceName } from '../api/azure'; import { DatabaseMigration, getResourceName } from '../api/azure';
import { logError, TelemetryViews } from '../telemtery'; import { logError, TelemetryViews } from '../telemtery';
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';
import { MigrationMode } from '../models/stateMachine';
import { DashboardStatusBar } from './DashboardStatusBar'; import { DashboardStatusBar } from './DashboardStatusBar';
export const MigrationsListTabId = 'MigrationsListTab'; export const MigrationsListTabId = 'MigrationsListTab';
@@ -582,8 +581,7 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
private _getMenuCommands(migration: DatabaseMigration): string[] { private _getMenuCommands(migration: DatabaseMigration): string[] {
const menuCommands: string[] = []; const menuCommands: string[] = [];
if (getMigrationModeEnum(migration) === MigrationMode.ONLINE && if (canCutoverMigration(migration)) {
canCutoverMigration(migration)) {
menuCommands.push(MenuCommands.Cutover); menuCommands.push(MenuCommands.Cutover);
} }

View File

@@ -9,7 +9,7 @@ import * as mssql from 'mssql';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { DatabaseMigration, getMigrationDetails } from '../api/azure'; import { DatabaseMigration, getMigrationDetails } from '../api/azure';
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils'; import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
import { canCancelMigration, canRetryMigration } from '../constants/helper'; import { canCancelMigration, canCutoverMigration, 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';
@@ -158,7 +158,7 @@ export class DashboardWidget {
try { try {
await this.clearError(args.connectionId); await this.clearError(args.connectionId);
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId); const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
if (canRetryMigration(migration)) { if (canCutoverMigration(migration)) {
const cutoverDialogModel = new MigrationCutoverDialogModel( const cutoverDialogModel = new MigrationCutoverDialogModel(
await MigrationLocalStorage.getMigrationServiceContext(), await MigrationLocalStorage.getMigrationServiceContext(),
migration!); migration!);