From 0dc8e182305ca501f9021841a40580a3e69a93aa Mon Sep 17 00:00:00 2001 From: Raymond Truong Date: Fri, 28 Oct 2022 17:14:12 -0700 Subject: [PATCH] [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 --- extensions/sql-migration/src/api/azure.ts | 2 +- extensions/sql-migration/src/api/utils.ts | 37 +++++++++------ .../sql-migration/src/constants/helper.ts | 40 ++++++++++------- .../sql-migration/src/constants/strings.ts | 45 ++++++++++++------- .../src/dashboard/migrationsListTab.ts | 6 +-- .../src/dashboard/sqlServerDashboard.ts | 4 +- 6 files changed, 81 insertions(+), 53 deletions(-) diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts index 5617e1f60c..7f84123c60 100644 --- a/extensions/sql-migration/src/api/azure.ts +++ b/extensions/sql-migration/src/api/azure.ts @@ -655,7 +655,7 @@ export interface DatabaseMigrationProperties { scope: string; provisioningState: 'Succeeded' | 'Failed' | 'Creating'; provisioningError: string; - migrationStatus: 'InProgress' | 'Failed' | 'Succeeded' | 'Creating' | 'Completing' | 'Canceling'; + migrationStatus: 'Canceled' | 'Canceling' | 'Completing' | 'Creating' | 'Failed' | 'InProgress' | 'ReadyForCutover' | 'Restoring' | 'Retriable' | 'Succeeded' | 'UploadingFullBackup' | 'UploadingLogBackup'; migrationStatusDetails?: MigrationStatusDetails; migrationStatusWarnings?: MigrationStatusWarnings; startedOn: string; diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index bdcf26f890..628bedc8ac 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -162,19 +162,23 @@ export function filterMigrations(databaseMigrations: azure.DatabaseMigration[], return filteredMigration.filter( value => { const status = getMigrationStatus(value); - return status === constants.MigrationStatus.InProgress - || status === constants.MigrationStatus.Retriable - || status === constants.MigrationStatus.Creating; + return status === constants.MigrationState.InProgress + || status === constants.MigrationState.ReadyForCutover + || status === constants.MigrationState.UploadingFullBackup + || status === constants.MigrationState.UploadingLogBackup + || status === constants.MigrationState.Restoring + || status === constants.MigrationState.Retriable + || status === constants.MigrationState.Creating; }); case AdsMigrationStatus.SUCCEEDED: return filteredMigration.filter( - value => getMigrationStatus(value) === constants.MigrationStatus.Succeeded); + value => getMigrationStatus(value) === constants.MigrationState.Succeeded); case AdsMigrationStatus.FAILED: return filteredMigration.filter( - value => getMigrationStatus(value) === constants.MigrationStatus.Failed); + value => getMigrationStatus(value) === constants.MigrationState.Failed); case AdsMigrationStatus.COMPLETING: return filteredMigration.filter( - value => getMigrationStatus(value) === constants.MigrationStatus.Completing); + value => getMigrationStatus(value) === constants.MigrationState.Completing); } return filteredMigration; } @@ -323,20 +327,25 @@ export function getPipelineStatusImage(status: string | undefined): IconPath { export function getMigrationStatusImage(migration: azure.DatabaseMigration): IconPath { const status = getMigrationStatus(migration); 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; - case constants.MigrationStatus.Succeeded: + case constants.MigrationState.ReadyForCutover: + return IconPathHelper.cutover; + case constants.MigrationState.Succeeded: return IconPathHelper.completedMigration; - case constants.MigrationStatus.Creating: + case constants.MigrationState.Creating: return IconPathHelper.notStartedMigration; - case constants.MigrationStatus.Completing: + case constants.MigrationState.Completing: return IconPathHelper.completingCutover; - case constants.MigrationStatus.Retriable: + case constants.MigrationState.Retriable: return IconPathHelper.retry; - case constants.MigrationStatus.Canceling: - case constants.MigrationStatus.Canceled: + case constants.MigrationState.Canceling: + case constants.MigrationState.Canceled: return IconPathHelper.cancel; - case constants.MigrationStatus.Failed: + case constants.MigrationState.Failed: default: return IconPathHelper.error; } diff --git a/extensions/sql-migration/src/constants/helper.ts b/extensions/sql-migration/src/constants/helper.ts index afaeabc2be..2502e86940 100644 --- a/extensions/sql-migration/src/constants/helper.ts +++ b/extensions/sql-migration/src/constants/helper.ts @@ -158,41 +158,49 @@ export function hasMigrationOperationId(migration: DatabaseMigration | undefined export function canCancelMigration(migration: DatabaseMigration | undefined): boolean { const status = getMigrationStatus(migration); return hasMigrationOperationId(migration) - && (status === loc.MigrationStatus.InProgress || - status === loc.MigrationStatus.Retriable || - status === loc.MigrationStatus.Creating); + && (status === loc.MigrationState.InProgress + || status === loc.MigrationState.Retriable + || 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 { const status = getMigrationStatus(migration); - return status === loc.MigrationStatus.Canceled - || status === loc.MigrationStatus.Failed - || status === loc.MigrationStatus.Retriable - || status === loc.MigrationStatus.Succeeded; + return status === loc.MigrationState.Canceled + || status === loc.MigrationState.Failed + || status === loc.MigrationState.Retriable + || status === loc.MigrationState.Succeeded; } export function canRetryMigration(migration: DatabaseMigration | undefined): boolean { const status = getMigrationStatus(migration); - return status === loc.MigrationStatus.Canceled - || status === loc.MigrationStatus.Retriable - || status === loc.MigrationStatus.Failed - || status === loc.MigrationStatus.Succeeded; + return status === loc.MigrationState.Canceled + || status === loc.MigrationState.Retriable + || status === loc.MigrationState.Failed + || status === loc.MigrationState.Succeeded; } export function canCutoverMigration(migration: DatabaseMigration | undefined): boolean { const status = getMigrationStatus(migration); return hasMigrationOperationId(migration) - && status === loc.MigrationStatus.InProgress && isOnlineMigration(migration) + && (status === loc.MigrationState.ReadyForCutover || status === loc.MigrationState.InProgress) // TODO: InProgress condition can be eventually deprecated && isFullBackupRestored(migration); } export function isActiveMigration(migration: DatabaseMigration | undefined): boolean { const status = getMigrationStatus(migration); - return status === loc.MigrationStatus.Completing - || status === loc.MigrationStatus.Retriable - || status === loc.MigrationStatus.Creating - || status === loc.MigrationStatus.InProgress; + return status === loc.MigrationState.Completing + || status === loc.MigrationState.Retriable + || status === loc.MigrationState.Creating + || 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 { diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index d9e01dea80..08d349f734 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -10,15 +10,20 @@ import { MigrationSourceAuthenticationType } from '../models/stateMachine'; import { formatNumber, ParallelCopyTypeCodes, PipelineStatusCodes } from './helper'; const localize = nls.loadMessageBundle(); -export enum MigrationStatus { - Failed = 'Failed', - Succeeded = 'Succeeded', - InProgress = 'InProgress', +// mirrors MigrationState as defined in RP +export enum MigrationState { Canceled = 'Canceled', + Canceling = 'Canceling', Completing = 'Completing', Creating = 'Creating', - Canceling = 'Canceling', + Failed = 'Failed', + InProgress = 'InProgress', + ReadyForCutover = 'ReadyForCutover', + Restoring = 'Restoring', Retriable = 'Retriable', + Succeeded = 'Succeeded', + UploadingFullBackup = 'UploadingFullBackup', + UploadingLogBackup = 'UploadingLogBackup', } export enum ProvisioningState { @@ -824,14 +829,18 @@ export interface LookupTable { } export const StatusLookup: LookupTable = { - [MigrationStatus.InProgress]: localize('sql.migration.status.inprogress', 'In progress'), - [MigrationStatus.Succeeded]: localize('sql.migration.status.succeeded', 'Succeeded'), - [MigrationStatus.Creating]: localize('sql.migration.status.creating', 'Creating'), - [MigrationStatus.Completing]: localize('sql.migration.status.completing', 'Completing'), - [MigrationStatus.Retriable]: localize('sql.migration.status.retriable', 'Retriable'), - [MigrationStatus.Canceling]: localize('sql.migration.status.canceling', 'Canceling'), - [MigrationStatus.Canceled]: localize('sql.migration.status.canceled', 'Canceled'), - [MigrationStatus.Failed]: localize('sql.migration.status.failed', 'Failed'), + [MigrationState.Canceled]: localize('sql.migration.status.canceled', 'Canceled'), + [MigrationState.Canceling]: localize('sql.migration.status.canceling', 'Canceling'), + [MigrationState.Completing]: localize('sql.migration.status.completing', 'Completing'), + [MigrationState.Creating]: localize('sql.migration.status.creating', 'Creating'), + [MigrationState.Failed]: localize('sql.migration.status.failed', 'Failed'), + [MigrationState.InProgress]: localize('sql.migration.status.inprogress', 'In progress'), + [MigrationState.ReadyForCutover]: localize('sql.migration.status.readyforcutover', 'Ready for cutover'), + [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 }; @@ -858,9 +867,13 @@ export const ParallelCopyType: LookupTable = { }; export function STATUS_WARNING_COUNT(status: string, count: number): string | undefined { - if (status === MigrationStatus.InProgress || - status === MigrationStatus.Creating || - status === MigrationStatus.Completing) { + if (status === MigrationState.InProgress || + status === MigrationState.ReadyForCutover || + status === MigrationState.UploadingFullBackup || + status === MigrationState.UploadingLogBackup || + status === MigrationState.Restoring || + status === MigrationState.Creating || + status === MigrationState.Completing) { switch (count) { case 0: return undefined; diff --git a/extensions/sql-migration/src/dashboard/migrationsListTab.ts b/extensions/sql-migration/src/dashboard/migrationsListTab.ts index 909d1ce134..0ac32b3f24 100644 --- a/extensions/sql-migration/src/dashboard/migrationsListTab.ts +++ b/extensions/sql-migration/src/dashboard/migrationsListTab.ts @@ -9,12 +9,11 @@ import { IconPathHelper } from '../constants/iconPathHelper'; import { getCurrentMigrations, getSelectedServiceStatus } from '../models/migrationLocalStorage'; import * as loc from '../constants/strings'; 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 { logError, TelemetryViews } from '../telemtery'; import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog'; import { AdsMigrationStatus, EmptySettingValue, ServiceContextChangeEvent, TabBase } from './tabBase'; -import { MigrationMode } from '../models/stateMachine'; import { DashboardStatusBar } from './DashboardStatusBar'; export const MigrationsListTabId = 'MigrationsListTab'; @@ -582,8 +581,7 @@ export class MigrationsListTab extends TabBase { private _getMenuCommands(migration: DatabaseMigration): string[] { const menuCommands: string[] = []; - if (getMigrationModeEnum(migration) === MigrationMode.ONLINE && - canCutoverMigration(migration)) { + if (canCutoverMigration(migration)) { menuCommands.push(MenuCommands.Cutover); } diff --git a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts index 125435e449..dc500cf2f4 100644 --- a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts +++ b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts @@ -9,7 +9,7 @@ import * as mssql from 'mssql'; import { promises as fs } from 'fs'; import { DatabaseMigration, getMigrationDetails } from '../api/azure'; 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 { MigrationNotebookInfo, NotebookPathHelper } from '../constants/notebookPathHelper'; import * as loc from '../constants/strings'; @@ -158,7 +158,7 @@ export class DashboardWidget { try { await this.clearError(args.connectionId); const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId); - if (canRetryMigration(migration)) { + if (canCutoverMigration(migration)) { const cutoverDialogModel = new MigrationCutoverDialogModel( await MigrationLocalStorage.getMigrationServiceContext(), migration!);