From 5acdca2b702254f080741bb281046954e3fed516 Mon Sep 17 00:00:00 2001 From: Candice Ye Date: Thu, 16 Jun 2022 15:04:03 -0700 Subject: [PATCH] Add readable secondaries and sync secondary to commit to SQL MIAA create (#19740) * Added syncSecondaryToCommit to SQL update and create, as well as notebook, wizard, and compute+storage interfaces * Added readable secondaries and syncSecondaryToCommit to cost and SQL MI create * Added readable secondaries to notebook * removed resource-deployment changes Co-authored-by: Candice Ye --- .../deploy.sql.existing.arc.ipynb | 4 +- extensions/arc/package.json | 154 +++++++++++++++++- extensions/arc/package.nls.json | 5 + extensions/arc/src/common/pricingUtils.ts | 10 +- extensions/arc/src/extension.ts | 8 + extensions/arc/src/localizedConstants.ts | 1 + .../miaa/miaaComputeAndStoragePage.ts | 37 ++++- extensions/azcli/src/api.ts | 1 + extensions/azcli/src/az.ts | 3 +- extensions/azcli/src/typings/az-ext.d.ts | 15 +- 10 files changed, 225 insertions(+), 13 deletions(-) diff --git a/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb b/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb index 4f4d7ea959..f96b8f886a 100644 --- a/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb +++ b/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb @@ -114,6 +114,8 @@ "cores_limit_option = f' --cores-limit \"{sql_cores_limit}\"' if sql_cores_limit else \"\"\n", "memory_request_option = f' --memory-request \"{sql_memory_request}Gi\"' if sql_memory_request else \"\"\n", "memory_limit_option = f' --memory-limit \"{sql_memory_limit}Gi\"' if sql_memory_limit else \"\"\n", + "readable_secondaries = f' --readable-secondaries \"{readable_secondaries}\"' if readable_secondaries else \"\"\n", + "sync_secondary_to_commit = f' --sync-secondary-to-commit \"{sync_secondary_to_commit}\"' if sync_secondary_to_commit else \"\"\n", "\n", "storage_class_data_option = f' --storage-class-data \"{sql_storage_class_data}\"'if sql_storage_class_data else \"\"\n", "storage_class_datalogs_option = f' --storage-class-datalogs \"{sql_storage_class_datalogs}\"'if sql_storage_class_datalogs else \"\"\n", @@ -133,7 +135,7 @@ "\n", "os.environ[\"AZDATA_USERNAME\"] = sql_username\n", "os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n", - "cmd = f'az sql mi-arc create --name {sql_instance_name}{namespace}{resource_group}{location}{custom_location} --replicas {sql_replicas}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}{storage_class_data_option}{storage_class_datalogs_option}{storage_class_logs_option}{storage_class_backup_option}{volume_size_data}{volume_size_datalogs}{volume_size_logs}{volume_size_backups}{retention_days}{service_tier}{dev_use}{license_type}{cores_limit}{use_k8s}'\n", + "cmd = f'az sql mi-arc create --name {sql_instance_name}{namespace}{resource_group}{location}{custom_location} --replicas {sql_replicas}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}{readable_secondaries}{sync_secondary_to_commit}{storage_class_data_option}{storage_class_datalogs_option}{storage_class_logs_option}{storage_class_backup_option}{volume_size_data}{volume_size_datalogs}{volume_size_logs}{volume_size_backups}{retention_days}{service_tier}{dev_use}{license_type}{cores_limit}{use_k8s}'\n", "out=run_command()" ] } diff --git a/extensions/arc/package.json b/extensions/arc/package.json index 492ed15a9f..793edd2464 100644 --- a/extensions/arc/package.json +++ b/extensions/arc/package.json @@ -1122,6 +1122,10 @@ "variableName": "AZDATA_NB_VAR_SQL_REPLICAS", "options": { "values": [ + { + "name": "1", + "displayName": "%arc.sql.one.replica%" + }, { "name": "2", "displayName": "%arc.sql.two.replicas%" @@ -1131,7 +1135,7 @@ "displayName": "%arc.sql.three.replicas%" } ], - "defaultValue": "2", + "defaultValue": "3", "optionsType": "radio" }, "dynamicOptions": { @@ -1150,6 +1154,136 @@ ] } }, + { + "label": "%arc.sql.readable.secondaries.label%", + "description": "%arc.sql.readable.secondaries.description%", + "variableName": "AZDATA_NB_VAR_READABLE_SECONDARIES", + "type": "options", + "editable": false, + "options": { + "values": [ + { + "name": "0", + "displayName": "0" + }, + { + "name": "1", + "displayName": "1" + }, + { + "name": "2", + "displayName": "2" + } + ], + "defaultValue": "2", + "optionsType": "radio" + }, + "dynamicOptions": + { + "target": "AZDATA_NB_VAR_SQL_REPLICAS", + "alternates": [ + { + "selection": "1", + "alternateValues": [ + { + "name": "0", + "displayName": "0" + } + ], + "defaultValue": "0" + }, + { + "selection": "2", + "alternateValues": [ + { + "name": "0", + "displayName": "0" + }, + { + "name": "1", + "displayName": "1" + } + ], + "defaultValue": "1" + } + ] + }, + "enabled": { + "target": "AZDATA_NB_VAR_SQL_SERVICE_TIER", + "value": "BusinessCritical" + } + }, + { + "label": "%arc.sql.sync.secondaries.label%", + "description": "%arc.sql.sync.secondaries.description%", + "variableName": "AZDATA_NB_VAR_SYNC_SECONDARY_TO_COMMIT", + "type": "options", + "editable": false, + "options": { + "values": [ + { + "name": "-1", + "displayName": "-1" + }, + { + "name": "0", + "displayName": "0" + }, + { + "name": "1", + "displayName": "1" + }, + { + "name": "2", + "displayName": "2" + } + ], + "defaultValue": "-1", + "optionsType": "radio" + }, + "dynamicOptions": + { + "target": "AZDATA_NB_VAR_SQL_REPLICAS", + "alternates": [ + { + "selection": "1", + "alternateValues": [ + { + "name": "-1", + "displayName": "-1" + }, + { + "name": "0", + "displayName": "0" + } + ], + "defaultValue": "-1" + }, + { + "selection": "2", + "alternateValues": [ + { + "name": "-1", + "displayName": "-1" + }, + { + "name": "0", + "displayName": "0" + }, + { + "name": "1", + "displayName": "1" + } + ], + "defaultValue": "-1" + } + ] + }, + "enabled": { + "target": "AZDATA_NB_VAR_SQL_SERVICE_TIER", + "value": "BusinessCritical" + } + }, { "type": "checkbox", "label": "%arc.sql.license.type.label%", @@ -1327,6 +1461,18 @@ ] } }, + { + "label": "%arc.sql.cost.summary.billable.replicas%", + "type": "readonly_text", + "isEvaluated": true, + "defaultValue": "x 1", + "valueProvider": { + "providerId": "params-to-billable-replicas", + "triggerFields": [ + "AZDATA_NB_VAR_READABLE_SECONDARIES" + ] + } + }, { "label": "%arc.sql.cost.summary.azure.hybrid.benefit.discount%", "type": "readonly_text", @@ -1338,7 +1484,8 @@ "AZDATA_NB_VAR_SQL_CORES_LIMIT", "AZDATA_NB_VAR_SQL_DEV_USE", "AZDATA_NB_VAR_SQL_SERVICE_TIER", - "AZDATA_NB_VAR_SQL_LICENSE_TYPE" + "AZDATA_NB_VAR_SQL_LICENSE_TYPE", + "AZDATA_NB_VAR_READABLE_SECONDARIES" ] } }, @@ -1353,7 +1500,8 @@ "AZDATA_NB_VAR_SQL_CORES_LIMIT", "AZDATA_NB_VAR_SQL_DEV_USE", "AZDATA_NB_VAR_SQL_SERVICE_TIER", - "AZDATA_NB_VAR_SQL_LICENSE_TYPE" + "AZDATA_NB_VAR_SQL_LICENSE_TYPE", + "AZDATA_NB_VAR_READABLE_SECONDARIES" ] } } diff --git a/extensions/arc/package.nls.json b/extensions/arc/package.nls.json index 195106036d..0a5efdf2ff 100644 --- a/extensions/arc/package.nls.json +++ b/extensions/arc/package.nls.json @@ -109,6 +109,10 @@ "arc.sql.one.replica": "1 replica", "arc.sql.two.replicas": "2 replicas", "arc.sql.three.replicas": "3 replicas", + "arc.sql.sync.secondaries.label": "Synchronized secondaries required to commit", + "arc.sql.sync.secondaries.description": "The number of synchronous replicas required to commit a transaction before the primary replica is allowed to commit. Setting this value to -1 will set the number of required synchronized secondaries to '(# of replicas - 1) / 2', rounded down.", + "arc.sql.readable.secondaries.label": "Readable secondary replicas", + "arc.sql.readable.secondaries.description": "The number of readable secondary replicas.", "arc.storage-class.data.label": "Storage Class (Data)", "arc.sql.storage-class.data.description": "The storage class to be used for data (.mdf). If no value is specified, the default storage class will be used.", "arc.sql.cost.summary.sql.miaa.cost.summary": "SQL Managed Instance - Azure Arc Cost Summary", @@ -121,6 +125,7 @@ "arc.sql.cost.summary.business.critical": "Business Critical", "arc.sql.cost.summary.cost.vcore": "Cost per vCore (in USD)", "arc.sql.cost.summary.vcore.limit": "CPU vCores Limit", + "arc.sql.cost.summary.billable.replicas": "Billable replicas", "arc.sql.cost.summary.azure.hybrid.benefit.discount": "Azure Hybrid Benefit discount (in USD)", "arc.sql.cost.summary.sql.connection.info": "SQL Connection Information", "arc.sql.cost.summary.sql.instance.settings": "SQL Instance Settings", diff --git a/extensions/arc/src/common/pricingUtils.ts b/extensions/arc/src/common/pricingUtils.ts index c0e302156e..52cc9a4b0f 100644 --- a/extensions/arc/src/common/pricingUtils.ts +++ b/extensions/arc/src/common/pricingUtils.ts @@ -44,6 +44,12 @@ export const serviceTierVarName = 'AZDATA_NB_VAR_SQL_SERVICE_TIER'; export const devUseVarName = 'AZDATA_NB_VAR_SQL_DEV_USE'; export const vcoresLimitVarName = 'AZDATA_NB_VAR_SQL_CORES_LIMIT'; export const licenseTypeVarName = 'AZDATA_NB_VAR_SQL_LICENSE_TYPE'; +export const readableSecondaries = 'AZDATA_NB_VAR_READABLE_SECONDARIES'; + +// Gets number of replicas charged +export function numBillableReplicas(mapping: { [key: string]: InputValueType }): number { + return 1 + Math.max(0, mapping[readableSecondaries] - 1); +} // Estimated base price for one vCore. export function estimatedBasePriceForOneVCore(mapping: { [key: string]: InputValueType }): number { @@ -87,12 +93,12 @@ export function numCores(mapping: { [key: string]: InputValueType }): number { // Full price for all selected vCores. export function vCoreFullPriceForAllCores(mapping: { [key: string]: InputValueType }): number { - return fullPriceForOneVCore(mapping) * numCores(mapping); + return fullPriceForOneVCore(mapping) * numCores(mapping) * numBillableReplicas(mapping); } // SQL Server License price for all vCores. This is shown on the cost summary card if customer has SQL server license. export function vCoreSqlServerLicensePriceForAllCores(mapping: { [key: string]: InputValueType }): number { - return estimatedSqlServerLicensePriceForOneVCore(mapping) * numCores(mapping); + return estimatedSqlServerLicensePriceForOneVCore(mapping) * numCores(mapping) * numBillableReplicas(mapping); } // If the customer doesn't already have SQL Server License, AHB discount is set to zero because the price will be included diff --git a/extensions/arc/src/extension.ts b/extensions/arc/src/extension.ts index 1977fcc0b7..544cf31e2c 100644 --- a/extensions/arc/src/extension.ts +++ b/extensions/arc/src/extension.ts @@ -89,6 +89,14 @@ export async function activate(context: vscode.ExtensionContext): Promise { + return 'x ' + pricing.numBillableReplicas(mapping).toString(); + } + })); + // Register valueprovider for getting the amount of hybrid benefit discount to be applied. context.subscriptions.push(rdApi.registerValueProvider({ id: 'params-to-hybrid-benefit-discount', diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index cc47f4bfcc..7e51e38442 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -223,6 +223,7 @@ export const coordinatorMemoryLimit = localize('arc.coordinatorMemoryLimit', "Co export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB)"); export const workerMemoryRequest = localize('arc.workerMemoryRequest', "Worker Nodes Memory request (in GB)"); export const coordinatorMemoryRequest = localize('arc.coordinatorMemoryRequest', "Coordinator Node Memory request (in GB)"); +export const syncSecondaryToCommit = localize('arc.syncSecondaryToCommit', "Sync Secondary To Commit"); export const arcResources = localize('arc.arcResources', "Azure Arc Resources"); export const enterANonEmptyPassword = localize('arc.enterANonEmptyPassword', "Enter a non empty password or press escape to exit."); export const thePasswordsDoNotMatch = localize('arc.thePasswordsDoNotMatch', "The passwords do not match. Confirm the password or press escape to exit."); diff --git a/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts b/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts index b1bb272014..8eccab803c 100644 --- a/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts +++ b/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts @@ -19,6 +19,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage { private coresRequestBox?: azdata.InputBoxComponent; private memoryLimitBox?: azdata.InputBoxComponent; private memoryRequestBox?: azdata.InputBoxComponent; + private syncSecondaryToCommitBox?: azdata.InputBoxComponent; private discardButton?: azdata.ButtonComponent; private saveButton?: azdata.ButtonComponent; @@ -27,7 +28,8 @@ export class MiaaComputeAndStoragePage extends DashboardPage { coresLimit?: string, coresRequest?: string, memoryLimit?: string, - memoryRequest?: string + memoryRequest?: string, + syncSecondaryToCommit?: string } = {}; private readonly _azApi: azExt.IExtension; @@ -180,6 +182,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage { try { this.editCores(); this.editMemory(); + this.editSyncSecondaryToCommit(); } catch (error) { vscode.window.showErrorMessage(loc.pageDiscardFailed(error)); } finally { @@ -266,19 +269,39 @@ export class MiaaComputeAndStoragePage extends DashboardPage { }) ); + this.syncSecondaryToCommitBox = this.modelView.modelBuilder.inputBox().withProps({ + readOnly: false, + min: -1, + inputType: 'number', + placeHolder: loc.loading, + ariaLabel: loc.syncSecondaryToCommit + }).component(); + + this.disposables.push( + this.syncSecondaryToCommitBox.onTextChanged(() => { + if (!(this.handleOnTextChanged(this.syncSecondaryToCommitBox!))) { + this.saveArgs.syncSecondaryToCommit = undefined; + } else { + this.saveArgs.syncSecondaryToCommit = this.syncSecondaryToCommitBox!.value; + } + }) + ); + } private createUserInputSection(): azdata.Component[] { if (this._miaaModel.configLastUpdated) { this.editCores(); this.editMemory(); + this.editSyncSecondaryToCommit(); } return [ this.createConfigurationSectionContainer(loc.coresRequest, this.coresRequestBox!), this.createConfigurationSectionContainer(loc.coresLimit, this.coresLimitBox!), this.createConfigurationSectionContainer(loc.memoryRequest, this.memoryRequestBox!), - this.createConfigurationSectionContainer(loc.memoryLimit, this.memoryLimitBox!) + this.createConfigurationSectionContainer(loc.memoryLimit, this.memoryLimitBox!), + this.createConfigurationSectionContainer(loc.syncSecondaryToCommit, this.syncSecondaryToCommitBox!), ]; } @@ -380,8 +403,18 @@ export class MiaaComputeAndStoragePage extends DashboardPage { this.saveArgs.memoryLimit = undefined; } + private editSyncSecondaryToCommit(): void { + let currentSyncSecondaryToCommit = this._miaaModel.config?.spec?.syncSecondaryToCommit; + + this.syncSecondaryToCommitBox!.placeHolder = currentSyncSecondaryToCommit!; + this.syncSecondaryToCommitBox!.value = ''; + + this.saveArgs.syncSecondaryToCommit = undefined; + } + private handleServiceUpdated() { this.editCores(); this.editMemory(); + this.editSyncSecondaryToCommit(); } } diff --git a/extensions/azcli/src/api.ts b/extensions/azcli/src/api.ts index 706859a10d..885722f742 100644 --- a/extensions/azcli/src/api.ts +++ b/extensions/azcli/src/api.ts @@ -167,6 +167,7 @@ export function getAzApi(localAzDiscovered: Promise, azTool memoryLimit?: string; memoryRequest?: string; noWait?: boolean; + syncSecondaryToCommit?: string; }, // Direct mode arguments resourceGroup?: string, diff --git a/extensions/azcli/src/az.ts b/extensions/azcli/src/az.ts index 7b6cc5d1c2..bb7ef05072 100644 --- a/extensions/azcli/src/az.ts +++ b/extensions/azcli/src/az.ts @@ -247,7 +247,8 @@ export class AzTool implements azExt.IAzApi { memoryLimit?: string, memoryRequest?: string, noWait?: boolean, - retentionDays?: string + retentionDays?: string, + syncSecondaryToCommit?: string }, // Direct mode arguments resourceGroup?: string, diff --git a/extensions/azcli/src/typings/az-ext.d.ts b/extensions/azcli/src/typings/az-ext.d.ts index e6c1323fcf..d2067eea6f 100644 --- a/extensions/azcli/src/typings/az-ext.d.ts +++ b/extensions/azcli/src/typings/az-ext.d.ts @@ -142,7 +142,9 @@ declare module 'az-ext' { spec: { backup?: { retentionPeriodInDays: number, // 1 - } + }, + readableSecondaries: string, // 0 + syncSecondaryToCommit: string, // -1, scheduling?: { default?: { resources?: { @@ -150,10 +152,10 @@ declare module 'az-ext' { requests?: SchedulingOptions } } - } + }, services: { primary: ServiceSpec - } + }, storage: { data: { volumes: StorageVolume[] @@ -188,7 +190,9 @@ declare module 'az-ext' { spec: { backup?: { retentionPeriodInDays: number, // 1 - } + }, + readableSecondaries: string, // 0 + syncSecondaryToCommit: string, // -1 scheduling?: { default?: { resources?: { @@ -248,6 +252,8 @@ declare module 'az-ext' { namespace: string, // namespace-name }, replicas: number, // 1, + readableSecondaries: string, // 0 + syncSecondaryToCommit: string, // -1, scheduling: { additionalProperties: string, // null, default: { @@ -607,6 +613,7 @@ declare module 'az-ext' { memoryRequest?: string, //1Gi noWait?: boolean, //true retentionDays?: string, //5 + syncSecondaryToCommit?: string //2 }, // Direct mode arguments resourceGroup?: string,