blob: d501e2963a6171fa7133c99a009ce9e68a4363fe [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 kie
import (
"bytes"
"context"
"encoding/json"
"fmt"
"math/rand"
"strings"
"time"
"github.com/apache/servicecomb-service-center/pkg/log"
"github.com/apache/servicecomb-service-center/pkg/gov"
"github.com/apache/servicecomb-service-center/server/config"
svc "github.com/apache/servicecomb-service-center/server/service/gov"
"github.com/ghodss/yaml"
"github.com/go-chassis/kie-client"
)
type Distributor struct {
name string
client *kie.Client
}
const (
KeyPrefix = "servicecomb."
KindMatchGroup = "match-group"
GroupNamePrefix = "scene-"
StatusEnabled = "enabled"
TypeText = "text"
KeyApp = "app"
KeyEnvironment = "environment"
EnvAll = "all"
Alias = "alias"
Method = "method"
Matches = "matches"
Rules = "rules"
)
var PolicyNames = []string{"retry", "rateLimiting", "circuitBreaker", "bulkhead"}
var rule = Validator{}
func (d *Distributor) Create(kind, project string, p *gov.Policy) ([]byte, error) {
if kind == KindMatchGroup {
err := d.generateID(project, p)
if err != nil {
return nil, err
}
}
err := rule.Validate(kind, p.Spec)
if err != nil {
return nil, err
}
if kind == KindMatchGroup {
setAliasIfEmpty(p.Spec, p.Name)
}
yamlByte, err := yaml.Marshal(p.Spec)
if err != nil {
return nil, err
}
kv := kie.KVRequest{
Key: toGovKeyPrefix(kind) + p.Name,
Value: string(yamlByte),
Status: StatusEnabled,
ValueType: TypeText,
Labels: map[string]string{KeyApp: p.Selector.App, KeyEnvironment: p.Selector.Environment},
}
res, err := d.client.Create(context.TODO(), kv, kie.WithProject(project))
if err != nil {
log.Error("kie create failed", err)
return nil, err
}
return []byte(res.ID), nil
}
func (d *Distributor) Update(kind, id, project string, p *gov.Policy) error {
err := rule.Validate(kind, p.Spec)
if err != nil {
return err
}
if kind == KindMatchGroup {
setAliasIfEmpty(p.Spec, p.Name)
}
yamlByte, err := yaml.Marshal(p.Spec)
if err != nil {
return err
}
kv := kie.KVRequest{
ID: id,
Value: string(yamlByte),
Status: p.Status,
}
_, err = d.client.Put(context.TODO(), kv, kie.WithProject(project))
if err != nil {
log.Error("kie update failed", err)
return err
}
return nil
}
func (d *Distributor) Delete(kind, id, project string) error {
if kind == KindMatchGroup {
// should remove all policies of this group
return d.DeleteMatchGroup(id, project)
}
err := d.client.Delete(context.TODO(), id, kie.WithProject(project))
if err != nil {
log.Error("kie delete failed", err)
return err
}
return nil
}
func (d *Distributor) DeleteMatchGroup(id string, project string) error {
policy, err := d.getPolicy(KindMatchGroup, id, project)
if err != nil {
log.Error("kie get failed", err)
return err
}
labels := make(map[string]string)
labels[KeyApp] = policy.Selector.App
labels[KeyEnvironment] = policy.Selector.Environment
ops := []kie.GetOption{
kie.WithKey("wildcard(" + KeyPrefix + "*." + policy.Name + ")"),
kie.WithLabels(labels),
kie.WithRevision(0),
kie.WithGetProject(project),
}
idList, _, err := d.client.List(context.TODO(), ops...)
if err != nil {
log.Error("kie list failed", err)
return err
}
var ids string
for _, res := range idList.Data {
ids += res.ID + ","
}
if len(ids) == 0 {
return nil
}
err = d.client.Delete(context.TODO(), ids[:len(ids)-1], kie.WithProject(project))
if err != nil {
log.Error("kie list failed", err)
return err
}
return nil
}
func (d *Distributor) Display(project, app, env string) ([]byte, error) {
list, _, err := d.listDataByKind(KindMatchGroup, project, app, env)
if err != nil {
return nil, err
}
policyMap := make(map[string]*gov.Policy)
for _, kind := range PolicyNames {
policies, _, err := d.listDataByKind(kind, project, app, env)
if err != nil {
continue
}
for _, policy := range policies.Data {
item, err := d.transform(policy, kind)
if err != nil {
log.Warn(fmt.Sprintf("transform config failed: key is [%s], value is [%s]", policy.Key, policy.Value))
continue
}
policyMap[item.Name+kind] = item
}
}
r := make([]*gov.DisplayData, 0, list.Total)
for _, item := range list.Data {
match, err := d.transform(item, KindMatchGroup)
if err != nil {
log.Warn(fmt.Sprintf("transform config failed: key is [%s], value is [%s]", item.Key, item.Value))
continue
}
var policies []*gov.Policy
for _, kind := range PolicyNames {
if policyMap[match.Name+kind] != nil {
policies = append(policies, policyMap[match.Name+kind])
}
}
result := &gov.DisplayData{
Policies: policies,
MatchGroup: match,
}
r = append(r, result)
}
b, _ := json.MarshalIndent(r, "", " ")
return b, nil
}
func setAliasIfEmpty(spec map[string]interface{}, name string) {
if spec["alias"] == nil {
spec["alias"] = name
return
}
alias := spec["alias"].(string)
if alias == "" {
spec["alias"] = name
}
}
func (d *Distributor) List(kind, project, app, env string) ([]byte, error) {
list, _, err := d.listDataByKind(kind, project, app, env)
if err != nil {
return nil, err
}
r := make([]*gov.Policy, 0, list.Total)
for _, item := range list.Data {
policy, err := d.transform(item, kind)
if err != nil {
log.Warn(fmt.Sprintf("transform config failed: key is [%s], value is [%s]", item.Key, item.Value))
continue
}
r = append(r, policy)
}
b, _ := json.MarshalIndent(r, "", " ")
return b, nil
}
func (d *Distributor) Get(kind, id, project string) ([]byte, error) {
policy, err := d.getPolicy(kind, id, project)
if err != nil {
return nil, err
}
b, _ := json.MarshalIndent(policy, "", " ")
return b, nil
}
func (d *Distributor) getPolicy(kind string, id string, project string) (*gov.Policy, error) {
kv, err := d.client.Get(context.TODO(), id, kie.WithGetProject(project))
if err != nil {
return nil, err
}
policy, err := d.transform(kv, kind)
if err != nil {
return nil, err
}
return policy, nil
}
func (d *Distributor) Type() string {
return svc.ConfigDistributorKie
}
func (d *Distributor) Name() string {
return d.name
}
func initClient(endpoint string) *kie.Client {
client, err := kie.NewClient(
kie.Config{Endpoint: endpoint,
DefaultLabels: map[string]string{},
})
if err != nil {
log.Fatal("init kie client failed, err: %s", err)
}
return client
}
func new(opts config.DistributorOptions) (svc.ConfigDistributor, error) {
return &Distributor{name: opts.Name, client: initClient(opts.Endpoint)}, nil
}
func toSnake(name string) string {
if name == "" {
return ""
}
temp := strings.Split(name, "-")
var buffer bytes.Buffer
for num, v := range temp {
vv := []rune(v)
if num == 0 {
buffer.WriteString(string(vv))
continue
}
if len(vv) > 0 {
if vv[0] >= 'a' && vv[0] <= 'z' { //首字母大写
vv[0] -= 32
}
buffer.WriteString(string(vv))
}
}
return buffer.String()
}
func (d *Distributor) listDataByKind(kind, project, app, env string) (*kie.KVResponse, int, error) {
ops := []kie.GetOption{
kie.WithKey("beginWith(" + toGovKeyPrefix(kind) + ")"),
kie.WithRevision(0),
kie.WithGetProject(project),
}
labels := map[string]string{}
if env != EnvAll {
labels[KeyEnvironment] = env
}
if app != "" {
labels[KeyApp] = app
}
if len(labels) > 0 {
ops = append(ops, kie.WithLabels(labels))
}
return d.client.List(context.TODO(), ops...)
}
func (d *Distributor) generateID(project string, p *gov.Policy) error {
if p.Name != "" {
return nil
}
kind := KindMatchGroup
list, _, err := d.listDataByKind(kind, project, p.Selector.App, p.Selector.Environment)
if err != nil {
return err
}
var id string
for {
var repeat bool
id = getID()
govKey := toGovKeyPrefix(kind) + id
for _, datum := range list.Data {
if govKey == datum.Key {
repeat = true
break
}
}
if !repeat {
break
}
}
p.Name = id
return nil
}
func getID() string {
str := "0123456789abcdefghijklmnopqrstuvwxyz"
b := []byte(str)
var result []byte
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 4; i++ {
result = append(result, b[r.Intn(len(b))])
}
return GroupNamePrefix + string(result)
}
func (d *Distributor) transform(kv *kie.KVDoc, kind string) (*gov.Policy, error) {
goc := &gov.Policy{
GovernancePolicy: &gov.GovernancePolicy{
Selector: &gov.Selector{},
},
}
spec := make(map[string]interface{})
specJSON, _ := yaml.YAMLToJSON([]byte(kv.Value))
err := json.Unmarshal(specJSON, &spec)
if err != nil {
log.Error("kie transform kv failed", err)
return nil, err
}
goc.Kind = kind
goc.ID = kv.ID
goc.Status = kv.Status
goc.Name = kv.Key[strings.LastIndex(kv.Key, ".")+1 : len(kv.Key)]
goc.Spec = spec
goc.Selector.App = kv.Labels[KeyApp]
goc.Selector.Environment = kv.Labels[KeyEnvironment]
goc.CreatTime = kv.CreatTime
goc.UpdateTime = kv.UpdateTime
return goc, nil
}
func toGovKeyPrefix(kind string) string {
return KeyPrefix + toSnake(kind) + "."
}
func init() {
svc.InstallDistributor(svc.ConfigDistributorKie, new)
}