[SQL Migration] Refactor resource selection filtering logic + misc UI improvements (#19152)

* WIP

* WIP

* WIP

* Fix location dropdown not working properly

* Clean up comments

* Switch button order in selectMigrationServiceDialog

* Vbump to 1.0.1

* Refactor to avoid duplicate API calls

* Add null checks

* Fix migration status dialog not sorting migrations properly

* Address comments, remove unnecessary code

* Address comments - separate util methods by resource type, use logError instead of console.log

* Remove unused methods

* Fix DMS creation on newly created resource group

* Fix stale account behavior

* Address comments - remove telemetry context from util method calls

* Clean up imports

* Fix dashboard service monitoring not working

* Fix null reference on database backup page, and resources not updating properly when location is changed

* Fix dashboard not auto selecting DMS after migration started

* Add null checks
This commit is contained in:
Raymond Truong
2022-05-03 16:22:47 -04:00
committed by GitHub
parent 8cc66dade3
commit b36ee9318f
12 changed files with 909 additions and 1081 deletions

View File

@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
import * as azurecore from 'azurecore';
import * as vscode from 'vscode';
import * as mssql from 'mssql';
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getSqlMigrationServices, getSubscriptions, SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer, getLocations, getLocationDisplayName, getSqlManagedInstanceDatabases, getBlobs, sortResourceArrayByName, getFullResourceGroupFromId, getResourceGroupFromId, getResourceGroups, getSqlMigrationServicesByResourceGroup } from '../api/azure';
import { SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, SqlVMServer, getLocationDisplayName, getSqlManagedInstanceDatabases } from '../api/azure';
import * as constants from '../constants/strings';
import * as nls from 'vscode-nls';
import { v4 as uuidv4 } from 'uuid';
@@ -180,7 +180,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _sourceDatabaseNames!: string[];
public _targetDatabaseNames!: string[];
public _sqlMigrationServiceResourceGroup!: string;
public _sqlMigrationServiceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
public _sqlMigrationService!: SqlMigrationService | undefined;
public _sqlMigrationServices!: SqlMigrationService[];
public _nodeNames!: string[];
@@ -821,51 +821,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
return this.extensionContext.extensionPath;
}
public async getAccountValues(): Promise<azdata.CategoryValue[]> {
let accountValues: azdata.CategoryValue[] = [];
try {
this._azureAccounts = await azdata.accounts.getAllAccounts();
if (this._azureAccounts.length === 0) {
accountValues = [{
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
name: ''
}];
}
accountValues = this._azureAccounts.map((account): azdata.CategoryValue => {
return {
name: account.displayInfo.userId,
displayName: account.isStale
? constants.ACCOUNT_CREDENTIALS_REFRESH(account.displayInfo.displayName)
: account.displayInfo.displayName
};
});
} catch (e) {
console.log(e);
accountValues = [{
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
name: ''
}];
}
return accountValues;
}
public getAccount(index: number): azdata.Account {
return this._azureAccounts[index];
}
public getTenantValues(): azdata.CategoryValue[] {
return this._accountTenants.map(tenant => {
return {
displayName: tenant.displayName,
name: tenant.id
};
});
}
public getTenant(index: number): azurecore.Tenant {
return this._accountTenants[index];
}
public async getSourceConnectionProfile(): Promise<azdata.connection.ConnectionProfile> {
const sqlConnections = await azdata.connection.getConnections();
return sqlConnections.find((value) => {
@@ -877,650 +832,16 @@ export class MigrationStateModel implements Model, vscode.Disposable {
})!;
}
public async getSubscriptionsDropdownValues(): Promise<azdata.CategoryValue[]> {
let subscriptionsValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount?.isStale === false) {
this._subscriptions = await getSubscriptions(this._azureAccount);
} else {
this._subscriptions = [];
}
this._subscriptions.forEach((subscription) => {
subscriptionsValues.push({
name: subscription.id,
displayName: `${subscription.name} - ${subscription.id}`
});
});
if (subscriptionsValues.length === 0) {
subscriptionsValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
subscriptionsValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
return subscriptionsValues;
}
public getSubscription(index: number): azurecore.azureResource.AzureResourceSubscription {
return this._subscriptions[index];
}
public async getAzureLocationDropdownValues(subscription: azurecore.azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let locationValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
this._locations = await getLocations(this._azureAccount, subscription);
} else {
this._locations = [];
}
this._locations.forEach((loc) => {
locationValues.push({
name: loc.name,
displayName: loc.displayName
});
});
if (locationValues.length === 0) {
locationValues = [
{
displayName: constants.NO_LOCATION_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
locationValues = [
{
displayName: constants.NO_LOCATION_FOUND,
name: ''
}
];
}
return locationValues;
}
public getLocation(index: number): azurecore.azureResource.AzureLocation {
return this._locations[index];
}
public getLocationDisplayName(location: string): Promise<string> {
return getLocationDisplayName(location);
}
public async getAzureResourceGroupDropdownValues(subscription: azurecore.azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let resourceGroupValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
this._resourceGroups = await getResourceGroups(this._azureAccount, subscription);
} else {
this._resourceGroups = [];
}
this._resourceGroups.forEach((rg) => {
resourceGroupValues.push({
name: rg.id,
displayName: rg.name
});
});
if (resourceGroupValues.length === 0) {
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
return resourceGroupValues;
}
public async getAzureResourceGroupForManagedInstancesDropdownValues(subscription: azurecore.azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let resourceGroupValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
let managedInstances = await getAvailableManagedInstanceProducts(this._azureAccount, subscription);
this._resourceGroups = managedInstances.map((mi) => {
return <azurecore.azureResource.AzureResourceResourceGroup>{
id: getFullResourceGroupFromId(mi.id),
name: getResourceGroupFromId(mi.id),
subscription: {
id: mi.subscriptionId
},
tenant: mi.tenantId,
};
});
// remove duplicates
this._resourceGroups = this._resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
sortResourceArrayByName(this._resourceGroups);
} else {
this._resourceGroups = [];
}
this._resourceGroups.forEach((rg) => {
resourceGroupValues.push({
name: rg.id,
displayName: rg.name
});
});
if (resourceGroupValues.length === 0) {
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
return resourceGroupValues;
}
public async getAzureResourceGroupForVirtualMachinesDropdownValues(subscription: azurecore.azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let resourceGroupValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
let virtualMachines = await getAvailableSqlVMs(this._azureAccount, subscription);
this._resourceGroups = virtualMachines.map((vm) => {
return <azurecore.azureResource.AzureResourceResourceGroup>{
id: getFullResourceGroupFromId(vm.id),
name: getResourceGroupFromId(vm.id),
subscription: {
id: vm.subscriptionId
},
tenant: vm.tenantId,
};
});
// remove duplicates
this._resourceGroups = this._resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
sortResourceArrayByName(this._resourceGroups);
} else {
this._resourceGroups = [];
}
this._resourceGroups.forEach((rg) => {
resourceGroupValues.push({
name: rg.id,
displayName: rg.name
});
});
if (resourceGroupValues.length === 0) {
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
return resourceGroupValues;
}
public async getAzureResourceGroupForStorageAccountsDropdownValues(subscription: azurecore.azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let resourceGroupValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
let storageAccounts = await getAvailableStorageAccounts(this._azureAccount, subscription);
this._resourceGroups = storageAccounts.map((sa) => {
return <azurecore.azureResource.AzureResourceResourceGroup>{
id: getFullResourceGroupFromId(sa.id),
name: getResourceGroupFromId(sa.id),
subscription: {
id: sa.subscriptionId
},
tenant: sa.tenantId,
};
});
// remove duplicates
this._resourceGroups = this._resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
sortResourceArrayByName(this._resourceGroups);
} else {
this._resourceGroups = [];
}
this._resourceGroups.forEach((rg) => {
resourceGroupValues.push({
name: rg.id,
displayName: rg.name
});
});
if (resourceGroupValues.length === 0) {
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
return resourceGroupValues;
}
public async getAzureResourceGroupForSqlMigrationServicesDropdownValues(subscription: azurecore.azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let resourceGroupValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
let dmsInstances = await getSqlMigrationServices(this._azureAccount, subscription);
this._resourceGroups = dmsInstances.map((dms) => {
return <azurecore.azureResource.AzureResourceResourceGroup>{
id: getFullResourceGroupFromId(dms.id),
name: getResourceGroupFromId(dms.id),
subscription: {
id: dms.properties.subscriptionId
}
};
});
// remove duplicates
this._resourceGroups = this._resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
sortResourceArrayByName(this._resourceGroups);
} else {
this._resourceGroups = [];
}
this._resourceGroups.forEach((rg) => {
resourceGroupValues.push({
name: rg.id,
displayName: rg.name
});
});
if (resourceGroupValues.length === 0) {
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
resourceGroupValues = [
{
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
name: ''
}
];
}
return resourceGroupValues;
}
public getAzureResourceGroup(index: number): azurecore.azureResource.AzureResourceResourceGroup {
return this._resourceGroups[index];
}
public async getManagedInstanceValues(subscription: azurecore.azureResource.AzureResourceSubscription, location: azurecore.azureResource.AzureLocation, resourceGroup: azurecore.azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let managedInstanceValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && location && resourceGroup) {
this._targetManagedInstances = (await getAvailableManagedInstanceProducts(this._azureAccount, subscription)).filter((mi) => {
if (mi.location.toLowerCase() === location?.name.toLowerCase() && mi.resourceGroup?.toLowerCase() === resourceGroup?.name.toLowerCase()) {
return true;
}
return false;
});
} else {
this._targetManagedInstances = [];
}
this._targetManagedInstances.forEach((managedInstance) => {
let managedInstanceValue: azdata.CategoryValue;
if (managedInstance.properties.state === 'Ready') {
managedInstanceValue = {
name: managedInstance.id,
displayName: `${managedInstance.name}`
};
} else {
managedInstanceValue = {
name: managedInstance.id,
displayName: constants.UNAVAILABLE_MANAGED_INSTANCE_PREFIX(managedInstance.name)
};
}
managedInstanceValues.push(managedInstanceValue);
});
if (managedInstanceValues.length === 0) {
managedInstanceValues = [
{
displayName: constants.NO_MANAGED_INSTANCE_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
managedInstanceValues = [
{
displayName: constants.NO_MANAGED_INSTANCE_FOUND,
name: ''
}
];
}
return managedInstanceValues;
}
public getManagedInstance(index: number): SqlManagedInstance {
return this._targetManagedInstances[index];
}
public async getManagedDatabases(): Promise<string[]> {
return (await getSqlManagedInstanceDatabases(this._azureAccount,
this._targetSubscription,
<SqlManagedInstance>this._targetServerInstance)).map(t => t.name);
}
public async getSqlVirtualMachineValues(subscription: azurecore.azureResource.AzureResourceSubscription, location: azurecore.azureResource.AzureLocation, resourceGroup: azurecore.azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let virtualMachineValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && location && resourceGroup) {
this._targetSqlVirtualMachines = (await getAvailableSqlVMs(this._azureAccount, subscription)).filter((virtualMachine) => {
if (virtualMachine?.location?.toLowerCase() === location?.name?.toLowerCase() && getResourceGroupFromId(virtualMachine.id).toLowerCase() === resourceGroup?.name.toLowerCase()) {
if (virtualMachine.properties.sqlImageOffer) {
return virtualMachine.properties.sqlImageOffer.toLowerCase().includes('-ws'); //filtering out all non windows sql vms.
}
return true; // Returning all VMs that don't have this property as we don't want to accidentally skip valid vms.
}
return false;
});
virtualMachineValues = this._targetSqlVirtualMachines.map((virtualMachine) => {
return {
name: virtualMachine.id,
displayName: `${virtualMachine.name}`
};
});
} else {
this._targetSqlVirtualMachines = [];
}
if (virtualMachineValues.length === 0) {
virtualMachineValues = [
{
displayName: constants.NO_VIRTUAL_MACHINE_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
virtualMachineValues = [
{
displayName: constants.NO_VIRTUAL_MACHINE_FOUND,
name: ''
}
];
}
return virtualMachineValues;
}
public getVirtualMachine(index: number): SqlVMServer {
return this._targetSqlVirtualMachines[index];
}
public async getStorageAccountValues(subscription: azurecore.azureResource.AzureResourceSubscription, resourceGroup: azurecore.azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let storageAccountValues: azdata.CategoryValue[] = [];
if (!resourceGroup) {
return storageAccountValues;
}
try {
if (this._azureAccount && subscription && resourceGroup) {
const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription));
this._storageAccounts = storageAccount.filter(sa => {
return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase();
});
} else {
this._storageAccounts = [];
}
this._storageAccounts.forEach((storageAccount) => {
storageAccountValues.push({
name: storageAccount.id,
displayName: `${storageAccount.name}`
});
});
if (storageAccountValues.length === 0) {
storageAccountValues = [
{
displayName: constants.NO_STORAGE_ACCOUNT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
storageAccountValues = [
{
displayName: constants.NO_STORAGE_ACCOUNT_FOUND,
name: ''
}
];
}
return storageAccountValues;
}
public getStorageAccount(index: number): StorageAccount {
return this._storageAccounts[index];
}
public async getFileShareValues(subscription: azurecore.azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise<azdata.CategoryValue[]> {
let fileShareValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && storageAccount) {
this._fileShares = await getFileShares(this._azureAccount, subscription, storageAccount);
} else {
this._fileShares = [];
}
this._fileShares.forEach((fileShare) => {
fileShareValues.push({
name: fileShare.id,
displayName: `${fileShare.name}`
});
});
if (fileShareValues.length === 0) {
fileShareValues = [
{
displayName: constants.NO_FILESHARES_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
fileShareValues = [
{
displayName: constants.NO_FILESHARES_FOUND,
name: ''
}
];
}
return fileShareValues;
}
public getFileShare(index: number): azurecore.azureResource.FileShare {
return this._fileShares[index];
}
public async getBlobContainerValues(subscription: azurecore.azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise<azdata.CategoryValue[]> {
let blobContainerValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && storageAccount) {
this._blobContainers = await getBlobContainers(this._azureAccount, subscription, storageAccount);
} else {
this._blobContainers = [];
}
this._blobContainers.forEach((blobContainer) => {
blobContainerValues.push({
name: blobContainer.id,
displayName: `${blobContainer.name}`
});
});
if (blobContainerValues.length === 0) {
blobContainerValues = [
{
displayName: constants.NO_BLOBCONTAINERS_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
blobContainerValues = [
{
displayName: constants.NO_BLOBCONTAINERS_FOUND,
name: ''
}
];
}
return blobContainerValues;
}
public getBlobContainer(index: number): azurecore.azureResource.BlobContainer {
return this._blobContainers[index];
}
public async getBlobLastBackupFileNameValues(subscription: azurecore.azureResource.AzureResourceSubscription, storageAccount: StorageAccount, blobContainer: azurecore.azureResource.BlobContainer): Promise<azdata.CategoryValue[]> {
let blobLastBackupFileValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && storageAccount && blobContainer) {
this._lastFileNames = await getBlobs(this._azureAccount, subscription, storageAccount, blobContainer.name);
} else {
this._lastFileNames = [];
}
this._lastFileNames.forEach((blob) => {
blobLastBackupFileValues.push({
name: blob.name,
displayName: `${blob.name}`,
});
});
if (blobLastBackupFileValues.length === 0) {
blobLastBackupFileValues = [
{
displayName: constants.NO_BLOBFILES_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
blobLastBackupFileValues = [
{
displayName: constants.NO_BLOBFILES_FOUND,
name: ''
}
];
}
return blobLastBackupFileValues;
}
public getBlobLastBackupFileName(index: number): string {
return this._lastFileNames[index]?.name;
}
public async getSqlMigrationServiceValues(subscription: azurecore.azureResource.AzureResourceSubscription, resourceGroupName: string): Promise<azdata.CategoryValue[]> {
let sqlMigrationServiceValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && resourceGroupName && this._targetServerInstance) {
const services = await getSqlMigrationServicesByResourceGroup(
this._azureAccount,
subscription,
resourceGroupName?.toLowerCase());
const targetLoc = this._targetServerInstance.location.toLowerCase();
this._sqlMigrationServices = services.filter(sms => sms.location.toLowerCase() === targetLoc);
} else {
this._sqlMigrationServices = [];
}
this._sqlMigrationServices.forEach((sqlMigrationService) => {
sqlMigrationServiceValues.push({
name: sqlMigrationService.id,
displayName: `${sqlMigrationService.name}`
});
});
if (sqlMigrationServiceValues.length === 0) {
sqlMigrationServiceValues = [
{
displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR,
name: ''
}
];
}
} catch (e) {
console.log(e);
sqlMigrationServiceValues = [
{
displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR,
name: ''
}
];
}
return sqlMigrationServiceValues;
}
public getMigrationService(index: number): SqlMigrationService {
return this._sqlMigrationServices[index];
}
public async startMigration() {
const sqlConnections = await azdata.connection.getConnections();
const currentConnection = sqlConnections.find((value) => {