diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts index 070c2e486b..bbf775bc86 100644 --- a/extensions/sql-migration/src/api/azure.ts +++ b/extensions/sql-migration/src/api/azure.ts @@ -263,27 +263,27 @@ export async function startDatabaseMigration(account: azdata.Account, subscripti }; } -export async function getDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, migrationId: string, sessionId: string): Promise { +export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration, sessionId: string, asyncUrl: string): Promise { const api = await getAzureCoreAPI(); - const path = `${migrationId}?api-version=2020-09-01-preview`; + const migrationOperationId = getMigrationOperationId(migration, asyncUrl); + const path = `${migration.id}?migrationOperationId=${migrationOperationId}&$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { - if (response.response.status === 404 && response.response.data.error.code === 'ResourceDoesNotExist') { - throw new Error(response.response.data.error.code); - } throw new Error(response.errors.toString()); } return response.response.data; } -export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration, sessionId: string): Promise { - const api = await getAzureCoreAPI(); - const path = `${migration.id}?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); - if (response.errors.length > 0) { - throw new Error(response.errors.toString()); +function getMigrationOperationId(migration: DatabaseMigration, asyncUrl: string): string { + // migrationOperationId may be undefined when provisioning has failed + // fall back to the operationId from the asyncUrl in the create migration response + if (migration.properties.migrationOperationId) { + return migration.properties.migrationOperationId; } - return response.response.data; + + return asyncUrl + ? vscode.Uri.parse(asyncUrl)?.path?.split('/').reverse()[0] + : ''; } export async function getMigrationAsyncOperationDetails(account: azdata.Account, subscription: Subscription, url: string, sessionId: string): Promise { @@ -436,6 +436,7 @@ export interface DatabaseMigration { export interface DatabaseMigrationProperties { scope: string; provisioningState: 'Succeeded' | 'Failed' | 'Creating'; + provisioningError: string; migrationStatus: 'InProgress' | 'Failed' | 'Succeeded' | 'Creating' | 'Completing' | 'Canceling'; migrationStatusDetails?: MigrationStatusDetails; startedOn: string; diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts index 697d8c2e95..d4b97b7492 100644 --- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts +++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts @@ -538,11 +538,16 @@ export class MigrationCutoverDialog { await this._model.fetchStatus(); const errors = []; errors.push(this._model.migrationOpStatus.error?.message); + errors.push(this._model._migration.asyncOperationResult?.error?.message); + errors.push(this._model.migrationStatus.properties.provisioningError); errors.push(this._model.migrationStatus.properties.migrationFailureError?.message); errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []); errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.restoreBlockingReason); this._dialogObject.message = { - text: errors.filter(e => e !== undefined).join(EOL), + // remove undefined and duplicate error entries + text: errors + .filter((e, i, arr) => e !== undefined && i === arr.indexOf(e)) + .join(EOL), level: this._model.migrationStatus.properties.migrationStatus === MigrationStatus.InProgress || this._model.migrationStatus.properties.migrationStatus === MigrationStatus.Completing ? azdata.window.MessageLevel.Warning @@ -612,9 +617,12 @@ export class MigrationCutoverDialog { const isBlobMigration = this._model.isBlobMigration(); // Displaying storage accounts and blob container for azure blob backups. if (isBlobMigration) { - backupLocation = `${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.storageAccountResourceId.split('/').pop()} - ${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.blobContainerName}`; + const storageAccountResourceId = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.azureBlob?.storageAccountResourceId; + const blobContainerName = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.azureBlob?.blobContainerName; + backupLocation = `${storageAccountResourceId?.split('/').pop()} - ${blobContainerName}`; } else { - backupLocation = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.fileShare?.path! ?? '-'; + const fileShare = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.fileShare; + backupLocation = fileShare?.path! ?? '-'; } this._backupLocationInfoField.text.value = backupLocation ?? '-'; diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts index e2bda92a59..8eb74e98f9 100644 --- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts +++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts @@ -30,6 +30,7 @@ export class MigrationCutoverDialogModel { this._migration.subscription, this._migration.migrationContext, this._migration.sessionId!, + this._migration.asyncUrl )); sendSqlMigrationActionEvent( @@ -98,7 +99,7 @@ export class MigrationCutoverDialogModel { } public isBlobMigration(): boolean { - return this._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob !== undefined; + return this._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.azureBlob !== undefined; } public confirmCutoverStepsString(): string { diff --git a/extensions/sql-migration/src/models/migrationLocalStorage.ts b/extensions/sql-migration/src/models/migrationLocalStorage.ts index 2d5a12151d..32ba8c26fb 100644 --- a/extensions/sql-migration/src/models/migrationLocalStorage.ts +++ b/extensions/sql-migration/src/models/migrationLocalStorage.ts @@ -32,14 +32,6 @@ export class MigrationLocalStorage { await this.refreshMigrationAzureAccount(migration); - migration.migrationContext = await getMigrationStatus( - migration.azureAccount, - migration.subscription, - migration.migrationContext, - migration.sessionId! - ); - migration.migrationContext.properties.sourceDatabaseName = sourceDatabase; - migration.migrationContext.properties.backupConfiguration = backupConfiguration; if (migration.asyncUrl) { migration.asyncOperationResult = await getMigrationAsyncOperationDetails( migration.azureAccount, @@ -47,6 +39,17 @@ export class MigrationLocalStorage { migration.asyncUrl, migration.sessionId! ); + + migration.migrationContext = await getMigrationStatus( + migration.azureAccount, + migration.subscription, + migration.migrationContext, + migration.sessionId!, + migration.asyncUrl + ); + + migration.migrationContext.properties.sourceDatabaseName = sourceDatabase; + migration.migrationContext.properties.backupConfiguration = backupConfiguration; } } catch (e) {