Adding blob storage support (WIP) (#15477)

Bumping version
Fixing cancel migration bug
This commit is contained in:
Aasim Khan
2021-05-14 17:24:36 +00:00
committed by GitHub
parent 12e0f24ef8
commit 71db7ed101
11 changed files with 575 additions and 505 deletions

View File

@@ -347,11 +347,11 @@ export interface StartDatabaseMigrationRequest {
sourceDatabaseName: string,
migrationService: string,
backupConfiguration: {
targetLocation: {
targetLocation?: {
storageAccountResourceId: string,
accountKey: string,
},
sourceLocation: SourceLocation
sourceLocation?: SourceLocation
},
sourceSqlConnection: {
authentication: string,
@@ -416,8 +416,8 @@ export interface SqlConnectionInfo {
}
export interface BackupConfiguration {
sourceLocation: SourceLocation;
targetLocation: TargetLocation;
sourceLocation?: SourceLocation;
targetLocation?: TargetLocation;
}
export interface AutoCutoverConfiguration {
@@ -454,7 +454,7 @@ export interface TargetLocation {
export interface BackupFileInfo {
fileName: string;
status: string;
status: 'Arrived' | 'Uploading' | 'Uploaded' | 'Restoring' | 'Restored' | 'Cancelled' | 'Ignored';
}
export interface DatabaseMigrationFileShare {

View File

@@ -51,7 +51,8 @@ export const VIEW_SELECT_BUTTON_LABEL = localize('sql.migration.view.select.butt
export function TOTAL_DATABASES_SELECTED(selectedDbCount: number, totalDbCount: number): string {
return localize('total.databases.selected', "{0} of {1} Database(s) selected.", selectedDbCount, totalDbCount);
}
export const SELECT_TARGET_TO_CONTINUE = localize('sql.migration.select.target.to.continue', "Please select a target to continue");
export const SELECT_DATABASE_TO_MIGRATE = localize('sql.migration.select.database.to.migrate', "Please select databases to migrate");
export const ASSESSMENT_COMPLETED = (serverName: string): string => {
return localize('sql.migration.generic.congratulations', "We have completed the assessment of your SQL Server Instance '{0}'.", serverName);
};
@@ -83,6 +84,8 @@ export const DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL = localize('sql.migrat
export const DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL = localize('sql.migration.nc.blob.storage.radio.label', "My database backups are in an Azure Storage Blob Container (Coming soon)");
export const DATABASE_BACKUP_NC_FILE_SHARE_RADIO_LABEL = localize('sql.migration.nc.file.share.radio.label', "My database backups are in an Azure Storage File Share (Coming soon)");
export const DATABASE_BACKUP_NETWORK_SHARE_HEADER_TEXT = localize('sql.migration.network.share.header.text', "Network share details");
export const DATABASE_BACKUP_NC_NETWORK_SHARE_HELP_TEXT = localize('sql.migration.network.share.help.text', "Provide the network share location that contains backups and the user credentials that has read access to the share");
export const DATABASE_BACKUP_NETWORK_SHARE_LOCATION_LABEL = localize('sql.migration.network.share.location.label', "Network share location that contains backups.");
@@ -97,8 +100,8 @@ export const DATABASE_BACKUP_SUBSCRIPTION_PLACEHOLDER = localize('sql.migration.
export const DATABASE_BACKUP_NETWORK_SHARE_NETWORK_STORAGE_ACCOUNT_LABEL = localize('sql.migration.network.share.storage.account.label', "Select the storage account where backup files will be copied.");
export const DATABASE_BACKUP_STORAGE_ACCOUNT_PLACEHOLDER = localize('sql.migration.network.share.storage.account.placeholder', "Select account");
export const DUPLICATE_NAME_ERROR = localize('sql.migration.unique.name', "Select a unique name for this target database");
export function DATABASE_ALREADY_EXISTS_MI(targetName: string): string {
return localize('sql.migration.database.already.exists', "Database with the same name already exists on target Managed Instance '{0}'", targetName);
export function DATABASE_ALREADY_EXISTS_MI(dbName: string, targetName: string): string {
return localize('sql.migration.database.already.exists', "Database '{0}' already exists on target Managed Instance '{1}'.", dbName, targetName);
}
export const DATABASE_BACKUP_BLOB_STORAGE_SUBSCRIPTION_LABEL = localize('sql.migration.blob.storage.subscription.label', "Select the subscription that contains the storage account.");
export const DATABASE_BACKUP_BLOB_STORAGE_ACCOUNT_LABEL = localize('sql.migration.blob.storage.account.label', "Select the storage account that contains the backup files.");
@@ -128,9 +131,6 @@ export const INVALID_FILESHARE_ERROR = localize('sql.migration.invalid.fileShare
export const INVALID_BLOBCONTAINER_ERROR = localize('sql.migration.invalid.blobContainer.error', "Please select a valid blob container to proceed.");
export const INVALID_NETWORK_SHARE_LOCATION = localize('sql.migration.invalid.network.share.location', "Invalid network share location format. Example: {0}", '\\\\Servername.domainname.com\\Backupfolder');
export const INVALID_USER_ACCOUNT = localize('sql.migration.invalid.user.account', "Invalid user account format. Example: {0}", 'Domain\\username');
export function TARGET_NAME_FOR_DATABASE(dbName: string): string {
return localize('sql.migration.target.name.for.database', 'Target name for database {0}', dbName);
}
export function TARGET_NETWORK_SHARE_LOCATION(dbName: string): string {
return localize('sql.migration.network.share.location', "Network share location to read backups for database {0}", dbName);
}
@@ -236,6 +236,7 @@ export const MODE = localize('sql.migration.mode', "Mode");
export const BACKUP_LOCATION = localize('sql.migration.backup.location', "Backup Location");
export const AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS = localize('sql.migration.azure.storage.account.to.upload.backups', "Azure Storage Account to Upload Backups");
export const SHIR = localize('sql.migration.shir', "Self-hosted Integration Runtime node");
export const TARGET_NAME = localize('sql.migration.summary.target.name', "Target Databases:");
// Open notebook quick pick string
export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform");

View File

@@ -538,7 +538,11 @@ export class MigrationCutoverDialog {
});
if (migrationStatusTextValue === MigrationStatus.InProgress) {
this._cutoverButton.enabled = tableData.length > 0;
const restoredCount = (this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets.filter(a => a.listOfBackupFiles[0].status === 'Restored'))?.length!;
if (restoredCount > 0) {
this._cutoverButton.enabled = true;
}
this._cancelButton.enabled = true;
} else {
this._cutoverButton.enabled = false;
this._cancelButton.enabled = false;

View File

@@ -28,11 +28,13 @@ export class MigrationLocalStorage {
if (refreshStatus) {
try {
const backupConfiguration = migration.migrationContext.properties.backupConfiguration;
const sourceDatabase = migration.migrationContext.properties.sourceDatabaseName;
migration.migrationContext = await getMigrationStatus(
migration.azureAccount,
migration.subscription,
migration.migrationContext
);
migration.migrationContext.properties.sourceDatabaseName = sourceDatabase;
migration.migrationContext.properties.backupConfiguration = backupConfiguration;
if (migration.asyncUrl) {
migration.asyncOperationResult = await getMigrationAsyncOperationDetails(

View File

@@ -55,26 +55,29 @@ export enum NetworkContainerType {
NETWORK_SHARE
}
export interface DatabaseBackupModel {
migrationMode: MigrationMode;
networkContainerType: NetworkContainerType;
storageKey: string;
networkShare: NetworkShare;
subscription: azureResource.AzureResourceSubscription;
blob: Blob;
}
export interface NetworkShare {
networkShareLocation: string;
windowsUser: string;
password: string;
}
export interface DatabaseBackupModel {
migrationMode: MigrationMode;
networkContainerType: NetworkContainerType;
networkShareLocation: string;
windowsUser: string;
password: string;
subscription: azureResource.AzureResourceSubscription;
resourceGroup: azureResource.AzureResourceResourceGroup;
storageAccount: StorageAccount;
storageKey: string;
azureSecurityToken: string;
fileShares: azureResource.FileShare[];
blobContainers: azureResource.BlobContainer[];
}
export interface Blob {
resourceGroup: azureResource.AzureResourceResourceGroup;
storageAccount: StorageAccount;
blobContainer: azureResource.BlobContainer;
}
export interface Model {
readonly sourceConnectionId: string;
readonly currentState: State;
@@ -140,6 +143,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
) {
this._currentState = State.INIT;
this._databaseBackup = {} as DatabaseBackupModel;
this._databaseBackup.networkShare = {} as NetworkShare;
this._databaseBackup.blob = {} as Blob;
}
public get sourceConnectionId(): string {
@@ -490,15 +495,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
return this._targetSqlVirtualMachines[index];
}
public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let storageAccountValues: azdata.CategoryValue[] = [];
if (!this._databaseBackup.resourceGroup) {
if (!resourceGroup) {
return storageAccountValues;
}
try {
const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription));
this._storageAccounts = storageAccount.filter(sa => {
return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === this._databaseBackup.resourceGroup.name.toLowerCase();
return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase();
});
this._storageAccounts.forEach((storageAccount) => {
storageAccountValues.push({
@@ -652,19 +657,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
properties: {
sourceDatabaseName: '',
migrationService: this._sqlMigrationService?.id!,
backupConfiguration: {
targetLocation: {
storageAccountResourceId: this._databaseBackup.storageAccount.id,
accountKey: this._databaseBackup.storageKey,
},
sourceLocation: {
fileShare: {
path: this._databaseBackup.networkShareLocation,
username: this._databaseBackup.windowsUser,
password: this._databaseBackup.password,
}
},
},
backupConfiguration: {},
sourceSqlConnection: {
dataSource: currentConnection?.serverName!,
authentication: this._authenticationType,
@@ -674,11 +667,39 @@ export class MigrationStateModel implements Model, vscode.Disposable {
scope: this._targetServerInstance.id
}
};
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
requestBody.properties.backupConfiguration = {
targetLocation: undefined!,
sourceLocation: {
azureBlob: {
storageAccountResourceId: this._databaseBackup.blob.storageAccount.id,
accountKey: this._databaseBackup.storageKey,
blobContainerName: this._databaseBackup.blob.blobContainer.name
}
}
};
break;
case NetworkContainerType.NETWORK_SHARE:
requestBody.properties.backupConfiguration = {
targetLocation: {
storageAccountResourceId: this._databaseBackup.networkShare.storageAccount.id,
accountKey: this._databaseBackup.storageKey,
},
sourceLocation: {
fileShare: {
path: this._databaseBackup.networkShare.networkShareLocation,
username: this._databaseBackup.networkShare.windowsUser,
password: this._databaseBackup.networkShare.password,
}
}
};
break;
}
for (let i = 0; i < this._migrationDbs.length; i++) {
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
try {
requestBody.properties.backupConfiguration.sourceLocation.fileShare!.path = this._databaseBackup.networkShareLocation;
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
const response = await startDatabaseMigration(
this._azureAccount,
this._targetSubscription,
@@ -687,6 +708,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._targetDatabaseNames[i],
requestBody
);
response.databaseMigration.properties.sourceDatabaseName = this._migrationDbs[i];
response.databaseMigration.properties.backupConfiguration = requestBody.properties.backupConfiguration!;
if (response.status === 201 || response.status === 200) {
MigrationLocalStorage.saveMigration(

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
private _refresh1!: azdata.ButtonComponent;
private _refresh2!: azdata.ButtonComponent;
private _firstEnter: boolean = true;
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.IR_PAGE_TITLE), migrationStateModel);
@@ -75,7 +76,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}
public async onPageEnter(): Promise<void> {
this.populateMigrationService();
if (this._firstEnter) {
this.populateMigrationService();
this._firstEnter = false;
}
this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
this.wizard.message = {

View File

@@ -45,6 +45,7 @@ export class MigrationModePage extends MigrationWizardPage {
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
name: buttonGroup,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
},
checked: true
@@ -53,6 +54,7 @@ export class MigrationModePage extends MigrationWizardPage {
const onlineDescription = view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
CSSStyles: {
'font-size': '13px',
'margin': '0 0 10px 20px'
}
}).component();
@@ -69,6 +71,7 @@ export class MigrationModePage extends MigrationWizardPage {
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
name: buttonGroup,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
},
}).component();
@@ -76,6 +79,7 @@ export class MigrationModePage extends MigrationWizardPage {
const offlineDescription = view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
CSSStyles: {
'font-size': '13px',
'margin': '0 0 10px 20px'
}
}).component();

View File

@@ -477,9 +477,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return true;
}
if (this.migrationStateModel._migrationDbs.length === 0) {
errors.push('Please select databases to migrate');
if (this._rbg.selectedCardId === undefined || this._rbg.selectedCardId === '') {
errors.push(constants.SELECT_TARGET_TO_CONTINUE);
}
if (this.migrationStateModel._migrationDbs.length === 0) {
errors.push(constants.SELECT_DATABASE_TO_MIGRATE);
}
if ((<azdata.CategoryValue>this._managedInstanceSubscriptionDropdown.value)?.displayName === constants.NO_SUBSCRIPTIONS_FOUND) {
errors.push(constants.INVALID_SUBSCRIPTION_ERROR);

View File

@@ -86,46 +86,40 @@ export class SummaryPage extends MigrationWizardPage {
flexContainer.addItems(
[
createInformationRow(this._view, constants.BACKUP_LOCATION, constants.NETWORK_SHARE),
createInformationRow(this._view, constants.NETWORK_SHARE, this.migrationStateModel._databaseBackup.networkShareLocation),
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.windowsUser),
createInformationRow(this._view, constants.NETWORK_SHARE, this.migrationStateModel._databaseBackup.networkShare.networkShareLocation),
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.networkShare.windowsUser),
createHeadingTextComponent(this._view, constants.AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS),
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.storageAccount.location),
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.storageAccount.resourceGroup!),
createInformationRow(this._view, constants.STORAGE_ACCOUNT, this.migrationStateModel._databaseBackup.storageAccount.name!),
createHeadingTextComponent(this._view, 'Target Databases:')
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.networkShare.storageAccount.location),
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.networkShare.storageAccount.resourceGroup!),
createInformationRow(this._view, constants.STORAGE_ACCOUNT, this.migrationStateModel._databaseBackup.networkShare.storageAccount.name!),
]
);
this.migrationStateModel._migrationDbs.forEach((db, index) => {
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_NAME_FOR_DATABASE(db), this.migrationStateModel._targetDatabaseNames[index]));
});
break;
case NetworkContainerType.FILE_SHARE:
flexContainer.addItems(
[
createInformationRow(this._view, constants.TYPE, constants.FILE_SHARE),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name),
]
);
this.migrationStateModel._migrationDbs.forEach((db, index) => {
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_NAME_FOR_DATABASE(db), this.migrationStateModel._targetDatabaseNames[index]));
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_FILE_SHARE(db), this.migrationStateModel._databaseBackup.fileShares[index].name));
});
break;
case NetworkContainerType.BLOB_CONTAINER:
flexContainer.addItems(
[
createInformationRow(this._view, constants.TYPE, constants.BLOB_CONTAINER),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name),
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.blob.storageAccount.location),
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.blob.storageAccount.resourceGroup!),
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.blob.storageAccount.name),
createInformationRow(this._view, constants.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.blob.blobContainer.name)
]
);
this.migrationStateModel._migrationDbs.forEach((db, index) => {
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_NAME_FOR_DATABASE(db), this.migrationStateModel._targetDatabaseNames[index]));
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_FILE_SHARE(db), this.migrationStateModel._databaseBackup.blobContainers[index].name));
});
}
flexContainer.addItem(createHeadingTextComponent(this._view, constants.TARGET_NAME));
this.migrationStateModel._migrationDbs.forEach((db, index) => {
flexContainer.addItem(createInformationRow(this._view, db, this.migrationStateModel._targetDatabaseNames[index]));
});
return flexContainer;
}
}