diff --git a/ui/src/app/components/common-env-conf-panels/environment-informed-action-panel.tsx b/ui/src/app/components/common-env-conf-panels/environment-informed-action-panel.tsx
index 47e94787a9a..bee7997da9b 100644
--- a/ui/src/app/components/common-env-conf-panels/environment-informed-action-panel.tsx
+++ b/ui/src/app/components/common-env-conf-panels/environment-informed-action-panel.tsx
@@ -122,7 +122,6 @@ export const EnvironmentInformedActionPanel = ({
)}
diff --git a/ui/src/app/components/runtime-configuration-panel.tsx b/ui/src/app/components/runtime-configuration-panel.tsx
index 3060f770955..ee07ca7fd03 100644
--- a/ui/src/app/components/runtime-configuration-panel.tsx
+++ b/ui/src/app/components/runtime-configuration-panel.tsx
@@ -32,7 +32,8 @@ import { findCdrVersion } from 'app/utils/cdr-versions';
import {
ComputeType,
DATAPROC_MIN_DISK_SIZE_GB,
- machineRunningCost,
+ derivePdFromAnalysisConfig,
+ machineRunningCostPerHour,
MIN_DISK_SIZE_GB,
} from 'app/utils/machines';
import {
@@ -197,7 +198,13 @@ export const getErrorsAndWarnings = ({
};
const runningCostErrors = validate(
- { currentRunningCost: machineRunningCost(analysisConfig) },
+ {
+ currentRunningCost: machineRunningCostPerHour({
+ ...analysisConfig,
+ // temp derive from analysisConfig
+ persistentDisk: derivePdFromAnalysisConfig(analysisConfig),
+ }),
+ },
{
currentRunningCost: runningCostValidatorWithMessage(),
}
@@ -363,10 +370,9 @@ export const RuntimeConfigurationPanel = fp.flow(
(analysisConfig.computeType === ComputeType.Dataproc &&
!analysisConfig.diskConfig.detachable));
- let runtimeCannotBeCreatedExplanation;
- if (workspace.billingStatus !== BillingStatus.ACTIVE) {
- runtimeCannotBeCreatedExplanation = BILLING_ACCOUNT_DISABLED_TOOLTIP;
- }
+ const runtimeCannotBeCreatedExplanation =
+ workspace.billingStatus !== BillingStatus.ACTIVE &&
+ BILLING_ACCOUNT_DISABLED_TOOLTIP;
const runtimeCanBeUpdated =
runtimeCanBeCreated &&
diff --git a/ui/src/app/components/runtime-configuration-panel/customize-panel.tsx b/ui/src/app/components/runtime-configuration-panel/customize-panel.tsx
index a6115dda0a6..e8b3c163a05 100644
--- a/ui/src/app/components/runtime-configuration-panel/customize-panel.tsx
+++ b/ui/src/app/components/runtime-configuration-panel/customize-panel.tsx
@@ -346,9 +346,15 @@ export const CustomizePanel = ({
gcePersistentDisk
);
+ const dataprocConfig = analysisConfig.dataprocConfig && {
+ ...analysisConfig.dataprocConfig,
+ masterDiskSize: diskConfig.size,
+ };
+
setAnalysisConfig({
...analysisConfig,
diskConfig,
+ dataprocConfig,
detachedDisk: diskConfig.detachable ? null : gcePersistentDisk,
});
}}
diff --git a/ui/src/app/components/runtime-configuration-panel/offer-delete-disk-with-update.tsx b/ui/src/app/components/runtime-configuration-panel/offer-delete-disk-with-update.tsx
index d3bf9de4700..2e0e59bf532 100644
--- a/ui/src/app/components/runtime-configuration-panel/offer-delete-disk-with-update.tsx
+++ b/ui/src/app/components/runtime-configuration-panel/offer-delete-disk-with-update.tsx
@@ -10,7 +10,7 @@ import { FlexRow } from 'app/components/flex';
import { ClrIcon } from 'app/components/icons';
import { RadioButton } from 'app/components/inputs';
import colors from 'app/styles/colors';
-import { detachableDiskPricePerMonth } from 'app/utils/machines';
+import { persistentDiskPricePerMonth } from 'app/utils/machines';
import { formatUsd } from 'app/utils/numbers';
const { useState, Fragment } = React;
@@ -69,7 +69,7 @@ export const OfferDeleteDiskWithUpdate = ({
Your disk will be saved for later and can be reattached when you
next configure a standard VM analysis environment. You will continue
to incur persistent disk cost at{' '}
- {formatUsd(detachableDiskPricePerMonth(disk))} per month.
+ {formatUsd(persistentDiskPricePerMonth(disk))} per month.
diff --git a/ui/src/app/utils/analysis-config.spec.tsx b/ui/src/app/utils/analysis-config.spec.tsx
index 56f9f47d25f..c04cb8841a7 100644
--- a/ui/src/app/utils/analysis-config.spec.tsx
+++ b/ui/src/app/utils/analysis-config.spec.tsx
@@ -1,5 +1,4 @@
import {
- DataprocConfig,
Disk,
DiskType,
GpuConfig,
@@ -813,19 +812,11 @@ describe(withAnalysisConfigDefaults.name, () => {
existingDiskName: null,
};
- // yes it removes 3 fields. why?
- const expectedDataprocConfig: DataprocConfig = {
- ...inputConfig.dataprocConfig,
- masterMachineType: undefined,
- masterDiskSize: undefined,
- numberOfWorkerLocalSSDs: undefined,
- };
-
const outConfig = withAnalysisConfigDefaults(inputConfig, inputDisk);
expect(outConfig.computeType).toEqual(ComputeType.Dataproc);
expect(outConfig.diskConfig).toEqual(expectedDiskConfig);
- expect(outConfig.dataprocConfig).toEqual(expectedDataprocConfig);
+ expect(outConfig.dataprocConfig).toEqual(inputConfig.dataprocConfig);
expect(outConfig.gpuConfig).toBeNull();
expect(outConfig.detachedDisk).toEqual(inputDisk);
@@ -839,13 +830,7 @@ describe(withAnalysisConfigDefaults.name, () => {
);
});
- const replaceableFields = [
- 'numberOfWorkers',
- 'workerMachineType',
- 'workerDiskSize',
- 'numberOfPreemptibleWorkers',
- ];
- it('replaces the replaceableFields with their defaults when dataprocConfig is missing', () => {
+ it('sets dataprocConfig to the preset default when it is missing', () => {
const inputConfig = {
...defaultAnalysisConfig,
computeType: ComputeType.Dataproc,
@@ -853,34 +838,11 @@ describe(withAnalysisConfigDefaults.name, () => {
};
const outConfig = withAnalysisConfigDefaults(inputConfig, undefined);
-
- replaceableFields.forEach((field: string) =>
- expect(outConfig.dataprocConfig[field]).toEqual(
- runtimePresets().hailAnalysis.runtimeTemplate.dataprocConfig[field]
- )
+ expect(outConfig.dataprocConfig).toEqual(
+ runtimePresets().hailAnalysis.runtimeTemplate.dataprocConfig
);
});
- test.each(replaceableFields)(
- "it replaces %s with the default when it's missing",
- (field: string) => {
- const inputConfig = {
- ...defaultAnalysisConfig,
- computeType: ComputeType.Dataproc,
- dataprocConfig: {
- ...defaultAnalysisConfig.dataprocConfig,
- [field]: undefined,
- },
- };
-
- const outConfig = withAnalysisConfigDefaults(inputConfig, undefined);
-
- expect(outConfig.dataprocConfig[field]).toEqual(
- runtimePresets().hailAnalysis.runtimeTemplate.dataprocConfig[field]
- );
- }
- );
-
// same as Standard VM
it("should replace a missing diskConfig size with the persistent disk's when it exists", () => {
const inputConfig = {
diff --git a/ui/src/app/utils/analysis-config.tsx b/ui/src/app/utils/analysis-config.tsx
index abeac7c1509..e3494a60c89 100644
--- a/ui/src/app/utils/analysis-config.tsx
+++ b/ui/src/app/utils/analysis-config.tsx
@@ -184,9 +184,15 @@ export const withAnalysisConfigDefaults = (
dataprocConfig = {
numberOfWorkers:
dataprocConfig?.numberOfWorkers ?? defaults.numberOfWorkers,
+ masterMachineType:
+ dataprocConfig?.masterMachineType ?? defaults.masterMachineType,
+ masterDiskSize: dataprocConfig?.masterDiskSize ?? defaults.masterDiskSize,
workerMachineType:
dataprocConfig?.workerMachineType ?? defaults.workerMachineType,
workerDiskSize: dataprocConfig?.workerDiskSize ?? defaults.workerDiskSize,
+ numberOfWorkerLocalSSDs:
+ dataprocConfig?.numberOfWorkerLocalSSDs ??
+ defaults.numberOfWorkerLocalSSDs,
numberOfPreemptibleWorkers:
dataprocConfig?.numberOfPreemptibleWorkers ??
defaults.numberOfPreemptibleWorkers,
diff --git a/ui/src/app/utils/machines.ts b/ui/src/app/utils/machines.ts
index 953a0cbcb1a..a0a7132e33e 100644
--- a/ui/src/app/utils/machines.ts
+++ b/ui/src/app/utils/machines.ts
@@ -1,7 +1,12 @@
import * as fp from 'lodash/fp';
import { SelectItem } from 'primereact/selectitem';
-import { Disk, DiskType } from 'generated/fetch';
+import {
+ DataprocConfig,
+ DiskType,
+ GpuConfig,
+ PersistentDiskRequest,
+} from 'generated/fetch';
import { DEFAULT, switchCase } from '@terra-ui-packages/core-utils';
@@ -371,24 +376,29 @@ export const DEFAULT_MACHINE_TYPE: Machine =
export const DEFAULT_DISK_SIZE = MIN_DISK_SIZE_GB;
const approxHoursPerMonth = 730;
-export const diskPricePerMonth = 0.04; // per GB month
-export const diskPrice = diskPricePerMonth / approxHoursPerMonth; // per GB hour, from https://cloud.google.com/compute/pricing
-export const ssdPricePerMonth = 0.17; // per GB month
-export const dataprocCpuPrice = 0.01; // dataproc costs $0.01 per cpu per hour
+const standardDiskPricePerMonth = 0.04; // per GB month, from https://cloud.google.com/compute/pricing
+const ssdPricePerMonth = 0.17; // per GB month
+const dataprocCpuPricePerHour = 0.01; // dataproc costs $0.01 per cpu per hour
-const dataprocSurcharge = ({
+interface DataprocSurcharge {
+ masterMachine: Machine;
+ workerMachine: Machine;
+ numberOfWorkers: number;
+ numberOfPreemptibleWorkers: number;
+}
+const dataprocCpuSurchargePerHour = ({
masterMachine,
+ workerMachine,
numberOfWorkers,
numberOfPreemptibleWorkers,
- workerMachine,
-}) => {
- const costs = [masterMachine.cpu * dataprocCpuPrice];
+}: DataprocSurcharge) => {
+ const costs = [masterMachine.cpu * dataprocCpuPricePerHour];
if (workerMachine && numberOfWorkers) {
- costs.push(numberOfWorkers * workerMachine.cpu * dataprocCpuPrice);
+ costs.push(numberOfWorkers * workerMachine.cpu * dataprocCpuPricePerHour);
}
if (workerMachine && numberOfPreemptibleWorkers) {
costs.push(
- numberOfPreemptibleWorkers * workerMachine.cpu * dataprocCpuPrice
+ numberOfPreemptibleWorkers * workerMachine.cpu * dataprocCpuPricePerHour
);
}
return fp.sum(costs);
@@ -397,87 +407,123 @@ const dataprocSurcharge = ({
// The following calculations were based off of Terra UI's cost estimator:
// https://github.com/DataBiosphere/terra-ui/blob/cf5ec4408db3bd1fcdbcc5302da62d42e4d03ca3/src/components/ClusterManager.js#L85
-export const diskConfigPricePerMonth = ({
- size,
- detachableType,
-}: Partial) => {
+const diskPricePerMonth = ({ size, detachableType }: Partial) => {
return (
size *
- (detachableType === DiskType.SSD ? ssdPricePerMonth : diskPricePerMonth)
+ // also covers the case where the disk is not detachable/persistent (type is undefined)
+ (detachableType === DiskType.SSD
+ ? ssdPricePerMonth
+ : standardDiskPricePerMonth)
);
};
-export const detachableDiskPricePerMonth = (disk: Disk) => {
- return diskConfigPricePerMonth({
+export const persistentDiskPricePerMonth = (disk: PersistentDiskRequest) => {
+ return diskPricePerMonth({
size: disk.size,
detachableType: disk.diskType,
});
};
-export const diskConfigPrice = (config: Partial) => {
- return diskConfigPricePerMonth(config) / approxHoursPerMonth;
+const persistentDiskPricePerHour = (disk: PersistentDiskRequest) => {
+ return persistentDiskPricePerMonth(disk) / approxHoursPerMonth;
+};
+
+const dataprocDiskPricePerHour = (size: number) => {
+ return diskPricePerMonth({ size }) / approxHoursPerMonth;
};
-const detachableDiskPrice = (disk: Disk) => {
- return detachableDiskPricePerMonth(disk) / approxHoursPerMonth;
+// temp until we can stop using AnalysisConfig in locations which need to calculate storage/running costs
+export const derivePdFromAnalysisConfig = (
+ analysisConfig: AnalysisConfig
+): PersistentDiskRequest => {
+ // detachable means: is the diskConfig a PD?
+ // - yes when there's an active GceWithPd
+ // detachedDisk is only present when the diskConfig is NOT a PD
+ return analysisConfig.diskConfig.detachable
+ ? {
+ name: analysisConfig.diskConfig.existingDiskName,
+ size: analysisConfig.diskConfig.size,
+ diskType: analysisConfig.diskConfig.detachableType,
+ }
+ : analysisConfig.detachedDisk;
};
-export const machineStorageCost = ({
- diskConfig,
+interface StorageCost {
+ dataprocConfig: DataprocConfig;
+ persistentDisk: PersistentDiskRequest;
+}
+export const machineStorageCostPerHour = ({
dataprocConfig,
- detachedDisk,
-}: AnalysisConfig) => {
- const { numberOfWorkers, numberOfPreemptibleWorkers, workerDiskSize } =
- dataprocConfig ?? {};
+ persistentDisk,
+}: StorageCost) => {
+ const {
+ numberOfWorkers,
+ numberOfPreemptibleWorkers,
+ masterDiskSize,
+ workerDiskSize,
+ } = dataprocConfig ?? {};
return fp.sum([
- diskConfigPrice(diskConfig),
- detachedDisk ? detachableDiskPrice(detachedDisk) : 0,
- numberOfWorkers ? numberOfWorkers * workerDiskSize * diskPrice : 0,
+ persistentDisk ? persistentDiskPricePerHour(persistentDisk) : 0,
+ masterDiskSize ? dataprocDiskPricePerHour(masterDiskSize) : 0,
+ numberOfWorkers
+ ? numberOfWorkers * dataprocDiskPricePerHour(workerDiskSize)
+ : 0,
numberOfPreemptibleWorkers
- ? numberOfPreemptibleWorkers * workerDiskSize * diskPrice
+ ? numberOfPreemptibleWorkers * dataprocDiskPricePerHour(workerDiskSize)
: 0,
]);
};
export const machineStorageCostBreakdown = ({
- diskConfig,
dataprocConfig,
- detachedDisk,
-}: AnalysisConfig) => {
- const { numberOfWorkers, numberOfPreemptibleWorkers, workerDiskSize } =
- dataprocConfig ?? {};
+ persistentDisk,
+}: StorageCost) => {
+ const {
+ numberOfWorkers,
+ numberOfPreemptibleWorkers,
+ masterDiskSize,
+ workerDiskSize,
+ } = dataprocConfig ?? {};
const costs = [];
if (dataprocConfig) {
- costs.push(`${formatUsd(diskConfigPrice(diskConfig))}/hr Master Disk`);
+ costs.push(
+ `${formatUsd(dataprocDiskPricePerHour(masterDiskSize))}/hr Master Disk`
+ );
if (numberOfWorkers) {
costs.push(
`${formatUsd(
- numberOfWorkers * workerDiskSize * diskPrice
+ numberOfWorkers * dataprocDiskPricePerHour(workerDiskSize)
)}/hr Worker Disk(s)`
);
}
if (numberOfPreemptibleWorkers) {
costs.push(
`${formatUsd(
- numberOfPreemptibleWorkers * workerDiskSize * diskPrice
+ numberOfPreemptibleWorkers * dataprocDiskPricePerHour(workerDiskSize)
)}/hr Preemptible Worker Disk(s)`
);
}
- } else {
- costs.push(`${formatUsd(diskConfigPrice(diskConfig))}/hr Disk`);
}
- if (detachedDisk) {
+ // note: there may still be a detached PD if there's an active Dataproc runtime
+ if (persistentDisk) {
costs.push(
- `${formatUsd(detachableDiskPrice(detachedDisk))}/hr Detached Disk`
+ `${formatUsd(
+ persistentDiskPricePerHour(persistentDisk)
+ )}/hr Persistent Disk`
);
}
return costs;
};
-export const machineRunningCost = (analysisConfig: AnalysisConfig) => {
- const { computeType, machine, gpuConfig, numNodes } = analysisConfig;
+export interface RunningCost extends StorageCost {
+ computeType: ComputeType;
+ machine: Machine;
+ gpuConfig: GpuConfig;
+}
+export const machineRunningCostPerHour = (props: RunningCost) => {
+ const { computeType, machine, gpuConfig, dataprocConfig } = props;
const { workerMachineType, numberOfWorkers, numberOfPreemptibleWorkers } =
- analysisConfig.dataprocConfig ?? {};
+ dataprocConfig ?? {};
const workerMachine =
workerMachineType && findMachineByName(workerMachineType);
@@ -485,7 +531,7 @@ export const machineRunningCost = (analysisConfig: AnalysisConfig) => {
const dataprocPrice =
computeType === ComputeType.Dataproc
? fp.sum([
- dataprocSurcharge({
+ dataprocCpuSurchargePerHour({
masterMachine: machine,
numberOfWorkers,
numberOfPreemptibleWorkers,
@@ -501,16 +547,16 @@ export const machineRunningCost = (analysisConfig: AnalysisConfig) => {
: 0;
return fp.sum([
dataprocPrice,
- machine.price * (numNodes ?? 1),
+ machine.price,
gpu ? gpu.price : 0,
- machineStorageCost(analysisConfig),
+ machineStorageCostPerHour(props),
]);
};
-export const machineRunningCostBreakdown = (analysisConfig: AnalysisConfig) => {
- const { computeType, machine, gpuConfig } = analysisConfig;
+export const machineRunningCostBreakdown = (props: RunningCost) => {
+ const { computeType, machine, gpuConfig, dataprocConfig } = props;
const { workerMachineType, numberOfWorkers, numberOfPreemptibleWorkers } =
- analysisConfig.dataprocConfig ?? {};
+ dataprocConfig ?? {};
const workerMachine =
workerMachineType && findMachineByName(workerMachineType);
@@ -534,7 +580,7 @@ export const machineRunningCostBreakdown = (analysisConfig: AnalysisConfig) => {
);
}
}
- const dataprocSurchargeAmount = dataprocSurcharge({
+ const dataprocSurchargeAmount = dataprocCpuSurchargePerHour({
masterMachine: machine,
numberOfWorkers: numberOfWorkers,
numberOfPreemptibleWorkers: numberOfPreemptibleWorkers,
@@ -549,6 +595,6 @@ export const machineRunningCostBreakdown = (analysisConfig: AnalysisConfig) => {
costs.push(`${formatUsd(gpu.price)}/hr GPU`);
}
}
- costs.push(...machineStorageCostBreakdown(analysisConfig));
+ costs.push(...machineStorageCostBreakdown(props));
return costs;
};