Skip to content

Commit 87b94c0

Browse files
Allow shared FS on any L40s slice count (CRUSOE-67560)
supportsFS() restricted shared-filesystem support on L40s to the full l40s-48gb.10x node. That slice-count gate was a virtiofs-era constraint: shared disks were backed per-host, so only a full node could share one. Post-NFS-migration there is no host-locality requirement. region-coordinator already encodes this — checkSharedVolumeSliceTypeandNumSlices only enforces slice counts for projects still on virtiofs (gated on IsProjectUsingVirtiofsForSharedDisks), and the node-side is-using-nfs check reads the same source of truth (nfs-support-shared-disks flag + tmp_nfs_migration table). A genuine virtiofs straggler still fails safe: the node takes the virtiofs path, RC's attach gate rejects the sub-full-node attach, and the pod stays in ContainerCreating — no data path risk. Surfaced during INC-522 (Roboflow L40s): the customer pivoted impacted pools to smaller L40s slices to dodge a kubelet GPU-allocation leak, and this CSI gate blocked shared-FS on those slices. Also add /internal/node/fs/ to CODEOWNERS so the slice gate + NFS mount path route to storage, keeping the CSI-side gate aligned with the RC server side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a4710d0 commit 87b94c0

3 files changed

Lines changed: 64 additions & 2 deletions

File tree

.gitlab/CODEOWNERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ Dockerfile.* @crusoeenergy/squads/managed-orchestration @crusoeenergy/squa
1717
/internal/common/constants.go @crusoeenergy/squads/storage @crusoeenergy/squads/managed-orchestration
1818
/internal/controller/controller.go @crusoeenergy/squads/storage @crusoeenergy/squads/managed-orchestration
1919
/internal/controller/util.go @crusoeenergy/squads/storage @crusoeenergy/squads/managed-orchestration
20+
21+
# Shared-filesystem support gating (supportsFS slice rules) and NFS mount path.
22+
# Coupled to region-coordinator's NFS migration + slice-type validation; storage
23+
# owns this so the CSI-side gate stays aligned with the server side (CRUSOE-67560).
24+
/internal/node/fs/ @crusoeenergy/squads/storage @crusoeenergy/squads/managed-orchestration

internal/node/fs/util.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,13 @@ func supportsFS(instance *crusoeapi.InstanceV1Alpha5) bool {
5252
return true
5353
}
5454

55-
// There are 10 slices in a L40s instance
56-
if typeSegments[0] == "l40s-48gb" && typeSegments[1] == "10x" {
55+
// L40s instances support shared filesystems on any slice count. The slice-count
56+
// restriction was a virtiofs-era constraint (shared disk was backed per-host, so
57+
// only a full node could share it). Post-NFS-migration there is no host-locality
58+
// requirement, and region-coordinator already only enforces slice counts for
59+
// projects still on virtiofs (see checkSharedVolumeSliceTypeandNumSlices, gated on
60+
// IsProjectUsingVirtiofsForSharedDisks). CRUSOE-67560.
61+
if typeSegments[0] == "l40s-48gb" {
5762
return true
5863
}
5964

internal/node/fs/util_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package fs
2+
3+
import (
4+
"testing"
5+
6+
crusoeapi "github.com/crusoecloud/client-go/swagger/v1alpha5"
7+
)
8+
9+
func TestSupportsFS(t *testing.T) {
10+
t.Parallel()
11+
12+
cases := []struct {
13+
name string
14+
instanceType string
15+
want bool
16+
}{
17+
// CPU instances always support shared FS, regardless of slice count.
18+
{name: "c1a", instanceType: "c1a.16x", want: true},
19+
{name: "s1a", instanceType: "s1a.8x", want: true},
20+
21+
// L40s: supported on any slice count (CRUSOE-67560). The full-node
22+
// .10x case must keep working; smaller slices must now also pass.
23+
{name: "l40s full node 10x", instanceType: "l40s-48gb.10x", want: true},
24+
{name: "l40s single slice 1x", instanceType: "l40s-48gb.1x", want: true},
25+
{name: "l40s partial 2x", instanceType: "l40s-48gb.2x", want: true},
26+
{name: "l40s partial 5x", instanceType: "l40s-48gb.5x", want: true},
27+
28+
// GB200: only the full 4x node supports shared FS.
29+
{name: "gb200 full node 4x", instanceType: "gb200-186gb-nvl.4x", want: true},
30+
{name: "gb200 partial 1x", instanceType: "gb200-186gb-nvl.1x", want: false},
31+
32+
// Other GPU SKUs: only the full 8x node supports shared FS.
33+
{name: "other gpu full node 8x", instanceType: "h100-80gb.8x", want: true},
34+
{name: "other gpu partial 1x", instanceType: "h100-80gb.1x", want: false},
35+
36+
// Malformed type strings (not exactly two dot-separated segments).
37+
{name: "missing slice segment", instanceType: "l40s-48gb", want: false},
38+
{name: "too many segments", instanceType: "l40s-48gb.10x.foo", want: false},
39+
{name: "empty", instanceType: "", want: false},
40+
}
41+
42+
for _, tc := range cases {
43+
t.Run(tc.name, func(t *testing.T) {
44+
t.Parallel()
45+
46+
instance := &crusoeapi.InstanceV1Alpha5{Type_: tc.instanceType}
47+
if got := supportsFS(instance); got != tc.want {
48+
t.Errorf("supportsFS(%q) = %v, want %v", tc.instanceType, got, tc.want)
49+
}
50+
})
51+
}
52+
}

0 commit comments

Comments
 (0)