Junierch/mi tde (#21573)

* WIP

* wip tde wizard

* wip for merge w master

* wip

* wip share

* tde wizard

* PR reviews updates

* PR review updates

* PR updates

* PR review updates

* PR reviews updates

* Bump STS to 4.4.0.22

* PR reviews updates

* fix localize build issue

* fix build issue with localize

* remove unused function

* Windows only flag. Bug Bash fixes

* Use azdata with latest STS changes

* revert azdata, other PR comments

* sts and extesion version upgraded. logins back
This commit is contained in:
junierch
2023-01-25 09:58:07 -05:00
committed by GitHub
parent 0e0fade5d8
commit 5157508dd5
16 changed files with 1406 additions and 11 deletions

View File

@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import * as utils from '../api/utils';
import * as mssql from 'mssql';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent } from '../models/stateMachine';
import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent, AssessmentRuleId } from '../models/stateMachine';
import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog';
import { SkuRecommendationResultsDialog } from '../dialog/skuRecommendationResults/skuRecommendationResultsDialog';
import { GetAzureRecommendationDialog } from '../dialog/skuRecommendationResults/getAzureRecommendationDialog';
@@ -19,6 +19,9 @@ import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import * as styles from '../constants/styles';
import { SkuEditParametersDialog } from '../dialog/skuRecommendationResults/skuEditParametersDialog';
import { logError, TelemetryViews } from '../telemtery';
import { TdeConfigurationDialog } from '../dialog/tdeConfiguration/tdeConfigurationDialog';
import { TdeMigrationModel } from '../models/tdeModels';
import * as os from 'os';
export interface Product {
type: MigrationTargetType;
@@ -46,6 +49,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
private _rootContainer!: azdata.FlexContainer;
private _viewAssessmentsHelperText!: azdata.TextComponent;
private _databaseSelectedHelperText!: azdata.TextComponent;
private _tdedatabaseSelectedHelperText!: azdata.TextComponent;
private _azureRecommendationSectionText!: azdata.TextComponent;
@@ -71,7 +75,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
private _skuEnableElasticRecommendationsText!: azdata.TextComponent;
private assessmentGroupContainer!: azdata.FlexContainer;
private _tdeInfoContainer!: azdata.FlexContainer;
private _disposables: vscode.Disposable[] = [];
private _tdeConfigurationDialog!: TdeConfigurationDialog;
private _previousMiTdeMigrationConfig: TdeMigrationModel = new TdeMigrationModel(); // avoid null checks
private _serverName: string = '';
private _supportedProducts: Product[] = [
@@ -172,12 +179,14 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._chooseTargetComponent = await this.createChooseTargetComponent(view);
const _azureRecommendationsContainer = this.createAzureRecommendationContainer(view);
this.assessmentGroupContainer = await this.createViewAssessmentsContainer();
this._tdeInfoContainer = await this.createTdeInfoContainer();
this._formContainer = view.modelBuilder.formContainer()
.withFormItems([
{ component: statusContainer, title: '' },
{ component: this._chooseTargetComponent },
{ component: _azureRecommendationsContainer },
{ component: this.assessmentGroupContainer }])
{ component: this.assessmentGroupContainer },
{ component: this._tdeInfoContainer }])
.withProps({
CSSStyles: {
'display': 'none',
@@ -207,6 +216,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
await this._view.initializeModel(this._rootContainer);
}
private createStatusComponent(view: azdata.ModelView): azdata.TextComponent {
const component = view.modelBuilder.text()
.withProps({
@@ -333,6 +343,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
if (value) {
this.assessmentGroupContainer.display = 'inline';
this.changeTargetType(value.cardId);
await this.refreshTdeView();
}
}));
@@ -346,6 +357,40 @@ export class SKURecommendationPage extends MigrationWizardPage {
return component;
}
private async createTdeInfoContainer(): Promise<azdata.FlexContainer> {
const container = this._view.modelBuilder.flexContainer().withProps({
CSSStyles: {
'flex-direction': 'column'
}
}).component();
const editButton = this._view.modelBuilder.button().withProps({
label: constants.TDE_BUTTON_CAPTION,
width: 180,
CSSStyles: {
...styles.BODY_CSS,
'margin': '0',
}
}).component();
this._tdeConfigurationDialog = new TdeConfigurationDialog(this, this.wizard, this.migrationStateModel, () => this._onTdeConfigClosed());
this._disposables.push(editButton.onDidClick(
async (e) => await this._tdeConfigurationDialog.openDialog()));
this._tdedatabaseSelectedHelperText = this._view.modelBuilder.text()
.withProps({
CSSStyles: { ...styles.BODY_CSS },
ariaLive: 'polite',
}).component();
container.addItems([
editButton,
this._tdedatabaseSelectedHelperText]);
await utils.updateControlDisplay(container, false);
return container;
}
private async createViewAssessmentsContainer(): Promise<azdata.FlexContainer> {
this._viewAssessmentsHelperText = this._view.modelBuilder.text().withProps({
value: constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI,
@@ -586,6 +631,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
});
await this.constructDetails();
this.wizard.nextButton.enabled = this.migrationStateModel._assessmentResults !== undefined;
this._previousMiTdeMigrationConfig = this.migrationStateModel.tdeMigrationConfig;
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
@@ -739,9 +785,77 @@ export class SKURecommendationPage extends MigrationWizardPage {
this.changeTargetType(this._rbg.selectedCardId);
}
await this.refreshTdeView();
this._rbgLoader.loading = false;
}
private _resetTdeConfiguration() {
this._previousMiTdeMigrationConfig = this.migrationStateModel.tdeMigrationConfig;
this.migrationStateModel.tdeMigrationConfig = new TdeMigrationModel();
}
private async refreshTdeView() {
if (this.migrationStateModel._targetType !== MigrationTargetType.SQLMI) {
//Reset the encrypted databases counter on the model to ensure the certificates migration is ignored.
this._resetTdeConfiguration();
} else {
const encryptedDbFound = this.migrationStateModel._assessmentResults.databaseAssessments
.filter(
db => this.migrationStateModel._databasesForMigration.findIndex(dba => dba === db.name) >= 0 &&
db.issues.findIndex(iss => iss.ruleId === AssessmentRuleId.TdeEnabled && iss.appliesToMigrationTargetPlatform === MigrationTargetType.SQLMI) >= 0
)
.map(db => db.name);
if (this._matchWithEncryptedDatabases(encryptedDbFound)) {
this.migrationStateModel.tdeMigrationConfig = this._previousMiTdeMigrationConfig;
} else {
if (os.platform() !== 'win32') //Only available for windows for now.
return;
//Set encrypted databases
this.migrationStateModel.tdeMigrationConfig.setTdeEnabledDatabasesCount(encryptedDbFound);
if (this.migrationStateModel.tdeMigrationConfig.hasTdeEnabledDatabases()) {
//Set the text when there are encrypted databases.
if (!this.migrationStateModel.tdeMigrationConfig.shownBefore()) {
await this._tdeConfigurationDialog.openDialog();
}
} else {
this._tdedatabaseSelectedHelperText.value = constants.TDE_WIZARD_MSG_EMPTY;
}
}
}
await utils.updateControlDisplay(this._tdeInfoContainer, this.migrationStateModel.tdeMigrationConfig.hasTdeEnabledDatabases());
}
private _onTdeConfigClosed() {
const tdeMsg = (this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAdsConfirmed()) ? constants.TDE_WIZARD_MSG_TDE : constants.TDE_WIZARD_MSG_MANUAL;
this._tdedatabaseSelectedHelperText.value = constants.TDE_MSG_DATABASES_SELECTED(this.migrationStateModel.tdeMigrationConfig.getTdeEnabledDatabasesCount(), tdeMsg);
}
private _matchWithEncryptedDatabases(encryptedDbList: string[]): boolean {
var currentTdeDbs = this._previousMiTdeMigrationConfig.getTdeEnabledDatabases();
if (encryptedDbList.length === 0 || encryptedDbList.length !== currentTdeDbs.length)
return false;
if (encryptedDbList.filter(db => currentTdeDbs.findIndex(dba => dba === db) < 0).length > 0)
return false; //There is at least one element that is not in the other array. There should be no risk of duplicates table names
return true;
}
public async startCardLoading(): Promise<void> {
// TO-DO: ideally the short SKU recommendation loading time should have a spinning indicator,
// but updating the card text will do for now

View File

@@ -163,6 +163,11 @@ export class SummaryPage extends MigrationWizardPage {
constants.SHIR,
this.migrationStateModel._nodeNames.join(', ')));
}
this.wizard.registerNavigationValidator(async (pageChangeInfo) => {
this.wizard.message = { text: '' };
return true;
});
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {

View File

@@ -16,6 +16,9 @@ import { azureResource } from 'azurecore';
import { AzureSqlDatabaseServer, getVMInstanceView, SqlVMServer } from '../api/azure';
import { collectTargetDatabaseInfo, TargetDatabaseInfo } from '../api/sqlUtils';
import { MigrationLocalStorage, MigrationServiceContext } from '../models/migrationLocalStorage';
import { TdeMigrationDialog } from '../dialog/tdeConfiguration/tdeMigrationDialog';
const TDE_MIGRATION_BUTTON_INDEX = 1;
export class TargetSelectionPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
@@ -81,10 +84,49 @@ export class TargetSelectionPage extends MigrationWizardPage {
await this.populateAzureAccountsDropdown();
}
this._disposables.push(
this.wizard.customButtons[TDE_MIGRATION_BUTTON_INDEX].onClick(
async e => await this._startTdeMigration()));
await this._view.initializeModel(form);
}
private async _startTdeMigration(): Promise<void> {
const dialog = new TdeMigrationDialog(this.migrationStateModel);
await dialog.openDialog();
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.customButtons[TDE_MIGRATION_BUTTON_INDEX].hidden = !this.migrationStateModel.tdeMigrationConfig.shouldAdsMigrateCertificates();
this._updateTdeMigrationButtonStatus();
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_MI_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
break;
case MigrationTargetType.SQLVM:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_VM_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
break;
case MigrationTargetType.SQLDB:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_SQLDB_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE;
this._updateConnectionButtonState();
if (this.migrationStateModel._didUpdateDatabasesForMigration) {
await this._resetTargetMapping();
this.migrationStateModel._didUpdateDatabasesForMigration = false;
}
break;
}
this.wizard.registerNavigationValidator((pageChangeInfo) => {
this.wizard.message = { text: '' };
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
@@ -174,6 +216,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
};
return false;
}
return true;
});
@@ -240,6 +283,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
this.wizard.customButtons[TDE_MIGRATION_BUTTON_INDEX].hidden = true;
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
@@ -701,6 +746,9 @@ export class TargetSelectionPage extends MigrationWizardPage {
};
}
}
this.migrationStateModel.tdeMigrationConfig.resetTdeMigrationResult();
break;
case MigrationTargetType.SQLDB:
const sqlDatabaseServer = this.migrationStateModel._targetSqlDatabaseServers?.find(
@@ -975,6 +1023,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
this.migrationStateModel._targetManagedInstances,
this.migrationStateModel._location,
this.migrationStateModel._resourceGroup);
this._updateTdeMigrationButtonStatus();
break;
case MigrationTargetType.SQLVM:
this._azureResourceDropdown.values = await utils.getVirtualMachinesDropdownValues(
@@ -1014,6 +1064,12 @@ export class TargetSelectionPage extends MigrationWizardPage {
}
}
private _updateTdeMigrationButtonStatus() {
this.wizard.customButtons[TDE_MIGRATION_BUTTON_INDEX].enabled = this.migrationStateModel.tdeMigrationConfig.shouldAdsMigrateCertificates() &&
this.migrationStateModel._targetManagedInstances.length > 0;
}
private async _populateResourceMappingTable(targetDatabases: TargetDatabaseInfo[]): Promise<void> {
// populate target database list
const databaseValues = this._getTargetDatabaseDropdownValues(

View File

@@ -79,7 +79,13 @@ export class WizardController {
validateButton.secondary = false;
validateButton.hidden = true;
this._wizardObject.customButtons = [validateButton, saveAndCloseButton];
const tdeMigrateButton = azdata.window.createButton(
loc.TDE_MIGRATE_BUTTON,
'left');
tdeMigrateButton.secondary = false;
tdeMigrateButton.hidden = true;
this._wizardObject.customButtons = [validateButton, tdeMigrateButton, saveAndCloseButton];
const databaseSelectorPage = new DatabaseSelectorPage(this._wizardObject, stateModel);
const skuRecommendationPage = new SKURecommendationPage(this._wizardObject, stateModel);
const targetSelectionPage = new TargetSelectionPage(this._wizardObject, stateModel);