Skip to content
Closed
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
6 changes: 3 additions & 3 deletions cmd/atecontroller/internal/controllers/workerpool_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import (
metav1ac "k8s.io/client-go/applyconfigurations/meta/v1"

"github.com/agent-substrate/substrate/internal/ateomcapacity"
"github.com/agent-substrate/substrate/internal/ateompath"
"github.com/agent-substrate/substrate/internal/deviceplugin"
"github.com/agent-substrate/substrate/internal/installdefaults"
"github.com/agent-substrate/substrate/internal/nodepath"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
)

Expand Down Expand Up @@ -167,7 +167,7 @@ func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool, otel ateomOTelSettin
WithReadOnly(true),
corev1ac.VolumeMount().
WithName("run-ateom").
WithMountPath(ateompath.BasePath).
WithMountPath(nodepath.BasePath).
WithMountPropagation(corev1.MountPropagationHostToContainer),
corev1ac.VolumeMount().
WithName(atunnelIdentityVolume).
Expand All @@ -194,7 +194,7 @@ func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool, otel ateomOTelSettin
corev1ac.Volume().
WithName("run-ateom").
WithHostPath(corev1ac.HostPathVolumeSource().
WithPath(ateompath.BasePath).
WithPath(nodepath.BasePath).
WithType(corev1.HostPathDirectoryOrCreate)),
corev1ac.Volume().
WithName(atunnelIdentityVolume).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ import (
metav1ac "k8s.io/client-go/applyconfigurations/meta/v1"

"github.com/agent-substrate/substrate/internal/ateomcapacity"
"github.com/agent-substrate/substrate/internal/ateompath"
"github.com/agent-substrate/substrate/internal/deviceplugin"
"github.com/agent-substrate/substrate/internal/installdefaults"
"github.com/agent-substrate/substrate/internal/nodepath"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
)

Expand Down Expand Up @@ -713,7 +713,7 @@ func expectedDeploymentApplyConfig(mutatePodSpec func(*corev1ac.PodSpecApplyConf
corev1ac.Volume().
WithName("run-ateom").
WithHostPath(corev1ac.HostPathVolumeSource().
WithPath(ateompath.BasePath).
WithPath(nodepath.BasePath).
WithType(corev1.HostPathDirectoryOrCreate)),
corev1ac.Volume().
WithName(atunnelIdentityVolume).
Expand Down Expand Up @@ -799,7 +799,7 @@ func expectedDeploymentApplyConfig(mutatePodSpec func(*corev1ac.PodSpecApplyConf
WithReadOnly(true),
corev1ac.VolumeMount().
WithName("run-ateom").
WithMountPath(ateompath.BasePath).
WithMountPath(nodepath.BasePath).
WithMountPropagation(corev1.MountPropagationHostToContainer),
corev1ac.VolumeMount().
WithName(atunnelIdentityVolume).
Expand Down
7 changes: 4 additions & 3 deletions cmd/atelet/imagegc.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ import (
"strings"
"time"

"github.com/agent-substrate/substrate/internal/ateompath"
"github.com/agent-substrate/substrate/cmd/atelet/internal/ateletpath"
"github.com/agent-substrate/substrate/internal/imagecache"
"github.com/agent-substrate/substrate/internal/nodepath"
"github.com/spf13/pflag"
"golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -79,7 +80,7 @@ func validateImageCacheGCFlags() error {
if imageCacheDirOutsideBasePath(*imageCacheDir) {
slog.Warn("Image cache dir is outside the ateom base path; its volume watermarks are measured separately from actor state",
slog.String("image_cache_dir", *imageCacheDir),
slog.String("actors_dir", ateompath.ActorsDir))
slog.String("actors_dir", ateletpath.ActorsDir))
}
return nil
}
Expand All @@ -93,7 +94,7 @@ func imageCacheDirOutsideBasePath(dir string) bool {
if err != nil {
abs = filepath.Clean(dir)
}
return !strings.HasPrefix(abs, ateompath.BasePath+string(os.PathSeparator))
return !strings.HasPrefix(abs, nodepath.BasePath+string(os.PathSeparator))
}

// imageCacheGCTarget computes the bytes a pass should free: the larger
Expand Down
14 changes: 7 additions & 7 deletions cmd/atelet/imagegc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import (
"testing"
"time"

"github.com/agent-substrate/substrate/internal/ateompath"
"github.com/agent-substrate/substrate/internal/imagecache"
"github.com/agent-substrate/substrate/internal/nodepath"
)

func TestImageCacheGCTarget(t *testing.T) {
Expand Down Expand Up @@ -160,13 +160,13 @@ func TestImageCacheDirOutsideBasePath(t *testing.T) {
dir string
want bool
}{
{"inside", filepath.Join(ateompath.BasePath, "image-cache"), false},
{"inside with doubled separator", ateompath.BasePath + "//image-cache", false},
{"inside via dot-dot", ateompath.BasePath + "/x/../image-cache", false},
{"base path itself is not inside", ateompath.BasePath, true},
{"sibling with the base path as name prefix", ateompath.BasePath + "-other/image-cache", true},
{"inside", filepath.Join(nodepath.BasePath, "image-cache"), false},
{"inside with doubled separator", nodepath.BasePath + "//image-cache", false},
{"inside via dot-dot", nodepath.BasePath + "/x/../image-cache", false},
{"base path itself is not inside", nodepath.BasePath, true},
{"sibling with the base path as name prefix", nodepath.BasePath + "-other/image-cache", true},
{"outside", "/var/lib/elsewhere/image-cache", true},
{"dot-dot escaping the base path", ateompath.BasePath + "/../elsewhere/image-cache", true},
{"dot-dot escaping the base path", nodepath.BasePath + "/../elsewhere/image-cache", true},
{"relative resolves against the cwd, not the base path", "image-cache", true},
}
for _, tc := range cases {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Ateom and atelet need to agree on many filesystem paths. They are defined in this package.
package ateompath
// Package ateletpath is atelet's on-node layout: the per-actor directories
// it passes to ateom as ActorDirs, and the directories only atelet uses.
package ateletpath

import (
"path/filepath"
)

const (
// The base path. This is both the path of the root shared folder on the
// host filesystem, and when it is mounted into ateom and atelet containers.
BasePath = "/var/lib/ateom-gvisor"
"github.com/agent-substrate/substrate/internal/nodepath"
"github.com/agent-substrate/substrate/internal/proto/ateompb"
)

var (
// StaticFilesDir holds things like downloaded runsc binaries.
StaticFilesDir = filepath.Join(BasePath, "static-files")
StaticFilesDir = filepath.Join(nodepath.BasePath, "static-files")

// ImageCacheDir is the node-local OCI image layer cache (see
// internal/imagecache). It lives under BasePath so the cached layer
// directories are visible at the same path in atelet (which writes them)
// and in every ateom pod (which mounts them as overlay lowerdirs).
ImageCacheDir = filepath.Join(BasePath, "image-cache")
ImageCacheDir = filepath.Join(nodepath.BasePath, "image-cache")

// ActorsDir holds the per-actor state directories (see ActorPath). The
// image cache's eviction root-set scan reads the bundle overlay specs
// under it.
ActorsDir = filepath.Join(BasePath, "actors")

// AteomSupportSocket is the node-local atelet socket used by atunnel
// to request credentials for the worker's current actor assignment.
AteomSupportSocket = filepath.Join(BasePath, "ateom-support.sock")
ActorsDir = filepath.Join(nodepath.BasePath, "actors")
)

func RunSCBinaryPath(sha256 string) string {
Expand All @@ -58,61 +52,6 @@ func GVisorReleaseDir(sha256 string) string {
return filepath.Join(StaticFilesDir, "gvisor-"+sha256)
}

// AteletOTLPSocketPath is the node-scoped unix socket atelet serves the OTLP
// relay on (see internal/otlprelay). It is node-scoped rather than per-pod
// because every ateom on the node pushes into the same relay: atelet is a
// DaemonSet, so one socket collapses N per-pod collector connections into one
// per-node connection.
//
// It sits directly under BasePath, which is the host directory already mounted
// at the same path into atelet and into every ateom pod, so no new volume is
// needed for ateom to reach it. Note that BasePath is mounted writable
// (workerpool_apply.go) and shared with CredentialBrokerSocket and the image
// cache, so a worker pod can unlink or replace this socket. Confining
// atelet-owned sockets to a subdirectory mounted read-only would be an
// improvement, but it is a property of the whole BasePath mount rather than of
// this socket — a read-only subdir needs its own volume and mount, and the pod
// keeps CAP_SYS_ADMIN. Tracked separately rather than solved here.
func AteletOTLPSocketPath() string {
return filepath.Join(
BasePath,
"atelet-otlp.sock",
)
}

// AteomsDir is the parent of every per-ateom directory. Each ateom creates
// AteomPath(podUID) under it when it boots, so listing this directory is how a
// scraper with no prior knowledge discovers the node's ateoms.
func AteomsDir() string {
return filepath.Join(BasePath, "ateoms")
}

func AteomPath(podUID string) string {
return filepath.Join(AteomsDir(), podUID)
}

func AteomSocketPath(podUID string) string {
return filepath.Join(
AteomPath(podUID),
"ateom.sock",
)
}

// ActorNetNSName names an actor's sandbox network namespace.
func ActorNetNSName(actorUID string) string {
return "ateom-actor:" + actorUID
}

// ActorNetNSPath is the mount path of the actor's named namespace.
func ActorNetNSPath(actorUID string) string {
return filepath.Join("/run/netns", ActorNetNSName(actorUID))
}

// ActorResolvConfPath is the resolver bind source outside the actor's rootfs.
func ActorResolvConfPath(actorUID string) string {
return filepath.Join(ActorPath(actorUID), "resolv.conf")
}

func ActorPath(actorUID string) string {
return filepath.Join(
ActorsDir,
Expand Down Expand Up @@ -154,27 +93,6 @@ func OCIBundlePath(actorUID, containerName string) string {
)
}

// ImageVolumeMountPath returns where ateom composes one image volume for a
// container. The path is per-container: containers of one actor may mount the
// same volume, and each needs its own mount point inside its own bundle.
func ImageVolumeMountPath(actorUID, containerName, volumeName string) string {
return ImageVolumeMountPathInBundle(OCIBundlePath(actorUID, containerName), volumeName)
}

// ImageVolumeMountPathInBundle returns the image volume mount path inside a
// bundle path.
func ImageVolumeMountPathInBundle(bundlePath, volumeName string) string {
return filepath.Join(bundlePath, "volumes", volumeName)
}

func RunscDebugLogDir(actorUID, containerName string) string {
return filepath.Join(
ActorPath(actorUID),
"runsc-debug-logs",
containerName,
)
}

func CheckpointStateDir(actorUID string) string {
return filepath.Join(
ActorPath(actorUID),
Expand All @@ -195,13 +113,6 @@ func LocalSnapshotDir(actorUID, snapshotName string) string {
return filepath.Join(LocalCheckpointsDir(actorUID), snapshotName)
}

// DurableDirTarFile is the snapshot file holding the tar of an
// actor's durable-dir volumes (entries are <volumeName>/... relative to
// DurableDirVolumeMountsDir). Written by ateom-microvm at checkpoint; a DATA
// snapshot consists of this file alone, so atelet uses the name to carve the
// durable data out of a FULL snapshot's file set.
const DurableDirTarFile = "durable-dir.tar"

// DurableDirVolumeMountsDir is the directory where individual durable-dir
// volumes are mounted.
func DurableDirVolumeMountsDir(actorUID string) string {
Expand Down Expand Up @@ -275,13 +186,6 @@ func PIDFileDir(actorUID string) string {
)
}

func PIDFilePath(actorUID, containerName string) string {
return filepath.Join(
PIDFileDir(actorUID),
containerName+".pid",
)
}

func VolumesDir(actorUID string) string {
return filepath.Join(
ActorPath(actorUID),
Expand All @@ -296,12 +200,18 @@ func VolumeHostPath(actorUID, volumeName string) string {
)
}

// StagingDirPrefix returns the prefix directory for staging CSI volumes.
func StagingDirPrefix() string {
return filepath.Join(BasePath, "staging")
}

// KubeletPluginSocketPath returns the path to the CSI driver socket in kubelet plugins directory.
func KubeletPluginSocketPath(driverName string) string {
return filepath.Join("/var/lib/kubelet/plugins", driverName, "csi.sock")
// ActorDirs is the directory set atelet passes to ateom for an actor. ateom
// takes these from the request rather than deriving them from the actor UID.
func ActorDirs(actorUID string) *ateompb.ActorDirs {
return &ateompb.ActorDirs{
RootDir: ActorPath(actorUID),
OciBundleDir: OCIBundleDir(actorUID),
RunscStateDir: RunSCStateDir(actorUID),
PidFileDir: PIDFileDir(actorUID),
CheckpointDir: CheckpointStateDir(actorUID),
RestoreDir: RestoreStateDir(actorUID),
DurableDirVolumeMountsDir: DurableDirVolumeMountsDir(actorUID),
SystemInfoVolumeRootsDir: SystemInfoVolumeRootsDir(actorUID),
VolumesDir: VolumesDir(actorUID),
}
}
50 changes: 50 additions & 0 deletions cmd/atelet/internal/ateletpath/ateletpath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 ateletpath

import (
"path/filepath"
"strings"
"testing"
)

func TestActorDirs(t *testing.T) {
const actorUID = "actor-uid-1"
actorDirs := ActorDirs(actorUID)
if actorDirs.GetRootDir() != ActorPath(actorUID) {
t.Fatalf("root_dir = %q, want %q", actorDirs.GetRootDir(), ActorPath(actorUID))
}
under := map[string]string{
"oci_bundle_dir": actorDirs.GetOciBundleDir(),
"runsc_state_dir": actorDirs.GetRunscStateDir(),
"pid_file_dir": actorDirs.GetPidFileDir(),
"checkpoint_dir": actorDirs.GetCheckpointDir(),
"restore_dir": actorDirs.GetRestoreDir(),
"durable_dir_volume_mounts_dir": actorDirs.GetDurableDirVolumeMountsDir(),
"system_info_volume_roots_dir": actorDirs.GetSystemInfoVolumeRootsDir(),
"volumes_dir": actorDirs.GetVolumesDir(),
}
seen := map[string]string{}
for field, dir := range under {
rel, err := filepath.Rel(actorDirs.GetRootDir(), dir)
if err != nil || rel == "." || strings.HasPrefix(rel, "..") {
t.Errorf("%s = %q is not a proper subdirectory of root_dir %q", field, dir, actorDirs.GetRootDir())
}
if other, dup := seen[dir]; dup {
t.Errorf("%s and %s share %q", field, other, dir)
}
seen[dir] = field
}
}
Loading
Loading