diff --git a/components/ws-daemon/pkg/content/service.go b/components/ws-daemon/pkg/content/service.go index 12fe393137957f..4d258c65a1b7c2 100644 --- a/components/ws-daemon/pkg/content/service.go +++ b/components/ws-daemon/pkg/content/service.go @@ -35,7 +35,6 @@ import ( "github.com/gitpod-io/gitpod/ws-daemon/pkg/container" "github.com/gitpod-io/gitpod/ws-daemon/pkg/internal/session" "github.com/gitpod-io/gitpod/ws-daemon/pkg/iws" - "github.com/gitpod-io/gitpod/ws-daemon/pkg/quota" ) // WorkspaceService implements the InitService and WorkspaceService @@ -45,7 +44,6 @@ type WorkspaceService struct { store *session.Store ctx context.Context stopService context.CancelFunc - sandboxes quota.SandboxProvider runtime container.Runtime api.UnimplementedInWorkspaceServiceServer @@ -242,11 +240,6 @@ func (s *WorkspaceService) InitWorkspace(ctx context.Context, req *api.InitWorks func (s *WorkspaceService) creator(req *api.InitWorkspaceRequest) session.WorkspaceFactory { return func(ctx context.Context, location string) (res *session.Workspace, err error) { - err = s.createSandbox(ctx, req, location) - if err != nil { - return nil, err - } - return &session.Workspace{ Location: location, CheckoutLocation: getCheckoutLocation(req), @@ -264,49 +257,6 @@ func (s *WorkspaceService) creator(req *api.InitWorkspaceRequest) session.Worksp } } -func (s *WorkspaceService) createSandbox(ctx context.Context, req *api.InitWorkspaceRequest, location string) (err error) { - if s.config.WorkspaceSizeLimit == 0 { - return - } - - //nolint:ineffassign - span, ctx := opentracing.StartSpanFromContext(ctx, "createSandbox") - defer tracing.FinishSpan(span, &err) - - owi := log.OWI(req.Metadata.Owner, req.Metadata.MetaId, req.Id) - if req.FullWorkspaceBackup { - msg := "cannot create sandboxes for workspaces with full workspace backup - skipping sandbox" - span.LogKV("warning", msg) - log.WithFields(owi).Warn(msg) - return - } - - // Create and mount sandbox - mode := os.FileMode(0755) - sandbox := filepath.Join(s.store.Location, req.Id+".sandbox") - err = s.sandboxes.Create(ctx, sandbox, s.config.WorkspaceSizeLimit) - if err != nil { - log.WithFields(owi).WithField("sandbox", sandbox).WithField("location", location).WithError(err).Error("cannot create sandbox") - return status.Error(codes.Internal, "cannot create sandbox") - } - if _, err := os.Stat(location); os.IsNotExist(err) { - // in the very unlikely event that the workspace Pod did not mount (and thus create) the workspace directory, create it - err = os.Mkdir(location, mode) - if os.IsExist(err) { - log.WithError(err).WithField("location", location).Debug("ran into non-atomic workspce location existence check") - } else if err != nil { - log.WithFields(owi).WithError(err).Error("cannot create workspace mount point") - return status.Error(codes.Internal, "cannot create workspace") - } - } - err = s.sandboxes.Mount(ctx, sandbox, location) - if err != nil { - log.WithFields(owi).WithField("sandbox", sandbox).WithField("location", location).WithError(err).Error("cannot mount sandbox") - return status.Error(codes.Internal, "cannot mount sandbox") - } - return nil -} - // getCheckoutLocation returns the first checkout location found of any Git initializer configured by this request func getCheckoutLocation(req *api.InitWorkspaceRequest) string { spec := req.Initializer.Spec @@ -399,17 +349,6 @@ func (s *WorkspaceService) DisposeWorkspace(ctx context.Context, req *api.Dispos resp.GitStatus = repo } - if s.config.WorkspaceSizeLimit > 0 && !sess.FullWorkspaceBackup { - // We can delete the sandbox here (rather than in the store) because WaitOrMarkForDisposal - // ensures we're doing this exclusively for this workspace. - err = s.sandboxes.Dispose(ctx, sess.Location) - if err != nil { - log.WithError(err).WithField("workspaceId", req.Id).Error("cannot dispose sandbox") - span.LogKV("error", err.Error()) - return nil, status.Error(codes.Internal, "cannot dispose sandbox") - } - } - err = s.store.Delete(ctx, req.Id) if err != nil { log.WithError(err).WithField("workspaceId", req.Id).Error("cannot delete workspace from store") diff --git a/components/ws-daemon/pkg/quota/mounts.go b/components/ws-daemon/pkg/quota/mounts.go deleted file mode 100644 index 48cb4b3037dac5..00000000000000 --- a/components/ws-daemon/pkg/quota/mounts.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2020 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License-AGPL.txt in the project root for license information. - -package quota - -import ( - "bufio" - "io" - "os" - "os/exec" - "strings" - - "golang.org/x/xerrors" -) - -// mountPoint represents an entry in a proc mounts file -type mountPoint struct { - Device string - Path string - FS string - Opts string -} - -// findmountPointFromProc calls FindmountPoint with /proc/mounts -func findMountPointFromProc(dir string) (res *mountPoint, err error) { - f, err := os.OpenFile("/proc/self/mounts", os.O_RDONLY, 0) - if err != nil { - return nil, xerrors.Errorf("cannot find mount point: %w", err) - } - defer f.Close() - - return findMountPoint(f, dir) -} - -// findmountPoint finds the mount entry for a directory -func findMountPoint(mountFile io.Reader, dir string) (res *mountPoint, err error) { - res = &mountPoint{} - scanner := bufio.NewScanner(mountFile) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - - if len(fields) < 4 { - continue - } - - if mp := fields[1]; strings.HasPrefix(dir, mp) && len(mp) > len(res.Path) { - res.Device = fields[0] - res.Path = fields[1] - res.FS = fields[2] - res.Opts = fields[3] - } - } - if err = scanner.Err(); err != nil { - res = nil - return - } - if res.Path == "" { - res = nil - } - - return -} - -// findLoopdevInLosetupOutput expects out to be the output of "losetup -a". -// It searches for lodev in that output and returns the backing filename. -// If the lodev isn't found it returns an empty string. -func findLoopdevInLosetupOutput(lodev string, out []byte) (fn string) { - lines := strings.Split(string(out), "\n") - for _, l := range lines { - fields := strings.Fields(l) - if len(fields) != 3 { - continue - } - if fields[0] != lodev+":" { - continue - } - - return fields[2] - } - - return "" -} - -// findLoopdevBacking runs "losetup -a" and uses findLoopdevInLosetupOutput -// to find the backing file of the lodev. If the lodev isn't found, this function -// returns an empty string and nil error. -func findLoopdevBacking(lodev string) (fn string, err error) { - out, err := exec.Command("losetup", "-a").CombinedOutput() - if err != nil { - return "", xerrors.Errorf("cannot run losetup: %w: %s", err, string(out)) - } - - return findLoopdevInLosetupOutput(lodev, out), nil -} diff --git a/components/ws-daemon/pkg/quota/mounts_test.go b/components/ws-daemon/pkg/quota/mounts_test.go deleted file mode 100644 index 5b24fa35086dcd..00000000000000 --- a/components/ws-daemon/pkg/quota/mounts_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2020 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License-AGPL.txt in the project root for license information. - -package quota - -import ( - "strings" - "testing" - - "github.com/google/go-cmp/cmp" -) - -const ( - mountTable00 = `group /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0 -/dev/sdb /workspace ext4 rw,relatime,discard,data=ordered 0 0 -/dev/sdb /theia ext4 ro,relatime,discard,data=ordered 0 0` - losetupAOut00 = `/dev/loop0: 0 /tmp/test -/dev/loop1: 0 /mnt/workingarea/10.sandbox -/dev/loop2: 0 /mnt/workingarea/20.sandbox -/dev/loop3: 0 /mnt/workingarea/30.sandbox -/dev/loop4: 0 /mnt/workingarea/40.sandbox -/dev/loop5: 0 /mnt/workingarea/50.sandbox -/dev/loop6: 0 /mnt/workingarea/60.sandbox -/dev/loop7: 0 /mnt/workingarea/70.sandbox` -) - -func TestFindMountPoint(t *testing.T) { - tests := []struct { - Desc string - MountTable string - Dir string - Expectation *mountPoint - }{ - {"valid rootpath", mountTable00, "/workspace", &mountPoint{Device: "/dev/sdb", Path: "/workspace", FS: "ext4", Opts: "rw,relatime,discard,data=ordered"}}, - {"valid subpath", mountTable00, "/workspace/foobar", &mountPoint{Device: "/dev/sdb", Path: "/workspace", FS: "ext4", Opts: "rw,relatime,discard,data=ordered"}}, - {"not found", mountTable00, "/fofofofo", nil}, - } - - for _, test := range tests { - t.Run(test.Desc, func(t *testing.T) { - mp, err := findMountPoint(strings.NewReader(test.MountTable), test.Dir) - if err != nil { - t.Error(err) - return - } - - if diff := cmp.Diff(test.Expectation, mp); diff != "" { - t.Errorf("unexpected result (-want +got):\n%s", diff) - } - }) - } -} - -func TestFindLoopdevInLosetupOutput(t *testing.T) { - tests := []struct { - Desc string - Losetup string - Lodev string - Expectation string - }{ - {"valid lodev", losetupAOut00, "/dev/loop1", "/mnt/workingarea/10.sandbox"}, - {"not found", losetupAOut00, "/fofofofo", ""}, - } - - for _, test := range tests { - t.Run(test.Desc, func(t *testing.T) { - act := findLoopdevInLosetupOutput(test.Lodev, []byte(test.Losetup)) - - if act != test.Expectation { - t.Errorf("unexpected result: %s, expected: %s", act, test.Expectation) - } - }) - } -} diff --git a/components/ws-daemon/pkg/quota/sandbox_linux.go b/components/ws-daemon/pkg/quota/sandbox_linux.go deleted file mode 100644 index 21c12d64eadcb2..00000000000000 --- a/components/ws-daemon/pkg/quota/sandbox_linux.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) 2020 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License-AGPL.txt in the project root for license information. - -// +build linux - -package quota - -import ( - "context" - "os" - "os/exec" - "strings" - "sync" - - "github.com/opentracing/opentracing-go" - "github.com/sirupsen/logrus" - "golang.org/x/xerrors" - - "github.com/gitpod-io/gitpod/common-go/log" - "github.com/gitpod-io/gitpod/common-go/tracing" -) - -// SandboxProvider can create/mount and dispose sandboxes -type SandboxProvider struct { - mu sync.Mutex -} - -// Create creates a new filesystem -func (sp *SandboxProvider) Create(ctx context.Context, path string, size Size) (err error) { - //nolint:ineffassign - span, ctx := opentracing.StartSpanFromContext(ctx, "SandboxProvider.Create") - defer tracing.FinishSpan(span, &err) - - if _, err := os.Stat(path); err == nil { - return xerrors.Errorf("sandbox exists already") - } - - f, err := os.Create(path) - if err != nil { - return err - } - err = f.Close() - if err != nil { - return err - } - err = os.Truncate(path, int64(size)) - if err != nil { - return err - } - - out, err := exec.Command("mkfs.ext4", path).CombinedOutput() - if err != nil { - return xerrors.Errorf("cannot make filesystem: %v: %s", err, string(out)) - } - span.LogKV("path", path) - - return nil -} - -// Mount mounts a preapred filesystem in dst -func (sp *SandboxProvider) Mount(ctx context.Context, path, dst string) (err error) { - //nolint:ineffassign - span, ctx := opentracing.StartSpanFromContext(ctx, "SandboxProvider.Mount") - defer tracing.FinishSpan(span, &err) - - // acquire the lock to synchronize loopback device creation and use - sp.mu.Lock() - defer sp.mu.Unlock() - - // get next loopback device - out, err := exec.Command("losetup", "-f").CombinedOutput() - if err != nil { - return xerrors.Errorf("cannot mount sandbox: %v: %s", err, string(out)) - } - lodev := strings.TrimSpace(string(out)) - // lodev might not exist yet, only the first 8 (i.e. up to /dev/loop7) exist by default - if _, err := os.Stat(lodev); os.IsNotExist(err) { - cmds := [][]string{ - {"mknod", lodev, "b", "7", strings.TrimPrefix(lodev, "/dev/loop")}, - {"chown", "--reference=/dev/loop0", lodev}, - {"chmod", "--reference=/dev/loop0", lodev}, - } - - log.WithField("lodev", lodev).Debug("created new loopback device") - for _, cmd := range cmds { - out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput() - if err != nil { - return xerrors.Errorf("cannot mount sandbox: %v: %s", err, string(out)) - } - } - } - - // mount using lodev - out, err = exec.Command("mount", "-o", "rw,relatime,discard", path, dst).CombinedOutput() - if err != nil { - return xerrors.Errorf("cannot mount sandbox: %v: %s", err, string(out)) - } - - span.LogKV("path", path, "dst", dst, "dev", lodev) - log.WithFields(logrus.Fields{"path": path, "dst": dst, "dev": lodev}).Debug("mounted sandbox") - return nil -} - -// Dispose will unmount the sandbox (if it's mounted) -// and remove the backing file. -func (sp *SandboxProvider) Dispose(ctx context.Context, mountPath string) (err error) { - //nolint:ineffassign - span, ctx := opentracing.StartSpanFromContext(ctx, "SandboxProvider.Dispose") - defer tracing.FinishSpan(span, &err) - - mp, err := findMountPointFromProc(mountPath) - if err != nil { - return xerrors.Errorf("cannot dispose sandbox: %w", err) - } - if mp == nil { - return xerrors.Errorf("cannot dispose sandbox: did not find mountpoint") - } - - backingFile, err := findLoopdevBacking(mp.Device) - if err != nil { - return xerrors.Errorf("cannot dispose sandbox: %w", err) - } - - out, err := exec.Command("umount", mp.Path).CombinedOutput() - if err != nil { - return xerrors.Errorf("cannot unmount sandbox: %w: %s", err, string(out)) - } - - err = os.Remove(backingFile) - if err != nil { - return xerrors.Errorf("cannot remove sandbox: %w", err) - } - - span.LogKV("mountPath", mountPath, "dev", mp.Device, "backingFile", backingFile) - log.WithFields(logrus.Fields{"mountPath": mountPath, "dev": mp.Device, "backingFile": backingFile}).Debug("unmounted and disposed sandbox") - return nil -}