Files
azuredatastudio/extensions/sql-migration/src/models/stateMachine.ts
AkshayMata 5e7446af6c Refactor functionality into LoginMigrationsModel (#21933)
This PR refactors to encapsulate all login migration functionality into LoginMigrationModel
2023-02-14 06:05:53 -08:00

1386 lines
51 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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 azurecore from 'azurecore';
import * as vscode from 'vscode';
import * as contracts from '../service/contracts';
import * as features from '../service/features';
import { SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, SqlVMServer, getLocationDisplayName, getSqlManagedInstanceDatabases, AzureSqlDatabaseServer, VirtualMachineInstanceView } from '../api/azure';
import * as constants from '../constants/strings';
import * as nls from 'vscode-nls';
import { v4 as uuidv4 } from 'uuid';
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemetry';
import { hashString, deepClone } from '../api/utils';
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
import { excludeDatabases, getEncryptConnectionValue, getSourceConnectionId, getSourceConnectionProfile, getSourceConnectionServerInfo, getSourceConnectionString, getSourceConnectionUri, getTrustServerCertificateValue, SourceDatabaseInfo, TargetDatabaseInfo } from '../api/sqlUtils';
import { LoginMigrationModel } from './loginMigrationModel';
import { TdeMigrationDbResult, TdeMigrationModel } from './tdeModels';
import { NetworkInterfaceModel } from '../api/dataModels/azure/networkInterfaceModel';
const localize = nls.loadMessageBundle();
export enum ValidateIrState {
Pending = 'Pending',
Running = 'Running',
Succeeded = 'Succeeded',
Failed = 'Failed',
Canceled = 'Canceled',
}
export interface ValidationResult {
errors: string[];
state: ValidateIrState;
}
export enum State {
INIT,
COLLECTING_SOURCE_INFO,
COLLECTION_SOURCE_INFO_ERROR,
TARGET_SELECTION,
TARGET_SELECTION_ERROR,
AZURE_SERVER_SELECTION,
AZURE_SERVER_SELECTION_ERROR,
AZURE_DB_BACKUP,
AZURE_DB_BACKUP_ERROR,
MIGRATION_AGENT_CREATION,
MIGRATION_AGENT_SELECTION,
MIGRATION_AGENT_ERROR,
MIGRATION_START,
NO_AZURE_SERVER,
EXIT,
}
export enum ServiceTier {
GeneralPurpose = 'GeneralPurpose',
BusinessCritical = 'BusinessCritical',
}
export enum MigrationTargetType {
SQLVM = 'AzureSqlVirtualMachine',
SQLMI = 'AzureSqlManagedInstance',
SQLDB = 'AzureSqlDatabase'
}
export enum MigrationSourceAuthenticationType {
Integrated = 'WindowsAuthentication',
Sql = 'SqlAuthentication'
}
export enum AssessmentRuleId {
TdeEnabled = 'TdeEnabled'
}
export enum MigrationMode {
ONLINE,
OFFLINE
}
export enum NetworkContainerType {
FILE_SHARE,
BLOB_CONTAINER,
NETWORK_SHARE
}
export enum FileStorageType {
FileShare = 'FileShare',
AzureBlob = 'AzureBlob',
None = 'None',
}
export enum Page {
DatabaseSelector,
SKURecommendation,
TargetSelection,
IntegrationRuntime,
DatabaseBackup,
Summary
}
export enum WizardEntryPoint {
Default = 'Default',
SaveAndClose = 'SaveAndClose',
RetryMigration = 'RetryMigration',
}
export enum PerformanceDataSourceOptions {
CollectData = 'CollectData',
OpenExisting = 'OpenExisting',
}
export interface DatabaseBackupModel {
migrationMode: MigrationMode;
networkContainerType: NetworkContainerType;
networkShares: NetworkShare[];
subscription: azurecore.azureResource.AzureResourceSubscription;
blobs: Blob[];
}
export interface NetworkShare {
networkShareLocation: string;
windowsUser: string;
password: string;
resourceGroup: azurecore.azureResource.AzureResourceResourceGroup;
storageAccount: StorageAccount;
storageKey: string;
}
export interface Blob {
resourceGroup: azurecore.azureResource.AzureResourceResourceGroup;
storageAccount: StorageAccount;
blobContainer: azurecore.azureResource.BlobContainer;
storageKey: string;
lastBackupFile?: string; // _todo: does it make sense to store the last backup file here?
}
export interface Model {
readonly currentState: State;
gatheringInformationError: string | undefined;
_azureAccount: azdata.Account | undefined;
_databaseBackup: DatabaseBackupModel | undefined;
}
export interface StateChangeEvent {
oldState: State;
newState: State;
}
export interface SavedInfo {
closedPage: number;
databaseAssessment: string[];
databaseList: string[];
databaseInfoList: SourceDatabaseInfo[];
migrationTargetType: MigrationTargetType | null;
azureAccount: azdata.Account | null;
azureTenant: azurecore.Tenant | null;
subscription: azurecore.azureResource.AzureResourceSubscription | null;
location: azurecore.azureResource.AzureLocation | null;
resourceGroup: azurecore.azureResource.AzureResourceResourceGroup | null;
targetServerInstance: azurecore.azureResource.AzureSqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer | null;
migrationMode: MigrationMode | null;
networkContainerType: NetworkContainerType | null;
networkShares: NetworkShare[];
blobs: Blob[];
targetDatabaseNames: string[];
sqlMigrationService: SqlMigrationService | undefined;
serverAssessment: ServerAssessment | null;
skuRecommendation: SkuRecommendationSavedInfo | null;
}
export interface SkuRecommendationSavedInfo {
skuRecommendationPerformanceDataSource: PerformanceDataSourceOptions;
skuRecommendationPerformanceLocation: string;
perfDataCollectionStartDate?: Date;
perfDataCollectionStopDate?: Date;
skuTargetPercentile: number;
skuScalingFactor: number;
skuEnablePreview: boolean;
}
export class MigrationStateModel implements Model, vscode.Disposable {
public _azureAccounts!: azdata.Account[];
public _azureAccount!: azdata.Account;
public _accountTenants!: azurecore.Tenant[];
public _azureTenant!: azurecore.Tenant;
public _connecionProfile!: azdata.connection.ConnectionProfile;
public _authenticationType!: MigrationSourceAuthenticationType;
public _sqlServerUsername!: string;
public _sqlServerPassword!: string;
public _subscriptions!: azurecore.azureResource.AzureResourceSubscription[];
public _targetSubscription!: azurecore.azureResource.AzureResourceSubscription;
public _locations!: azurecore.azureResource.AzureLocation[];
public _location!: azurecore.azureResource.AzureLocation;
public _resourceGroups!: azurecore.azureResource.AzureResourceResourceGroup[];
public _resourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
public _targetManagedInstances!: SqlManagedInstance[];
public _targetSqlVirtualMachines!: SqlVMServer[];
public _targetSqlDatabaseServers!: AzureSqlDatabaseServer[];
public _targetServerInstance!: SqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer;
public _vmInstanceView!: VirtualMachineInstanceView;
public _databaseBackup!: DatabaseBackupModel;
public _storageAccounts!: StorageAccount[];
public _fileShares!: azurecore.azureResource.FileShare[];
public _blobContainers!: azurecore.azureResource.BlobContainer[];
public _lastFileNames!: azurecore.azureResource.Blob[];
public _sourceDatabaseNames!: string[];
public _targetDatabaseNames!: string[];
public _targetServerName!: string;
public _targetUserName!: string;
public _targetPassword!: string;
public _sourceTargetMapping: Map<string, TargetDatabaseInfo | undefined> = new Map();
public _sqlMigrationServiceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
public _sqlMigrationService!: SqlMigrationService | undefined;
public _sqlMigrationServices!: SqlMigrationService[];
public _nodeNames!: string[];
public _databasesForAssessment!: string[];
public _assessmentResults!: ServerAssessment;
public _assessedDatabaseList!: string[];
public _runAssessments: boolean = true;
private _assessmentApiResponse!: contracts.AssessmentResult;
public _assessmentReportFilePath: string;
public mementoString: string;
public _databasesForMigration: string[] = [];
public _databaseInfosForMigration: SourceDatabaseInfo[] = [];
public _didUpdateDatabasesForMigration: boolean = false;
public _didDatabaseMappingChange: boolean = false;
public _vmDbs: string[] = [];
public _miDbs: string[] = [];
public _sqldbDbs: string[] = [];
public _targetType!: MigrationTargetType;
public _validateIrSqlDb: ValidationResult[] = [];
public _validateIrSqlMi: ValidationResult[] = [];
public _validateIrSqlVm: ValidationResult[] = [];
public _skuRecommendationResults!: SkuRecommendation;
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
private _skuRecommendationApiResponse!: contracts.SkuRecommendationResult;
public _skuRecommendationReportFilePaths: string[];
public _skuRecommendationPerformanceLocation!: string;
public _perfDataCollectionStartDate!: Date | undefined;
public _perfDataCollectionStopDate!: Date | undefined;
public _perfDataCollectionLastRefreshedDate!: Date;
public _perfDataCollectionMessages!: string[];
public _perfDataCollectionErrors!: string[];
public _perfDataCollectionIsCollecting!: boolean;
public _aadDomainName!: string;
public _loginMigrationModel: LoginMigrationModel;
public readonly _refreshGetSkuRecommendationIntervalInMinutes = 10;
public readonly _performanceDataQueryIntervalInSeconds = 30;
public readonly _staticDataQueryIntervalInSeconds = 60;
public readonly _numberOfPerformanceDataQueryIterations = 19;
public readonly _defaultDataPointStartTime = '1900-01-01 00:00:00';
public readonly _defaultDataPointEndTime = '2200-01-01 00:00:00';
public readonly _recommendationTargetPlatforms = [MigrationTargetType.SQLDB, MigrationTargetType.SQLMI, MigrationTargetType.SQLVM];
public refreshPerfDataCollectionFrequency = this._performanceDataQueryIntervalInSeconds * 1000;
public refreshGetSkuRecommendationFrequency = constants.TIME_IN_MINUTES(this._refreshGetSkuRecommendationIntervalInMinutes);
public _skuScalingFactor!: number;
public _skuTargetPercentile!: number;
public _skuEnablePreview!: boolean;
public _skuEnableElastic!: boolean;
public refreshDatabaseBackupPage!: boolean;
public retryMigration!: boolean;
public resumeAssessment!: boolean;
public savedInfo!: SavedInfo;
public closedPage!: number;
public _sessionId: string = uuidv4();
public serverName!: string;
public tdeMigrationConfig: TdeMigrationModel = new TdeMigrationModel();
private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>();
private _currentState: State;
private _gatheringInformationError: string | undefined;
private _skuRecommendationRecommendedDatabaseList!: string[];
private _startPerfDataCollectionApiResponse!: contracts.StartPerfDataCollectionResult;
private _stopPerfDataCollectionApiResponse!: contracts.StopPerfDataCollectionResult;
private _refreshPerfDataCollectionApiResponse!: contracts.RefreshPerfDataCollectionResult;
private _autoRefreshPerfDataCollectionHandle!: NodeJS.Timeout;
private _autoRefreshGetSkuRecommendationHandle!: NodeJS.Timeout;
constructor(
public extensionContext: vscode.ExtensionContext,
public readonly migrationService: features.SqlMigrationService,
) {
this._currentState = State.INIT;
this._databaseBackup = {} as DatabaseBackupModel;
this._databaseBackup.networkShares = [];
this._databaseBackup.blobs = [];
this._databaseBackup.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
this._targetDatabaseNames = [];
this._assessmentReportFilePath = '';
this._skuRecommendationReportFilePaths = [];
this.mementoString = 'sqlMigration.assessmentResults';
this._targetManagedInstances = [];
this._skuScalingFactor = 100;
this._skuTargetPercentile = 95;
this._skuEnablePreview = false;
this._skuEnableElastic = false;
this._loginMigrationModel = new LoginMigrationModel();
}
public get validationTargetResults(): ValidationResult[] {
switch (this._targetType) {
case MigrationTargetType.SQLDB:
return this._validateIrSqlDb;
case MigrationTargetType.SQLMI:
return this._validateIrSqlMi;
case MigrationTargetType.SQLVM:
return this._validateIrSqlVm;
default:
return [];
}
}
public resetIrValidationResults(): void {
if (this.isIrMigration) {
this._validateIrSqlDb = [];
this._validateIrSqlMi = [];
this._validateIrSqlVm = [];
}
}
public get isSqlVmTarget(): boolean {
return this._targetType === MigrationTargetType.SQLVM;
}
public get isSqlMiTarget(): boolean {
return this._targetType === MigrationTargetType.SQLMI;
}
public get isSqlDbTarget(): boolean {
return this._targetType === MigrationTargetType.SQLDB;
}
public get isIrTargetValidated(): boolean {
const results = this.validationTargetResults ?? [];
return results.length > 1
&& results.every(r =>
r.errors.length === 0 &&
r.state === ValidateIrState.Succeeded)
}
public get isBackupContainerNetworkShare(): boolean {
return this._databaseBackup?.networkContainerType === NetworkContainerType.NETWORK_SHARE;
}
public get isBackupContainerBlobContainer(): boolean {
return this._databaseBackup?.networkContainerType === NetworkContainerType.BLOB_CONTAINER;
}
public get isIrMigration(): boolean {
return this.isSqlDbTarget
|| this.isBackupContainerNetworkShare;
}
public get currentState(): State {
return this._currentState;
}
public set currentState(newState: State) {
const oldState = this.currentState;
this._currentState = newState;
this._stateChangeEventEmitter.fire({ oldState, newState: this.currentState });
}
public async getDatabases(): Promise<string[]> {
const temp = await azdata.connection.listDatabases(await getSourceConnectionId());
const finalResult = temp.filter((name) => !excludeDatabases.includes(name));
return finalResult;
}
public hasRecommendedDatabaseListChanged(): boolean {
const oldDbList = this._skuRecommendationRecommendedDatabaseList;
const newDbList = this._databasesForAssessment;
if (!oldDbList || !newDbList) {
return false;
}
return !((oldDbList.length === newDbList.length) && oldDbList.every(function (element, index) {
return element === newDbList[index];
}));
}
public async getDatabaseAssessments(targetType: MigrationTargetType[]): Promise<ServerAssessment> {
const connectionString = await getSourceConnectionString();
try {
const xEventsFilesFolderPath = ''; // to-do: collect by prompting the user in the UI - for now, blank = disabled
const response = (await this.migrationService.getAssessments(connectionString, this._databasesForAssessment, xEventsFilesFolderPath))!;
this._assessmentApiResponse = response;
this._assessedDatabaseList = this._databasesForAssessment.slice();
if (response?.assessmentResult) {
response.assessmentResult.items = response.assessmentResult.items?.filter(
issue => targetType.includes(
<MigrationTargetType>issue.appliesToMigrationTargetPlatform));
response.assessmentResult.databases?.forEach(
database => database.items = database.items?.filter(
issue => targetType.includes(
<MigrationTargetType>issue.appliesToMigrationTargetPlatform)));
this._assessmentResults = {
issues: this._assessmentApiResponse?.assessmentResult?.items || [],
databaseAssessments: this._assessmentApiResponse?.assessmentResult?.databases?.map(d => {
return {
name: d.name,
issues: d.items,
errors: d.errors,
};
}) ?? [],
errors: this._assessmentApiResponse?.errors ?? []
};
this._assessmentReportFilePath = response.assessmentReportPath;
} else {
this._assessmentResults = {
issues: [],
databaseAssessments: this._databasesForAssessment?.map(database => {
return {
name: database,
issues: [],
errors: []
};
}) ?? [],
errors: response?.errors ?? [],
};
}
} catch (error) {
this._assessmentResults = {
issues: [],
databaseAssessments: this._databasesForAssessment?.map(database => {
return {
name: database,
issues: [],
errors: []
};
}) ?? [],
errors: [],
assessmentError: error
};
}
// Generating all the telemetry asynchronously as we don't need to block the user for it.
this.generateAssessmentTelemetry().catch(e => console.error(e));
return this._assessmentResults;
}
public async getSkuRecommendations(): Promise<SkuRecommendation> {
try {
let fullInstanceName: string;
// execute a query against the source to get the correct instance name
const connectionProfile = await getSourceConnectionProfile();
const connectionUri = await getSourceConnectionUri();
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(connectionProfile.providerId, azdata.DataProviderType.QueryProvider);
const queryString = 'SELECT SERVERPROPERTY(\'ServerName\');';
const queryResult = await queryProvider.runQueryAndReturn(connectionUri, queryString);
if (queryResult.rowCount > 0) {
fullInstanceName = queryResult.rows[0][0].displayValue;
} else {
// get the instance name from connection info in case querying for the instance name doesn't work for whatever reason
const serverInfo = await getSourceConnectionServerInfo();
const machineName = (<any>serverInfo)['machineName']; // contains the correct machine name but not necessarily the correct instance name
const instanceName = connectionProfile.serverName; // contains the correct instance name but not necessarily the correct machine name
if (instanceName.includes('\\')) {
fullInstanceName = machineName + '\\' + instanceName.substring(instanceName.indexOf('\\') + 1);
} else {
fullInstanceName = machineName;
}
}
const response = (await this.migrationService.getSkuRecommendations(
this._skuRecommendationPerformanceLocation,
this._performanceDataQueryIntervalInSeconds,
this._recommendationTargetPlatforms.map(p => p.toString()),
fullInstanceName,
this._skuTargetPercentile,
this._skuScalingFactor,
this._defaultDataPointStartTime,
this._defaultDataPointEndTime,
this._skuEnablePreview,
this._databasesForAssessment))!;
this._skuRecommendationApiResponse = response;
// clone list of databases currently being assessed and store them, so that if the user ever changes the list we can refresh new recommendations
this._skuRecommendationRecommendedDatabaseList = this._databasesForAssessment.slice();
if (response) {
this._skuRecommendationResults = {
recommendations: response
};
this._skuRecommendationReportFilePaths = this._skuEnableElastic ? response.elasticSkuRecommendationReportPaths : response.skuRecommendationReportPaths;
}
} catch (error) {
logError(TelemetryViews.SkuRecommendationWizard, 'GetSkuRecommendationFailed', error);
this._skuRecommendationResults = {
recommendations: this._skuRecommendationApiResponse,
recommendationError: error
};
} // Generating all the telemetry asynchronously as we don't need to block the user for it.
this.generateSkuRecommendationTelemetry().catch(e => console.error(e));
return this._skuRecommendationResults;
}
private async generateSkuRecommendationTelemetry(): Promise<void> {
try {
this._skuRecommendationResults?.recommendations?.sqlDbRecommendationResults
.map((e, i) => [e, this._skuRecommendationResults?.recommendations?.elasticSqlDbRecommendationResults[i]])
.forEach(resultPair => {
// Send telemetry for recommended DB SKUs
sendSqlMigrationActionEvent(
TelemetryViews.SkuRecommendationWizard,
TelemetryAction.GetDBSkuRecommendation,
{
'sessionId': this._sessionId,
'recommendedSku': JSON.stringify(resultPair[0]?.targetSku),
'elasticRecommendedSku': JSON.stringify(resultPair[1]?.targetSku),
'recommendationDurationInMs': JSON.stringify(this._skuRecommendationResults?.recommendations?.sqlDbRecommendationDurationInMs),
'elasticRecommendationDurationInMs': JSON.stringify(this._skuRecommendationResults?.recommendations?.elasticSqlDbRecommendationDurationInMs),
},
{}
);
});
this._skuRecommendationResults?.recommendations?.sqlMiRecommendationResults
.map((e, i) => [e, this._skuRecommendationResults?.recommendations?.elasticSqlMiRecommendationResults[i]])
.forEach(resultPair => {
// Send telemetry for recommended MI SKUs
sendSqlMigrationActionEvent(
TelemetryViews.SkuRecommendationWizard,
TelemetryAction.GetMISkuRecommendation,
{
'sessionId': this._sessionId,
'recommendedSku': JSON.stringify(resultPair[0]?.targetSku),
'elasticRecommendedSku': JSON.stringify(resultPair[1]?.targetSku),
'recommendationDurationInMs': JSON.stringify(this._skuRecommendationResults?.recommendations?.sqlMiRecommendationDurationInMs),
'elasticRecommendationDurationInMs': JSON.stringify(this._skuRecommendationResults?.recommendations?.elasticSqlMiRecommendationDurationInMs),
},
{}
);
});
this._skuRecommendationResults?.recommendations?.sqlVmRecommendationResults
.map((e, i) => [e, this._skuRecommendationResults?.recommendations?.elasticSqlVmRecommendationResults[i]])
.forEach(resultPair => {
// Send telemetry for recommended VM SKUs
sendSqlMigrationActionEvent(
TelemetryViews.SkuRecommendationWizard,
TelemetryAction.GetVMSkuRecommendation,
{
'sessionId': this._sessionId,
'recommendedSku': JSON.stringify(resultPair[0]?.targetSku),
'elasticRecommendedSku': JSON.stringify(resultPair[1]?.targetSku),
'recommendationDurationInMs': JSON.stringify(this._skuRecommendationResults?.recommendations?.sqlVmRecommendationDurationInMs),
'elasticRecommendationDurationInMs': JSON.stringify(this._skuRecommendationResults?.recommendations?.elasticSqlVmRecommendationDurationInMs),
},
{}
);
});
// Send Instance requirements used for calculating recommendations
sendSqlMigrationActionEvent(
TelemetryViews.SkuRecommendationWizard,
TelemetryAction.GetInstanceRequirements,
{
'sessionId': this._sessionId,
'performanceDataSource': this._skuRecommendationPerformanceDataSource,
'scalingFactor': this._skuScalingFactor?.toString(),
'targetPercentile': this._skuTargetPercentile?.toString(),
'enablePreviewSkus': this._skuEnablePreview?.toString(),
'databaseLevelRequirements': JSON.stringify(this._skuRecommendationResults?.recommendations?.instanceRequirements?.databaseLevelRequirements?.map(i => {
return {
cpuRequirementInCores: i.cpuRequirementInCores,
dataIOPSRequirement: i.dataIOPSRequirement,
logIOPSRequirement: i.logIOPSRequirement,
ioLatencyRequirementInMs: i.ioLatencyRequirementInMs,
ioThroughputRequirementInMBps: i.ioThroughputRequirementInMBps,
dataStorageRequirementInMB: i.dataStorageRequirementInMB,
logStorageRequirementInMB: i.logStorageRequirementInMB,
databaseName: hashString(i.databaseName),
memoryRequirementInMB: i.memoryRequirementInMB,
cpuRequirementInPercentageOfTotalInstance: i.cpuRequirementInPercentageOfTotalInstance,
numberOfDataPointsAnalyzed: i.numberOfDataPointsAnalyzed,
fileLevelRequirements: i.fileLevelRequirements?.map(file => {
return {
fileType: file.fileType,
sizeInMB: file.sizeInMB,
readLatencyInMs: file.readLatencyInMs,
writeLatencyInMs: file.writeLatencyInMs,
iopsRequirement: file.iopsRequirement,
ioThroughputRequirementInMBps: file.ioThroughputRequirementInMBps,
numberOfDataPointsAnalyzed: file.numberOfDataPointsAnalyzed
};
})
};
}))
},
{
'cpuRequirementInCores': this._skuRecommendationResults?.recommendations?.instanceRequirements?.cpuRequirementInCores!,
'dataStorageRequirementInMB': this._skuRecommendationResults?.recommendations?.instanceRequirements?.dataStorageRequirementInMB!,
'logStorageRequirementInMB': this._skuRecommendationResults?.recommendations?.instanceRequirements?.logStorageRequirementInMB!,
'memoryRequirementInMB': this._skuRecommendationResults?.recommendations?.instanceRequirements?.memoryRequirementInMB!,
'dataIOPSRequirement': this._skuRecommendationResults?.recommendations?.instanceRequirements?.dataIOPSRequirement!,
'logIOPSRequirement': this._skuRecommendationResults?.recommendations?.instanceRequirements?.logIOPSRequirement!,
'ioLatencyRequirementInMs': this._skuRecommendationResults?.recommendations?.instanceRequirements?.ioLatencyRequirementInMs!,
'ioThroughputRequirementInMBps': this._skuRecommendationResults?.recommendations?.instanceRequirements?.ioThroughputRequirementInMBps!,
'tempDBSizeInMB': this._skuRecommendationResults?.recommendations?.instanceRequirements?.tempDBSizeInMB!,
'aggregationTargetPercentile': this._skuRecommendationResults?.recommendations?.instanceRequirements?.aggregationTargetPercentile!,
'numberOfDataPointsAnalyzed': this._skuRecommendationResults?.recommendations?.instanceRequirements?.numberOfDataPointsAnalyzed!,
}
);
} catch (e) {
logError(TelemetryViews.SkuRecommendationWizard, 'GetSkuRecommendationTelemetryFailed', e);
}
}
public async startPerfDataCollection(
dataFolder: string,
perfQueryIntervalInSec: number,
staticQueryIntervalInSec: number,
numberOfIterations: number,
page: SKURecommendationPage): Promise<boolean> {
try {
if (!this.performanceCollectionInProgress()) {
const connectionString = await getSourceConnectionString();
const response = await this.migrationService.startPerfDataCollection(
connectionString,
dataFolder,
perfQueryIntervalInSec,
staticQueryIntervalInSec,
numberOfIterations);
this._startPerfDataCollectionApiResponse = response!;
this._perfDataCollectionStartDate = this._startPerfDataCollectionApiResponse.dateTimeStarted;
this._perfDataCollectionStopDate = undefined;
void vscode.window.showInformationMessage(constants.AZURE_RECOMMENDATION_START_POPUP);
await this.startSkuTimers(page, this.refreshPerfDataCollectionFrequency);
}
}
catch (error) {
console.log(error);
}
// Generate telemetry for start data collection request
this.generateStartDataCollectionTelemetry().catch(e => console.error(e));
return true;
}
private async generateStartDataCollectionTelemetry(): Promise<void> {
try {
sendSqlMigrationActionEvent(
TelemetryViews.DataCollectionWizard,
TelemetryAction.StartDataCollection,
{
'sessionId': this._sessionId,
'timeDataCollectionStarted': this._perfDataCollectionStartDate?.toString() || ''
},
{});
} catch (e) {
logError(TelemetryViews.DataCollectionWizard, 'StartDataCollectionTelemetryFailed', e);
}
}
public async startSkuTimers(page: SKURecommendationPage, refreshIntervalInMs: number): Promise<void> {
const classVariable = this;
if (!this._autoRefreshPerfDataCollectionHandle) {
clearInterval(this._autoRefreshPerfDataCollectionHandle);
if (this.refreshPerfDataCollectionFrequency !== -1) {
this._autoRefreshPerfDataCollectionHandle = setInterval(
async function () {
await classVariable.refreshPerfDataCollection();
if (await classVariable.isWaitingForFirstTimeRefresh()) {
await page.refreshSkuRecommendationComponents(); // update timer
}
},
refreshIntervalInMs);
}
}
if (!this._autoRefreshGetSkuRecommendationHandle) {
// start one-time timer to get SKU recommendation
clearTimeout(this._autoRefreshGetSkuRecommendationHandle);
if (this.refreshGetSkuRecommendationFrequency !== -1) {
this._autoRefreshGetSkuRecommendationHandle = setTimeout(
async function () {
await page.refreshAzureRecommendation();
},
this.refreshGetSkuRecommendationFrequency);
}
}
}
public async stopPerfDataCollection(): Promise<boolean> {
try {
const response = await this.migrationService.stopPerfDataCollection();
void vscode.window.showInformationMessage(constants.AZURE_RECOMMENDATION_STOP_POPUP);
this._stopPerfDataCollectionApiResponse = response!;
this._perfDataCollectionStopDate = this._stopPerfDataCollectionApiResponse.dateTimeStopped;
// stop auto refresh
clearInterval(this._autoRefreshPerfDataCollectionHandle);
clearInterval(this._autoRefreshGetSkuRecommendationHandle);
}
catch (error) {
logError(TelemetryViews.DataCollectionWizard, 'StopDataCollectionFailed', error);
}
// Generate telemetry for stop data collection request
this.generateStopDataCollectionTelemetry()
.catch(e => console.error(e));
return true;
}
private async generateStopDataCollectionTelemetry(): Promise<void> {
try {
sendSqlMigrationActionEvent(
TelemetryViews.DataCollectionWizard,
TelemetryAction.StopDataCollection,
{
'sessionId': this._sessionId,
'timeDataCollectionStopped': this._perfDataCollectionStopDate?.toString() || ''
},
{});
} catch (e) {
logError(TelemetryViews.DataCollectionWizard, 'StopDataCollectionTelemetryFailed', e);
}
}
public async refreshPerfDataCollection(): Promise<boolean> {
try {
const response = await this.migrationService.refreshPerfDataCollection(this._perfDataCollectionLastRefreshedDate ?? new Date());
this._refreshPerfDataCollectionApiResponse = response!;
this._perfDataCollectionLastRefreshedDate = this._refreshPerfDataCollectionApiResponse.refreshTime;
this._perfDataCollectionMessages = this._refreshPerfDataCollectionApiResponse.messages;
this._perfDataCollectionErrors = this._refreshPerfDataCollectionApiResponse.errors;
this._perfDataCollectionIsCollecting = this._refreshPerfDataCollectionApiResponse.isCollecting;
if (this._perfDataCollectionErrors?.length > 0) {
void vscode.window.showInformationMessage(
constants.PERF_DATA_COLLECTION_ERROR(
this._assessmentApiResponse?.assessmentResult?.name,
this._perfDataCollectionErrors));
}
}
catch (error) {
logError(TelemetryViews.DataCollectionWizard, 'RefreshDataCollectionFailed', error);
}
return true;
}
public async isWaitingForFirstTimeRefresh(): Promise<boolean> {
const elapsedTimeInMins = Math.abs(new Date().getTime() - new Date(this._perfDataCollectionStartDate!).getTime()) / constants.TIME_IN_MINUTES(1);
const skuRecAutoRefreshTimeInMins = this.refreshGetSkuRecommendationFrequency / constants.TIME_IN_MINUTES(1);
return elapsedTimeInMins < skuRecAutoRefreshTimeInMins;
}
public performanceCollectionNotStarted(): boolean {
return !this._perfDataCollectionStartDate
&& !this._perfDataCollectionStopDate;
}
public performanceCollectionInProgress(): boolean {
return this._perfDataCollectionStartDate !== undefined
&& this._perfDataCollectionStopDate === undefined;
}
public performanceCollectionStopped(): boolean {
return this._perfDataCollectionStartDate !== undefined
&& this._perfDataCollectionStopDate !== undefined;
}
private async generateAssessmentTelemetry(): Promise<void> {
try {
const serverIssues = this._assessmentResults?.issues.map(i => {
return {
ruleId: i.ruleId,
count: i.impactedObjects.length
};
});
const serverAssessmentErrorsMap: Map<number, number> = new Map();
this._assessmentApiResponse?.assessmentResult?.errors?.forEach(e => {
serverAssessmentErrorsMap.set(
e.errorId,
serverAssessmentErrorsMap.get(e.errorId) ?? 0 + 1);
});
const serverErrors: { errorId: number, count: number }[] = [];
serverAssessmentErrorsMap.forEach(
(v, k) => serverErrors.push(
{ errorId: k, count: v }));
const startTime = new Date(this._assessmentApiResponse?.startTime);
const endTime = new Date(this._assessmentApiResponse?.endedTime);
sendSqlMigrationActionEvent(
TelemetryViews.MigrationWizardTargetSelectionPage,
TelemetryAction.ServerAssessment,
{
'sessionId': this._sessionId,
'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name),
'startTime': startTime.toString(),
'endTime': endTime.toString(),
'serverVersion': this._assessmentApiResponse?.assessmentResult?.serverVersion,
'serverEdition': this._assessmentApiResponse?.assessmentResult?.serverEdition,
'platform': this._assessmentApiResponse?.assessmentResult?.serverHostPlatform,
'engineEdition': this._assessmentApiResponse?.assessmentResult?.serverEngineEdition,
'serverIssues': JSON.stringify(serverIssues),
'serverErrors': JSON.stringify(serverErrors),
},
{
'issuesCount': this._assessmentResults?.issues.length,
'warningsCount': this._assessmentResults?.databaseAssessments.reduce((count, d) => count + d.issues.length, 0),
'durationInMilliseconds': endTime.getTime() - startTime.getTime(),
'databaseCount': this._assessmentResults?.databaseAssessments.length,
'serverHostCpuCount': this._assessmentApiResponse?.assessmentResult?.cpuCoreCount,
'serverHostPhysicalMemoryInBytes': this._assessmentApiResponse?.assessmentResult?.physicalServerMemory,
'serverDatabases': this._assessmentApiResponse?.assessmentResult?.numberOfUserDatabases,
'serverDatabasesReadyForMigration': this._assessmentApiResponse?.assessmentResult?.sqlManagedInstanceTargetReadiness?.numberOfDatabasesReadyForMigration,
'offlineDatabases': this._assessmentApiResponse?.assessmentResult?.sqlManagedInstanceTargetReadiness?.numberOfNonOnlineDatabases,
}
);
const databaseWarningsMap: Map<string, number> = new Map();
const databaseErrorsMap: Map<number, number> = new Map();
this._assessmentApiResponse?.assessmentResult?.databases.forEach(d => {
sendSqlMigrationActionEvent(
TelemetryViews.MigrationWizardTargetSelectionPage,
TelemetryAction.DatabaseAssessment,
{
'sessionId': this._sessionId,
'hashedDatabaseName': hashString(d.name),
'compatibilityLevel': d.compatibilityLevel
},
{
'warningsCount': d.items.length,
'errorsCount': d.errors.length,
'assessmentTimeMs': d.assessmentTimeInMilliseconds,
'numberOfBlockerIssues': d.sqlManagedInstanceTargetReadiness.numOfBlockerIssues,
'databaseSizeInMb': d.databaseSize
});
d.items.forEach(i => {
databaseWarningsMap.set(
i.ruleId,
databaseWarningsMap.get(i.ruleId) ?? 0 + i.impactedObjects.length);
});
d.errors.forEach(
e => databaseErrorsMap.set(
e.errorId,
databaseErrorsMap.get(e.errorId) ?? 0 + 1));
});
const databaseWarnings: { warningId: string, count: number }[] = [];
databaseWarningsMap.forEach(
(v, k) => databaseWarnings.push(
{ warningId: k, count: v }));
sendSqlMigrationActionEvent(
TelemetryViews.MigrationWizardTargetSelectionPage,
TelemetryAction.DatabaseAssessmentWarning,
{
'sessionId': this._sessionId,
'warnings': JSON.stringify(databaseWarnings)
},
{});
const databaseErrors: { errorId: number, count: number }[] = [];
databaseErrorsMap.forEach(
(v, k) => databaseErrors.push(
{ errorId: k, count: v }));
sendSqlMigrationActionEvent(
TelemetryViews.MigrationWizardTargetSelectionPage,
TelemetryAction.DatabaseAssessmentError,
{
'sessionId': this._sessionId,
'errors': JSON.stringify(databaseErrors)
},
{});
} catch (e) {
console.log('error during assessment telemetry:');
console.log(e);
}
}
public get gatheringInformationError(): string | undefined {
return this._gatheringInformationError;
}
public set gatheringInformationError(error: string | undefined) {
this._gatheringInformationError = error;
}
public get stateChangeEvent(): vscode.Event<StateChangeEvent> {
return this._stateChangeEventEmitter.event;
}
dispose() {
this._stateChangeEventEmitter.dispose();
}
public getExtensionPath(): string {
return this.extensionContext.extensionPath;
}
public getLocationDisplayName(location: string): Promise<string> {
return getLocationDisplayName(location);
}
public async getManagedDatabases(): Promise<string[]> {
return (
await getSqlManagedInstanceDatabases(this._azureAccount,
this._targetSubscription,
<SqlManagedInstance>this._targetServerInstance)
).map(t => t.name);
}
public async startTdeMigration(
accessToken: string,
reportUpdate: (dbName: string, succeeded: boolean, message: string) => Promise<void>): Promise<OperationResult<TdeMigrationDbResult[]>> {
const tdeEnabledDatabases = this.tdeMigrationConfig.getTdeEnabledDatabases();
const connectionString = await getSourceConnectionString();
const opResult: OperationResult<TdeMigrationDbResult[]> = {
success: false,
result: [],
errors: []
};
try {
const migrationResult = await this.migrationService.migrateCertificate(
tdeEnabledDatabases,
connectionString,
this._targetSubscription?.id,
this._resourceGroup?.name,
this._targetServerInstance.name,
this.tdeMigrationConfig._networkPath,
accessToken,
reportUpdate);
opResult.errors = migrationResult!.migrationStatuses
.filter(entry => !entry.success)
.map(entry => constants.TDE_MIGRATION_ERROR_DB(entry.dbName, entry.message));
opResult.result = migrationResult!.migrationStatuses.map(m => ({
name: m.dbName,
success: m.success,
message: m.message
}));
} catch (e) {
opResult.errors = [constants.TDE_MIGRATION_ERROR(e.message)];
opResult.result = tdeEnabledDatabases.map(m => ({
name: m,
success: false,
message: e.message
}));
}
opResult.success = opResult.errors.length === 0; //Set success when there are no errors.
return opResult;
}
public async startMigration() {
const currentConnection = await getSourceConnectionProfile();
const isOfflineMigration = this._databaseBackup.migrationMode === MigrationMode.OFFLINE;
const isSqlDbTarget = this.isSqlDbTarget;
const requestBody: StartDatabaseMigrationRequest = {
location: this._sqlMigrationService?.location!,
properties: {
sourceDatabaseName: '',
migrationService: this._sqlMigrationService?.id!,
backupConfiguration: {},
sourceSqlConnection: {
dataSource: currentConnection?.serverName!,
authentication: this._authenticationType,
userName: this._sqlServerUsername,
password: this._sqlServerPassword,
encryptConnection: getEncryptConnectionValue(currentConnection),
trustServerCertificate: getTrustServerCertificateValue(currentConnection)
},
scope: this._targetServerInstance.id,
offlineConfiguration: {
offline: isOfflineMigration
}
}
};
for (let i = 0; i < this._databasesForMigration.length; i++) {
try {
if (isSqlDbTarget) {
const sourceDatabaseName = this._databasesForMigration[i];
const targetDatabaseInfo = this._sourceTargetMapping.get(sourceDatabaseName);
const totalTables = targetDatabaseInfo?.sourceTables.size ?? 0;
// skip databases that don't have tables
if (totalTables === 0) {
continue;
}
const sourceTables: string[] = [];
let selectedTables = 0;
targetDatabaseInfo?.sourceTables.forEach(sourceTableInfo => {
if (sourceTableInfo.selectedForMigration) {
selectedTables++;
sourceTables.push(sourceTableInfo.tableName);
}
});
// skip databases that don't have tables selected
if (selectedTables === 0) {
continue;
}
const sqlDbTarget = this._targetServerInstance as AzureSqlDatabaseServer;
requestBody.properties.offlineConfiguration = undefined;
requestBody.properties.sourceSqlConnection = {
dataSource: currentConnection?.serverName!,
authentication: this._authenticationType,
userName: this._sqlServerUsername,
password: this._sqlServerPassword,
encryptConnection: getEncryptConnectionValue(currentConnection),
trustServerCertificate: getTrustServerCertificateValue(currentConnection)
};
requestBody.properties.targetSqlConnection = {
dataSource: sqlDbTarget.properties.fullyQualifiedDomainName,
authentication: MigrationSourceAuthenticationType.Sql,
userName: this._targetUserName,
password: this._targetPassword,
// when connecting to a target Azure SQL DB, use true/false
encryptConnection: true,
trustServerCertificate: false,
};
// send an empty array when 'all' tables are selected for migration
requestBody.properties.tableList = selectedTables === totalTables
? []
: sourceTables;
} else {
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
requestBody.properties.backupConfiguration = {
targetLocation: undefined!,
sourceLocation: {
fileStorageType: FileStorageType.AzureBlob,
azureBlob: {
storageAccountResourceId: this._databaseBackup.blobs[i].storageAccount.id,
accountKey: this._databaseBackup.blobs[i].storageKey,
blobContainerName: this._databaseBackup.blobs[i].blobContainer.name
}
}
};
if (isOfflineMigration) {
requestBody.properties.offlineConfiguration = {
offline: isOfflineMigration,
lastBackupName: this._databaseBackup.blobs[i]?.lastBackupFile
};
}
break;
case NetworkContainerType.NETWORK_SHARE:
requestBody.properties.backupConfiguration = {
targetLocation: {
storageAccountResourceId: this._databaseBackup.networkShares[i].storageAccount.id,
accountKey: this._databaseBackup.networkShares[i].storageKey,
},
sourceLocation: {
fileStorageType: FileStorageType.FileShare,
fileShare: {
path: this._databaseBackup.networkShares[i].networkShareLocation,
username: this._databaseBackup.networkShares[i].windowsUser,
password: this._databaseBackup.networkShares[i].password,
}
}
};
break;
}
}
requestBody.properties.sourceDatabaseName = this._databasesForMigration[i];
const response = await startDatabaseMigration(
this._azureAccount,
this._targetSubscription,
this._sqlMigrationService?.location!,
this._targetServerInstance,
this._targetDatabaseNames[i],
requestBody,
this._sessionId);
response.databaseMigration.properties.sourceDatabaseName = this._databasesForMigration[i];
response.databaseMigration.properties.backupConfiguration = requestBody.properties.backupConfiguration!;
response.databaseMigration.properties.offlineConfiguration = requestBody.properties.offlineConfiguration!;
let wizardEntryPoint = WizardEntryPoint.Default;
if (this.resumeAssessment) {
wizardEntryPoint = WizardEntryPoint.SaveAndClose;
} else if (this.retryMigration) {
wizardEntryPoint = WizardEntryPoint.RetryMigration;
}
if (response.status === 201 || response.status === 200) {
sendSqlMigrationActionEvent(
TelemetryViews.MigrationWizardSummaryPage,
TelemetryAction.StartMigration,
{
'sessionId': this._sessionId,
'tenantId': this._azureAccount.properties.tenants[0].id,
'subscriptionId': this._targetSubscription?.id,
'resourceGroup': this._resourceGroup?.name,
'location': this._targetServerInstance.location,
'targetType': this._targetType,
'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name),
'hashedDatabaseName': hashString(this._databasesForMigration[i]),
'migrationMode': isOfflineMigration ? 'offline' : 'online',
'migrationStartTime': new Date().toString(),
'targetDatabaseName': this._targetDatabaseNames[i],
'serverName': this._targetServerInstance.name,
'sqlMigrationServiceId': Buffer.from(this._sqlMigrationService?.id!).toString('base64'),
'irRegistered': (this._nodeNames?.length > 0).toString(),
'wizardEntryPoint': wizardEntryPoint,
},
{
});
void vscode.window.showInformationMessage(
localize(
"sql.migration.starting.migration.message",
'Starting migration for database {0} to {1} - {2}',
this._databasesForMigration[i],
this._targetServerInstance.name,
this._targetDatabaseNames[i]));
}
} catch (e) {
void vscode.window.showErrorMessage(
localize(
'sql.migration.starting.migration.error',
"An error occurred while starting the migration: '{0}'",
e.message));
logError(TelemetryViews.MigrationLocalStorage, 'StartMigrationFailed', e);
}
finally {
// kill existing data collection if user start migration
await this.refreshPerfDataCollection();
if ((!this.resumeAssessment || this.retryMigration) && this._perfDataCollectionIsCollecting) {
void this.stopPerfDataCollection();
void vscode.window.showInformationMessage(
constants.AZURE_RECOMMENDATION_STOP_POPUP);
}
}
}
}
public async saveInfo(serverName: string, currentPage: Page): Promise<void> {
const saveInfo: SavedInfo = {
closedPage: currentPage,
databaseAssessment: [],
databaseList: [],
databaseInfoList: [],
migrationTargetType: null,
azureAccount: null,
azureTenant: null,
subscription: null,
location: null,
resourceGroup: null,
targetServerInstance: null,
migrationMode: null,
networkContainerType: null,
networkShares: [],
blobs: [],
targetDatabaseNames: [],
sqlMigrationService: undefined,
serverAssessment: null,
skuRecommendation: null,
};
switch (currentPage) {
case Page.Summary:
case Page.IntegrationRuntime:
saveInfo.sqlMigrationService = this._sqlMigrationService;
saveInfo.migrationMode = this._databaseBackup.migrationMode;
saveInfo.networkContainerType = this._databaseBackup.networkContainerType;
case Page.DatabaseBackup:
saveInfo.networkShares = this._databaseBackup.networkShares;
saveInfo.blobs = this._databaseBackup.blobs;
saveInfo.targetDatabaseNames = this._targetDatabaseNames;
case Page.TargetSelection:
saveInfo.azureAccount = deepClone(this._azureAccount);
saveInfo.azureTenant = deepClone(this._azureTenant);
saveInfo.subscription = this._targetSubscription;
saveInfo.location = this._location;
saveInfo.resourceGroup = this._resourceGroup;
saveInfo.targetServerInstance = this._targetServerInstance;
case Page.SKURecommendation:
saveInfo.migrationTargetType = this._targetType;
saveInfo.databaseList = this._databasesForMigration;
saveInfo.serverAssessment = this._assessmentResults;
if (this._skuRecommendationPerformanceDataSource) {
const skuRecommendation: SkuRecommendationSavedInfo = {
skuRecommendationPerformanceDataSource: this._skuRecommendationPerformanceDataSource,
skuRecommendationPerformanceLocation: this._skuRecommendationPerformanceLocation,
perfDataCollectionStartDate: this._perfDataCollectionStartDate,
perfDataCollectionStopDate: this._perfDataCollectionStopDate,
skuTargetPercentile: this._skuTargetPercentile,
skuScalingFactor: this._skuScalingFactor,
skuEnablePreview: this._skuEnablePreview,
};
saveInfo.skuRecommendation = skuRecommendation;
}
case Page.DatabaseSelector:
saveInfo.databaseAssessment = this._databasesForAssessment;
await this.extensionContext.globalState.update(`${this.mementoString}.${serverName}`, saveInfo);
}
}
public async loadSavedInfo(): Promise<Boolean> {
try {
this._targetType = this.savedInfo.migrationTargetType || undefined!;
this._databasesForAssessment = this.savedInfo.databaseAssessment;
this._databasesForMigration = this.savedInfo.databaseList;
this._didUpdateDatabasesForMigration = true;
this._didDatabaseMappingChange = true;
this.refreshDatabaseBackupPage = true;
switch (this._targetType) {
case MigrationTargetType.SQLMI:
this._miDbs = this._databasesForMigration;
break;
case MigrationTargetType.SQLVM:
this._vmDbs = this._databasesForMigration;
break;
case MigrationTargetType.SQLDB:
this._sqldbDbs = this._databasesForMigration;
break;
}
this._azureAccount = this.savedInfo.azureAccount || undefined!;
this._azureTenant = this.savedInfo.azureTenant || undefined!;
this._targetSubscription = this.savedInfo.subscription || undefined!;
this._location = this.savedInfo.location || undefined!;
this._resourceGroup = this.savedInfo.resourceGroup || undefined!;
this._targetServerInstance = this.savedInfo.targetServerInstance || undefined!;
this._databaseBackup.migrationMode = this.savedInfo.migrationMode || undefined!;
this._sourceDatabaseNames = this._databasesForMigration;
this._targetDatabaseNames = this.savedInfo.targetDatabaseNames;
this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType ?? NetworkContainerType.BLOB_CONTAINER;
this._databaseBackup.networkShares = this.savedInfo.networkShares;
this._databaseBackup.blobs = this.savedInfo.blobs;
this._databaseBackup.subscription = this.savedInfo.subscription || undefined!;
this._sqlMigrationService = this.savedInfo.sqlMigrationService;
const savedAssessmentResults = this.savedInfo.serverAssessment;
if (savedAssessmentResults) {
this._assessmentResults = savedAssessmentResults;
this._assessedDatabaseList = this.savedInfo.databaseAssessment;
}
const savedSkuRecommendation = this.savedInfo.skuRecommendation;
if (savedSkuRecommendation) {
this._skuRecommendationPerformanceDataSource = savedSkuRecommendation.skuRecommendationPerformanceDataSource;
this._skuRecommendationPerformanceLocation = savedSkuRecommendation.skuRecommendationPerformanceLocation;
this._perfDataCollectionStartDate = savedSkuRecommendation.perfDataCollectionStartDate;
this._perfDataCollectionStopDate = savedSkuRecommendation.perfDataCollectionStopDate;
this._skuTargetPercentile = savedSkuRecommendation.skuTargetPercentile;
this._skuScalingFactor = savedSkuRecommendation.skuScalingFactor;
this._skuEnablePreview = savedSkuRecommendation.skuEnablePreview;
}
return true;
} catch {
return false;
}
}
public GetTargetType(): string {
switch (this._targetType) {
case MigrationTargetType.SQLMI:
return constants.LOGIN_MIGRATIONS_MI_TEXT;
case MigrationTargetType.SQLVM:
return constants.LOGIN_MIGRATIONS_VM_TEXT;
case MigrationTargetType.SQLDB:
return constants.LOGIN_MIGRATIONS_DB_TEXT;
}
return "";
}
public get isWindowsAuthMigrationSupported(): boolean {
return this._targetType === MigrationTargetType.SQLMI;
}
public setTargetServerName(): void {
switch (this._targetType) {
case MigrationTargetType.SQLMI:
const sqlMi = this._targetServerInstance as SqlManagedInstance;
this._targetServerName = sqlMi.properties.fullyQualifiedDomainName;
case MigrationTargetType.SQLDB:
const sqlDb = this._targetServerInstance as AzureSqlDatabaseServer;
this._targetServerName = sqlDb.properties.fullyQualifiedDomainName;
case MigrationTargetType.SQLVM:
// For sqlvm, we need to use ip address from the network interface to connect to the server
const sqlVm = this._targetServerInstance as SqlVMServer;
const networkInterfaces = Array.from(sqlVm.networkInterfaces.values());
this._targetServerName = NetworkInterfaceModel.getIpAddress(networkInterfaces);
}
}
public get targetServerName(): string {
// If the target server name is not already set, return it
if (!this._targetServerName) {
this.setTargetServerName();
}
return this._targetServerName;
}
}
export interface ServerAssessment {
issues: contracts.SqlMigrationAssessmentResultItem[];
databaseAssessments: {
name: string;
issues: contracts.SqlMigrationAssessmentResultItem[];
errors?: contracts.ErrorModel[];
}[];
errors?: contracts.ErrorModel[];
assessmentError?: Error;
}
export interface SkuRecommendation {
recommendations?: contracts.SkuRecommendationResult;
recommendationError?: Error;
}
export interface OperationResult<T> {
success: boolean;
result: T;
errors: string[];
}