Add SQL DB offline migration wizard experience (#20403)

* sql db wizard with target selection

* add database table selection

* add sqldb to service and IR page

* Code complete

* navigation bug fixes

* fix target db selection

* improve sqldb error and status reporting

* fix error count bug

* remove table status inference

* address review feedback

* update resource strings and content

* fix migraton status string, use localized value

* fix ux navigation issues

* fix back/fwd w/o changes from changing data
This commit is contained in:
brian-harris
2022-08-19 18:12:34 -07:00
committed by GitHub
parent c0b09dcedd
commit 7a736b76fa
42 changed files with 5716 additions and 4209 deletions

View File

@@ -8,12 +8,12 @@ import * as azdata from 'azdata';
import * as azurecore from 'azurecore';
import * as constants from '../constants/strings';
import { getSessionIdHeader } from './utils';
import { ProvisioningState } from '../models/migrationLocalStorage';
import { URL } from 'url';
const ARM_MGMT_API_VERSION = '2021-04-01';
const SQL_VM_API_VERSION = '2021-11-01-preview';
const SQL_MI_API_VERSION = '2021-11-01-preview';
const SQL_SQLDB_API_VERSION = '2021-11-01-preview';
const DMSV2_API_VERSION = '2022-03-30-preview';
async function getAzureCoreAPI(): Promise<azurecore.IExtension> {
@@ -93,6 +93,100 @@ export async function getAvailableSqlServers(account: azdata.Account, subscripti
return result.resources;
}
export interface SKU {
name: string,
tier: 'GeneralPurpose' | 'BusinessCritical',
family: string,
capacity: number,
}
export interface AzureSqlDatabase {
id: string,
name: string,
location: string,
tags: any,
type: string,
sku: SKU,
kind: string,
properties: {
collation: string,
maxSizeBytes: number,
status: string,
databaseId: string,
creationDate: string,
currentServiceObjectiveName: string,
requestedServiceObjectiveName: string,
defaultSecondaryLocation: string,
catalogCollation: string,
zoneRedundant: boolean,
earliestRestoreDate: string,
readScale: string,
currentSku: SKU,
currentBackupStorageRedundancy: string,
requestedBackupStorageRedundancy: string,
maintenanceConfigurationId: string,
isLedgerOn: boolean
isInfraEncryptionEnabled: boolean,
licenseType: string,
maxLogSizeBytes: number,
},
}
export interface ServerAdministrators {
administratorType: string,
azureADOnlyAuthentication: boolean,
login: string,
principalType: string,
sid: string,
tenantId: string,
}
export interface PrivateEndpointProperty {
id?: string;
}
export interface PrivateLinkServiceConnectionStateProperty {
status: string;
description: string;
readonly actionsRequired?: string;
}
export interface PrivateEndpointConnectionProperties {
groupIds: string[];
privateEndpoint?: PrivateEndpointProperty;
privateLinkServiceConnectionState?: PrivateLinkServiceConnectionStateProperty;
readonly provisioningState?: string;
}
export interface ServerPrivateEndpointConnection {
readonly id?: string;
readonly properties?: PrivateEndpointConnectionProperties;
}
export interface AzureSqlDatabaseServer {
id: string,
name: string,
kind: string,
location: string,
tags?: { [propertyName: string]: string; };
type: string,
// sku: SKU,
// subscriptionId: string,
// tenantId: string,
// fullName: string,
properties: {
administratorLogin: string,
administrators: ServerAdministrators,
fullyQualifiedDomainName: string,
minimalTlsVersion: string,
privateEndpointConnections: ServerPrivateEndpointConnection[],
publicNetworkAccess: string,
restrictOutboundNetworkAccess: string,
state: string,
version: string,
},
}
export type SqlVMServer = {
properties: {
virtualMachineResourceId: string,
@@ -108,6 +202,31 @@ export type SqlVMServer = {
tenantId: string,
subscriptionId: string
};
export async function getAvailableSqlDatabaseServers(account: azdata.Account, subscription: Subscription): Promise<AzureSqlDatabaseServer[]> {
const api = await getAzureCoreAPI();
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.Sql/servers?api-version=${SQL_SQLDB_API_VERSION}`);
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
sortResourceArrayByName(response.response.data.value);
return response.response.data.value;
}
export async function getAvailableSqlDatabases(account: azdata.Account, subscription: Subscription, resourceGroupName: string, serverName: string): Promise<AzureSqlDatabase[]> {
const api = await getAzureCoreAPI();
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/servers/${serverName}/databases?api-version=${SQL_SQLDB_API_VERSION}`);
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
sortResourceArrayByName(response.response.data.value);
return response.response.data.value;
}
export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise<SqlVMServer[]> {
const api = await getAzureCoreAPI();
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=${SQL_VM_API_VERSION}`);
@@ -203,10 +322,10 @@ export async function getSqlMigrationServices(account: azdata.Account, subscript
export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise<SqlMigrationService> {
const api = await getAzureCoreAPI();
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=${DMSV2_API_VERSION}`);
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
const requestBody = {
'location': regionName
};
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host, getSessionIdHeader(sessionId));
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
@@ -219,9 +338,9 @@ export async function createSqlMigrationService(account: azdata.Account, subscri
for (i = 0; i < maxRetry; i++) {
const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncPath, azurecore.HttpRequestMethod.GET, undefined, true, host);
const creationStatus = asyncResponse.response.data.status;
if (creationStatus === ProvisioningState.Succeeded) {
if (creationStatus === constants.ProvisioningState.Succeeded) {
break;
} else if (creationStatus === ProvisioningState.Failed) {
} else if (creationStatus === constants.ProvisioningState.Failed) {
throw new Error(asyncResponse.errors.toString());
}
await new Promise(resolve => setTimeout(resolve, 5000)); //adding 5 sec delay before getting creation status
@@ -287,7 +406,15 @@ export async function getSqlMigrationServiceMonitoringData(account: azdata.Accou
return response.response.data;
}
export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, targetServer: SqlManagedInstance | SqlVMServer, targetDatabaseName: string, requestBody: StartDatabaseMigrationRequest, sessionId: string): Promise<StartDatabaseMigrationResponse> {
export async function startDatabaseMigration(
account: azdata.Account,
subscription: Subscription,
regionName: string,
targetServer: SqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer,
targetDatabaseName: string,
requestBody: StartDatabaseMigrationRequest,
sessionId: string): Promise<StartDatabaseMigrationResponse> {
const api = await getAzureCoreAPI();
const path = encodeURI(`${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}`);
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
@@ -438,10 +565,10 @@ export interface SqlMigrationServiceProperties {
}
export interface SqlMigrationService {
properties: SqlMigrationServiceProperties;
location: string;
id: string;
name: string;
location: string;
properties: SqlMigrationServiceProperties;
error: {
code: string,
message: string
@@ -485,14 +612,29 @@ export interface StartDatabaseMigrationRequest {
},
sourceLocation?: SourceLocation
},
sourceSqlConnection: {
authentication: string,
targetSqlConnection?: {
dataSource: string,
username: string,
password: string
authentication: string,
userName: string,
password: string,
encryptConnection?: boolean,
trustServerCertificate?: boolean,
},
sourceSqlConnection: {
dataSource: string,
authentication: string,
userName: string,
password: string,
encryptConnection?: boolean,
trustServerCertificate?: boolean,
},
sqlDataCopyThresholds?: {
cidxrowthreshold: number,
cidxkbsthreshold: number,
},
tableList?: string[],
scope: string,
offlineConfiguration: OfflineConfiguration,
offlineConfiguration?: OfflineConfiguration,
}
}
@@ -503,10 +645,10 @@ export interface StartDatabaseMigrationResponse {
}
export interface DatabaseMigration {
properties: DatabaseMigrationProperties;
id: string;
name: string;
type: string;
properties: DatabaseMigrationProperties;
}
export interface DatabaseMigrationProperties {
@@ -525,6 +667,7 @@ export interface DatabaseMigrationProperties {
backupConfiguration: BackupConfiguration;
offlineConfiguration: OfflineConfiguration;
migrationFailureError: ErrorInfo;
tableList: string[];
}
export interface MigrationStatusDetails {
@@ -543,6 +686,7 @@ export interface MigrationStatusDetails {
pendingLogBackupsCount: number;
invalidFiles: string[];
listOfCopyProgressDetails: CopyProgressDetail[];
sqlDataCopyErrors: string[];
}
export interface CopyProgressDetail {