Compare commits

...

2 Commits

Author SHA1 Message Date
hysyeah
4b7a1d9925 feat: v2 stop support all to stop server (#2195) 2025-12-10 22:03:28 +08:00
hys
870cd164f9 feat: v2 stop support all to stop server 2025-12-10 21:46:13 +08:00
21 changed files with 149 additions and 39 deletions

View File

@@ -229,7 +229,7 @@ func (r *AppEnvController) triggerApplyEnv(ctx context.Context, appEnv *sysv1alp
OpID: opID,
State: appv1alpha1.ApplyingEnv.String(),
RawAppName: am.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: apputils.AppTitle(am.Spec.Config),
})

View File

@@ -250,8 +250,9 @@ func (r *EntranceStatusManagerController) updateEntranceStatus(ctx context.Conte
State: am.Status.State.String(),
EntranceStatuses: appCopy.Status.EntranceStatuses,
RawAppName: appCopy.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: app.AppTitle(am.Spec.Config),
SharedEntrances: appCopy.Spec.SharedEntrances,
})
}
}

View File

@@ -245,7 +245,7 @@ func (r *PodAbnormalSuspendAppController) trySuspendApp(ctx context.Context, own
State: appv1alpha1.Stopping.String(),
Progress: message,
RawAppName: am.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: apputils.AppTitle(am.Spec.Config),
Reason: reason,
Message: message,

View File

@@ -13,6 +13,8 @@ const (
AppMarketSourceKey = constants.AppMarketSourceKey
AppInstallSourceKey = "bytetrade.io/install-source"
AppUninstallAllKey = "bytetrade.io/uninstall-all"
AppStopAllKey = "bytetrade.io/stop-all"
AppResumeAllKey = "bytetrade.io/resume-all"
AppImagesKey = "bytetrade.io/images"
)
@@ -144,6 +146,11 @@ type UninstallRequest struct {
All bool `json:"all"`
}
// StopRequest represents a request to stop an application.
type StopRequest struct {
All bool `json:"all"`
}
type ManifestRenderRequest struct {
Content string `json:"content"`
}

View File

@@ -50,6 +50,11 @@ func (h *Handler) appApplyEnv(req *restful.Request, resp *restful.Response) {
appCopy := appMgr.DeepCopy()
appCopy.Spec.OpType = appv1alpha1.ApplyEnvOp
if appCopy.Annotations == nil {
klog.Errorf("not support operation %s,name:%s", appv1alpha1.ApplyEnvOp, appCopy.Spec.AppName)
api.HandleError(resp, req, fmt.Errorf("not support operation %s", appv1alpha1.ApplyEnvOp))
return
}
appCopy.Annotations[api.AppTokenKey] = token
err = h.ctrlClient.Patch(req.Request.Context(), appCopy, client.MergeFrom(&appMgr))
@@ -81,7 +86,7 @@ func (h *Handler) appApplyEnv(req *restful.Request, resp *restful.Response) {
OpID: opID,
State: appv1alpha1.ApplyingEnv.String(),
RawAppName: am.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: apputils.AppTitle(am.Spec.Config),
})

View File

@@ -94,7 +94,7 @@ func (h *Handler) cancel(req *restful.Request, resp *restful.Response) {
OpID: opID,
State: cancelState.String(),
RawAppName: a.Spec.RawAppName,
Type: "app",
Type: a.Spec.Type.String(),
Title: apputils.AppTitle(a.Spec.Config),
})

View File

@@ -623,7 +623,7 @@ func (h *installHandlerHelper) applyApplicationManager(marketSource string) (opI
OpID: opID,
State: v1alpha1.Pending.String(),
RawAppName: a.Spec.RawAppName,
Type: "app",
Type: a.Spec.Type.String(),
Title: apputils.AppTitle(a.Spec.Config),
})
return

View File

@@ -103,7 +103,7 @@ func (h *Handler) uninstall(req *restful.Request, resp *restful.Response) {
OpID: opID,
State: v1alpha1.Uninstalling.String(),
RawAppName: a.Spec.RawAppName,
Type: "app",
Type: a.Spec.Type.String(),
Title: apputils.AppTitle(a.Spec.Config),
})

View File

@@ -348,7 +348,9 @@ func (h *Handler) appUpgrade(req *restful.Request, resp *restful.Response) {
appCopy.Spec.Config = config
appCopy.Spec.OpType = appv1alpha1.UpgradeOp
if appCopy.Annotations == nil {
appCopy.Annotations = make(map[string]string)
klog.Errorf("not support operation %s,name: %s", appv1alpha1.UpgradeOp, appCopy.Spec.AppName)
api.HandleError(resp, req, fmt.Errorf("not support operation %s", appv1alpha1.UpgradeOp))
return
}
appCopy.Annotations[api.AppRepoURLKey] = request.RepoURL
appCopy.Annotations[api.AppVersionKey] = request.Version
@@ -384,7 +386,7 @@ func (h *Handler) appUpgrade(req *restful.Request, resp *restful.Response) {
OpID: opID,
State: appv1alpha1.Upgrading.String(),
RawAppName: am.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: apputils.AppTitle(am.Spec.Config),
})

View File

@@ -276,7 +276,7 @@ func (h *Handler) setupAppEntranceDomain(req *restful.Request, resp *restful.Res
OpID: opID,
State: v1alpha1.Upgrading.String(),
RawAppName: am.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: apputils.AppTitle(am.Spec.Config),
Message: fmt.Sprintf("app %s was upgrade via setup domain by user %s", am.Spec.AppName, am.Spec.AppOwner),
})

View File

@@ -10,6 +10,7 @@ import (
"bytetrade.io/web3os/app-service/pkg/apiserver/api"
"bytetrade.io/web3os/app-service/pkg/appstate"
"bytetrade.io/web3os/app-service/pkg/constants"
"bytetrade.io/web3os/app-service/pkg/kubesphere"
"bytetrade.io/web3os/app-service/pkg/users/userspace"
"bytetrade.io/web3os/app-service/pkg/utils"
apputils "bytetrade.io/web3os/app-service/pkg/utils/app"
@@ -22,6 +23,15 @@ import (
func (h *Handler) suspend(req *restful.Request, resp *restful.Response) {
app := req.PathParameter(ParamAppName)
owner := req.Attribute(constants.UserContextAttribute).(string)
// read optional body to support all=true
request := &api.StopRequest{}
if req.Request.ContentLength > 0 {
if err := req.ReadEntity(request); err != nil {
api.HandleBadRequest(resp, req, err)
return
}
}
if userspace.IsSysApp(app) {
api.HandleBadRequest(resp, req, errors.New("sys app can not be suspend"))
return
@@ -42,6 +52,8 @@ func (h *Handler) suspend(req *restful.Request, resp *restful.Response) {
return
}
am.Spec.OpType = v1alpha1.StopOp
am.Annotations[api.AppStopAllKey] = fmt.Sprintf("%t", request.All)
err = h.ctrlClient.Update(req.Request.Context(), &am)
if err != nil {
api.HandleError(resp, req, err)
@@ -72,7 +84,7 @@ func (h *Handler) suspend(req *restful.Request, resp *restful.Response) {
OpID: opID,
State: v1alpha1.Stopping.String(),
RawAppName: a.Spec.RawAppName,
Type: "app",
Type: a.Spec.Type.String(),
Title: apputils.AppTitle(a.Spec.Config),
Reason: constants.AppStopByUser,
Message: fmt.Sprintf("app %s was stop by user %s", a.Spec.AppName, a.Spec.AppOwner),
@@ -106,6 +118,16 @@ func (h *Handler) resume(req *restful.Request, resp *restful.Response) {
}
am.Spec.OpType = v1alpha1.ResumeOp
// if current user is admin, also resume server side
isAdmin, err := kubesphere.IsAdmin(req.Request.Context(), h.kubeConfig, owner)
if err != nil {
api.HandleError(resp, req, err)
return
}
am.Annotations[api.AppResumeAllKey] = fmt.Sprintf("%t", false)
if isAdmin {
am.Annotations[api.AppResumeAllKey] = fmt.Sprintf("%t", true)
}
err = h.ctrlClient.Update(req.Request.Context(), &am)
if err != nil {
api.HandleError(resp, req, err)
@@ -134,7 +156,7 @@ func (h *Handler) resume(req *restful.Request, resp *restful.Response) {
OpID: opID,
State: v1alpha1.Resuming.String(),
RawAppName: a.Spec.RawAppName,
Type: "app",
Type: a.Spec.Type.String(),
Title: apputils.AppTitle(a.Spec.Config),
Message: fmt.Sprintf("app %s was resume by user %s", a.Spec.AppName, a.Spec.AppOwner),
})

View File

@@ -1144,7 +1144,7 @@ func (h *Handler) applicationManagerMutate(req *restful.Request, resp *restful.R
OpID: pam.Status.OpID,
State: pam.Status.State.String(),
RawAppName: pam.Spec.RawAppName,
Type: "app",
Type: pam.Spec.Type.String(),
Title: apputils.AppTitle(pam.Spec.Config),
})
}

View File

@@ -86,6 +86,10 @@ func (h *HelmOps) upgrade() error {
}
}
if h.app.Type == appv1alpha1.Middleware.String() {
return nil
}
err = h.UpdatePolicy()
if err != nil {
klog.Errorf("Failed to update policy err=%v", err)
@@ -97,9 +101,7 @@ func (h *HelmOps) upgrade() error {
klog.Errorf("Failed to register app provider err=%v", err)
return err
}
if h.app.Type == appv1alpha1.Middleware.String() {
return nil
}
ok, err := h.WaitForStartUp()
if !ok {
// canceled

View File

@@ -75,6 +75,10 @@ func (h *HelmOpsV2) Upgrade() error {
return err
}
if h.App().Type == appv1alpha1.Middleware.String() {
return nil
}
err = h.UpdatePolicy()
if err != nil {
klog.Errorf("Failed to update policy for app %s err=%v", h.App().AppName, err)
@@ -86,10 +90,6 @@ func (h *HelmOpsV2) Upgrade() error {
return err
}
if h.App().Type == appv1alpha1.Middleware.String() {
return nil
}
ok, err := h.WaitForStartUp()
if err != nil && errors.Is(err, errcode.ErrPodPending) {
return err

View File

@@ -104,7 +104,7 @@ func (p *PendingApp) Exec(ctx context.Context) (StatefulInProgressApp, error) {
OpID: p.manager.Status.OpID,
State: appsv1.Downloading.String(),
RawAppName: p.manager.Spec.RawAppName,
Type: "app",
Type: p.manager.Spec.Type.String(),
Title: apputils.AppTitle(p.manager.Spec.Config),
})

View File

@@ -2,10 +2,13 @@ package appstate
import (
"context"
"encoding/json"
"fmt"
"time"
appsv1 "bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
"bytetrade.io/web3os/app-service/pkg/apiserver/api"
"bytetrade.io/web3os/app-service/pkg/appcfg"
"bytetrade.io/web3os/app-service/pkg/constants"
"bytetrade.io/web3os/app-service/pkg/kubeblocks"
"bytetrade.io/web3os/app-service/pkg/users/userspace"
@@ -62,6 +65,34 @@ func (p *ResumingApp) exec(ctx context.Context) error {
klog.Errorf("resume %s %s failed %v", p.manager.Spec.Type, p.manager.Spec.AppName, err)
return fmt.Errorf("resume app %s failed %w", p.manager.Spec.AppName, err)
}
// If resume-all is requested, also resume v2 server-side shared charts by scaling them up
if p.manager.Annotations[api.AppResumeAllKey] == "true" {
var appCfg *appcfg.ApplicationConfig
if err := json.Unmarshal([]byte(p.manager.Spec.Config), &appCfg); err != nil {
klog.Errorf("unmarshal to appConfig failed %v", err)
return err
}
if appCfg != nil && appCfg.IsV2() && appCfg.HasClusterSharedCharts() {
for _, chart := range appCfg.SubCharts {
if !chart.Shared {
continue
}
ns := chart.Namespace(appCfg.OwnerName)
// create a shallow copy with target namespace/name for scaling logic
amCopy := p.manager.DeepCopy()
amCopy.Spec.AppNamespace = ns
amCopy.Spec.AppName = chart.Name
klog.Infof("resume-amCopy.Spec.AppNamespace: %s", ns)
klog.Infof("resume-amCopy.Spec.AppName: %s", chart.Name)
if err := suspendOrResumeApp(ctx, p.client, amCopy, int32(1)); err != nil {
klog.Errorf("failed to resume shared chart %s in namespace %s: %v", chart.Name, ns, err)
return err
}
}
}
}
if p.manager.Spec.Type == "middleware" && userspace.IsKbMiddlewares(p.manager.Spec.AppName) {
err = p.execMiddleware(ctx)
if err != nil {

View File

@@ -2,10 +2,13 @@ package appstate
import (
"context"
"encoding/json"
"fmt"
"time"
appsv1 "bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
"bytetrade.io/web3os/app-service/pkg/apiserver/api"
"bytetrade.io/web3os/app-service/pkg/appcfg"
"bytetrade.io/web3os/app-service/pkg/constants"
"bytetrade.io/web3os/app-service/pkg/kubeblocks"
"bytetrade.io/web3os/app-service/pkg/users/userspace"
@@ -75,13 +78,42 @@ func (p *SuspendingApp) Exec(ctx context.Context) (StatefulInProgressApp, error)
}
func (p *SuspendingApp) exec(ctx context.Context) error {
err := suspendOrResumeApp(ctx, p.client, p.manager, int32(0))
if err != nil {
klog.Errorf("suspend %s %s failed %v", p.manager.Spec.Type, p.manager.Spec.AppName, err)
return fmt.Errorf("suspend app %s failed %w", p.manager.Spec.AppName, err)
// If stop-all is requested, also stop v2 server-side shared charts by scaling them down
if p.manager.Annotations[api.AppStopAllKey] == "true" {
var appCfg *appcfg.ApplicationConfig
if err := json.Unmarshal([]byte(p.manager.Spec.Config), &appCfg); err != nil {
klog.Errorf("unmarshal to appConfig failed %v", err)
return err
}
if appCfg != nil && appCfg.IsV2() && appCfg.HasClusterSharedCharts() {
for _, chart := range appCfg.SubCharts {
if !chart.Shared {
continue
}
ns := chart.Namespace(appCfg.OwnerName)
// create a shallow copy with target namespace/name for scaling logic
amCopy := p.manager.DeepCopy()
amCopy.Spec.AppNamespace = ns
amCopy.Spec.AppName = chart.Name
klog.Infof("amCopy.Spec.AppNamespace: %s", ns)
klog.Infof("amCopy.Spec.AppName: %s", chart.Name)
if err := suspendOrResumeApp(ctx, p.client, amCopy, int32(0)); err != nil {
klog.Errorf("failed to stop shared chart %s in namespace %s: %v", chart.Name, ns, err)
return err
}
}
}
} else {
err := suspendOrResumeApp(ctx, p.client, p.manager, int32(0))
if err != nil {
klog.Errorf("suspend %s %s failed %v", p.manager.Spec.Type, p.manager.Spec.AppName, err)
return fmt.Errorf("suspend app %s failed %w", p.manager.Spec.AppName, err)
}
}
if p.manager.Spec.Type == appsv1.Middleware && userspace.IsKbMiddlewares(p.manager.Spec.AppName) {
err = p.execMiddleware(ctx)
err := p.execMiddleware(ctx)
if err != nil {
klog.Errorf("suspend middleware %s failed %v", p.manager.Spec.AppName, err)
return err

View File

@@ -84,7 +84,7 @@ func (b *baseStatefulApp) updateStatus(ctx context.Context, am *appsv1.Applicati
OpID: b.manager.Status.OpID,
State: state.String(),
RawAppName: b.manager.Spec.RawAppName,
Type: "app",
Type: b.manager.Spec.Type.String(),
Title: apputils.AppTitle(b.manager.Spec.Config),
Reason: reason,
Message: message,

View File

@@ -87,7 +87,7 @@ func PublishAppEventToQueue(p utils.EventParams) {
EventID: fmt.Sprintf("%s-%s-%d", p.Owner, p.Name, now.UnixMilli()),
CreateTime: now,
Name: p.Name,
Type: "app",
Type: p.Type,
OpType: p.OpType,
OpID: p.OpID,
State: p.State,
@@ -99,9 +99,10 @@ func PublishAppEventToQueue(p utils.EventParams) {
}
return p.RawAppName
}(),
Title: p.Title,
Reason: p.Reason,
Message: p.Message,
Title: p.Title,
Reason: p.Reason,
Message: p.Message,
SharedEntrances: p.SharedEntrances,
}
if len(p.EntranceStatuses) > 0 {
data.EntranceStatuses = p.EntranceStatuses

View File

@@ -198,7 +198,7 @@ func (imc *ImageManagerClient) PollDownloadProgress(ctx context.Context, am *app
}
}
err = imc.updateProgress(ctx, am, &lastProgress, ret*100)
err = imc.updateProgress(ctx, am, &lastProgress, ret*100, am.Spec.OpType == appv1alpha1.UpgradeOp)
if err == nil {
return nil
}
@@ -209,7 +209,7 @@ func (imc *ImageManagerClient) PollDownloadProgress(ctx context.Context, am *app
}
}
func (imc *ImageManagerClient) updateProgress(ctx context.Context, am *appv1alpha1.ApplicationManager, lastProgress *float64, progress float64) error {
func (imc *ImageManagerClient) updateProgress(ctx context.Context, am *appv1alpha1.ApplicationManager, lastProgress *float64, progress float64, isUpgrade bool) error {
if *lastProgress > progress {
return errors.New("no need to update progress")
}
@@ -219,14 +219,19 @@ func (imc *ImageManagerClient) updateProgress(ctx context.Context, am *appv1alph
*lastProgress = progress
appevent.PublishAppEventToQueue(utils.EventParams{
Owner: am.Spec.AppOwner,
Name: am.Spec.AppName,
OpType: string(am.Status.OpType),
OpID: am.Status.OpID,
State: appv1alpha1.Downloading.String(),
Owner: am.Spec.AppOwner,
Name: am.Spec.AppName,
OpType: string(am.Status.OpType),
OpID: am.Status.OpID,
State: func() string {
if isUpgrade {
return appv1alpha1.Upgrading.String()
}
return appv1alpha1.Downloading.String()
}(),
Progress: progressStr,
RawAppName: am.Spec.RawAppName,
Type: "app",
Type: am.Spec.Type.String(),
Title: apputils.AppTitle(am.Spec.Config),
})
}

View File

@@ -27,6 +27,7 @@ type Event struct {
Title string `json:"title,omitempty"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
SharedEntrances []v1alpha1.Entrance `json:"sharedEntrances,omitempty"`
}
// EventParams defines parameters to publish an app-related event
@@ -43,6 +44,7 @@ type EventParams struct {
Title string
Reason string
Message string
SharedEntrances []v1alpha1.Entrance
}
type UserEvent struct {