Compare commits

...

3 Commits

Author SHA1 Message Date
dkeven
6da48e9679 feat(bfl): reuse owner's proxy config when activating sub-accounts 2025-12-23 20:04:32 +08:00
dkeven
fe98eaf1ba bfl: update image version to v0.4.36 2025-12-16 21:03:24 +08:00
dkeven
2a54db6af3 chore(bfl): remove some unused API handlers 2025-12-16 20:59:55 +08:00
11 changed files with 35 additions and 364 deletions

View File

@@ -266,7 +266,7 @@ spec:
containers:
- name: api
image: beclab/bfl:v0.4.35
image: beclab/bfl:v0.4.36
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 1000

View File

@@ -2,8 +2,6 @@ package v1
import (
"fmt"
"math"
"net/http"
"strconv"
"time"
@@ -11,17 +9,12 @@ import (
"bytetrade.io/web3os/bfl/pkg/api"
"bytetrade.io/web3os/bfl/pkg/api/response"
"bytetrade.io/web3os/bfl/pkg/apis"
"bytetrade.io/web3os/bfl/pkg/apis/backend/v1/metrics"
iamV1alpha1 "bytetrade.io/web3os/bfl/pkg/apis/iam/v1alpha1"
"bytetrade.io/web3os/bfl/pkg/apis/iam/v1alpha1/operator"
monitov1alpha1 "bytetrade.io/web3os/bfl/pkg/apis/monitor/v1alpha1"
"bytetrade.io/web3os/bfl/pkg/apiserver/runtime"
"bytetrade.io/web3os/bfl/pkg/app_service/v1"
"bytetrade.io/web3os/bfl/pkg/client/clientset/v1alpha1"
"bytetrade.io/web3os/bfl/pkg/constants"
"bytetrade.io/web3os/bfl/pkg/utils"
"bytetrade.io/web3os/bfl/pkg/utils/certmanager"
"bytetrade.io/web3os/bfl/pkg/utils/k8sutil"
iamV1alpha2 "github.com/beclab/api/iam/v1alpha2"
"github.com/emicklei/go-restful/v3"
@@ -42,53 +35,6 @@ func New() *Handler {
}
}
func (h *Handler) handleGetIPAddress(req *restful.Request, resp *restful.Response) {
ctx := req.Request.Context()
master := req.QueryParameter("master")
var masterInternalIP string
if master == "true" {
ip, err := k8sutil.GetL4ProxyNodeIP(ctx, 10*time.Second)
if err != nil {
log.Warnf("no master hostIP: %v", err)
}
if ip != nil && *ip != "" {
masterInternalIP = *ip
}
resp.Write([]byte(masterInternalIP))
return
}
ipAddr := IPAddress{
IsNatted: constants.IsNatted,
Internal: utils.RemoteIp(req.Request),
}
if masterInternalIP != "" {
ipAddr.MasterInternalIP = masterInternalIP
}
// master external ip
masterExternalIP := k8sutil.GetMasterExternalIP(ctx)
if masterExternalIP == nil {
response.HandleError(resp, errors.New("no master external ip"))
return
}
ipAddr.MasterExternalIP = *masterExternalIP
external := utils.GetMyExternalIPAddr()
if external == "" {
response.HandleInternalError(resp, errors.New("no external ip address"))
return
}
ipAddr.External = external
response.Success(resp, ipAddr)
}
func (h *Handler) handleUserInfo(req *restful.Request, resp *restful.Response) {
var (
isEphemeral bool
@@ -154,50 +100,6 @@ func (h *Handler) handleUserInfo(req *restful.Request, resp *restful.Response) {
response.Success(resp, uInfo)
}
func (h *Handler) handleVerifyUserPassword(req *restful.Request, resp *restful.Response) {
var u iamV1alpha1.UserPassword
err := req.ReadEntity(&u)
if err != nil {
response.HandleBadRequest(resp, errors.Errorf("verify password: %v", err))
return
}
log.Info("verify user password")
if u.UserName == "" {
u.UserName = constants.Username
}
// the user's password must be provided
if u.Password == "" {
response.HandleBadRequest(resp, errors.New("verify password: no password provided"))
return
}
data := map[string]string{
"username": u.UserName,
"password": u.Password,
"client_id": constants.KubeSphereClientID,
"client_secret": constants.KubeSphereClientSecret,
"grant_type": "password",
}
token, code, err := iamV1alpha1.RequestToken("", data)
if err != nil {
resp.WriteHeaderAndEntity(http.StatusOK, response.Header{
Code: code,
Message: err.Error(),
})
return
}
if token.AccessToken != "" {
response.SuccessNoData(resp)
return
}
response.HandleUnauthorized(resp, errors.New(response.UnexpectedError))
}
func (h *Handler) handleReDownloadCert(req *restful.Request, resp *restful.Response) {
var (
ctx = req.Request.Context()
@@ -469,68 +371,3 @@ func (h *Handler) myapps(req *restful.Request, resp *restful.Response) {
response.Success(resp, api.NewListResult(list))
}
func (h *Handler) getClusterMetric(req *restful.Request, resp *restful.Response) {
prome, err := metrics.NewPrometheus(metrics.PrometheusEndpoint)
if err != nil {
response.HandleError(resp, err)
return
}
opts := metrics.QueryOptions{
Level: metrics.LevelCluster,
}
metricsResult := prome.GetNamedMetrics(req.Request.Context(), []string{
"cluster_cpu_usage",
"cluster_cpu_total",
"cluster_disk_size_usage",
"cluster_disk_size_capacity",
"cluster_memory_total",
"cluster_memory_usage_wo_cache",
"cluster_net_bytes_transmitted",
"cluster_net_bytes_received",
}, time.Now(), opts)
var clusterMetrics monitov1alpha1.ClusterMetrics
for _, m := range metricsResult {
switch m.MetricName {
case "cluster_cpu_usage":
clusterMetrics.CPU.Usage = metrics.GetValue(&m)
case "cluster_cpu_total":
clusterMetrics.CPU.Total = metrics.GetValue(&m)
case "cluster_disk_size_usage":
clusterMetrics.Disk.Usage = metrics.GetValue(&m)
case "cluster_disk_size_capacity":
clusterMetrics.Disk.Total = metrics.GetValue(&m)
case "cluster_memory_total":
clusterMetrics.Memory.Total = metrics.GetValue(&m)
case "cluster_memory_usage_wo_cache":
clusterMetrics.Memory.Usage = metrics.GetValue(&m)
case "cluster_net_bytes_transmitted":
clusterMetrics.Net.Transmitted = metrics.GetValue(&m)
case "cluster_net_bytes_received":
clusterMetrics.Net.Received = metrics.GetValue(&m)
}
}
roundToGB := func(v float64) float64 { return math.Round((v/1000000000.00)*100.00) / 100.00 }
fmtMetricsValue(&clusterMetrics.CPU, "Cores", func(v float64) float64 { return v })
fmtMetricsValue(&clusterMetrics.Memory, "GB", roundToGB)
fmtMetricsValue(&clusterMetrics.Disk, "GB", roundToGB)
response.Success(resp, clusterMetrics)
}
func fmtMetricsValue(v *monitov1alpha1.MetricV, unit string, unitFunc func(float64) float64) {
v.Unit = unit
v.Usage = unitFunc(v.Usage)
v.Total = unitFunc(v.Total)
v.Ratio = math.Round((v.Usage / v.Total) * 100)
}

View File

@@ -4,7 +4,6 @@ import (
"net/http"
"bytetrade.io/web3os/bfl/pkg/api/response"
iamV1alpha1 "bytetrade.io/web3os/bfl/pkg/apis/iam/v1alpha1"
"bytetrade.io/web3os/bfl/pkg/apiserver/runtime"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
@@ -23,13 +22,6 @@ func AddContainer(c *restful.Container) error {
handler := New()
ws.Route(ws.POST("/verify-password").
To(handler.handleVerifyUserPassword).
Doc("Verify user password.").
Reads(iamV1alpha1.UserPassword{}).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, "", response.Response{}))
ws.Route(ws.GET("/user-info").
To(handler.handleUserInfo).
Doc("User information.").
@@ -48,13 +40,6 @@ func AddContainer(c *restful.Container) error {
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, "", response.Response{}))
ws.Route(ws.GET("/ip").
To(handler.handleGetIPAddress).
Doc("IP Address.").
Param(ws.QueryParameter("master", "get master nodeIP only").DataType("string").Required(false)).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, "", response.Response{}))
ws.Route(ws.GET("/re-download-cert").
To(handler.handleReDownloadCert).
Doc("Re-download ssl certificate").
@@ -67,12 +52,6 @@ func AddContainer(c *restful.Container) error {
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, "", response.Response{}))
ws.Route(ws.GET("/cluster").
To(handler.getClusterMetric).
Doc("get the cluster current metrics ( cpu, memory, disk ).").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, "", response.Response{}))
ws.Route(ws.GET("/config-system").
To(handler.HandleGetSysConfig).
Doc("get user locale.").
@@ -85,12 +64,6 @@ func AddContainer(c *restful.Container) error {
wsWizard.Consumes(restful.MIME_JSON)
wsWizard.Produces(restful.MIME_JSON)
wsWizard.Route(wsWizard.GET("/terminus-info").
To(handler.handleTerminusInfo).
Doc("terminus information.").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, "", response.Response{}))
wsWizard.Route(wsWizard.GET("/olares-info").
To(handler.handleOlaresInfo).
Doc("olares information.").

View File

@@ -1,43 +0,0 @@
package v1alpha1
import (
"bytetrade.io/web3os/bfl/pkg/api/response"
"github.com/emicklei/go-restful/v3"
"k8s.io/klog/v2"
)
const BackupCancelCode = 493
type handler struct {
}
func newHandler() *handler {
return &handler{}
}
func (h *handler) backupNew(req *restful.Request, resp *restful.Response) {
klog.Info("backup start callback")
for _, cb := range callbackHandlers {
err := cb.BackupNew()
if err != nil {
klog.Error("backup start response error, ", err)
resp.WriteError(BackupCancelCode, err)
return
}
}
response.SuccessNoData(resp)
}
func (h *handler) backupFinish(req *restful.Request, resp *restful.Response) {
klog.Info("backup finished callback")
for _, cb := range callbackHandlers {
err := cb.BackupFinish()
if err != nil {
klog.Warning("backup finished callback error, ", err)
}
}
response.SuccessNoData(resp)
}

View File

@@ -1,51 +0,0 @@
package v1alpha1
import (
"net/http"
"bytetrade.io/web3os/bfl/pkg/api/response"
"bytetrade.io/web3os/bfl/pkg/apiserver/runtime"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
)
type CallbackHandler struct {
BackupNew func() error
BackupFinish func() error
}
var (
MODULE_TAGS = []string{"callbacks"}
callbackHandlers []*CallbackHandler
)
var ModuleVersion = runtime.ModuleVersion{Name: "callback", Version: "v1alpha1"}
func AddToContainer(c *restful.Container) error {
ws := runtime.NewWebService(ModuleVersion)
handler := newHandler()
ws.Route(ws.POST("/backup/new").
To(handler.backupNew).
Doc("Provide system backup phase-new to callback").
Metadata(restfulspec.KeyOpenAPITags, MODULE_TAGS).
Returns(http.StatusOK, "Success", response.Response{}))
ws.Route(ws.POST("/backup/finish").
To(handler.backupFinish).
Doc("Provide system backup phase-success / failed / canceled to callback").
Metadata(restfulspec.KeyOpenAPITags, MODULE_TAGS).
Returns(http.StatusOK, "Success", response.Response{}))
c.Add(ws)
return nil
}
func AddBackupCallbackHandler(backupNew func() error, backupFinish func() error) {
callbackHandlers = append(callbackHandlers, &CallbackHandler{
BackupNew: backupNew,
BackupFinish: backupFinish,
})
}

View File

@@ -55,41 +55,6 @@ func New(ctrlClient client.Client) *Handler {
}
}
func (h *Handler) handleUserLogin(req *restful.Request, resp *restful.Response) {
var u UserPassword
err := req.ReadEntity(&u)
if err != nil {
response.HandleBadRequest(resp, errors.Errorf("login user, read entity: %v", err))
return
}
log.Infow("read user entity", "userPassword", u)
if u.UserName != constants.Username {
response.HandleBadRequest(resp, errors.New("login user: mismatch input username and userspace"))
return
}
data := map[string]string{
"username": u.UserName,
"password": u.Password,
"client_id": constants.KubeSphereClientID,
"client_secret": constants.KubeSphereClientSecret,
"grant_type": "password",
}
token, code, err := RequestToken("", data)
if err != nil {
// response.HandleError(resp, errors.Errorf("login user, request token: %v", err))
resp.WriteHeaderAndEntity(http.StatusOK, response.Header{
Code: code,
Message: err.Error(),
})
return
}
response.Success(resp, token)
}
func (h *Handler) getRolesByUserName(ctx context.Context, name string) ([]string, error) {
var globalRoleBindings iamV1alpha2.GlobalRoleBindingList
err := h.ctrlClient.List(ctx, &globalRoleBindings)
@@ -374,18 +339,6 @@ func RequestToken(token string, data map[string]string) (*TokenResponse, int, er
return nil, -1, err
}
func (h *Handler) isUserCreating() bool {
return h.userCreatingCount.Load() > 0
}
func (h *Handler) lockUserCreating() {
h.userCreatingCount.Store(-1)
}
func (h *Handler) unlockUserCreating() {
h.userCreatingCount.Store(0)
}
func (h *Handler) handleGetUserMetrics(req *restful.Request, resp *restful.Response) {
user := req.PathParameter("user")
token := req.HeaderParameter(constants.UserAuthorizationTokenKey)

View File

@@ -41,11 +41,6 @@ type LoginRecord struct {
LoginTime *int64 `json:"login_time"`
}
type UserPassword struct {
UserName string `json:"username,omitempty"`
Password string `json:"password"`
}
type KubesphereError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`

View File

@@ -1,7 +1,6 @@
package v1alpha1
import (
"errors"
"net/http"
"bytetrade.io/web3os/bfl/pkg/api/response"
@@ -32,7 +31,7 @@ var (
userTags = []string{"users"}
)
func AddToContainer(c *restful.Container, addCallback func(func() error, func() error)) error {
func AddToContainer(c *restful.Container) error {
ws := runtime.NewWebService(ModuleVersion)
config, err := ctrl.GetConfig()
if err != nil {
@@ -85,22 +84,5 @@ func AddToContainer(c *restful.Container, addCallback func(func() error, func()
Returns(http.StatusOK, "", response.Response{}))
c.Add(ws)
// add user creating event to backup callback
addCallback(
func() error { // phase backup-new
if handler.isUserCreating() {
return errors.New("user createing")
}
handler.lockUserCreating()
return nil
},
func() error { // phase backup-finished
handler.unlockUserCreating()
return nil
},
)
return nil
}

View File

@@ -243,11 +243,34 @@ func (h *Handler) handleActivate(req *restful.Request, resp *restful.Response) {
return
}
// for owner user, use the reverse proxy config from the payload
// for non-owner user, reuse the owner's reverse proxy config
isOwner := userOp.GetUserAnnotation(user, constants.UserAnnotationOwnerRole) == constants.RoleOwner
reverseProxyConf := &ReverseProxyConfig{}
if payload.FRP.Host != "" {
reverseProxyConf.EnableFRP = true
reverseProxyConf.FRPServer = payload.FRP.Host
reverseProxyConf.FRPAuthMethod = FRPAuthMethodJWS
if isOwner {
if payload.FRP.Host != "" {
reverseProxyConf.EnableFRP = true
reverseProxyConf.FRPServer = payload.FRP.Host
reverseProxyConf.FRPAuthMethod = FRPAuthMethodJWS
}
} else {
ownerUser, ownerErr := userOp.GetOwnerUser()
if ownerErr != nil {
err = fmt.Errorf("activate system: failed to get owner user for reverse proxy config: %v", ownerErr)
klog.Error(err)
response.HandleError(resp, err)
return
}
ownerNamespace := fmt.Sprintf(constants.UserspaceNameFormat, ownerUser.Name)
ownerConf, ownerConfErr := GetReverseProxyConfigFromNamespace(ctx, ownerNamespace)
if ownerConfErr != nil {
err = fmt.Errorf("activate system: failed to get owner's reverse proxy config: %v", ownerConfErr)
klog.Error(err)
response.HandleError(resp, err)
return
}
reverseProxyConf = ownerConf
}
// no matter whether the reverse proxy is enabled, we need to persist the configuration
// so that the configuration can be fetched by the frontend

View File

@@ -549,7 +549,11 @@ func (conf *ReverseProxyConfig) generateReverseProxyConfigMapData() map[string]s
}
func GetReverseProxyConfig(ctx context.Context) (*ReverseProxyConfig, error) {
configData, err := k8sutil.GetConfigMapData(ctx, constants.Namespace, constants.ReverseProxyConfigMapName)
return GetReverseProxyConfigFromNamespace(ctx, constants.Namespace)
}
func GetReverseProxyConfigFromNamespace(ctx context.Context, namespace string) (*ReverseProxyConfig, error) {
configData, err := k8sutil.GetConfigMapData(ctx, namespace, constants.ReverseProxyConfigMapName)
if err != nil {
return nil, errors.Wrap(err, "error getting configmap")
}

View File

@@ -7,7 +7,6 @@ import (
"bytetrade.io/web3os/bfl/internal/log"
"bytetrade.io/web3os/bfl/pkg/api/response"
backendv1 "bytetrade.io/web3os/bfl/pkg/apis/backend/v1"
callbackV1alpha1 "bytetrade.io/web3os/bfl/pkg/apis/callback/v1alpha1"
iamV1alpha1 "bytetrade.io/web3os/bfl/pkg/apis/iam/v1alpha1"
monitov1alpha1 "bytetrade.io/web3os/bfl/pkg/apis/monitor/v1alpha1"
@@ -114,8 +113,7 @@ func (s *APIServer) installAPIDocs() {
}
func (s *APIServer) installModuleAPI() {
urlruntime.Must(callbackV1alpha1.AddToContainer(s.container))
urlruntime.Must(iamV1alpha1.AddToContainer(s.container, callbackV1alpha1.AddBackupCallbackHandler))
urlruntime.Must(iamV1alpha1.AddToContainer(s.container))
urlruntime.Must(backendv1.AddContainer(s.container))
urlruntime.Must(settingsV1alpha1.AddContainer(s.container))
urlruntime.Must(monitov1alpha1.AddContainer(s.container))