package controller
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/go-chi/chi"
httputil "github.com/goodrain/rainbond/util/http"
"github.com/sirupsen/logrus"
)
type KubeBlocksController struct{}
const blockMechanicaBaseURL = "http://kb-adapter-rbdplugin.rbd-system.svc:80"
func (c *KubeBlocksController) GetSupportedDatabases(w http.ResponseWriter, r *http.Request) {
c.forwardRequest(w, r, "/v1/addons", "GET")
}
func (c *KubeBlocksController) GetStorageClasses(w http.ResponseWriter, r *http.Request) {
c.forwardRequest(w, r, "/v1/storageclasses", "GET")
}
func (c *KubeBlocksController) GetBackupRepos(w http.ResponseWriter, r *http.Request) {
c.forwardRequest(w, r, "/v1/backuprepos", "GET")
}
func (c *KubeBlocksController) CreateCluster(w http.ResponseWriter, r *http.Request) {
c.forwardRequest(w, r, "/v1/clusters", "POST")
}
func (c *KubeBlocksController) GetClusterConnectInfos(w http.ResponseWriter, r *http.Request) {
c.forwardRequest(w, r, "/v1/clusters/connect-infos", "GET")
}
func (c *KubeBlocksController) GetClusterByID(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s", serviceID), "GET")
}
func (c *KubeBlocksController) ExpansionCluster(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s", serviceID), "PUT")
}
func (c *KubeBlocksController) UpdateClusterBackupSchedules(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/backup-schedules", serviceID), "PUT")
}
func (c *KubeBlocksController) CreateClusterBackup(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/backups", serviceID), "POST")
}
func (c *KubeBlocksController) GetClusterBackups(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/backups", serviceID), "GET")
}
func (c *KubeBlocksController) DeleteClusters(w http.ResponseWriter, r *http.Request) {
c.forwardRequest(w, r, "/v1/clusters", "DELETE")
}
func (c *KubeBlocksController) DeleteClusterBackup(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/backups", serviceID), "DELETE")
}
func (c *KubeBlocksController) ManageCluster(w http.ResponseWriter, r *http.Request) {
logrus.Infof("ManageCluster request: %v", r.Body)
c.forwardRequest(w, r, "/v1/clusters/actions", "POST")
}
func (c *KubeBlocksController) GetClusterPodDetail(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
podName := chi.URLParam(r, "pod_name")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/pods/%s/details", serviceID, podName), "GET")
}
func (c *KubeBlocksController) GetClusterEvents(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/events", serviceID), "GET")
}
func (c *KubeBlocksController) GetClusterParameters(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/parameters", serviceID), "GET")
}
func (c *KubeBlocksController) ChangeClusterParameters(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/parameters", serviceID), "POST")
}
func (c *KubeBlocksController) RestoreClusterFromBackup(w http.ResponseWriter, r *http.Request) {
serviceID := chi.URLParam(r, "service_id")
c.forwardRequest(w, r, fmt.Sprintf("/v1/clusters/%s/restores", serviceID), "POST")
}
func (c *KubeBlocksController) forwardRequest(w http.ResponseWriter, r *http.Request, api, method string) {
targetURL := blockMechanicaBaseURL + api
if r.URL.RawQuery != "" {
targetURL += "?" + r.URL.RawQuery
}
logrus.Debugf("request block-mechanica service: %s %s", method, targetURL)
var req *http.Request
var err error
var body io.Reader
requestBody, readErr := io.ReadAll(r.Body)
if readErr != nil {
logrus.Errorf("read request body failed: %v", readErr)
httputil.ReturnError(r, w, 400, "read request body failed: "+readErr.Error())
return
}
if len(requestBody) > 0 {
body = strings.NewReader(string(requestBody))
}
req, err = http.NewRequest(method, targetURL, body)
if err != nil {
logrus.Errorf("create request to block-mechanica failed: %v", err)
httputil.ReturnError(r, w, 500, "create request to block-mechanica failed: "+err.Error())
return
}
for key, values := range r.Header {
for _, value := range values {
req.Header.Set(key, value)
}
}
if req.Header.Get("Content-Type") == "" && (method == "POST" || method == "PUT" || method == "DELETE") {
req.Header.Set("Content-Type", "application/json")
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
logrus.Errorf("request block-mechanica service failed: %v", err)
httputil.ReturnError(r, w, 500, "request block-mechanica service failed: "+err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
logrus.Errorf("block-mechanica service returned error status code %d: %s", resp.StatusCode, string(body))
httputil.ReturnError(r, w, resp.StatusCode, "block-mechanica service returned error: "+string(body))
return
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
logrus.Errorf("parse block-mechanica response failed: %v", err)
httputil.ReturnError(r, w, 500, "parse block-mechanica response failed: "+err.Error())
return
}
if list, exists := result["list"]; exists {
if total, pageNum, ok := extractPaginationInfo(result); ok {
httputil.ReturnList(r, w, total, pageNum, list)
return
}
httputil.ReturnSuccess(r, w, list)
} else if field, exists := result["bean"]; exists {
httputil.ReturnSuccess(r, w, field)
} else {
httputil.ReturnSuccess(r, w, result)
}
}
func extractPaginationInfo(result map[string]interface{}) (total, pageNum int, ok bool) {
number, hasNumber := result["number"]
if !hasNumber {
return 0, 0, false
}
page, hasPage := result["page"]
if !hasPage {
return 0, 0, false
}
total, totalOk := safeToInt(number)
if !totalOk {
return 0, 0, false
}
pageNum, pageOk := safeToInt(page)
if !pageOk {
return 0, 0, false
}
return total, pageNum, true
}
func safeToInt(value interface{}) (int, bool) {
switch v := value.(type) {
case int:
return v, true
case int32:
return int(v), true
case int64:
return int(v), true
case float32:
return int(v), true
case float64:
return int(v), true
case json.Number:
if i, err := v.Int64(); err == nil {
return int(i), true
}
}
logrus.Warnf("Failed to convert %T(%v) to int", value, value)
return 0, false
}