Skip to content
Merged
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
22 changes: 22 additions & 0 deletions pkg/servers/e2b/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (sc *Controller) createSandboxWithClaim(ctx context.Context, request models
ClaimTimeout: time.Duration(request.Extensions.TimeoutSeconds) * time.Second,
Modifier: func(sbx infra.Sandbox) {
sc.basicSandboxCreateModifier(ctx, sbx, request)
sc.csiMountOptionsConfigRecord(ctx, sbx, request)
},
ReserveFailedSandbox: request.Extensions.ReserveFailedSandbox,
CreateOnNoStock: request.Extensions.CreateOnNoStock,
Expand Down Expand Up @@ -257,3 +258,24 @@ func (sc *Controller) basicSandboxCreateModifier(ctx context.Context, sbx infra.
}
sbx.SetAnnotations(annotations)
}

func (sc *Controller) csiMountOptionsConfigRecord(ctx context.Context, sbx infra.Sandbox, request models.NewSandboxRequest) {
log := klog.FromContext(ctx)
// fetch the csi mount config from request
if len(request.Extensions.CSIMount.MountConfigs) == 0 {
return
}
// marshal the csi mount confit to json
csiMountConfigRaw, err := json.Marshal(request.Extensions.CSIMount.MountConfigs)
if err != nil {
log.Info("failed to marshal csi mount config", err)
return
}
annotations := sbx.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
// record the csi mount config to annotation
annotations[models.ExtensionKeyClaimWithCSIMount_MountConfig] = string(csiMountConfigRaw)
sbx.SetAnnotations(annotations)
}
174 changes: 174 additions & 0 deletions pkg/servers/e2b/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package e2b

import (
"context"
"encoding/json"
"reflect"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

agentsv1alpha1 "github.com/openkruise/agents/api/v1alpha1"
"github.com/openkruise/agents/pkg/sandbox-manager/infra/sandboxcr"
"github.com/openkruise/agents/pkg/servers/e2b/models"
)

// TestCsiMountOptionsConfigRecord tests the csiMountOptionsConfigRecord function
func TestCsiMountOptionsConfigRecord(t *testing.T) {
tests := []struct {
name string
request models.NewSandboxRequest
initialAnnotations map[string]string
expectedAnnotationKey string
expectedAnnotationVal string
shouldSet bool
}{
{
name: "empty mount configs",
request: models.NewSandboxRequest{
Extensions: models.NewSandboxRequestExtension{
CSIMount: models.CSIMountExtension{
MountConfigs: []models.CSIMountConfig{},
},
},
},
shouldSet: false,
},
{
name: "single mount config with all fields",
request: models.NewSandboxRequest{
Extensions: models.NewSandboxRequestExtension{
CSIMount: models.CSIMountExtension{
MountConfigs: []models.CSIMountConfig{
{
MountID: "mount-123",
PvName: "pv-nas-001",
MountPath: "/data",
SubPath: "subdir",
ReadOnly: true,
},
},
},
},
Metadata: map[string]string{
"user-id": "user-456",
},
},
initialAnnotations: map[string]string{},
expectedAnnotationKey: models.ExtensionKeyClaimWithCSIMount_MountConfig,
expectedAnnotationVal: `[{"mountID":"mount-123","pvName":"pv-nas-001","mountPath":"/data","subPath":"subdir","readOnly":true}]`,
shouldSet: true,
},
{
name: "multiple mount configs with optional fields omitted",
request: models.NewSandboxRequest{
Extensions: models.NewSandboxRequestExtension{
CSIMount: models.CSIMountExtension{
MountConfigs: []models.CSIMountConfig{
{
PvName: "pv-nas-001",
MountPath: "/data",
},
{
PvName: "pv-oss-002",
MountPath: "/models",
ReadOnly: true,
},
},
},
},
},
initialAnnotations: map[string]string{"existing-key": "existing-val"},
expectedAnnotationKey: models.ExtensionKeyClaimWithCSIMount_MountConfig,
expectedAnnotationVal: `[{"pvName":"pv-nas-001","mountPath":"/data"},{"pvName":"pv-oss-002","mountPath":"/models","readOnly":true}]`,
shouldSet: true,
},
{
name: "with metadata merging",
request: models.NewSandboxRequest{
Extensions: models.NewSandboxRequestExtension{
CSIMount: models.CSIMountExtension{
MountConfigs: []models.CSIMountConfig{
{
PvName: "pv-test",
MountPath: "/workspace",
},
},
},
},
},
initialAnnotations: map[string]string{
"old-key": "old-val",
},
expectedAnnotationKey: models.ExtensionKeyClaimWithCSIMount_MountConfig,
expectedAnnotationVal: `[{"pvName":"pv-test","mountPath":"/workspace"}]`,
shouldSet: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create mock sandbox
mockSbx := &sandboxcr.Sandbox{
Sandbox: &agentsv1alpha1.Sandbox{
ObjectMeta: metav1.ObjectMeta{
Name: "test-sandbox",
Namespace: "default",
Annotations: tt.initialAnnotations,
},
},
}

// Create controller instance
ctrl := &Controller{}

// Call the function
ctx := context.Background()
ctrl.csiMountOptionsConfigRecord(ctx, mockSbx, tt.request)

// Verify results
annotations := mockSbx.GetAnnotations()

if !tt.shouldSet {
// Should not set any annotation when mount configs are empty
if len(annotations) != len(tt.initialAnnotations) {
t.Errorf("expected no annotations to be added, got %d", len(annotations))
}
return
}

// Check if expected annotation is set
val, exists := annotations[tt.expectedAnnotationKey]
if !exists {
t.Errorf("expected annotation %q to exist", tt.expectedAnnotationKey)
return
}

// Verify the annotation value (parse JSON for comparison to avoid ordering issues)
var expectedConfigs, actualConfigs []models.CSIMountConfig
if err := json.Unmarshal([]byte(tt.expectedAnnotationVal), &expectedConfigs); err != nil {
t.Fatalf("failed to unmarshal expected value: %v", err)
}
if err := json.Unmarshal([]byte(val), &actualConfigs); err != nil {
t.Fatalf("failed to unmarshal actual value: %v", err)
}

if !reflect.DeepEqual(expectedConfigs, actualConfigs) {
t.Errorf("csi mount config mismatch:\nexpected: %#v\ngot: %#v", expectedConfigs, actualConfigs)
}

if !reflect.DeepEqual(expectedConfigs, actualConfigs) {
t.Errorf("csi mount config mismatch:\nexpected: %#v\ngot: %#v", expectedConfigs, actualConfigs)
}

// Verify existing annotations are preserved
if tt.initialAnnotations != nil {
for k, v := range tt.initialAnnotations {
if annotations[k] != v {
t.Errorf("expected existing annotation %q=%q, got %q", k, v, annotations[k])
}
}
}
})
}
}
10 changes: 5 additions & 5 deletions pkg/servers/e2b/models/mount_types.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package models

type CSIMountConfig struct {
MountID string `json:"mountID"` // mount id
PvName string `json:"pvName"` // persistent volume name for mounting
MountPath string `json:"mountPath"` // mount target in container to mount the persistent volume
SubPath string `json:"subPath"` // sub path address in persistent volume
ReadOnly bool `json:"readOnly"` // whether to mount the persistent volume as read-only
MountID string `json:"mountID,omitempty"` // mount id
PvName string `json:"pvName"` // persistent volume name for mounting
MountPath string `json:"mountPath"` // mount target in container to mount the persistent volume
SubPath string `json:"subPath,omitempty"` // sub path address in persistent volume
ReadOnly bool `json:"readOnly,omitempty"` // whether to mount the persistent volume as read-only
}
Loading