blob: 11b92fe1168f900e6b45e6c57536b702ffa47903 [file] [log] [blame]
/*
* 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 model
import (
"strings"
meshproto "github.com/apache/dubbo-admin/api/mesh/v1alpha1"
"github.com/apache/dubbo-admin/pkg/common/constants"
coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
)
type SearchConditionRuleReq struct {
coremodel.PageReq
Mesh string `form:"mesh"`
Keywords string `form:"keywords"`
}
func (s *SearchConditionRuleReq) PageRequest() coremodel.PageReq {
return s.PageReq
}
func NewSearchConditionRuleReq() *SearchConditionRuleReq {
return &SearchConditionRuleReq{
PageReq: coremodel.PageReq{PageSize: 15},
}
}
type ConditionRuleSearchResp struct {
CreateTime string `json:"createTime"`
Enabled bool `json:"enabled"`
RuleName string `json:"ruleName"`
Scope string `json:"scope"`
}
type ConditionRuleResp struct {
Conditions []string `json:"conditions"`
ConfigVersion string `json:"configVersion"`
Enabled bool `json:"enabled"`
Key string `json:"key"`
Runtime bool `json:"runtime"`
Scope string `json:"scope"`
}
type ServiceArgumentRoute struct {
Routes []ServiceArgument `json:"routes"`
}
type ServiceArgument struct {
Conditions []RouteCondition `json:"conditions"`
Destinations []Destination `json:"destinations"`
Method string `json:"method"`
}
// ToExpression Convert ServiceArgument to expression string
func (sa *ServiceArgument) ToExpression() string {
expression := "method=" + sa.Method
// Add route conditions
for _, condition := range sa.Conditions {
expression += " & " + condition.string()
}
// Add destinations if any
if len(sa.Destinations) > 0 {
expression += " => "
// Process each destination
for destIdx, destination := range sa.Destinations {
for condIdx, condition := range destination.Conditions {
if destIdx > 0 || condIdx > 0 {
expression += " & "
}
expression += condition.string()
}
// If there are multiple destinations, separate them (using comma as separator)
if destIdx < len(sa.Destinations)-1 {
expression += ", "
}
}
}
return expression
}
func (sa *ServiceArgument) toFrom() *meshproto.ConditionRuleFrom {
res := "method=" + sa.Method
if len(sa.Conditions) != 0 {
for i := 0; len(sa.Conditions) > i; i++ {
res += " & " + sa.Conditions[i].string()
}
}
return &meshproto.ConditionRuleFrom{
Match: res,
}
}
func (sa *ServiceArgument) toTo() []*meshproto.ConditionRuleTo {
res := make([]*meshproto.ConditionRuleTo, 0, len(sa.Destinations))
for _, destination := range sa.Destinations {
match := ""
for _, condition := range destination.Conditions {
if match == "" {
match += condition.string()
} else {
match += " & " + condition.string()
}
}
res = append(res, &meshproto.ConditionRuleTo{
Match: match,
Weight: destination.Weight,
})
}
return res
}
type RouteCondition struct {
Index string `json:"index"`
Relation string `json:"relation"`
Value string `json:"value"`
}
func (r *RouteCondition) string() string {
if r.Relation == constants.Equal {
return "arguments[" + r.Index + "]" + constants.Equal + r.Value
} else {
return "arguments[" + r.Index + "]" + constants.NotEqual + r.Value
}
}
type Destination struct {
Conditions []DestinationCondition `json:"conditions"`
Weight int32 `json:"weight"`
}
type DestinationCondition struct {
Relation string `json:"relation"`
Tag string `json:"tag"`
Value string `json:"value"`
}
func (d *DestinationCondition) string() string {
if d.Relation == constants.Equal {
return d.Tag + constants.Equal + d.Value
} else {
return d.Tag + constants.NotEqual + d.Value
}
}
func (s *ServiceArgumentRoute) ToConditionV3x1Condition() []*meshproto.ConditionRule {
res := make([]*meshproto.ConditionRule, 0, len(s.Routes))
for _, route := range s.Routes {
res = append(res, &meshproto.ConditionRule{
From: route.toFrom(),
To: route.toTo(),
})
}
return res
}
func ConditionV3x1ToServiceArgumentRoute(mesh []*meshproto.ConditionRule) *ServiceArgumentRoute {
res := &ServiceArgumentRoute{
Routes: make([]ServiceArgument, 0, len(mesh)),
}
for i := range mesh {
method, _ := mesh[i].IsMatchMethod()
cond := ServiceArgument{
Conditions: matchValueToRouteCondition(mesh[i].From.Match),
Destinations: make([]Destination, 0, len(mesh[i].To)),
Method: method,
}
for _, to := range mesh[i].To {
cond.Destinations = append(cond.Destinations, Destination{
Conditions: matchValueToDestinationCondition(to.Match),
Weight: to.Weight,
})
}
res.Routes = append(res.Routes, cond)
}
return res
}
func matchValueToRouteCondition(val string) []RouteCondition {
subsets := strings.Split(val, "&")
res := make([]RouteCondition, 0, len(subsets))
for _, subset := range subsets {
if index := strings.Index(subset, constants.NotEqual); index != -1 {
res = append(res, RouteCondition{
Index: strings.Trim(subset[:index], " "),
Relation: constants.NotEqual,
Value: strings.Trim(subset[index+len(constants.NotEqual):], " "),
})
} else if index := strings.Index(subset, constants.Equal); index != -1 {
res = append(res, RouteCondition{
Index: strings.Trim(subset[:index], " "),
Relation: constants.Equal,
Value: strings.Trim(subset[index+len(constants.Equal):], " "),
})
}
}
return res
}
func matchValueToDestinationCondition(val string) []DestinationCondition {
subsets := strings.Split(val, "&")
res := make([]DestinationCondition, 0, len(subsets))
for _, subset := range subsets {
if index := strings.Index(subset, constants.NotEqual); index != -1 {
res = append(res, DestinationCondition{
Tag: strings.Trim(subset[:index], " "),
Relation: constants.NotEqual,
Value: strings.Trim(subset[index+len(constants.NotEqual):], " "),
})
} else if index := strings.Index(subset, constants.Equal); index != -1 {
res = append(res, DestinationCondition{
Tag: strings.Trim(subset[:index], " "),
Relation: constants.Equal,
Value: strings.Trim(subset[index+len(constants.Equal):], " "),
})
}
}
return res
}
func GenConditionRuleToResp(data *meshproto.ConditionRoute) *CommonResp {
if data == nil {
return NewSuccessResp(nil)
}
return NewSuccessResp(ConditionRuleResp{
Conditions: data.Conditions,
ConfigVersion: data.ConfigVersion,
Enabled: data.Enabled,
Key: data.Key,
Runtime: data.Runtime,
Scope: data.Scope,
})
}
type ConditionRuleV3X1 struct {
Conditions []Condition `json:"conditions"`
ConfigVersion string `json:"configVersion"`
Enabled bool `json:"enabled"`
Force bool `json:"force"`
Key string `json:"key"`
Runtime bool `json:"runtime"`
Scope string `json:"scope"`
}
type AffinityAware struct {
Enabled bool `json:"enabled"`
Key string `json:"key"`
}
type Condition struct {
From ConditionFrom `json:"from"`
To []ConditionTo `json:"to"`
}
type ConditionFrom struct {
Match string `json:"match"`
}
type ConditionTo struct {
Match string `json:"match"`
Weight int32 `json:"weight"`
}
// ParseConditionExpression converts an expression string to a ServiceArgument struct
// Expression format: "method=methodName & condition1 => destination1"
func ParseConditionExpression(expression string) ServiceArgument {
// Split into from and to parts (separated by "=>")
parts := strings.Split(expression, "=>")
fromPart := strings.TrimSpace(parts[0])
toPart := ""
if len(parts) > 1 {
toPart = strings.TrimSpace(parts[1])
}
// Parse from part to get method and conditions
method, conditions := parseFromPart(fromPart)
// Parse to part to get destinations
destinations := parseToPart(toPart)
return ServiceArgument{
Method: method,
Conditions: conditions,
Destinations: destinations,
}
}
// parseFromPart parses the from part containing method and arguments conditions
func parseFromPart(fromPart string) (string, []RouteCondition) {
conditions := []RouteCondition{}
method := ""
// Split conditions by "&"
subConditions := strings.Split(fromPart, "&")
for _, condition := range subConditions {
condition = strings.TrimSpace(condition)
// Check if it's a method condition
if strings.HasPrefix(condition, "method=") {
method = strings.TrimPrefix(condition, "method=")
method = strings.TrimSpace(method)
} else {
// Parse arguments condition
routeCond := parseRouteCondition(condition)
if routeCond.Index != "" {
conditions = append(conditions, routeCond)
}
}
}
return method, conditions
}
// parseRouteCondition parses a single route condition
func parseRouteCondition(condition string) RouteCondition {
var relation string
var relationOp string
// Check if it's "=" or "!="
if index := strings.Index(condition, constants.NotEqual); index != -1 {
relation = constants.NotEqual
relationOp = constants.NotEqual
} else if index := strings.Index(condition, constants.Equal); index != -1 {
relation = constants.Equal
relationOp = constants.Equal
} else {
return RouteCondition{} // Invalid condition
}
// Find the position of the relation
index := strings.Index(condition, relationOp)
leftPart := strings.TrimSpace(condition[:index])
rightPart := strings.TrimSpace(condition[index+len(relationOp):])
// Parse arguments[index] format
if strings.HasPrefix(leftPart, "arguments[") && strings.HasSuffix(leftPart, "]") {
argIndex := leftPart[10 : len(leftPart)-1] // Extract index within brackets
return RouteCondition{
Index: argIndex,
Relation: relation,
Value: rightPart,
}
}
// If not arguments format, return generic condition
return RouteCondition{
Index: leftPart,
Relation: relation,
Value: rightPart,
}
}
// parseToPart parses the to part (destination conditions)
func parseToPart(toPart string) []Destination {
if toPart == "" {
return []Destination{}
}
// Split multiple conditions by "&"
conditions := strings.Split(toPart, "&")
destinationConditions := []DestinationCondition{}
for _, condition := range conditions {
condition = strings.TrimSpace(condition)
destCond := parseDestinationCondition(condition)
if destCond.Tag != "" {
destinationConditions = append(destinationConditions, destCond)
}
}
return []Destination{
{
Conditions: destinationConditions,
Weight: 0, // Default weight is 0, can be adjusted as needed
},
}
}
// parseDestinationCondition parses destination condition
func parseDestinationCondition(condition string) DestinationCondition {
var relation string
var relationOp string
// Check if it's "=" or "!="
if index := strings.Index(condition, constants.NotEqual); index != -1 {
relation = constants.NotEqual
relationOp = constants.NotEqual
} else if index := strings.Index(condition, constants.Equal); index != -1 {
relation = constants.Equal
relationOp = constants.Equal
} else {
return DestinationCondition{} // Invalid condition
}
// Find the position of the relation
index := strings.Index(condition, relationOp)
leftPart := strings.TrimSpace(condition[:index])
rightPart := strings.TrimSpace(condition[index+len(relationOp):])
return DestinationCondition{
Tag: leftPart,
Relation: relation,
Value: rightPart,
}
}