pkg/admin/handler/instance.go (341 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package handler // API Definition: https://app.apifox.com/project/3732499 // 资源详情-实例 import ( "net/http" "strconv" "strings" ) import ( "github.com/gin-gonic/gin" "github.com/pkg/errors" ) import ( mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1" "github.com/apache/dubbo-kubernetes/pkg/admin/model" "github.com/apache/dubbo-kubernetes/pkg/admin/service" "github.com/apache/dubbo-kubernetes/pkg/core/consts" "github.com/apache/dubbo-kubernetes/pkg/core/resources/apis/mesh" core_store "github.com/apache/dubbo-kubernetes/pkg/core/resources/store" core_runtime "github.com/apache/dubbo-kubernetes/pkg/core/runtime" ) func GetInstanceDetail(rt core_runtime.Runtime) gin.HandlerFunc { return func(c *gin.Context) { req := &model.InstanceDetailReq{} if err := c.ShouldBindQuery(req); err != nil { c.JSON(http.StatusBadRequest, model.NewErrorResp(err.Error())) return } resp, err := service.GetInstanceDetail(rt, req) if err != nil { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) return } if len(resp) == 0 { c.JSON(http.StatusNotFound, model.NewErrorResp("instance not exist")) return } c.JSON(http.StatusOK, model.NewSuccessResp(resp[0])) } } func SearchInstances(rt core_runtime.Runtime) gin.HandlerFunc { return func(c *gin.Context) { req := model.NewSearchInstanceReq() if err := c.ShouldBindQuery(req); err != nil { c.JSON(http.StatusBadRequest, model.NewErrorResp(err.Error())) return } instances, err := service.SearchInstances(rt, req) if err != nil { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) return } c.JSON(http.StatusOK, model.NewSuccessResp(instances)) } } func InstanceConfigTrafficDisableGET(rt core_runtime.Runtime) gin.HandlerFunc { return func(c *gin.Context) { resp := struct { TrafficDisable bool `json:"trafficDisable"` }{false} applicationName := c.Query("appName") if applicationName == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("application name is empty")) return } instanceIP := strings.TrimSpace(c.Query("instanceIP")) if instanceIP == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("instanceIP is empty")) return } res, err := service.GetConditionRule(rt, applicationName) if err != nil { if core_store.IsResourceNotFound(err) { c.JSON(http.StatusOK, model.NewSuccessResp(resp)) return } c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) return } if res.Spec.GetVersion() != consts.ConfiguratorVersionV3 { c.JSON(http.StatusServiceUnavailable, model.NewErrorResp("this config only serve condition-route.configVersion == v3, got v3.1 config ")) return } cr := res.Spec.ToConditionRouteV3() cr.RangeConditions(func(condition string) (isStop bool) { _, resp.TrafficDisable = isTrafficDisabledV3(condition, instanceIP) return resp.TrafficDisable }) c.JSON(http.StatusOK, model.NewSuccessResp(resp)) } } func isTrafficDisabledV3X1(r *mesh_proto.ConditionRule, targetIP string) bool { if len(r.To) != 0 { return false } // rule must match `host=x1{,x2,x3}` if r.From.Match != "" && !strings.Contains(r.From.Match, "&") && strings.Index(r.From.Match, "!=") == -1 { idx := strings.Index(r.From.Match, "=") if idx == -1 { return false } then := r.From.Match[idx+1:] Ips := strings.Split(then, ",") for _, ip := range Ips { if strings.TrimSpace(ip) == targetIP { return true } } } return false } /* * isTrafficDisabledV3 judge if a condition is disabled or not. A condition include fromCondition and toCondition which is seperated by `=>`. The first return parameter `exist` indicates if a condition of specific targetIP exists. The second return parameter `disabled` indicates if the traffic of targetIP is disabled. */ func isTrafficDisabledV3(condition string, targetIP string) (exist bool, disabled bool) { if len(condition) == 0 { return false, false } condition = strings.ReplaceAll(condition, " ", "") // only accept string start with `=>` if !strings.HasPrefix(condition, "=>") { return false, false } toCondition := strings.TrimPrefix(condition, "=>") // TODO more specific judge if !strings.Contains(toCondition, targetIP) { return false, false } targetExpression := "host!=" + targetIP if targetExpression != toCondition { return true, false } return true, true } func InstanceConfigTrafficDisablePUT(rt core_runtime.Runtime) gin.HandlerFunc { return func(c *gin.Context) { appName := strings.TrimSpace(c.Query("appName")) if appName == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("application name is empty")) return } instanceIP := strings.TrimSpace(c.Query("instanceIP")) if instanceIP == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("instanceIP is empty")) return } newDisabled, err := strconv.ParseBool(c.Query(`trafficDisable`)) if err != nil { c.JSON(http.StatusBadRequest, model.NewErrorResp(errors.Wrap(err, "parse trafficDisable fail").Error())) return } existRule := true rawRes, err := service.GetConditionRule(rt, appName) var res *mesh_proto.ConditionRouteV3 if err != nil { if !core_store.IsResourceNotFound(err) { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) return } else if !newDisabled { // not found && cancel traffic-disable c.JSON(http.StatusOK, model.NewSuccessResp(nil)) return } existRule = false res = generateDefaultConditionV3(true, true, true, appName, consts.ScopeApplication) rawRes = &mesh.ConditionRouteResource{Spec: res.ToConditionRoute()} } else if res = rawRes.Spec.ToConditionRouteV3(); res == nil { c.JSON(http.StatusServiceUnavailable, model.NewErrorResp("this config only serve condition-route.configVersion == v3.1, got v3.0 config ")) return } // enable traffic if !newDisabled { for i, condition := range res.Conditions { existCondition, oldDisabled := isTrafficDisabledV3(condition, instanceIP) if existCondition { if oldDisabled != newDisabled { res.Conditions = append(res.Conditions[:i], res.Conditions[i+1:]...) rawRes.Spec = res.ToConditionRoute() if err = updateORCreateConditionRule(rt, existRule, appName, rawRes); err != nil { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) } c.JSON(http.StatusOK, model.NewSuccessResp(nil)) return } } } } else { // disable traffic // check if condition exists for _, condition := range res.Conditions { existCondition, oldDisabled := isTrafficDisabledV3(condition, instanceIP) if existCondition && oldDisabled { c.JSON(http.StatusBadRequest, model.NewErrorResp("The instance has been disabled!")) return } } res.Conditions = append(res.Conditions, disableExpression(instanceIP)) if err = updateORCreateConditionRule(rt, existRule, appName, rawRes); err != nil { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) } c.JSON(http.StatusOK, model.NewSuccessResp(nil)) } } } func disableExpression(instanceIP string) string { return "=>host!=" + instanceIP } func updateORCreateConditionRule(rt core_runtime.Runtime, existRule bool, appName string, rawRes *mesh.ConditionRouteResource) error { if !existRule { return service.CreateConditionRule(rt, appName, rawRes) } else { return service.UpdateConditionRule(rt, appName, rawRes) } } func newDisableConditionV3x1(ip string) *mesh_proto.ConditionRule { return &mesh_proto.ConditionRule{ From: &mesh_proto.ConditionRuleFrom{Match: "host=" + ip}, To: nil, } } func newDisableConditionV3(ip string) string { return "=>host!=" + ip } func generateDefaultConditionV3x1(Enabled, Force, Runtime bool, Key, Scope string) *mesh_proto.ConditionRouteV3X1 { return &mesh_proto.ConditionRouteV3X1{ ConfigVersion: consts.ConfiguratorVersionV3x1, Enabled: Enabled, Force: Force, Runtime: Runtime, Key: Key, Scope: Scope, Conditions: make([]*mesh_proto.ConditionRule, 0), } } func generateDefaultConditionV3(Enabled, Force, Runtime bool, Key, Scope string) *mesh_proto.ConditionRouteV3 { return &mesh_proto.ConditionRouteV3{ ConfigVersion: consts.ConfiguratorVersionV3, Priority: 0, Enabled: true, Force: Force, Runtime: Runtime, Key: Key, Scope: Scope, Conditions: make([]string, 0), } } func InstanceConfigOperatorLogGET(rt core_runtime.Runtime) gin.HandlerFunc { return func(c *gin.Context) { resp := struct { OperatorLog bool `json:"operatorLog"` }{false} applicationName := c.Query(`appName`) if applicationName == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("application name is empty")) return } instanceIP := c.Query(`instanceIP`) if instanceIP == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("instanceIP is empty")) return } res, err := service.GetConfigurator(rt, applicationName) if err != nil { if core_store.IsResourceNotFound(err) { c.JSON(http.StatusOK, model.NewSuccessResp(resp)) return } c.JSON(http.StatusNotFound, model.NewErrorResp(err.Error())) return } if res.Spec.Enabled { res.Spec.RangeConfig(func(conf *mesh_proto.OverrideConfig) (isStop bool) { resp.OperatorLog = isInstanceOperatorLogOpen(conf, instanceIP) return resp.OperatorLog }) } c.JSON(http.StatusOK, model.NewSuccessResp(resp)) } } func isInstanceOperatorLogOpen(conf *mesh_proto.OverrideConfig, IP string) bool { if conf != nil && conf.Match != nil && conf.Match.Address != nil && conf.Match.Address.Wildcard == IP+`:*` && conf.Side == consts.SideProvider && conf.Parameters != nil && conf.Parameters[`accesslog`] == `true` { return true } return false } func InstanceConfigOperatorLogPUT(rt core_runtime.Runtime) gin.HandlerFunc { return func(c *gin.Context) { applicationName := c.Query(`appName`) if applicationName == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("application name is empty")) return } instanceIP := c.Query(`instanceIP`) if instanceIP == "" { c.JSON(http.StatusBadRequest, model.NewErrorResp("instanceIP is empty")) return } adminOperatorLog, err := strconv.ParseBool(c.Query(`operatorLog`)) if err != nil { c.JSON(http.StatusBadRequest, model.NewErrorResp(err.Error())) return } res, err := service.GetConfigurator(rt, applicationName) notExist := false if err != nil { if !core_store.IsResourceNotFound(err) { c.JSON(http.StatusNotFound, model.NewErrorResp(err.Error())) return } res = generateDefaultConfigurator(applicationName, consts.ScopeApplication, consts.ConfiguratorVersionV3, true) notExist = true } if !adminOperatorLog { res.Spec.RangeConfigsToRemove(func(conf *mesh_proto.OverrideConfig) (IsRemove bool) { return isInstanceOperatorLogOpen(conf, instanceIP) }) } else { var isExist bool res.Spec.RangeConfig(func(conf *mesh_proto.OverrideConfig) (isStop bool) { isExist = isInstanceOperatorLogOpen(conf, instanceIP) return isExist }) if !isExist { res.Spec.Configs = append(res.Spec.Configs, &mesh_proto.OverrideConfig{ Side: consts.SideProvider, Match: &mesh_proto.ConditionMatch{Address: &mesh_proto.AddressMatch{Wildcard: instanceIP + `:*`}}, Parameters: map[string]string{`accesslog`: `true`}, XGenerateByCp: true, }) } } if notExist { err = service.CreateConfigurator(rt, applicationName, res) if err != nil { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) return } } else { err = service.UpdateConfigurator(rt, applicationName, res) if err != nil { c.JSON(http.StatusInternalServerError, model.NewErrorResp(err.Error())) return } } c.JSON(http.StatusOK, model.NewSuccessResp(nil)) } }