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
14 changes: 0 additions & 14 deletions cmd/atelet/internal/ateletpath/ateletpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,6 @@ func ActorSandboxAssetsFile(actorUID string) string {
)
}

func RunSCStateDir(actorUID string) string {
return filepath.Join(
ActorPath(actorUID),
"runsc-state",
)
}

func OCIBundleDir(actorUID string) string {
return filepath.Join(
ActorPath(actorUID),
Expand Down Expand Up @@ -171,13 +164,6 @@ func RestoreStateDir(actorUID string) string {
)
}

func PIDFileDir(actorUID string) string {
return filepath.Join(
ActorPath(actorUID),
"pidfiles",
)
}

func VolumesDir(actorUID string) string {
return filepath.Join(
ActorPath(actorUID),
Expand Down
16 changes: 0 additions & 16 deletions cmd/atelet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1874,22 +1874,6 @@ func resetActorDirs(actorUID string) error {
return wrapFileSystemErr("while creating bundle dir: %w", err)
}

runscDir := ateletpath.RunSCStateDir(actorUID)
if err := os.RemoveAll(runscDir); err != nil {
return wrapFileSystemErr("while deleting runsc state dir: %w", err)
}
if err := os.MkdirAll(runscDir, 0o700); err != nil {
return wrapFileSystemErr("while creating runsc state dir: %w", err)
}

pidFileDir := ateletpath.PIDFileDir(actorUID)
if err := os.RemoveAll(pidFileDir); err != nil {
return wrapFileSystemErr("while deleting PID file dir: %w", err)
}
if err := os.MkdirAll(pidFileDir, 0o700); err != nil {
return wrapFileSystemErr("while creating PID file dir: %w", err)
}

checkpointDir := ateletpath.CheckpointStateDir(actorUID)
if err := os.RemoveAll(checkpointDir); err != nil {
return wrapFileSystemErr("while deleting checkpoint-state dir: %w", err)
Expand Down
71 changes: 71 additions & 0 deletions cmd/ateom-gvisor/actorpath.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//go:build linux

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The paths ateom-gvisor derives from the ActorDirs atelet sends.

package main

import (
"fmt"
"os"
"path/filepath"

"github.com/agent-substrate/substrate/internal/imagecache"
"github.com/agent-substrate/substrate/internal/proto/ateompb"
)

// ociBundlePath is the container's OCI bundle.
func ociBundlePath(actorDirs *ateompb.ActorDirs, containerName string) string {
return filepath.Join(actorDirs.GetOciBundleDir(), containerName)
}

// runscStateDir is runsc --root for the actor's sandbox.
func runscStateDir(actorDirs *ateompb.ActorDirs) string {
return filepath.Join(actorDirs.GetRootDir(), "runsc-state")
}

// pidFileDir is where runsc writes <container>.pid.
func pidFileDir(actorDirs *ateompb.ActorDirs) string {
return filepath.Join(actorDirs.GetRootDir(), "pidfiles")
}

// pidFilePath is where runsc writes the container's pid.
func pidFilePath(actorDirs *ateompb.ActorDirs, containerName string) string {
return filepath.Join(pidFileDir(actorDirs), containerName+".pid")
}

// resolvConfPath is the resolver bind source outside the actor's rootfs.
func resolvConfPath(actorDirs *ateompb.ActorDirs) string {
return filepath.Join(actorDirs.GetRootDir(), "resolv.conf")
}

// resetRunscStateAndPidFileDirs empties both directories for a new activation.
// runsc can leave mounts behind in its state directory (its null-netns), which
// must be detached in this mount namespace before they can be removed.
func resetRunscStateAndPidFileDirs(actorDirs *ateompb.ActorDirs) error {
for _, dir := range []string{runscStateDir(actorDirs), pidFileDir(actorDirs)} {
if err := imagecache.UnmountAllUnder(dir); err != nil {
return fmt.Errorf("while unmounting under %q: %w", dir, err)
}
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("while clearing %q: %w", dir, err)
}
if err := os.MkdirAll(dir, 0o700); err != nil {
return fmt.Errorf("while creating %q: %w", dir, err)
}
}
return nil
}
71 changes: 71 additions & 0 deletions cmd/ateom-gvisor/actorpath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//go:build linux

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"os"
"path/filepath"
"testing"

"golang.org/x/sys/unix"

"github.com/agent-substrate/substrate/internal/proto/ateompb"
"github.com/agent-substrate/substrate/internal/roottest"
)

func TestResetRunscStateAndPidFileDirs(t *testing.T) {
Comment thread
HavenXia marked this conversation as resolved.
Resolved
actorDirs := &ateompb.ActorDirs{RootDir: t.TempDir()}
if err := os.MkdirAll(filepath.Join(runscStateDir(actorDirs), "stale"), 0o700); err != nil {
t.Fatal(err)
}
if err := resetRunscStateAndPidFileDirs(actorDirs); err != nil {
t.Fatalf("resetRunscStateAndPidFileDirs() = %v", err)
}
for _, dir := range []string{runscStateDir(actorDirs), pidFileDir(actorDirs)} {
if entries, err := os.ReadDir(dir); err != nil || len(entries) != 0 {
t.Errorf("ReadDir(%q) = %v, %v; want an empty directory", dir, entries, err)
}
}
}

// runsc leaves mounts in its state directory; unlinking one fails with EBUSY
// until it is detached.
func TestResetRunscStateAndPidFileDirs_DetachesMounts(t *testing.T) {
roottest.Require(t, "mount/unmount")
actorDirs := &ateompb.ActorDirs{RootDir: t.TempDir()}
if err := os.MkdirAll(runscStateDir(actorDirs), 0o700); err != nil {
t.Fatal(err)
}
src := filepath.Join(t.TempDir(), "netns")
target := filepath.Join(runscStateDir(actorDirs), "null-netns")
for _, f := range []string{src, target} {
if err := os.WriteFile(f, nil, 0o600); err != nil {
t.Fatal(err)
}
}
if err := unix.Mount(src, target, "", unix.MS_BIND, ""); err != nil {
t.Fatalf("bind mount: %v", err)
}
t.Cleanup(func() { _ = unix.Unmount(target, unix.MNT_DETACH) })

if err := resetRunscStateAndPidFileDirs(actorDirs); err != nil {
t.Fatalf("resetRunscStateAndPidFileDirs() = %v", err)
}
if entries, err := os.ReadDir(runscStateDir(actorDirs)); err != nil || len(entries) != 0 {
t.Errorf("ReadDir(%q) = %v, %v; want an empty directory", runscStateDir(actorDirs), entries, err)
}
}
2 changes: 1 addition & 1 deletion cmd/ateom-gvisor/durable.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (

// durableTarFile is the snapshot file holding the tar of the actor's durable-dir
// volumes. Its entries are <volumeName>/... relative to
// ateompath.DurableDirVolumeMountsDir, so extraction restores the same layout.
// ActorDirs.durable_dir_volume_mounts_dir, so extraction restores the same layout.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still TODO to stop sharing this info entirely.

// The name is shared with atelet, which uses it to carve durable data out of a
// FULL snapshot's file set when uploading a paused checkpoint as DATA.
const durableTarFile = resources.DurableDirTarFile
Expand Down
11 changes: 8 additions & 3 deletions cmd/ateom-gvisor/hosted.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/agent-substrate/substrate/internal/ateomnet"
"github.com/agent-substrate/substrate/internal/atunnel"
"github.com/agent-substrate/substrate/internal/proto/ateompb"
"github.com/agent-substrate/substrate/internal/resources"
)

Expand All @@ -35,6 +36,8 @@ type hostedActor struct {
// Attribution is available during boot.
attribution resources.ActorAttribution
network *ateomnet.SandboxSession
// resolvConf is the actor's resolver bind source, removed with its network.
resolvConf string
// Set once the containers exist.
session *workloadSession
}
Expand All @@ -56,7 +59,7 @@ func (s *AteomService) admitActor(attribution resources.ActorAttribution) (*host
}

// hostActor sets up the actor's network, replacing any stale one.
func (s *AteomService) hostActor(ctx context.Context, attribution resources.ActorAttribution) (*hostedActor, error) {
func (s *AteomService) hostActor(ctx context.Context, attribution resources.ActorAttribution, actorDirs *ateompb.ActorDirs) (*hostedActor, error) {
uid := attribution.UID
if uid == "" {
return nil, fmt.Errorf("actor UID is required")
Expand Down Expand Up @@ -89,7 +92,8 @@ func (s *AteomService) hostActor(ctx context.Context, attribution resources.Acto
}

// Use the actor's gateway as its DNS resolver.
if _, err := actorResolvConf(uid); err != nil {
resolvConf, err := actorResolvConf(actorDirs)
if err != nil {
_ = session.Close(ctx)
s.actorsMu.Lock()
delete(s.actors, uid)
Expand All @@ -99,6 +103,7 @@ func (s *AteomService) hostActor(ctx context.Context, attribution resources.Acto

s.actorsMu.Lock()
hosted.network = session
hosted.resolvConf = resolvConf
s.actorsMu.Unlock()
return hosted, nil
}
Expand All @@ -122,7 +127,7 @@ func (s *AteomService) unhostActor(ctx context.Context, actorUID string) error {
s.actorsMu.Unlock()
}()

removeActorResolvConf(ctx, actorUID)
removeActorResolvConf(ctx, hosted.resolvConf)
if hosted.network == nil {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/ateom-gvisor/hosted_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestDrainingActorsStillCountAgainstTheCeiling(t *testing.T) {
maxActors: 1,
draining: 1,
}
if _, err := s.hostActor(context.Background(), resources.ActorAttribution{UID: "actor-a"}); err == nil {
if _, err := s.hostActor(context.Background(), resources.ActorAttribution{UID: "actor-a"}, nil); err == nil {
t.Error("admitted an actor while a draining one still held the only place")
}
}
Expand Down
Loading
Loading