/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import * as azdata from 'azdata'; import * as azurecore from 'azurecore'; import { azureResource } from 'azureResource'; import * as loc from '../constants/strings'; async function getAzureCoreAPI(): Promise { const api = (await vscode.extensions.getExtension(azurecore.extension.name)?.activate()) as azurecore.IExtension; if (!api) { throw new Error('azure core API undefined for sql-migration'); } return api; } export type Subscription = azureResource.AzureResourceSubscription; export async function getSubscriptions(account: azdata.Account): Promise { const api = await getAzureCoreAPI(); const subscriptions = await api.getSubscriptions(account, false); let listOfSubscriptions = subscriptions.subscriptions; sortResourceArrayByName(listOfSubscriptions); return subscriptions.subscriptions; } export type AzureProduct = azureResource.AzureGraphResource; export async function getResourceGroups(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const result = await api.getResourceGroups(account, subscription, false); sortResourceArrayByName(result.resourceGroups); return result.resourceGroups; } export type SqlManagedInstance = AzureProduct; export async function getAvailableManagedInstanceProducts(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const result = await api.getSqlManagedInstances(account, [subscription], false); sortResourceArrayByName(result.resources); return result.resources; } export type SqlServer = AzureProduct; export async function getAvailableSqlServers(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const result = await api.getSqlServers(account, [subscription], false); return result.resources; } export type SqlVMServer = { properties: { virtualMachineResourceId: string, provisioningState: string, sqlImageOffer: string, sqlManagement: string, sqlImageSku: string }, location: string, id: string, name: string, type: string, tenantId: string, subscriptionId: string }; export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const path = `/subscriptions/${subscription.id}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=2017-03-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } sortResourceArrayByName(response.response.data.value); return response.response.data.value; } export type StorageAccount = AzureProduct; export async function getAvailableStorageAccounts(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const result = await api.getStorageAccounts(account, [subscription], false); sortResourceArrayByName(result.resources); return result.resources; } export async function getFileShares(account: azdata.Account, subscription: Subscription, storageAccount: StorageAccount): Promise { const api = await getAzureCoreAPI(); let result = await api.getFileShares(account, subscription, storageAccount, true); let fileShares = result.fileShares; sortResourceArrayByName(fileShares); return fileShares!; } export async function getBlobContainers(account: azdata.Account, subscription: Subscription, storageAccount: StorageAccount): Promise { const api = await getAzureCoreAPI(); let result = await api.getBlobContainers(account, subscription, storageAccount, true); let blobContainers = result.blobContainers; sortResourceArrayByName(blobContainers); return blobContainers!; } export async function getSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return response.response.data; } export async function getSqlMigrationServices(account: azdata.Account, subscription: Subscription, regionName: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration/sqlMigrationServices?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } sortResourceArrayByName(response.response.data.value); return response.response.data.value; } export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`; const requestBody = { 'location': regionName }; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return response.response.data; } export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/ListAuthKeys?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return { authKey1: response?.response?.data?.authKey1 ?? '', authKey2: response?.response?.data?.authKey2 ?? '' }; } export async function getStorageAccountAccessKeys(account: azdata.Account, subscription: Subscription, storageAccount: StorageAccount): Promise { const api = await getAzureCoreAPI(); const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/listKeys?api-version=2019-06-01`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return { keyName1: response?.response?.data?.keys[0].value ?? '', keyName2: response?.response?.data?.keys[0].value ?? '', }; } export async function getSqlMigrationServiceMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationService: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationService}/monitoringData?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } console.log(response); return response.response.data; } export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, targetServer: SqlManagedInstance | SqlVMServer, targetDatabaseName: string, requestBody: StartDatabaseMigrationRequest): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return { status: response.response.status, databaseMigration: response.response.data }; } export async function getDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, migrationId: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; const path = `${migrationId}?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { if (response.response.status === 404 && response.response.data.error.code === 'ResourceDoesNotExist') { throw new Error(response.response.data.error.code); } throw new Error(response.errors.toString()); } return response.response.data; } export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise { const api = await getAzureCoreAPI(); const host = `https://eastus2euap.management.azure.com`; const path = `${migration.id}?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return response.response.data; } export async function listMigrationsBySqlMigrationService(account: azdata.Account, subscription: Subscription, sqlMigrationService: SqlMigrationService): Promise { const api = await getAzureCoreAPI(); const host = `https://eastus2euap.management.azure.com`; const path = `${sqlMigrationService.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return response.response.data.value; } export async function startMigrationCutover(account: azdata.Account, subscription: Subscription, migrationStatus: DatabaseMigration): Promise { const api = await getAzureCoreAPI(); const host = `https://eastus2euap.management.azure.com`; const path = `${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cutover?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return response.response.data.value; } export async function stopMigration(account: azdata.Account, subscription: Subscription, migrationStatus: DatabaseMigration): Promise { const api = await getAzureCoreAPI(); const host = `https://eastus2euap.management.azure.com`; const path = `${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cancel?api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } } /** * For now only east us euap is supported. Actual API calls will be added in the public release. */ export function getSqlMigrationServiceRegions(): azdata.CategoryValue[] { return [ { displayName: loc.EASTUS2EUAP, name: 'eastus2euap' } ]; } type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.AzureResourceSubscription | SqlMigrationService; function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void { if (!resourceArray) { return; } resourceArray.sort((a: SortableAzureResources, b: SortableAzureResources) => { if (a.name.toLowerCase() < b.name.toLowerCase()) { return -1; } if (a.name.toLowerCase() > b.name.toLowerCase()) { return 1; } return 0; }); } export interface SqlMigrationServiceProperties { name: string; subscriptionId: string; resourceGroup: string; location: string; provisioningState: string; integrationRuntimeState?: string; isProvisioned?: boolean; } export interface SqlMigrationService { properties: SqlMigrationServiceProperties; location: string; id: string; name: string; error: { code: string, message: string } } export interface SqlMigrationServiceAuthenticationKeys { authKey1: string, authKey2: string } export interface GetStorageAccountAccessKeysResult { keyName1: string, keyName2: string } export interface IntegrationRuntimeMonitoringData { name: string, nodes: IntegrationRuntimeNode[]; } export interface IntegrationRuntimeNode { availableMemoryInMB: number, concurrentJobsLimit: number concurrentJobsRunning: number, cpuUtilization: number, nodeName: string receivedBytes: number sentBytes: number } export interface StartDatabaseMigrationRequest { location: string, properties: { sourceDatabaseName: string, migrationService: string, backupConfiguration: { targetLocation: { storageAccountResourceId: string, accountKey: string, } sourceLocation: { fileShare?: { path: string, username: string, password: string, }, azureBlob?: { storageAccountResourceId: string, accountKey: string, blobContainerName: string } }, }, sourceSqlConnection: { authentication: string, dataSource: string, username: string, password: string }, scope: string } } export interface StartDatabaseMigrationResponse { status: number, databaseMigration: DatabaseMigration } export interface DatabaseMigration { properties: DatabaseMigrationProperties; id: string; name: string; type: string; } export interface DatabaseMigrationProperties { scope: string; provisioningState: string; migrationStatus: string; migrationStatusDetails?: MigrationStatusDetails; startedOn: string; endedOn: string; sourceSqlConnection: SqlConnectionInfo; sourceDatabaseName: string; targetDatabaseCollation: string; migrationService: string; migrationOperationId: string; backupConfiguration: BackupConfiguration; autoCutoverConfiguration: AutoCutoverConfiguration; migrationFailureError: ErrorInfo; } export interface MigrationStatusDetails { migrationState: string; startedOn: string; endedOn: string; fullBackupSetInfo: BackupSetInfo; lastRestoredBackupSetInfo: BackupSetInfo; activeBackupSets: BackupSetInfo[]; blobContainerName: string; isFullBackupRestored: boolean; restoreBlockingReason: string; fileUploadBlockingErrors: string[]; currentRestoringFileName: string; lastRestoredFilename: string; } export interface SqlConnectionInfo { dataSource: string; authentication: string; username: string; password: string; encryptConnection: string; trustServerCertificate: string; } export interface BackupConfiguration { sourceLocation: SourceLocation; targetLocation: TargetLocation; } export interface AutoCutoverConfiguration { lastBackupName: string; } export interface ErrorInfo { code: string; message: string; } export interface BackupSetInfo { backupSetId: string; firstLSN: string; lastLSN: string; backupType: string; listOfBackupFiles: BackupFileInfo[]; backupStartDate: string; backupFinishDate: string; isBackupRestored: boolean; backupSize: number; compressedBackupSize: number; } export interface SourceLocation { fileShare: DatabaseMigrationFileShare; azureBlob: DatabaseMigrationAzureBlob; } export interface TargetLocation { storageAccountResourceId: string; accountKey: string; } export interface BackupFileInfo { fileName: string; status: string; } export interface DatabaseMigrationFileShare { path: string; username: string; password: string; } export interface DatabaseMigrationAzureBlob { storageAccountResourceId: string; accountKey: string; blobContainerName: string; }