mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
167 lines
5.7 KiB
Go
167 lines
5.7 KiB
Go
// Copyright (c) 2021 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 content
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
|
|
"github.com/gitpod-io/gitpod/common-go/log"
|
|
"github.com/gitpod-io/gitpod/common-go/tracing"
|
|
"github.com/gitpod-io/gitpod/content-service/pkg/initializer"
|
|
"github.com/gitpod-io/gitpod/content-service/pkg/storage"
|
|
"github.com/gitpod-io/gitpod/ws-daemon/api"
|
|
"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"
|
|
"github.com/opentracing/opentracing-go"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
// WorkspaceLifecycleHooks configures the lifecycle hooks for all workspaces
|
|
func WorkspaceLifecycleHooks(cfg Config, workspaceCIDR string, uidmapper *iws.Uidmapper, xfs *quota.XFS, cgroupMountPoint string) map[session.WorkspaceState][]session.WorkspaceLivecycleHook {
|
|
// startIWS starts the in-workspace service for a workspace. This lifecycle hook is idempotent, hence can - and must -
|
|
// be called on initialization and ready. The on-ready hook exists only to support ws-daemon restarts.
|
|
startIWS := iws.ServeWorkspace(uidmapper, api.FSShiftMethod(cfg.UserNamespaces.FSShift), cgroupMountPoint, workspaceCIDR)
|
|
|
|
return map[session.WorkspaceState][]session.WorkspaceLivecycleHook{
|
|
session.WorkspaceInitializing: {
|
|
hookSetupWorkspaceLocation,
|
|
startIWS, // workspacekit is waiting for starting IWS, so it needs to start as soon as possible.
|
|
hookSetupRemoteStorage(cfg),
|
|
// When starting a workspace, use soft limit for the following reason to ensure content is restored
|
|
// - workspacekit needs to generate some temporary file when starting a workspace
|
|
// - when extracting tar file, tar command create some symlinks following a original content
|
|
hookInstallQuota(xfs, false),
|
|
},
|
|
session.WorkspaceReady: {
|
|
startIWS,
|
|
hookSetupRemoteStorage(cfg),
|
|
hookInstallQuota(xfs, true),
|
|
},
|
|
session.WorkspaceDisposed: {
|
|
iws.StopServingWorkspace,
|
|
hookRemoveQuota(xfs),
|
|
},
|
|
}
|
|
}
|
|
|
|
// hookSetupRemoteStorage configures the remote storage for a workspace
|
|
func hookSetupRemoteStorage(cfg Config) session.WorkspaceLivecycleHook {
|
|
return func(ctx context.Context, ws *session.Workspace) (err error) {
|
|
span, ctx := opentracing.StartSpanFromContext(ctx, "hook.SetupRemoteStorage")
|
|
defer tracing.FinishSpan(span, &err)
|
|
|
|
if _, ok := ws.NonPersistentAttrs[session.AttrRemoteStorage]; !ws.RemoteStorageDisabled && !ok {
|
|
remoteStorage, err := storage.NewDirectAccess(&cfg.Storage)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot use configured storage: %w", err)
|
|
}
|
|
|
|
err = remoteStorage.Init(ctx, ws.Owner, ws.WorkspaceID, ws.InstanceID)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot use configured storage: %w", err)
|
|
}
|
|
|
|
err = remoteStorage.EnsureExists(ctx)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot use configured storage: %w", err)
|
|
}
|
|
|
|
ws.NonPersistentAttrs[session.AttrRemoteStorage] = remoteStorage
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// hookSetupWorkspaceLocation recreates the workspace location
|
|
func hookSetupWorkspaceLocation(ctx context.Context, ws *session.Workspace) (err error) {
|
|
//nolint:ineffassign
|
|
span, _ := opentracing.StartSpanFromContext(ctx, "hook.SetupWorkspaceLocation")
|
|
defer tracing.FinishSpan(span, &err)
|
|
location := ws.Location
|
|
|
|
// 1. Clean out the workspace directory
|
|
if _, err := os.Stat(location); errors.Is(err, fs.ErrNotExist) {
|
|
// in the very unlikely event that the workspace Pod did not mount (and thus create) the workspace directory, create it
|
|
err = os.Mkdir(location, 0755)
|
|
if os.IsExist(err) {
|
|
log.WithError(err).WithFields(ws.OWI()).WithField("location", location).Debug("ran into non-atomic workspace location existence check")
|
|
} else if err != nil {
|
|
return xerrors.Errorf("cannot create workspace: %w", err)
|
|
}
|
|
}
|
|
|
|
// Chown the workspace directory
|
|
err = os.Chown(location, initializer.GitpodUID, initializer.GitpodGID)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot create workspace: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// hookInstallQuota enforces filesystem quota on the workspace location (if the filesystem supports it)
|
|
func hookInstallQuota(xfs *quota.XFS, isHard bool) session.WorkspaceLivecycleHook {
|
|
return func(ctx context.Context, ws *session.Workspace) (err error) {
|
|
span, _ := opentracing.StartSpanFromContext(ctx, "hook.InstallQuota")
|
|
defer tracing.FinishSpan(span, &err)
|
|
|
|
if xfs == nil {
|
|
log.WithFields(ws.OWI()).Warn("no xfs definition")
|
|
return nil
|
|
}
|
|
|
|
if ws.StorageQuota == 0 {
|
|
log.WithFields(ws.OWI()).Warn("no storage quota defined")
|
|
return nil
|
|
}
|
|
|
|
size := quota.Size(ws.StorageQuota)
|
|
|
|
log.WithFields(ws.OWI()).WithField("isHard", isHard).WithField("size", size).WithField("directory", ws.Location).Debug("setting disk quota")
|
|
|
|
var (
|
|
prj int
|
|
)
|
|
if ws.XFSProjectID != 0 {
|
|
xfs.RegisterProject(ws.XFSProjectID)
|
|
prj, err = xfs.SetQuotaWithPrjId(ws.Location, size, ws.XFSProjectID, isHard)
|
|
} else {
|
|
prj, err = xfs.SetQuota(ws.Location, size, isHard)
|
|
}
|
|
|
|
if err != nil {
|
|
log.WithFields(ws.OWI()).WithError(err).Warn("cannot enforce workspace size limit")
|
|
}
|
|
ws.XFSProjectID = int(prj)
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// hookRemoveQuota removes the filesystem quota, freeing up resources if need be
|
|
func hookRemoveQuota(xfs *quota.XFS) session.WorkspaceLivecycleHook {
|
|
return func(ctx context.Context, ws *session.Workspace) (err error) {
|
|
span, _ := opentracing.StartSpanFromContext(ctx, "hook.RemoveQuota")
|
|
defer tracing.FinishSpan(span, &err)
|
|
|
|
if xfs == nil {
|
|
return nil
|
|
}
|
|
|
|
if xfs == nil {
|
|
return nil
|
|
}
|
|
if ws.XFSProjectID == 0 {
|
|
return nil
|
|
}
|
|
|
|
return xfs.RemoveQuota(ws.XFSProjectID)
|
|
}
|
|
}
|