| /* |
| * 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)) |
| } |
| } |