Skip to content

Commit 4119783

Browse files
committed
Fix disk-root mount validation and stale volume recovery
1 parent 74bfc3b commit 4119783

4 files changed

Lines changed: 121 additions & 2 deletions

File tree

lib/builds/disk_root_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,60 @@ func TestSetupDiskRootVolume_LeftoverDeleteError(t *testing.T) {
120120
assert.Empty(t, volID)
121121
}
122122

123+
func TestSetupDiskRootVolume_LeftoverInUseCleansStaleBuilder(t *testing.T) {
124+
mgr, instanceMgr, volumeMgr, tempDir := setupTestManager(t)
125+
defer os.RemoveAll(tempDir)
126+
mgr.config.DiskRootEnabled = true
127+
128+
buildID := "build-1"
129+
staleBuilderID := "inst-builder-build-1"
130+
meta := &buildMetadata{
131+
ID: buildID,
132+
Status: StatusBuilding,
133+
Request: &CreateBuildRequest{Dockerfile: "FROM alpine"},
134+
CreatedAt: time.Now(),
135+
BuilderInstance: &staleBuilderID,
136+
}
137+
require.NoError(t, writeMetadata(mgr.paths, meta))
138+
instanceMgr.instances[staleBuilderID] = &instances.Instance{
139+
StoredMetadata: instances.StoredMetadata{
140+
Id: staleBuilderID,
141+
Name: "builder-build-1",
142+
},
143+
State: instances.StateRunning,
144+
}
145+
146+
created := false
147+
volumeMgr.createFunc = func(ctx context.Context, req volumes.CreateVolumeRequest) (*volumes.Volume, error) {
148+
if !created {
149+
created = true
150+
return nil, volumes.ErrAlreadyExists
151+
}
152+
return &volumes.Volume{Id: *req.Id, Name: req.Name, SizeGb: req.SizeGb}, nil
153+
}
154+
155+
deleteAttempts := 0
156+
volumeMgr.deleteFunc = func(ctx context.Context, id string) error {
157+
deleteAttempts++
158+
if deleteAttempts == 1 {
159+
return volumes.ErrInUse
160+
}
161+
return nil
162+
}
163+
164+
volID, err := mgr.setupDiskRootVolume(context.Background(), buildID)
165+
166+
require.NoError(t, err)
167+
assert.Equal(t, "build-disk-build-1", volID)
168+
assert.Equal(t, 1, instanceMgr.deleteCallCount)
169+
assert.Equal(t, 2, deleteAttempts)
170+
assert.Equal(t, 2, volumeMgr.createCallCount)
171+
172+
metaAfter, err := readMetadata(mgr.paths, buildID)
173+
require.NoError(t, err)
174+
assert.Nil(t, metaAfter.BuilderInstance)
175+
}
176+
123177
func TestBuilderVolumeAttachments(t *testing.T) {
124178
attachments := builderVolumeAttachments("src-vol", "cfg-vol", "")
125179
require.Len(t, attachments, 2)

lib/builds/manager.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,16 @@ func (m *manager) setupDiskRootVolume(ctx context.Context, buildID string) (stri
803803
if errors.Is(err, volumes.ErrAlreadyExists) {
804804
// A previous attempt at this build crashed before cleanup. Delete
805805
// the leftover volume and start fresh.
806-
if delErr := m.volumeManager.DeleteVolume(ctx, volID); delErr != nil {
806+
delErr := m.volumeManager.DeleteVolume(ctx, volID)
807+
if errors.Is(delErr, volumes.ErrInUse) {
808+
// The leftover volume may still be attached to a stale builder
809+
// instance from a crashed process. Remove it and retry.
810+
if cleanupErr := m.cleanupStaleBuilderInstance(ctx, buildID); cleanupErr != nil {
811+
return "", fmt.Errorf("cleanup stale builder instance: %w", cleanupErr)
812+
}
813+
delErr = m.volumeManager.DeleteVolume(ctx, volID)
814+
}
815+
if delErr != nil {
807816
return "", fmt.Errorf("delete leftover buildkit root volume: %w", delErr)
808817
}
809818
_, err = m.volumeManager.CreateVolume(ctx, volumes.CreateVolumeRequest{
@@ -818,6 +827,28 @@ func (m *manager) setupDiskRootVolume(ctx context.Context, buildID string) (stri
818827
return volID, nil
819828
}
820829

830+
func (m *manager) cleanupStaleBuilderInstance(ctx context.Context, buildID string) error {
831+
meta, err := readMetadata(m.paths, buildID)
832+
if err != nil {
833+
return fmt.Errorf("read build metadata: %w", err)
834+
}
835+
if meta.BuilderInstance == nil || *meta.BuilderInstance == "" {
836+
return nil
837+
}
838+
839+
builderInstanceID := *meta.BuilderInstance
840+
if err := m.instanceManager.DeleteInstance(ctx, builderInstanceID); err != nil && !errors.Is(err, instances.ErrNotFound) {
841+
return fmt.Errorf("delete stale builder instance %s: %w", builderInstanceID, err)
842+
}
843+
844+
meta.BuilderInstance = nil
845+
if err := writeMetadata(m.paths, meta); err != nil {
846+
return fmt.Errorf("clear stale builder instance metadata: %w", err)
847+
}
848+
849+
return nil
850+
}
851+
821852
// builderVolumeAttachments returns the volume attachments for a builder VM.
822853
// diskRootVolID is empty when the disk root feature is disabled.
823854
func builderVolumeAttachments(sourceVolID, configVolID, diskRootVolID string) []instances.VolumeAttachment {

lib/instances/create.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ var systemDirectories = []string{
5151
"/var",
5252
}
5353

54+
// allowedSystemMountPaths are explicit exceptions under system directories that
55+
// are required for internal platform workloads.
56+
var allowedSystemMountPaths = map[string]struct{}{
57+
"/var/lib/buildkit": {},
58+
}
59+
5460
// generateVsockCID converts first 8 chars of instance ID to a unique CID
5561
// CIDs 0-2 are reserved (hypervisor, loopback, host)
5662
// Returns value in range 3 to 4294967295
@@ -665,7 +671,7 @@ func validateVolumeAttachments(volumes []VolumeAttachment) error {
665671
cleanPath := filepath.Clean(vol.MountPath)
666672

667673
// Check for system directories
668-
if isSystemDirectory(cleanPath) {
674+
if isSystemDirectory(cleanPath) && !isAllowedSystemMountPath(cleanPath) {
669675
return fmt.Errorf("volume %s: cannot mount to system directory %q", vol.VolumeID, cleanPath)
670676
}
671677

@@ -704,6 +710,11 @@ func isSystemDirectory(path string) bool {
704710
return false
705711
}
706712

713+
func isAllowedSystemMountPath(path string) bool {
714+
_, ok := allowedSystemMountPaths[filepath.Clean(path)]
715+
return ok
716+
}
717+
707718
// startAndBootVM starts the VMM and boots the VM
708719
func (m *manager) startAndBootVM(
709720
ctx context.Context,

lib/instances/resource_limits_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ func TestValidateVolumeAttachments_SystemDirectory(t *testing.T) {
4444
assert.Contains(t, err.Error(), "system directory")
4545
}
4646

47+
func TestValidateVolumeAttachments_BuildkitRootSystemPathAllowed(t *testing.T) {
48+
t.Parallel()
49+
volumes := []VolumeAttachment{{
50+
VolumeID: "vol-1",
51+
MountPath: "/var/lib/buildkit",
52+
}}
53+
54+
err := validateVolumeAttachments(volumes)
55+
assert.NoError(t, err)
56+
}
57+
58+
func TestValidateVolumeAttachments_VarSubdirectoryStillBlocked(t *testing.T) {
59+
t.Parallel()
60+
volumes := []VolumeAttachment{{
61+
VolumeID: "vol-1",
62+
MountPath: "/var/lib/other",
63+
}}
64+
65+
err := validateVolumeAttachments(volumes)
66+
assert.Error(t, err)
67+
assert.Contains(t, err.Error(), "system directory")
68+
}
69+
4770
func TestValidateVolumeAttachments_DuplicatePaths(t *testing.T) {
4871
t.Parallel()
4972
volumes := []VolumeAttachment{

0 commit comments

Comments
 (0)