blob: 2c364e62bebbbb35a767f5015defda5b32f3fd43 [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 tags
import (
"crypto/sha256"
"fmt"
"regexp"
"sort"
"strings"
)
import (
"github.com/pkg/errors"
)
import (
mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1"
"github.com/apache/dubbo-kubernetes/pkg/util/maps"
)
const TagsHeaderName = "x-dubbo-tags"
// Format of split cluster name and regex for parsing it, see usages
const splitClusterFmtString = "%s-%x"
var splitClusterRegex = regexp.MustCompile("(.*)-[[:xdigit:]]{16}$")
type Tags map[string]string
func ServiceFromClusterName(name string) string {
matchedGroups := splitClusterRegex.FindStringSubmatch(name)
if len(matchedGroups) == 0 {
return name
}
return matchedGroups[1]
}
// DestinationClusterName generates a unique cluster name for the
// destination. identifyingTags are useful for adding extra metadata outside of just tags. Tags must at least contain `dubbo.io/service`
func (t Tags) DestinationClusterName(
additionalIdentifyingTags map[string]string,
) (string, error) {
serviceName := t[mesh_proto.ServiceTag]
if serviceName == "" {
return "", fmt.Errorf("missing %s tag", mesh_proto.ServiceTag)
}
// If there's no tags other than serviceName just return the serviceName
if len(additionalIdentifyingTags) == 0 && len(t) == 1 {
return serviceName, nil
}
// If cluster is splitting the target service with selector tags,
// hash the tag names to generate a unique cluster name.
h := sha256.New()
for _, k := range maps.SortedKeys(t) {
h.Write([]byte(k))
h.Write([]byte(t[k]))
}
for _, k := range maps.SortedKeys(additionalIdentifyingTags) {
h.Write([]byte(k))
h.Write([]byte(additionalIdentifyingTags[k]))
}
// The qualifier is 16 hex digits. Unscientifically balancing the length
// of the hex against the likelihood of collisions.
// Note: policy configuration is sensitive to this format!
return fmt.Sprintf(splitClusterFmtString, serviceName, h.Sum(nil)[:8]), nil
}
func (t Tags) WithoutTags(tags ...string) Tags {
tagSet := map[string]bool{}
for _, t := range tags {
tagSet[t] = true
}
result := Tags{}
for tagName, tagValue := range t {
if !tagSet[tagName] {
result[tagName] = tagValue
}
}
return result
}
func (t Tags) WithTags(keysAndValues ...string) Tags {
result := Tags{}
for tagName, tagValue := range t {
result[tagName] = tagValue
}
for i := 0; i < len(keysAndValues); {
key, value := keysAndValues[i], keysAndValues[i+1]
result[key] = value
i += 2
}
return result
}
func (t Tags) Keys() TagKeys {
var keys []string
for key := range t {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func (t Tags) String() string {
var pairs []string
for _, key := range t.Keys() {
pairs = append(pairs, fmt.Sprintf("%s=%s", key, t[key]))
}
return strings.Join(pairs, ",")
}
type (
TagsSlice []Tags
TagKeys []string
TagKeysSlice []TagKeys
)
func (t TagsSlice) ToTagKeysSlice() TagKeysSlice {
out := []TagKeys{}
for _, v := range t {
out = append(out, v.Keys())
}
return out
}
// Transform applies each transformer to each TagKeys and returns a sorted unique TagKeysSlice.
func (t TagKeysSlice) Transform(transformers ...TagKeyTransformer) TagKeysSlice {
allSlices := map[string]TagKeys{}
for _, tagKeys := range t {
res := tagKeys.Transform(transformers...)
if len(res) > 0 {
h := strings.Join(res, ", ")
allSlices[h] = res
}
}
out := TagKeysSlice{}
for _, n := range allSlices {
out = append(out, n)
}
sort.Slice(out, func(i, j int) bool {
for k := 0; k < len(out[i]) && k < len(out[j]); k++ {
if out[i][k] != out[j][k] {
return out[i][k] < out[j][k]
}
}
return len(out[i]) < len(out[j])
})
return out
}
type TagKeyTransformer interface {
Apply(slice TagKeys) TagKeys
}
type TagKeyTransformerFunc func(slice TagKeys) TagKeys
func (f TagKeyTransformerFunc) Apply(slice TagKeys) TagKeys {
return f(slice)
}
// Transform applies a list of transformers on the tag keys and return a new set of keys (always return sorted, unique sets).
func (t TagKeys) Transform(transformers ...TagKeyTransformer) TagKeys {
tmp := t
for _, tr := range transformers {
tmp = tr.Apply(tmp)
}
// Make tags unique and sorted
tagSet := map[string]bool{}
out := TagKeys{}
for _, n := range tmp {
if !tagSet[n] {
tagSet[n] = true
out = append(out, n)
}
}
sort.Strings(out)
return out
}
func Without(tags ...string) TagKeyTransformer {
tagSet := map[string]bool{}
for _, t := range tags {
tagSet[t] = true
}
return TagKeyTransformerFunc(func(slice TagKeys) TagKeys {
out := []string{}
for _, t := range slice {
if !tagSet[t] {
out = append(out, t)
}
}
return out
})
}
func With(tags ...string) TagKeyTransformer {
return TagKeyTransformerFunc(func(slice TagKeys) TagKeys {
res := make([]string, len(tags)+len(slice))
copy(res, slice)
copy(res[len(slice):], tags)
return res
})
}
func TagsFromString(tagsString string) (Tags, error) {
result := Tags{}
tagPairs := strings.Split(tagsString, ",")
for _, pair := range tagPairs {
split := strings.Split(pair, "=")
if len(split) != 2 {
return nil, errors.New("invalid format of tags, pairs should be separated by , and key should be separated from value by =")
}
result[split[0]] = split[1]
}
return result, nil
}
func DistinctTags(tags []Tags) []Tags {
used := map[string]bool{}
var result []Tags
for _, tag := range tags {
str := tag.String()
if !used[str] {
result = append(result, tag)
used[str] = true
}
}
return result
}
func TagKeySlice(tags []Tags) TagKeysSlice {
r := make([]TagKeys, len(tags))
for i := range tags {
r[i] = tags[i].Keys()
}
return r
}
func MatchingRegex(tags mesh_proto.SingleValueTagSet) string {
var re string
for _, key := range tags.Keys() {
keyIsEqual := fmt.Sprintf(`&%s=`, key)
var value string
switch tags[key] {
case "*":
value = ``
default:
value = fmt.Sprintf(`[^&]*%s[,&]`, tags[key])
}
value = strings.ReplaceAll(value, ".", `\.`)
expr := keyIsEqual + value + `.*`
re += expr
}
re = `.*` + re
return re
}
func RegexOR(r ...string) string {
if len(r) == 0 {
return ""
}
if len(r) == 1 {
return r[0]
}
return fmt.Sprintf("(%s)", strings.Join(r, "|"))
}