Skip to content

Commit 9660118

Browse files
authored
fix(BRE2-907): Specify exact image in nebius deployments (#114)
1 parent 2ecce6e commit 9660118

2 files changed

Lines changed: 57 additions & 196 deletions

File tree

v1/providers/nebius/instance.go

Lines changed: 27 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
)
1616

1717
const (
18-
platformTypeCPU = "cpu"
18+
platformTypeCPU = "cpu"
19+
nebiusGPUImageFamily = "ubuntu24.04-cuda13.0"
20+
nebiusCPUImageFamily = "ubuntu24.04-driverless"
1921
)
2022

2123
//nolint:gocyclo,funlen // Complex instance creation with resource management
@@ -1151,10 +1153,7 @@ func (c *NebiusClient) createBootDisk(ctx context.Context, attrs v1.CreateInstan
11511153
diskName := fmt.Sprintf("%s-boot-disk", attrs.RefID)
11521154

11531155
// Try to use image family first, then fallback to specific image ID
1154-
createReq, err := c.buildDiskCreateRequest(ctx, diskName, attrs)
1155-
if err != nil {
1156-
return "", fmt.Errorf("failed to build disk create request: %w", err)
1157-
}
1156+
createReq := c.buildDiskCreateRequest(ctx, diskName, attrs)
11581157

11591158
operation, err := c.sdk.Services().Compute().V1().Disk().Create(ctx, createReq)
11601159
if err != nil {
@@ -1180,12 +1179,14 @@ func (c *NebiusClient) createBootDisk(ctx context.Context, attrs v1.CreateInstan
11801179
return diskID, nil
11811180
}
11821181

1183-
// buildDiskCreateRequest builds a disk creation request, trying image family first, then image ID
1184-
func (c *NebiusClient) buildDiskCreateRequest(ctx context.Context, diskName string, attrs v1.CreateInstanceAttrs) (*compute.CreateDiskRequest, error) {
1182+
// buildDiskCreateRequest builds a disk creation request using the fixed Nebius image family for the instance type.
1183+
func (c *NebiusClient) buildDiskCreateRequest(_ context.Context, diskName string, attrs v1.CreateInstanceAttrs) *compute.CreateDiskRequest {
11851184
if attrs.DiskSize == 0 {
11861185
attrs.DiskSize = 1280 * units.Gibibyte // Defaulted by the Nebius Console
11871186
}
11881187

1188+
imageFamily := getNebiusBootImageFamily(attrs.InstanceType)
1189+
11891190
baseReq := &compute.CreateDiskRequest{
11901191
Metadata: &common.ResourceMetadata{
11911192
ParentId: c.projectID,
@@ -1194,148 +1195,45 @@ func (c *NebiusClient) buildDiskCreateRequest(ctx context.Context, diskName stri
11941195
"created-by": "brev-cloud-sdk",
11951196
"brev-user": c.refID,
11961197
"environment-id": attrs.RefID,
1198+
"image-family": imageFamily,
11971199
},
11981200
},
11991201
Spec: &compute.DiskSpec{
12001202
Size: &compute.DiskSpec_SizeGibibytes{
12011203
SizeGibibytes: int64(attrs.DiskSize / units.Gibibyte),
12021204
},
12031205
Type: compute.DiskSpec_NETWORK_SSD,
1204-
},
1205-
}
1206-
1207-
// First, try to resolve and use image family
1208-
if imageFamily, err := c.resolveImageFamily(ctx, attrs.ImageID); err == nil {
1209-
publicImagesParent := c.getPublicImagesParent()
1210-
1211-
// Skip validation for known-good common families to speed up instance start
1212-
knownFamilies := []string{"ubuntu22.04-cuda12", "mk8s-worker-node-v-1-32-ubuntu24.04", "mk8s-worker-node-v-1-32-ubuntu24.04-cuda12.8"}
1213-
isKnownFamily := false
1214-
for _, known := range knownFamilies {
1215-
if imageFamily == known {
1216-
isKnownFamily = true
1217-
break
1218-
}
1219-
}
1220-
1221-
if isKnownFamily {
1222-
// Use known family without validation
1223-
baseReq.Spec.Source = &compute.DiskSpec_SourceImageFamily{
1206+
Source: &compute.DiskSpec_SourceImageFamily{
12241207
SourceImageFamily: &compute.SourceImageFamily{
12251208
ImageFamily: imageFamily,
1226-
ParentId: publicImagesParent,
1209+
ParentId: c.getPublicImagesParent(),
12271210
},
1228-
}
1229-
baseReq.Metadata.Labels["image-family"] = imageFamily
1230-
return baseReq, nil
1231-
}
1232-
1233-
// For unknown families, validate first
1234-
_, err := c.sdk.Services().Compute().V1().Image().GetLatestByFamily(ctx, &compute.GetImageLatestByFamilyRequest{
1235-
ParentId: publicImagesParent,
1236-
ImageFamily: imageFamily,
1237-
})
1238-
if err == nil {
1239-
// Family works, use it
1240-
baseReq.Spec.Source = &compute.DiskSpec_SourceImageFamily{
1241-
SourceImageFamily: &compute.SourceImageFamily{
1242-
ImageFamily: imageFamily,
1243-
ParentId: publicImagesParent,
1244-
},
1245-
}
1246-
baseReq.Metadata.Labels["image-family"] = imageFamily
1247-
return baseReq, nil
1248-
}
1249-
}
1250-
1251-
// Family approach failed, try to use a known working public image ID
1252-
publicImageID, err := c.getWorkingPublicImageID(ctx, attrs.ImageID)
1253-
if err == nil {
1254-
baseReq.Spec.Source = &compute.DiskSpec_SourceImageId{
1255-
SourceImageId: publicImageID,
1256-
}
1257-
baseReq.Metadata.Labels["source-image-id"] = publicImageID
1258-
return baseReq, nil
1211+
},
1212+
},
12591213
}
12601214

1261-
// Both approaches failed
1262-
return nil, fmt.Errorf("could not resolve image %s to either a working family or image ID: %w", attrs.ImageID, err)
1215+
return baseReq
12631216
}
12641217

1265-
// getWorkingPublicImageID gets a working public image ID based on the requested image type
1266-
//
1267-
//nolint:gocognit,gocyclo // Complex function trying multiple image resolution strategies
1268-
func (c *NebiusClient) getWorkingPublicImageID(ctx context.Context, requestedImage string) (string, error) {
1269-
// Get available public images from the correct region
1270-
publicImagesParent := c.getPublicImagesParent()
1271-
imagesResp, err := c.sdk.Services().Compute().V1().Image().List(ctx, &compute.ListImagesRequest{
1272-
ParentId: publicImagesParent,
1273-
})
1274-
if err != nil {
1275-
return "", fmt.Errorf("failed to list public images: %w", err)
1276-
}
1277-
1278-
if len(imagesResp.GetItems()) == 0 {
1279-
return "", fmt.Errorf("no public images available")
1280-
}
1281-
1282-
// Try to find the best match based on the requested image
1283-
requestedLower := strings.ToLower(requestedImage)
1284-
1285-
var bestMatch *compute.Image
1286-
var fallbackImage *compute.Image
1287-
1288-
for _, image := range imagesResp.GetItems() {
1289-
if image.Metadata == nil {
1290-
continue
1291-
}
1292-
1293-
imageName := strings.ToLower(image.Metadata.Name)
1294-
1295-
// Set fallback to first available image
1296-
if fallbackImage == nil {
1297-
fallbackImage = image
1298-
}
1299-
1300-
// Look for Ubuntu matches
1301-
if strings.Contains(requestedLower, "ubuntu") && strings.Contains(imageName, "ubuntu") {
1302-
// Prefer specific version matches
1303-
//nolint:gocritic // if-else chain is clearer than switch for version matching logic
1304-
if strings.Contains(requestedLower, "24.04") || strings.Contains(requestedLower, "24") {
1305-
if strings.Contains(imageName, "ubuntu24.04") {
1306-
bestMatch = image
1307-
break
1308-
}
1309-
} else if strings.Contains(requestedLower, "22.04") || strings.Contains(requestedLower, "22") {
1310-
if strings.Contains(imageName, "ubuntu22.04") {
1311-
bestMatch = image
1312-
break
1313-
}
1314-
} else if strings.Contains(requestedLower, "20.04") || strings.Contains(requestedLower, "20") {
1315-
if strings.Contains(imageName, "ubuntu20.04") {
1316-
bestMatch = image
1317-
break
1318-
}
1319-
}
1320-
1321-
// Any Ubuntu image is better than non-Ubuntu
1322-
if bestMatch == nil {
1323-
bestMatch = image
1324-
}
1325-
}
1218+
func getNebiusBootImageFamily(instanceType string) string {
1219+
if isNebiusGPUInstanceType(instanceType) {
1220+
return nebiusGPUImageFamily
13261221
}
1222+
return nebiusCPUImageFamily
1223+
}
13271224

1328-
// Use best match if found, otherwise fallback
1329-
selectedImage := bestMatch
1330-
if selectedImage == nil {
1331-
selectedImage = fallbackImage
1225+
func isNebiusGPUInstanceType(instanceType string) bool {
1226+
instanceTypeLower := strings.ToLower(instanceType)
1227+
if instanceTypeLower == "" {
1228+
return false
13321229
}
13331230

1334-
if selectedImage == nil {
1335-
return "", fmt.Errorf("no suitable public image found")
1231+
// GPU instance types start with "gpu-" (see getInstanceTypesForLocation)
1232+
if strings.HasPrefix(instanceTypeLower, "gpu-") {
1233+
return true
13361234
}
13371235

1338-
return selectedImage.Metadata.Id, nil
1236+
return false
13391237
}
13401238

13411239
// getPublicImagesParent determines the correct public images parent ID based on project routing code
@@ -1575,73 +1473,6 @@ func (c *NebiusClient) parseInstanceType(ctx context.Context, instanceTypeID str
15751473
return "", "", fmt.Errorf("could not parse instance type %s or find suitable platform/preset", instanceTypeID)
15761474
}
15771475

1578-
// resolveImageFamily resolves an ImageID to an image family name
1579-
// If ImageID is already a family name, use it directly
1580-
// Otherwise, try to get the image and extract its family
1581-
//
1582-
//nolint:gocyclo,unparam // Complex image family resolution with fallback logic
1583-
func (c *NebiusClient) resolveImageFamily(ctx context.Context, imageID string) (string, error) {
1584-
// Common Nebius image families - if ImageID matches one of these, use it directly
1585-
commonFamilies := []string{
1586-
"ubuntu22.04-cuda12",
1587-
"mk8s-worker-node-v-1-32-ubuntu24.04",
1588-
"mk8s-worker-node-v-1-32-ubuntu24.04-cuda12.8",
1589-
"mk8s-worker-node-v-1-31-ubuntu24.04-cuda12",
1590-
"ubuntu22.04",
1591-
"ubuntu20.04",
1592-
}
1593-
1594-
// Check if ImageID is already a known family name
1595-
for _, family := range commonFamilies {
1596-
if imageID == family {
1597-
return family, nil
1598-
}
1599-
}
1600-
1601-
// If ImageID looks like a family name pattern (contains dots, dashes, no UUIDs)
1602-
// and doesn't look like a UUID, assume it's a family name
1603-
if !strings.Contains(imageID, "-") || len(imageID) < 32 {
1604-
// Likely a family name, use it directly
1605-
return imageID, nil
1606-
}
1607-
1608-
// If it looks like a UUID/ID, try to get the image and extract its family
1609-
image, err := c.sdk.Services().Compute().V1().Image().Get(ctx, &compute.GetImageRequest{
1610-
Id: imageID,
1611-
})
1612-
if err != nil {
1613-
// If we can't get the image, try using the ID as a family name anyway
1614-
// This allows for custom family names that don't match our patterns
1615-
return imageID, nil
1616-
}
1617-
1618-
// Extract family from image metadata/labels if available
1619-
if image.Metadata != nil && image.Metadata.Labels != nil {
1620-
if family, exists := image.Metadata.Labels["family"]; exists && family != "" {
1621-
return family, nil
1622-
}
1623-
if family, exists := image.Metadata.Labels["image-family"]; exists && family != "" {
1624-
return family, nil
1625-
}
1626-
}
1627-
1628-
// Extract family from image name as fallback
1629-
if image.Metadata != nil && image.Metadata.Name != "" {
1630-
// Try to extract a reasonable family name from the image name
1631-
name := strings.ToLower(image.Metadata.Name)
1632-
if strings.Contains(name, "ubuntu22") || strings.Contains(name, "ubuntu-22") {
1633-
return "ubuntu22.04", nil
1634-
}
1635-
if strings.Contains(name, "ubuntu20") || strings.Contains(name, "ubuntu-20") {
1636-
return "ubuntu20.04", nil
1637-
}
1638-
}
1639-
1640-
// Default fallback - use the original ImageID as family
1641-
// This handles cases where users provide custom family names
1642-
return imageID, nil
1643-
}
1644-
16451476
// deleteBootDisk deletes a boot disk by ID
16461477
func (c *NebiusClient) deleteBootDisk(ctx context.Context, diskID string) error {
16471478
operation, err := c.sdk.Services().Compute().V1().Disk().Delete(ctx, &compute.DeleteDiskRequest{

v1/providers/nebius/instance_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,36 @@ func TestExtractGPUTypeAndName(t *testing.T) {
270270
}
271271
}
272272

273+
func TestGetNebiusBootImageFamily(t *testing.T) {
274+
tests := []struct {
275+
name string
276+
instanceType string
277+
expected string
278+
}{
279+
{
280+
name: "gpu dot format uses cuda image",
281+
instanceType: "gpu-h100-sxm.8gpu-128vcpu-1600gb",
282+
expected: nebiusGPUImageFamily,
283+
},
284+
{
285+
name: "cpu dot format uses driverless image",
286+
instanceType: "cpu-e2.4vcpu-16gb",
287+
expected: nebiusCPUImageFamily,
288+
},
289+
{
290+
name: "empty instance type defaults to cpu image",
291+
instanceType: "",
292+
expected: nebiusCPUImageFamily,
293+
},
294+
}
295+
296+
for _, tt := range tests {
297+
t.Run(tt.name, func(t *testing.T) {
298+
assert.Equal(t, tt.expected, getNebiusBootImageFamily(tt.instanceType))
299+
})
300+
}
301+
}
302+
273303
func TestIsPlatformSupported(t *testing.T) {
274304
client := createTestClient()
275305

0 commit comments

Comments
 (0)