Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "VerschlΓΌsselung nicht verfΓΌgbar",
"pci_projects_project_storages_blocks_guaranteed": "garantiert",
"pci_projects_project_storages_blocks_up_to": "bis zu",
"pci_projects_project_storages_blocks_iops_base_range": "Basis von {{min}} IOPS zwischen {{minSize}} {{sizeUnit}} und {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Basis von {{min}} {{unit}} zwischen {{minSize}} {{sizeUnit}} und {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Guides",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Alle Guides zu Block Storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Erste Schritte mit Volumes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "Encryption not available",
"pci_projects_project_storages_blocks_guaranteed": "guaranteed",
"pci_projects_project_storages_blocks_up_to": "up to",
"pci_projects_project_storages_blocks_iops_base_range": "Base of {{min}} IOPS between {{minSize}} {{sizeUnit}} and {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Base of {{min}} {{unit}} between {{minSize}} {{sizeUnit}} and {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Guides",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "All block storage guides",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Getting started with volumes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "Cifrado no disponible",
"pci_projects_project_storages_blocks_guaranteed": "garantizada",
"pci_projects_project_storages_blocks_up_to": "hasta",
"pci_projects_project_storages_blocks_iops_base_range": "Base de {{min}} IOPS entre {{minSize}} {{sizeUnit}} y {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Base de {{min}} {{unit}} entre {{minSize}} {{sizeUnit}} y {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "GuΓ­as",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Todas las guΓ­as del block storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Primeros pasos con los volΓΊmenes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "Chiffrement indisponible",
"pci_projects_project_storages_blocks_guaranteed": "garantie",
"pci_projects_project_storages_blocks_up_to": "jusqu'Γ ",
"pci_projects_project_storages_blocks_iops_base_range": "Base de {{min}} IOPS entre {{minSize}} {{sizeUnit}} et {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Base de {{min}} {{unit}} entre {{minSize}} {{sizeUnit}} et {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Guides",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Tous les guides block storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Premiers pas avec les volumes"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "Chiffrement indisponible",
"pci_projects_project_storages_blocks_guaranteed": "garantie",
"pci_projects_project_storages_blocks_up_to": "jusqu'Γ ",
"pci_projects_project_storages_blocks_iops_base_range": "Base de {{min}} IOPS entre {{minSize}} {{sizeUnit}} et {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Base de {{min}} {{unit}} entre {{minSize}} {{sizeUnit}} et {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Guides",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Tous les guides block storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Premiers pas avec les volumes"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "Crittografia non disponibile",
"pci_projects_project_storages_blocks_guaranteed": "garantita",
"pci_projects_project_storages_blocks_up_to": "fino a",
"pci_projects_project_storages_blocks_iops_base_range": "Base di {{min}} IOPS tra {{minSize}} {{sizeUnit}} e {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Base di {{min}} {{unit}} tra {{minSize}} {{sizeUnit}} e {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Guide",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Tutte le guide Block Storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Iniziare a utilizzare i volumi",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "Szyfrowanie niedostΔ™pne",
"pci_projects_project_storages_blocks_guaranteed": "o gwarantowanej przepustowoΕ›ci",
"pci_projects_project_storages_blocks_up_to": "do",
"pci_projects_project_storages_blocks_iops_base_range": "Podstawa {{min}} IOPS miΔ™dzy {{minSize}} {{sizeUnit}} a {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Podstawa {{min}} {{unit}} miΔ™dzy {{minSize}} {{sizeUnit}} a {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Przewodniki",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Przewodniki dotyczΔ…ce block storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "Pierwsze kroki z wolumenami",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"pci_projects_project_storages_blocks_encryption_unavailable": "EncriptaΓ§Γ£o indisponΓ­vel",
"pci_projects_project_storages_blocks_guaranteed": "garantida",
"pci_projects_project_storages_blocks_up_to": "atΓ©",
"pci_projects_project_storages_blocks_iops_base_range": "Base de {{min}} IOPS entre {{minSize}} {{sizeUnit}} e {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_bandwidth_base_range": "Base de {{min}} {{unit}} entre {{minSize}} {{sizeUnit}} e {{maxSize}} {{sizeUnit}}",
"pci_projects_project_storages_blocks_guides_header": "Manuais",
"pci_projects_project_storages_blocks_guides_all_block_storage_guides": "Todos os guias Block Storage",
"pci_projects_project_storages_blocks_guides_first_steps_with_volumes": "IntroduΓ§Γ£o aos volumes",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) OVH SAS

import { describe, expect, it } from 'vitest';
import { applyHardcodedSpecOverrides } from './catalog-spec-overrides';
import { TVolumeCatalog, TVolumePricing } from './catalog';

const makePricing = (
overrides: Partial<TVolumePricing['specs']['volume']['iops']> = {},
bandwidthOverrides: Partial<
NonNullable<TVolumePricing['specs']['bandwidth']>
> | null = {},
): TVolumePricing =>
({
price: 0,
regions: [],
showAvailabilityZones: false,
interval: 'hour',
areIOPSDynamic: true,
isBandwidthDynamic: true,
specs: {
name: 'spec',
maxAttachedInstances: 1,
bandwidth:
bandwidthOverrides === null
? null
: {
guaranteed: false,
level: 1,
max: 1000,
...bandwidthOverrides,
},
volume: {
iops: {
level: 1,
max: 10000,
guaranteed: false,
unit: 'u',
maxUnit: 'u',
...overrides,
},
capacity: { max: 4000 },
},
},
} as TVolumePricing);

const makeCatalog = (
modelName: string,
pricings: TVolumePricing[],
): TVolumeCatalog =>
(({
filters: { deployment: [], region: [] },
regions: [],
models: [
{
name: modelName,
tags: [],
filters: {},
pricings,
},
],
} as unknown) as TVolumeCatalog);

describe('applyHardcodedSpecOverrides', () => {
it('injects iops min and bandwidth min on high-speed-gen2 when not guaranteed', () => {
const catalog = makeCatalog('high-speed-gen2', [makePricing()]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBe(3000);
expect(pricing.specs.bandwidth?.min).toBe(50);
});

it('injects iops min and bandwidth min on high-speed-gen2-luks when not guaranteed', () => {
const catalog = makeCatalog('high-speed-gen2-luks', [makePricing()]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBe(3000);
expect(pricing.specs.bandwidth?.min).toBe(50);
});

it('does not override iops or bandwidth when the spec is guaranteed', () => {
const catalog = makeCatalog('high-speed-gen2', [
makePricing({ guaranteed: true }, { guaranteed: true }),
]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBeUndefined();
expect(pricing.specs.bandwidth?.min).toBeUndefined();
});

it('does not override when the API already returned a positive min', () => {
const catalog = makeCatalog('high-speed-gen2', [
makePricing({ min: 9999 }, { min: 999 }),
]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBe(9999);
expect(pricing.specs.bandwidth?.min).toBe(999);
});

it('overrides when the API returned a non-positive min (0 or null)', () => {
const catalog = makeCatalog('high-speed-gen2', [
makePricing({ min: 0 }, { min: null }),
]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBe(3000);
expect(pricing.specs.bandwidth?.min).toBe(50);
});

it('leaves non-overridden models untouched', () => {
const catalog = makeCatalog('classic', [makePricing()]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBeUndefined();
expect(pricing.specs.bandwidth?.min).toBeUndefined();
});

it('handles a null bandwidth spec without throwing', () => {
const catalog = makeCatalog('high-speed-gen2', [makePricing({}, null)]);

const result = applyHardcodedSpecOverrides(catalog);

const pricing = result.models[0].pricings[0];
expect(pricing.specs.volume.iops.min).toBe(3000);
expect(pricing.specs.bandwidth).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) OVH SAS

// TEMPORARY β€” hard-coded overrides for gen2 / gen2-luks volume specs.
// /cloud/project/{id}/catalog/volume will eventually return a `min` value on
// iops and bandwidth specs for these models. Until then, we inject the values
// client-side here so the rest of the app can consume the catalog as if `min`
// were always provided.
//
// The override never replaces a positive `min` already returned by the API:
// once the catalog ships its own value the patch becomes inert, and removal
// becomes purely cosmetic.
//
// To remove: delete this file and the call in catalog.ts:getVolumeCatalog.

import { TVolumeCatalog } from './catalog';

const HARDCODED_IOPS_MIN = 3000;
const HARDCODED_BANDWIDTH_MIN = 50; // MiB/s
const OVERRIDDEN_MODEL_NAMES = ['high-speed-gen2', 'high-speed-gen2-luks'];

const hasPositiveMin = (value: number | null | undefined): boolean =>
typeof value === 'number' && value > 0;

export const applyHardcodedSpecOverrides = (
catalog: TVolumeCatalog,
): TVolumeCatalog => {
catalog.models
.filter((model) => OVERRIDDEN_MODEL_NAMES.includes(model.name))
.forEach((model) => {
model.pricings.forEach((pricing) => {
const iops = pricing.specs.volume.iops;
if (iops && !iops.guaranteed && !hasPositiveMin(iops.min)) {
iops.min = HARDCODED_IOPS_MIN;
}
const bandwidth = pricing.specs.bandwidth;
if (
bandwidth &&
!bandwidth.guaranteed &&
!hasPositiveMin(bandwidth.min)
) {
bandwidth.min = HARDCODED_BANDWIDTH_MIN;
}
});
});
return catalog;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TAddon } from '@ovh-ux/manager-pci-common';
import { v6 } from '@ovh-ux/manager-core-api';
import { TRegion } from '@/api/data/regions';
import { applyHardcodedSpecOverrides } from '@/api/data/catalog-spec-overrides';

export type TCatalogGroup = {
name: string;
Expand All @@ -19,6 +20,7 @@ export type TVolumePricing = Pick<TAddon['pricings'][number], 'price'> & {
level: number;
/* in GB */
max: number;
min?: number | null;
} | null;
volume: {
iops: {
Expand All @@ -27,6 +29,7 @@ export type TVolumePricing = Pick<TAddon['pricings'][number], 'price'> & {
guaranteed: boolean;
unit: string;
maxUnit: string;
min?: number | null;
};
capacity: {
max: number;
Expand Down Expand Up @@ -62,6 +65,9 @@ export type TVolumeCatalog = {

export const getVolumeCatalog = async (
projectId: string,
): Promise<TVolumeCatalog> =>
(await v6.get<TVolumeCatalog>(`/cloud/project/${projectId}/catalog/volume`))
.data;
): Promise<TVolumeCatalog> => {
const { data } = await v6.get<TVolumeCatalog>(
`/cloud/project/${projectId}/catalog/volume`,
);
return applyHardcodedSpecOverrides(data);
};
Loading
Loading