diff --git a/packages/manager/.changeset/pr-13347-changed-1769758380402.md b/packages/manager/.changeset/pr-13347-changed-1769758380402.md new file mode 100644 index 00000000000..a68bba1b450 --- /dev/null +++ b/packages/manager/.changeset/pr-13347-changed-1769758380402.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +UIE-10060 : Support new GPU v3 RTX Pro 6000 Blackwell plans in Kubernetes for both LKE and LKE-E ([#13347](https://github.com/linode/manager/pull/13347)) diff --git a/packages/manager/cypress/e2e/core/kubernetes/lke-create.spec.ts b/packages/manager/cypress/e2e/core/kubernetes/lke-create.spec.ts index 5f2b2ef1b3d..2ee572c5e8b 100644 --- a/packages/manager/cypress/e2e/core/kubernetes/lke-create.spec.ts +++ b/packages/manager/cypress/e2e/core/kubernetes/lke-create.spec.ts @@ -1469,8 +1469,6 @@ describe('LKE Cluster Creation with LKE-E', () => { validEnterprisePlanTabs.forEach((tab) => { ui.tabList.findTabByTitle(tab).should('be.visible'); }); - // Confirm the GPU tab is not visible in the plans panel for LKE-E. - ui.tabList.findTabByTitle('GPU').should('not.exist'); // Add a node pool for each selected plan, and confirm that the // selected node pool plan is added to the checkout bar. @@ -1910,106 +1908,51 @@ describe('smoketest for Nvidia Blackwell GPUs in kubernetes/create page', () => mockGetRegionAvailability(mockRegion.id, mockRegionAvailability).as( 'getRegionAvailability' ); + mockGetTieredKubernetesVersions('enterprise', [ + latestEnterpriseTierKubernetesVersion, + ]).as('getEnterpriseTieredVersions'); }); - describe('standard tier', () => { - it('enabled feature flag includes blackwells', () => { - mockAppendFeatureFlags({ - kubernetesBlackwellPlans: true, - }).as('getFeatureFlags'); - cy.visitWithLogin('/kubernetes/create'); - cy.wait(['@getFeatureFlags', '@getRegions', '@getLinodeTypes']); - - ui.regionSelect.find().click(); - ui.regionSelect.find().clear(); - ui.regionSelect.find().type(`${mockRegion.label}{enter}`); - cy.wait('@getRegionAvailability'); - // Navigate to "GPU" tab - ui.tabList.findTabByTitle('GPU').scrollIntoView(); - ui.tabList.findTabByTitle('GPU').should('be.visible').click(); - - cy.findByRole('table', { - name: 'List of Linode Plans', - }).within(() => { - cy.get('tbody tr') - .should('have.length', 4) - .each((row, index) => { - cy.wrap(row).within(() => { - cy.get('td') - .eq(0) - .within(() => { - cy.findByText(mockBlackwellLinodeTypes[index].label).should( - 'be.visible' - ); - }); - ui.button - .findByTitle('Configure Pool') - .should('be.visible') - .should('be.enabled'); - }); - }); - }); - }); - - it('disabled feature flag excludes blackwells', () => { - mockAppendFeatureFlags({ - kubernetesBlackwellPlans: false, - }).as('getFeatureFlags'); - - cy.visitWithLogin('/kubernetes/create'); - cy.wait(['@getFeatureFlags', '@getRegions', '@getLinodeTypes']); - - ui.regionSelect.find().click(); - ui.regionSelect.find().clear(); - ui.regionSelect.find().type(`${mockRegion.label}{enter}`); - cy.wait('@getRegionAvailability'); - // Navigate to "GPU" tab - // "GPU" tab hidden - ui.tabList.findTabByTitle('GPU').should('not.exist'); - }); - }); - describe('enterprise tier hides GPU tab', () => { - beforeEach(() => { - // necessary to prevent crash after selecting Enterprise button - mockGetTieredKubernetesVersions('enterprise', [ - latestEnterpriseTierKubernetesVersion, - ]).as('getEnterpriseTieredVersions'); - }); - it('enabled feature flag', () => { - mockAppendFeatureFlags({ - kubernetesBlackwellPlans: true, - }).as('getFeatureFlags'); + it('both tiers should include blackwell GPUs', () => { + cy.visitWithLogin('/kubernetes/create'); + cy.wait(['@getRegions', '@getLinodeTypes']); - cy.visitWithLogin('/kubernetes/create'); - cy.wait(['@getFeatureFlags', '@getRegions', '@getLinodeTypes']); - - cy.findByText('LKE Enterprise').click(); - cy.wait(['@getEnterpriseTieredVersions']); - ui.regionSelect.find().click(); - ui.regionSelect.find().clear(); - ui.regionSelect.find().type(`${mockRegion.label}{enter}`); - cy.wait('@getRegionAvailability'); - // "GPU" tab hidden - ui.tabList.findTabByTitle('GPU').should('not.exist'); + ui.regionSelect.find().click(); + ui.regionSelect.find().clear(); + ui.regionSelect.find().type(`${mockRegion.label}{enter}`); + cy.wait('@getRegionAvailability'); + // Navigate to "GPU" tab + ui.tabList.findTabByTitle('GPU').scrollIntoView(); + ui.tabList.findTabByTitle('GPU').should('be.visible').click(); + + cy.findByRole('table', { + name: 'List of Linode Plans', + }).within(() => { + cy.get('tbody tr') + .should('have.length', 4) + .each((row, index) => { + cy.wrap(row).within(() => { + cy.get('td') + .eq(0) + .within(() => { + cy.findByText(mockBlackwellLinodeTypes[index].label).should( + 'be.visible' + ); + }); + ui.button + .findByTitle('Configure Pool') + .should('be.visible') + .should('be.enabled'); + }); + }); }); - it('disabled feature flag', () => { - mockAppendFeatureFlags({ - kubernetesBlackwellPlans: false, - }).as('getFeatureFlags'); - - cy.visitWithLogin('/kubernetes/create'); - cy.wait(['@getFeatureFlags', '@getRegions', '@getLinodeTypes']); - - ui.regionSelect.find().click(); - ui.regionSelect.find().clear(); - ui.regionSelect.find().type(`${mockRegion.label}{enter}`); - cy.findByText('LKE Enterprise').click(); - cy.wait(['@getEnterpriseTieredVersions']); - 2; - // "GPU" tab hidden - ui.tabList.findTabByTitle('GPU').should('not.exist'); - }); + cy.findByText('LKE Enterprise').click(); + cy.wait(['@getEnterpriseTieredVersions']); + ui.regionSelect.find().click(); + ui.regionSelect.find().clear(); + ui.regionSelect.find().type(`${mockRegion.label}{enter}`); + ui.tabList.findTabByTitle('GPU').should('exist'); }); }); diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts index 00d6e01d834..e7f50b554ed 100644 --- a/packages/manager/src/featureFlags.ts +++ b/packages/manager/src/featureFlags.ts @@ -241,7 +241,6 @@ export interface Flags { iamDelegation: BaseFeatureFlag; iamLimitedAvailabilityBadges: boolean; ipv6Sharing: boolean; - kubernetesBlackwellPlans: boolean; limitsEvolution: LimitsEvolution; linodeCloneFirewall: boolean; linodeDiskEncryption: boolean; diff --git a/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/KubernetesPlansPanel.tsx b/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/KubernetesPlansPanel.tsx index f8ebb4822af..78620a6da47 100644 --- a/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/KubernetesPlansPanel.tsx +++ b/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/KubernetesPlansPanel.tsx @@ -99,13 +99,7 @@ export const KubernetesPlansPanel = (props: Props) => { } return ( - !type.id.includes('dedicated-edge') && - !type.id.includes('nanode-edge') && - // Filter out GPU types for enterprise; otherwise, return the rest of the types. - // TODO: remove this once GPU plans are supported in LKE-E (Q3 2025) - (selectedTier === 'enterprise' ? !type.id.includes('gpu') : true) && - // Filter out Blackwell plans for kubernetes (for now) - !(type.id.includes('blackwell') && !flags.kubernetesBlackwellPlans) + !type.id.includes('dedicated-edge') && !type.id.includes('nanode-edge') ); }); diff --git a/packages/manager/src/features/components/PlansPanel/GpuFilters.tsx b/packages/manager/src/features/components/PlansPanel/GpuFilters.tsx index 0fcaabe5c46..4add8bbc92a 100644 --- a/packages/manager/src/features/components/PlansPanel/GpuFilters.tsx +++ b/packages/manager/src/features/components/PlansPanel/GpuFilters.tsx @@ -32,6 +32,7 @@ const ALL_GPU_OPTIONS: SelectOption[] = [ ]; interface GPUPlanFilterComponentProps { + disabled?: boolean; onResult: (result: PlanFilterRenderResult) => void; plans: PlanWithAvailability[]; resetPagination: () => void; @@ -39,7 +40,7 @@ interface GPUPlanFilterComponentProps { const GPUPlanFilterComponent = React.memo( (props: GPUPlanFilterComponentProps) => { - const { onResult, plans, resetPagination } = props; + const { disabled = false, onResult, plans, resetPagination } = props; // Local state - persists automatically because component stays mounted const [gpuType, setGpuType] = @@ -112,6 +113,7 @@ const GPUPlanFilterComponent = React.memo(