diff --git a/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.spec.ts b/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.spec.ts index 584ac8fcb8..55207eca39 100644 --- a/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.spec.ts +++ b/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.spec.ts @@ -10,6 +10,7 @@ import { ElectronTestingModule } from "@batch-flask/electron/testing"; import { BreadcrumbService } from "@batch-flask/ui/breadcrumbs"; import { TableTestingModule } from "@batch-flask/ui/testing"; import { AppLicensePickerComponent } from "app/components/pool/action/add"; +import { SoftwareBillingUnit } from "app/models"; import { PricingService } from "app/services"; import { SoftwarePricing } from "app/services/pricing"; import { of } from "rxjs"; @@ -22,8 +23,8 @@ class TestComponent { } const pricing = new SoftwarePricing(); -pricing.add("maya", 12, false); -pricing.add("arnold", 5, true); +pricing.add("maya", 12, SoftwareBillingUnit.node); +pricing.add("arnold", 5, SoftwareBillingUnit.node); describe("AppLicensePickerComponent", () => { let fixture: ComponentFixture; @@ -64,9 +65,9 @@ describe("AppLicensePickerComponent", () => { fixture.detectChanges(); }); - it("Should show 4 licenses", () => { + it("Should show 5 licenses", () => { const tableRows = debugElement.queryAll(By.css("bl-row-render")); - expect(tableRows.length).toBe(4); + expect(tableRows.length).toBe(5); const row1Columns = tableRows[0].queryAll(By.css(".bl-table-cell")); expect(row1Columns.length).toBe(3, "Row has 3 columns"); @@ -82,12 +83,17 @@ describe("AppLicensePickerComponent", () => { const row3Columns = tableRows[2].queryAll(By.css(".bl-table-cell")); expect(row3Columns[0].nativeElement.textContent).toContain("Autodesk Arnold"); expect(row3Columns[1].nativeElement.textContent).toContain("EULA"); - expect(row3Columns[2].nativeElement.textContent).toContain("$5/core/hour"); + expect(row3Columns[2].nativeElement.textContent).toContain("$5/node/hour"); const row4Columns = tableRows[3].queryAll(By.css(".bl-table-cell")); expect(row4Columns[0].nativeElement.textContent).toContain("Chaos Group V-Ray"); expect(row4Columns[1].nativeElement.textContent).toContain("EULA"); expect(row4Columns[2].nativeElement.textContent).toContain("-"); + + const row5Columns = tableRows[4].queryAll(By.css(".bl-table-cell")); + expect(row5Columns[0].nativeElement.textContent).toContain("Chaos Group V-Ray RT"); + expect(row5Columns[1].nativeElement.textContent).toContain("EULA"); + expect(row5Columns[2].nativeElement.textContent).toContain("-"); }); it("Should select license by checking checkbox", () => { diff --git a/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.ts b/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.ts index 72b96e4804..32979caf8a 100644 --- a/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.ts +++ b/src/app/components/pool/action/add/app-license-picker/app-license-picker.component.ts @@ -30,6 +30,10 @@ const softwares = [ id: "vray", description: "Chaos Group V-Ray", }, + { + id: "vrayrt", + description: "Chaos Group V-Ray RT", + }, ]; @Component({ @@ -134,8 +138,7 @@ export class AppLicensePickerComponent implements ControlValueAccessor, OnInit, const cost = this._pricing && this._pricing.get(software.id); let costStr = "-"; if (cost) { - const unit = cost.perCore ? "core" : "node"; - costStr = `$${cost.price}/${unit}/hour`; + costStr = `$${cost.price}/${cost.billingUnit as string}/hour`; } return new ApplicationLicense({ ...software, diff --git a/src/app/components/pool/action/add/license-eula-dialog/license-eula-dialog.component.ts b/src/app/components/pool/action/add/license-eula-dialog/license-eula-dialog.component.ts index d72baf88ea..0802dac43a 100644 --- a/src/app/components/pool/action/add/license-eula-dialog/license-eula-dialog.component.ts +++ b/src/app/components/pool/action/add/license-eula-dialog/license-eula-dialog.component.ts @@ -22,7 +22,8 @@ export class LicenseEulaDialogComponent { } public get isVray(): boolean { - return this.license && this.license.id === "vray"; + return this.license && (this.license.id === "vray" + || this.license.id === "vrayrt"); } public openLink(link: string) { diff --git a/src/app/models/batch-software-license.ts b/src/app/models/batch-software-license.ts index daf31d66f3..4a945bc39b 100644 --- a/src/app/models/batch-software-license.ts +++ b/src/app/models/batch-software-license.ts @@ -6,4 +6,5 @@ export enum BatchSoftwareLicense { arnold = "arnold", maya = "maya", vray = "vray", + vrayrt = "vrayrt", } diff --git a/src/app/models/index.ts b/src/app/models/index.ts index 89dd3ac689..70e95b4df8 100644 --- a/src/app/models/index.ts +++ b/src/app/models/index.ts @@ -14,6 +14,7 @@ export * from "./autoscale-formula"; export * from "./azure-batch"; export * from "./batch-account"; export * from "./batch-software-license"; +export * from "./software-billing-unit"; export * from "./blob-container"; export * from "./certificate-reference"; export * from "./certificate"; diff --git a/src/app/models/software-billing-unit.ts b/src/app/models/software-billing-unit.ts new file mode 100644 index 0000000000..80df7e0b98 --- /dev/null +++ b/src/app/models/software-billing-unit.ts @@ -0,0 +1,8 @@ +/** + * List of pricing units for software licenses + */ +export enum SoftwareBillingUnit { + core = "core", + gpu = "gpu", + node = "node", +} diff --git a/src/app/services/pricing.service.ts b/src/app/services/pricing.service.ts index 60e52d45f1..c23db1a1bb 100644 --- a/src/app/services/pricing.service.ts +++ b/src/app/services/pricing.service.ts @@ -1,7 +1,7 @@ import { Injectable } from "@angular/core"; import { GlobalStorage } from "@batch-flask/core"; import { log } from "@batch-flask/utils"; -import { ArmBatchAccount, BatchSoftwareLicense, Pool, RateCardMeter } from "app/models"; +import { ArmBatchAccount, BatchSoftwareLicense, Pool, RateCardMeter, SoftwareBillingUnit } from "app/models"; import { BatchPricing, OSPricing, OsType, SoftwarePricing, VMPrices } from "app/services/pricing"; import { PoolPrice, PoolPriceOptions, PoolUtils } from "app/utils"; import { DateTime } from "luxon"; @@ -55,10 +55,26 @@ const regionMapping = { }; const softwareMeterId = { - "089f79d8-0349-432c-96a6-8add90b8a40e": BatchSoftwareLicense.arnold, - "0ec88494-2022-4939-b809-0d914d954692": BatchSoftwareLicense["3dsmax"], - "1d3bb602-0cde-4618-9fb0-f9d94805c2a6": BatchSoftwareLicense.maya, - "e2d2d63e-8741-499a-8989-f5f7ec5c3b3f": BatchSoftwareLicense.vray, + "da155550-4041-54ce-bf5c-385c0bd5eaba": { + license: BatchSoftwareLicense.arnold, + billingUnit: SoftwareBillingUnit.node, + }, + "0ec88494-2022-4939-b809-0d914d954692": { + license: BatchSoftwareLicense["3dsmax"], + billingUnit: SoftwareBillingUnit.node, + }, + "1d3bb602-0cde-4618-9fb0-f9d94805c2a6": { + license: BatchSoftwareLicense.maya, + billingUnit: SoftwareBillingUnit.node, + }, + "e2d2d63e-8741-499a-8989-f5f7ec5c3b3f": { + license: BatchSoftwareLicense.vray, + billingUnit: SoftwareBillingUnit.core, + }, + "450f680c-b109-486a-8fec-2b9e7ab0fbc9": { + license: BatchSoftwareLicense.vrayrt, + billingUnit: SoftwareBillingUnit.gpu, + }, }; @Injectable({ providedIn: "root" }) @@ -172,12 +188,17 @@ export class PricingService { return pricing; } + /** + * Sets the software prices using meter IDs + * @param meters RateCardMeter[] + * @param pricing BatchPricing + */ private _processSoftwaresPricings(meters: RateCardMeter[], pricing: BatchPricing) { for (const meter of meters) { if (meter.MeterId in softwareMeterId) { - const software = softwareMeterId[meter.MeterId]; - const perCore = meter.MeterName.toLowerCase().includes("1 vcpu"); - pricing.softwares.add(software, meter.MeterRates["0"], perCore); + const software = softwareMeterId[meter.MeterId].license; + const unit = softwareMeterId[meter.MeterId].billingUnit; + pricing.softwares.add(software, meter.MeterRates["0"], unit); } } } diff --git a/src/app/services/pricing/pricing.model.ts b/src/app/services/pricing/pricing.model.ts index b3cc7b4496..d3bd9abb37 100644 --- a/src/app/services/pricing/pricing.model.ts +++ b/src/app/services/pricing/pricing.model.ts @@ -1,3 +1,5 @@ +import { SoftwareBillingUnit } from "app/models/software-billing-unit"; + export type OsType = "linux" | "windows"; export interface RegionPrices { @@ -132,7 +134,7 @@ export class NodePricing { export interface SoftwarePrice { name: string; price: number; - perCore: boolean; + billingUnit: SoftwareBillingUnit; } export class SoftwarePricing { @@ -143,11 +145,11 @@ export class SoftwarePricing { } private _map: Map = new Map(); - public add(software: string, price: number, perCore = false) { + public add(software: string, price: number, billingUnit: SoftwareBillingUnit) { this._map.set(software, { name: software, price, - perCore, + billingUnit, }); } @@ -162,7 +164,7 @@ export class SoftwarePricing { public getPrice(software: string, coreCount = 1): number { const data = this._map.get(software); if (!data) { return null; } - return data.perCore ? coreCount * data.price : data.price; + return data.billingUnit === "core" ? coreCount * data.price : data.price; } } diff --git a/src/app/utils/pool-utils.spec.ts b/src/app/utils/pool-utils.spec.ts index 75f2989701..b9f3b3d4b0 100644 --- a/src/app/utils/pool-utils.spec.ts +++ b/src/app/utils/pool-utils.spec.ts @@ -1,4 +1,4 @@ -import { CloudServiceOsFamily, Pool, VmSize } from "app/models"; +import { CloudServiceOsFamily, Pool, SoftwareBillingUnit, VmSize } from "app/models"; import { SoftwarePricing } from "app/services/pricing"; import { PoolUtils } from "app/utils"; @@ -183,9 +183,9 @@ describe("PoolUtils", () => { } as any); const softwares = new SoftwarePricing(); - softwares.add("vray", 0.02, true); - softwares.add("3dsmax", 0.65, false); - softwares.add("maya", 0.75, false); + softwares.add("vray", 0.02, SoftwareBillingUnit.core); + softwares.add("3dsmax", 0.65, SoftwareBillingUnit.node); + softwares.add("maya", 0.75, SoftwareBillingUnit.node); it("works for a basic pool", () => { const windowsConfig = {