From c3cfe19d1984eacc96c2790e19f453dc6091bce8 Mon Sep 17 00:00:00 2001 From: skywli Date: Thu, 19 Oct 2023 19:37:52 +0800 Subject: [PATCH] feat(config): support gray --- apiserver/httpserver/config/console_access.go | 4 - cache/api/types.go | 14 +- cache/cache.go | 5 + cache/config/config_file.go | 4 +- cache/default.go | 3 + cache/gray/gray.go | 129 ++++++++++++++++++ common/model/config_file.go | 18 +-- common/model/gray.go | 47 +++++++ common/utils/match.go | 50 +++++++ common/utils/match_test.go | 58 ++++++++ config/client.go | 47 +++++-- config/config_chain.go | 4 +- config/config_file_release.go | 54 +++++++- config/server.go | 2 + go.mod | 5 +- go.sum | 2 + release/conf/polaris-server.yaml | 2 + store/api.go | 8 ++ store/boltdb/config_file_release.go | 5 + store/boltdb/default.go | 3 +- store/boltdb/gray_resource.go | 94 +++++++++++++ store/mock/api_mock.go | 29 ++++ store/mysql/config_file_release.go | 4 +- store/mysql/default.go | 2 + store/mysql/gray_resouce.go | 108 +++++++++++++++ store/mysql/scripts/polaris_server.sql | 27 +--- 26 files changed, 663 insertions(+), 65 deletions(-) create mode 100644 cache/gray/gray.go create mode 100644 common/model/gray.go create mode 100644 common/utils/match.go create mode 100644 common/utils/match_test.go create mode 100644 store/boltdb/gray_resource.go create mode 100644 store/mysql/gray_resouce.go diff --git a/apiserver/httpserver/config/console_access.go b/apiserver/httpserver/config/console_access.go index cea0b69cc..d641a7b00 100644 --- a/apiserver/httpserver/config/console_access.go +++ b/apiserver/httpserver/config/console_access.go @@ -291,16 +291,12 @@ func (h *HTTPServer) PublishConfigFile(req *restful.Request, rsp *restful.Respon configFile := &apiconfig.ConfigFileRelease{} ctx, err := handler.Parse(configFile) - requestId := ctx.Value(utils.StringContext("request-id")) - if err != nil { configLog.Error("[Config][HttpServer] parse config file release from request error.", - zap.String("requestId", requestId.(string)), zap.String("error", err.Error())) handler.WriteHeaderAndProto(api.NewConfigFileReleaseResponseWithMessage(apimodel.Code_ParseException, err.Error())) return } - handler.WriteHeaderAndProto(h.configServer.PublishConfigFile(ctx, configFile)) } diff --git a/cache/api/types.go b/cache/api/types.go index a64dc1475..d081dd2bb 100644 --- a/cache/api/types.go +++ b/cache/api/types.go @@ -25,6 +25,7 @@ import ( apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" apitraffic "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "github.com/polarismesh/polaris/common/metrics" "github.com/polarismesh/polaris/common/model" @@ -64,6 +65,8 @@ const ( StrategyRuleName = "strategyRule" // ServiceContractName service contract config name ServiceContractName = "serviceContract" + // GrayName gray config name + GrayName = "gray" ) type CacheIndex int @@ -85,6 +88,7 @@ const ( CacheFaultDetector CacheConfigGroup CacheServiceContract + CacheGray CacheLast ) @@ -440,7 +444,7 @@ type ( Cache // GetActiveRelease GetGroupActiveReleases(namespace, group string) ([]*model.ConfigFileRelease, string) - GetActiveRelease(namespace, group, fileName string, typ model.ConfigeFileType) *model.ConfigFileRelease + GetActiveRelease(namespace, group, fileName string, typ model.ReleaseType) *model.ConfigFileRelease // GetRelease GetRelease(key model.ConfigFileReleaseKey) *model.ConfigFileRelease // QueryReleases @@ -646,3 +650,11 @@ func (bc *BaseCache) Clear() { func (bc *BaseCache) Close() error { return nil } + +type ( + // GrayCache 灰度 Cache 接口 + GrayCache interface { + Cache + GetGrayRule(name string) *apimodel.MatchTerm + } +) diff --git a/cache/cache.go b/cache/cache.go index daa634d72..72adb15ea 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -219,6 +219,11 @@ func (nc *CacheManager) ConfigGroup() types.ConfigGroupCache { return nc.caches[types.CacheConfigGroup].(types.ConfigGroupCache) } +// Gray get Gray cache information +func (nc *CacheManager) Gray() types.GrayCache { + return nc.caches[types.CacheGray].(types.GrayCache) +} + // GetCacher get types.Cache impl func (nc *CacheManager) GetCacher(cacheIndex types.CacheIndex) types.Cache { return nc.caches[cacheIndex] diff --git a/cache/config/config_file.go b/cache/config/config_file.go index 44c2b1fdf..d82c18f1e 100644 --- a/cache/config/config_file.go +++ b/cache/config/config_file.go @@ -371,7 +371,7 @@ func (fc *fileCache) GetGroupActiveReleases(namespace, group string) ([]*model.C } // GetActiveRelease -func (fc *fileCache) GetActiveRelease(namespace, group, fileName string, typ model.ConfigeFileType) *model.ConfigFileRelease { +func (fc *fileCache) GetActiveRelease(namespace, group, fileName string, typ model.ReleaseType) *model.ConfigFileRelease { nsBucket, ok := fc.activeReleases.Load(namespace) if !ok { return nil @@ -455,7 +455,7 @@ func (fc *fileCache) QueryReleases(args *types.ConfigReleaseArgs) (uint32, []*mo if args.ReleaseName != "" && utils.IsWildNotMatch(item.Name, args.ReleaseName) { return } - if !args.IncludeGray && item.Typ == model.ConfigeFileTypeGray { + if !args.IncludeGray && item.Typ == model.ReleaseTypeGray { return } if args.OnlyActive && !item.Active { diff --git a/cache/default.go b/cache/default.go index a091bbb57..b42ad28e7 100644 --- a/cache/default.go +++ b/cache/default.go @@ -28,6 +28,7 @@ import ( cacheconfig "github.com/polarismesh/polaris/cache/config" cachens "github.com/polarismesh/polaris/cache/namespace" cachesvc "github.com/polarismesh/polaris/cache/service" + cachegray "github.com/polarismesh/polaris/cache/gray" "github.com/polarismesh/polaris/store" ) @@ -46,6 +47,7 @@ func init() { RegisterCache(types.StrategyRuleName, types.CacheAuthStrategy) RegisterCache(types.ClientName, types.CacheClient) RegisterCache(types.ServiceContractName, types.CacheServiceContract) + RegisterCache(types.GrayName, types.CacheGray) } var ( @@ -106,6 +108,7 @@ func newCacheManager(ctx context.Context, cacheOpt *Config, storage store.Store) mgr.RegisterCacher(types.CacheAuthStrategy, cacheauth.NewStrategyCache(storage, mgr)) // 北极星SDK Client mgr.RegisterCacher(types.CacheClient, cacheclient.NewClientCache(storage, mgr)) + mgr.RegisterCacher(types.CacheGray, cachegray.NewGrayCache(storage, mgr)) if len(mgr.caches) != int(types.CacheLast) { return nil, errors.New("some Cache implement not loaded into CacheManager") diff --git a/cache/gray/gray.go b/cache/gray/gray.go new file mode 100644 index 000000000..c3c706cc3 --- /dev/null +++ b/cache/gray/gray.go @@ -0,0 +1,129 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 gray + +import ( + "bytes" + "time" + + "go.uber.org/zap" + "golang.org/x/sync/singleflight" + + "github.com/golang/protobuf/jsonpb" + types "github.com/polarismesh/polaris/cache/api" + "github.com/polarismesh/polaris/common/log" + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/store" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" +) + +var ( + _ types.GrayCache = (*grayCache)(nil) +) + +type grayCache struct { + *types.BaseCache + storage store.Store + grayResources *utils.SyncMap[string, *apimodel.MatchTerm] + updater *singleflight.Group +} + +// NewGrayCache create gray cache obj +func NewGrayCache(storage store.Store, cacheMgr types.CacheManager) types.GrayCache { + return &grayCache{ + BaseCache: types.NewBaseCache(storage, cacheMgr), + storage: storage, + } +} + +// Initialize init gray cache +func (gc *grayCache) Initialize(opt map[string]interface{}) error { + gc.grayResources = utils.NewSyncMap[string, *apimodel.MatchTerm]() + gc.updater = &singleflight.Group{} + return nil +} + +// Update update cache +func (gc *grayCache) Update() error { + // 多个线程竞争,只有一个线程进行更新 + _, err, _ := gc.updater.Do(gc.Name(), func() (interface{}, error) { + return nil, gc.DoCacheUpdate(gc.Name(), gc.realUpdate) + }) + return err +} + +func (gc *grayCache) realUpdate() (map[string]time.Time, int64, error) { + grayResources, err := gc.storage.GetMoreGrayResouces(gc.IsFirstUpdate(), gc.LastFetchTime()) + + if err != nil { + log.Error("[Cache][Gray] get storage more", zap.Error(err)) + return nil, -1, err + } + if len(grayResources) == 0 { + return nil, 0, nil + } + lastMtimes := gc.setGrayResources(grayResources) + log.Info("[Cache][Gray] get more gray resource", + zap.Int("total", len(grayResources))) + return lastMtimes, int64(len(grayResources)), nil +} + +func (gc *grayCache) setGrayResources(grayResources []*model.GrayResource) map[string]time.Time { + lastMtime := gc.LastMtime(gc.Name()).Unix() + for _, grayResource := range grayResources { + modifyUnix := grayResource.ModifyTime.Unix() + if modifyUnix > lastMtime { + lastMtime = modifyUnix + } + grayRule := &apimodel.MatchTerm{} + reader := bytes.NewReader([]byte(grayResource.MatchRule)) + err := jsonpb.Unmarshal(reader, grayRule) + if err != nil { + log.Error("[Cache][Gray] setGrayResources unmarshal gray rule fail.", + zap.String("name", grayResource.Name), zap.Error(err)) + continue + } + gc.grayResources.Store(grayResource.Name, grayRule) + } + + return map[string]time.Time{ + gc.Name(): time.Unix(lastMtime, 0), + } +} + +// Clear clear cache +func (gc *grayCache) Clear() error { + gc.BaseCache.Clear() + gc.grayResources = utils.NewSyncMap[string, *apimodel.MatchTerm]() + return nil +} + +// Name return gray name +func (gc *grayCache) Name() string { + return types.GrayName +} + +// GetGrayRule get gray rule +func (gc *grayCache) GetGrayRule(name string) *apimodel.MatchTerm { + val, ok := gc.grayResources.Load(name) + if !ok { + return nil + } + return val +} diff --git a/common/model/config_file.go b/common/model/config_file.go index e6de4e816..a719cb746 100644 --- a/common/model/config_file.go +++ b/common/model/config_file.go @@ -27,13 +27,14 @@ import ( "github.com/polarismesh/polaris/common/utils" ) -type ConfigeFileType uint32 +type ReleaseType uint32 const ( - // ConfigeFileTypeFull 全量类型 - ConfigeFileTypeFull ConfigeFileType = iota - // ConfigeFileTypeGray 灰度类型 - ConfigeFileTypeGray + _ ReleaseType = iota + // ReleaseTypeFull 全量类型 + ReleaseTypeFull + // ReleaseTypeGray 灰度类型 + ReleaseTypeGray ) /** ----------- DataObject ------------- */ @@ -133,7 +134,7 @@ type ConfigFileReleaseKey struct { Namespace string Group string FileName string - Typ ConfigeFileType + Typ ReleaseType } func (c ConfigFileReleaseKey) ToFileKey() *ConfigFileKey { @@ -149,11 +150,11 @@ func (c ConfigFileReleaseKey) OwnerKey() string { } func (c ConfigFileReleaseKey) ActiveKey() string { - return fmt.Sprintf("%v@%v@%v@%v", c.Namespace, c.Group, c.FileName, c.Typ) + return fmt.Sprintf("%v@%v@%v@%v", c.Namespace, c.Group, c.FileName, c.Typ) } func (c ConfigFileReleaseKey) ReleaseKey() string { - return fmt.Sprintf("%v@%v@%v@%v@%v", c.Namespace, c.Group, c.FileName, c.Typ,c.Name) + return fmt.Sprintf("%v@%v@%v@%v", c.Namespace, c.Group, c.FileName, c.Name) } // SimpleConfigFileRelease 配置文件发布数据持久化对象 @@ -336,6 +337,7 @@ func ToConfiogFileReleaseApi(release *ConfigFileRelease) *config_manage.ConfigFi ReleaseDescription: utils.NewStringValue(release.ReleaseDescription), Tags: FromTagMap(release.Metadata), Active: utils.NewBoolValue(release.Active), + Type: utils.NewUInt32Value(uint32(release.Typ)), } } diff --git a/common/model/gray.go b/common/model/gray.go new file mode 100644 index 000000000..ae5bb3d66 --- /dev/null +++ b/common/model/gray.go @@ -0,0 +1,47 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 ( + "fmt" + "time" +) + +type GrayModule string + +const ( + GrayModuleConfig GrayModule = "config" + GrayModuleRatelimit GrayModule = "ratelimit" + GrayModuleCircuitbreaker GrayModule = "circuitbreaker" + GrayModuleRoute GrayModule = "route" +) + +// GrayRule 灰度资源 +type GrayResource struct { + Name string + MatchRule string + CreateTime time.Time + ModifyTime time.Time + CreateBy string + ModifyBy string +} + +// GetGrayConfigRealseKey 获取灰度资源key +func GetGrayConfigRealseKey(release *SimpleConfigFileRelease) string { + return fmt.Sprintf("%v@%v@%v@%v", GrayModuleConfig, release.Namespace, release.Group, release.FileName) +} diff --git a/common/utils/match.go b/common/utils/match.go new file mode 100644 index 000000000..b16ca2b49 --- /dev/null +++ b/common/utils/match.go @@ -0,0 +1,50 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 utils + +import ( + "strings" + + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" +) + +// Match 查找tags 中寻找匹配term 的tag +func Match(term *apimodel.MatchTerm, tags []*apimodel.Tag) bool { + for _, tag := range tags { + if term.GetKey().GetValue() == tag.GetKey().GetValue() { + val := term.GetValue() + express := val.GetValue().GetValue() + tagValue := tag.GetValue().GetValue() + switch val.GetType() { + case apimodel.MatchString_EXACT: + if tagValue == express { + return true + } + case apimodel.MatchString_IN: + fields := strings.Split(express, ",") + for _, field := range fields { + if tagValue == field { + return true + } + } + } + return false + } + } + return false +} diff --git a/common/utils/match_test.go b/common/utils/match_test.go new file mode 100644 index 000000000..4af6f86c9 --- /dev/null +++ b/common/utils/match_test.go @@ -0,0 +1,58 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 utils + +import ( + "testing" + + "github.com/golang/protobuf/ptypes/wrappers" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + "github.com/stretchr/testify/assert" +) + +func TestMatch(t *testing.T) { + tag := &apimodel.Tag{ + Key: &wrappers.StringValue{Value: "ip"}, + Value: &wrappers.StringValue{Value: "127.0.0.1"}, + } + tags := []*apimodel.Tag{tag} + + // 1. 全匹配 + matchKv := apimodel.MatchTerm{ + Key: &wrappers.StringValue{Value: "ip"}, + Value: &apimodel.MatchString{ + Type: apimodel.MatchString_EXACT, + Value: &wrappers.StringValue{Value: "127.0.0.1"}, + }, + } + + ok := Match(&matchKv, tags) + assert.Equal(t, ok, true) + + // 2. in 匹配 + matchKv = apimodel.MatchTerm{ + Key: &wrappers.StringValue{Value: "ip"}, + Value: &apimodel.MatchString{ + Type: apimodel.MatchString_IN, + Value: &wrappers.StringValue{Value: "127.0.0.1,196.10.10.1"}, + }, + } + + ok = Match(&matchKv, tags) + assert.Equal(t, ok, true) +} diff --git a/config/client.go b/config/client.go index 120424c1c..f605cd286 100644 --- a/config/client.go +++ b/config/client.go @@ -43,23 +43,44 @@ func (s *Server) GetConfigFileForClient(ctx context.Context, group := client.GetGroup().GetValue() fileName := client.GetFileName().GetValue() clientVersion := client.GetVersion().GetValue() - tags := client.GetTags() - if len(tags) != 0 { - log.Info("[Config][Service] get config file to client info", zap.String("tags", tags[0].Value.GetValue())) - } - - + configFileTags := client.GetTags() + if namespace == "" || group == "" || fileName == "" { return api.NewConfigClientResponseWithInfo( apimodel.Code_BadRequest, "namespace & group & fileName can not be empty") } - // 从缓存中获取配置内容 - release := s.fileCache.GetActiveRelease(namespace, group, fileName, model.ConfigeFileTypeFull) - if release == nil { - return api.NewConfigClientResponse(apimodel.Code_NotFoundResource, nil) + // 从缓存中获取灰度文件 + var release *model.ConfigFileRelease + var match bool + if len(configFileTags) > 0 { + release = s.fileCache.GetActiveRelease(namespace, group, fileName, model.ReleaseTypeGray) + if release != nil { + key := model.GetGrayConfigRealseKey(release.SimpleConfigFileRelease) + if grayRule := s.grayCache.GetGrayRule(key); grayRule == nil { + log.Error("[Config][Service] get config file not find gray rule", + utils.RequestID(ctx), zap.String("file", fileName)) + } else { + tags := make([]*apimodel.Tag, 0, len(configFileTags)) + for _, item := range configFileTags { + tag := &apimodel.Tag{ + Key: item.Key, + Value: item.Value, + } + tags = append(tags, tag) + } + if ok := utils.Match(grayRule, tags); ok { + match = true + } + } + } + } + if !match { + release = s.fileCache.GetActiveRelease(namespace, group, fileName, model.ReleaseTypeFull) + if release == nil { + return api.NewConfigClientResponse(apimodel.Code_NotFoundResource, nil) + } } - - // 客户端版本号大于服务端版本号,服务端不返回变更 + // 客户端版本号大于服务端版本号,服务端不返回变更 todo: 结合灰度和全量版本 判断 if clientVersion > release.Version { return api.NewConfigClientResponse(apimodel.Code_DataNoChange, nil) } @@ -180,7 +201,7 @@ func (s *Server) checkClientConfigFile(ctx context.Context, files []*apiconfig.C "namespace & group & fileName can not be empty"), false } // 从缓存中获取最新的配置文件信息 - release := s.fileCache.GetActiveRelease(namespace, group, fileName,model.ConfigeFileTypeFull) + release := s.fileCache.GetActiveRelease(namespace, group, fileName, model.ReleaseTypeFull) if release != nil && compartor(configFile, release) { ret := &apiconfig.ClientConfigFileInfo{ Namespace: utils.NewStringValue(namespace), diff --git a/config/config_chain.go b/config/config_chain.go index bb0cc57eb..88cbf78d6 100644 --- a/config/config_chain.go +++ b/config/config_chain.go @@ -272,7 +272,7 @@ func (chain *ReleaseConfigFileChain) AfterGetFile(ctx context.Context, group := file.Group name := file.Name // 首先检测灰度版本 - if grayFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name, model.ConfigeFileTypeGray); grayFile != nil { + if grayFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name, model.ReleaseTypeGray); grayFile != nil { if grayFile.Content == file.OriginContent { file.Status = utils.ReleaseTypeGray file.ReleaseBy = grayFile.ModifyBy @@ -280,7 +280,7 @@ func (chain *ReleaseConfigFileChain) AfterGetFile(ctx context.Context, } else { file.Status = utils.ReleaseStatusToRelease } - } else if fullFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name, model.ConfigeFileTypeFull); fullFile != nil { + } else if fullFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name, model.ReleaseTypeFull); fullFile != nil { // 如果最后一次发布的内容和当前文件内容一致,则展示最后一次发布状态。否则说明文件有修改,待发布 if fullFile.Content == file.OriginContent { file.Status = utils.ReleaseTypeNormal diff --git a/config/config_file_release.go b/config/config_file_release.go index 1c21d2441..109edcf51 100644 --- a/config/config_file_release.go +++ b/config/config_file_release.go @@ -18,6 +18,7 @@ package config import ( + "bytes" "context" "errors" "fmt" @@ -25,7 +26,7 @@ import ( "sync/atomic" "time" - "github.com/gogo/protobuf/jsonpb" + "github.com/golang/protobuf/jsonpb" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "go.uber.org/zap" @@ -55,9 +56,12 @@ func (s *Server) PublishConfigFile(ctx context.Context, req *apiconfig.ConfigFil return api.NewConfigResponse(apimodel.Code_NotFoundNamespace) } - if req.GetType().GetValue() != uint32(model.ConfigeFileTypeGray) && req.GetType().GetValue() != uint32(model.ConfigeFileTypeFull) { + if req.GetType().GetValue() != uint32(model.ReleaseTypeGray) && req.GetType().GetValue() != uint32(model.ReleaseTypeFull) { return api.NewConfigResponse(apimodel.Code_InvalidParameter) } + if req.GetType().GetValue() == uint32(model.ReleaseTypeGray) && req.GetGrayRule() == nil { + return api.NewConfigResponse(apimodel.Code_InvalidMatchRule) + } tx, err := s.storage.StartTx() if err != nil { @@ -82,7 +86,7 @@ func (s *Server) PublishConfigFile(ctx context.Context, req *apiconfig.ConfigFil log.Error("[Config][Release] publish config file commit tx.", utils.RequestID(ctx), zap.Error(err)) return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } - if req.GetType().GetValue() == uint32(model.ConfigeFileTypeFull) { + if req.GetType().GetValue() == uint32(model.ReleaseTypeFull) { s.recordReleaseSuccess(ctx, utils.ReleaseTypeNormal, data) } else { s.recordReleaseSuccess(ctx, utils.ReleaseTypeGray, data) @@ -127,7 +131,7 @@ func (s *Server) handlePublishConfigFile(ctx context.Context, tx store.Tx, Namespace: namespace, Group: group, FileName: fileName, - Typ: model.ConfigeFileType(req.GetType().GetValue()), + Typ: model.ReleaseType(req.GetType().GetValue()), }, Format: toPublishFile.Format, Metadata: toPublishFile.Metadata, @@ -162,6 +166,32 @@ func (s *Server) handlePublishConfigFile(ctx context.Context, tx store.Tx, return fileRelease, api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } } + if req.GetType().GetValue() == uint32(model.ReleaseTypeGray) { + grayRule := req.GetGrayRule() + var buffer bytes.Buffer + marshaler := jsonpb.Marshaler{} + err := marshaler.Marshal(&buffer, grayRule) + if err != nil { + if err != nil { + log.Error("[Config][Release] marshal gary rule error.", + utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(group), + utils.ZapFileName(fileName), zap.Error(err)) + return fileRelease, api.NewConfigResponse(apimodel.Code_InvalidMatchRule) + } + } + grayResource := &model.GrayResource{ + Name: model.GetGrayConfigRealseKey(fileRelease.SimpleConfigFileRelease), + MatchRule: buffer.String(), + CreateBy: utils.ParseUserName(ctx), + ModifyBy: utils.ParseUserName(ctx), + } + if err := s.storage.CreateGrayResourceTx(tx, grayResource); err != nil { + log.Error("[Config][Release] create gray resource error.", + utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(group), + utils.ZapFileName(fileName), zap.Error(err)) + return fileRelease, api.NewConfigFileResponse(commonstore.StoreCode2APICode(err), nil) + } + } s.RecordHistory(ctx, configFileReleaseRecordEntry(ctx, req, fileRelease, model.OCreate)) return fileRelease, api.NewConfigResponse(apimodel.Code_ExecuteSuccess) @@ -177,7 +207,6 @@ func (s *Server) GetConfigFileRelease(ctx context.Context, req *apiconfig.Config if errCode, errMsg := checkBaseReleaseParam(req, false); errCode != apimodel.Code_ExecuteSuccess { return api.NewConfigResponseWithInfo(errCode, errMsg) } - var ( ret *model.ConfigFileRelease err error @@ -207,6 +236,7 @@ func (s *Server) GetConfigFileRelease(ctx context.Context, req *apiconfig.Config if ret == nil { return api.NewConfigResponse(apimodel.Code_ExecuteSuccess) } + ret, err = s.chains.AfterGetFileRelease(ctx, ret) if err != nil { log.Error("[Config][Release] get config file release run chain.", utils.RequestID(ctx), @@ -214,7 +244,16 @@ func (s *Server) GetConfigFileRelease(ctx context.Context, req *apiconfig.Config out := api.NewConfigResponse(apimodel.Code_ExecuteException) return out } + release := model.ToConfiogFileReleaseApi(ret) + if ret.Typ == model.ReleaseTypeGray { + key := model.GetGrayConfigRealseKey(ret.SimpleConfigFileRelease) + if grayRule := s.grayCache.GetGrayRule(key); grayRule == nil { + return api.NewConfigResponse(apimodel.Code_InvalidMatchRule) + } else { + release.GrayRule = grayRule + } + } return api.NewConfigFileReleaseResponse(apimodel.Code_ExecuteSuccess, release) } @@ -251,6 +290,7 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, Namespace: req.GetNamespace().GetValue(), Group: req.GetGroup().GetValue(), FileName: req.GetFileName().GetValue(), + Typ: model.ReleaseType(req.GetType().GetValue()), }, }, } @@ -419,7 +459,7 @@ func (s *Server) handleDescribeConfigFileReleases(ctx context.Context, ModifyBy: utils.NewStringValue(item.ModifyBy), ReleaseDescription: utils.NewStringValue(item.ReleaseDescription), Tags: model.FromTagMap(item.Metadata), - Type: utils.NewUInt32Value(uint32(item.Typ)), + Type: utils.NewUInt32Value(uint32(item.Typ)), }) } @@ -461,7 +501,7 @@ func (s *Server) RollbackConfigFileRelease(ctx context.Context, Namespace: req.GetNamespace().GetValue(), Group: req.GetGroup().GetValue(), FileName: req.GetFileName().GetValue(), - Typ: model.ConfigeFileTypeFull, + Typ: model.ReleaseTypeFull, }, }, } diff --git a/config/server.go b/config/server.go index e401ff2c7..bcc7431f6 100644 --- a/config/server.go +++ b/config/server.go @@ -107,6 +107,7 @@ type Server struct { storage store.Store fileCache cachetypes.ConfigFileCache groupCache cachetypes.ConfigGroupCache + grayCache cachetypes.GrayCache caches *cache.CacheManager watchCenter *watchCenter namespaceOperator namespace.NamespaceOperateServer @@ -157,6 +158,7 @@ func (s *Server) initialize(ctx context.Context, config Config, ss store.Store, s.namespaceOperator = namespaceOperator s.fileCache = cacheMgn.ConfigFile() s.groupCache = cacheMgn.ConfigGroup() + s.grayCache = cacheMgn.Gray() s.watchCenter, err = NewWatchCenter() if err != nil { diff --git a/go.mod b/go.mod index 8c7d11e57..f70a8fb90 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/polarismesh/polaris -go 1.20 +go 1.21 require ( github.com/BurntSushi/toml v1.2.0 @@ -83,7 +83,7 @@ require ( require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/agiledragon/gomonkey/v2 v2.10.1 - github.com/polarismesh/specification v0.0.0-00010101000000-000000000000 + github.com/polarismesh/specification v1.4.2-alpha ) require ( @@ -94,4 +94,3 @@ require ( replace gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.2.2 -replace github.com/polarismesh/specification => ../specification diff --git a/go.sum b/go.sum index ff45bd2f8..8eb8453c8 100644 --- a/go.sum +++ b/go.sum @@ -318,6 +318,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polarismesh/go-restful-openapi/v2 v2.0.0-20220928152401-083908d10219 h1:XnFyNUWnciM6zgXaz6tm+Egs35rhoD0KGMmKh4gCdi0= github.com/polarismesh/go-restful-openapi/v2 v2.0.0-20220928152401-083908d10219/go.mod h1:4WhwBysTom9Eoy0hQ4W69I0FmO+T0EpjEW9/5sgHoUk= +github.com/polarismesh/specification v1.4.2-alpha h1:rur+PV6DEaj/Q0hQKn/vEkWcfTC7xyQYSQF+EcPMcxU= +github.com/polarismesh/specification v1.4.2-alpha/go.mod h1:rDvMMtl5qebPmqiBLNa5Ps0XtwkP31ZLirbH4kXA0YU= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= diff --git a/release/conf/polaris-server.yaml b/release/conf/polaris-server.yaml index ccff8cd55..b4f879b96 100644 --- a/release/conf/polaris-server.yaml +++ b/release/conf/polaris-server.yaml @@ -423,6 +423,7 @@ config: # Maximum number of number of file characters contentMaxLength: 20000 # Cache configuration + cache: open: true resources: @@ -445,6 +446,7 @@ cache: - name: configGroup - name: faultDetectRule - name: serviceContract + - name: gray # - name: l5 # Load L5 data # Maintain configuration maintain: diff --git a/store/api.go b/store/api.go index 8aa0abf20..f9726c43b 100644 --- a/store/api.go +++ b/store/api.go @@ -47,6 +47,8 @@ type Store interface { ClientStore // AdminStore Maintain inteface AdminStore + // GrayStore mgr gray resource + GrayStore } // NamespaceStore Namespace storage interface @@ -66,6 +68,12 @@ type NamespaceStore interface { GetMoreNamespaces(mtime time.Time) ([]*model.Namespace, error) } +// GrayStore Gray storage interface +type GrayStore interface { + CreateGrayResourceTx(tx Tx, data *model.GrayResource) error + GetMoreGrayResouces(firstUpdate bool, mtime time.Time) ([]*model.GrayResource, error) +} + // Transaction Transaction interface, does not support multi-level concurrency operation, // currently only support a single price serial operation type Transaction interface { diff --git a/store/boltdb/config_file_release.go b/store/boltdb/config_file_release.go index 425fd130d..8f8284d5e 100644 --- a/store/boltdb/config_file_release.go +++ b/store/boltdb/config_file_release.go @@ -50,6 +50,7 @@ const ( FileReleaseFieldValid string = "Valid" FileReleaseFieldActive string = "Active" FileReleaseFieldMetadata string = "Metadata" + FileReleaseFieldType string = "Typ" ) var ( @@ -297,6 +298,7 @@ func (cfr *configFileReleaseStore) ActiveConfigFileReleaseTx(tx store.Tx, releas properties[FileReleaseFieldVersion] = maxVersion + 1 properties[FileReleaseFieldActive] = true properties[FileReleaseFieldModifyTime] = time.Now() + properties[FileReleaseFieldType] = uint32(release.Typ) return updateValue(dbTx, tblConfigFileRelease, release.ReleaseKey(), properties) } @@ -387,6 +389,7 @@ type ConfigFileRelease struct { ModifyTime time.Time ModifyBy string Content string + Typ uint32 } func (cfr *configFileReleaseStore) toModelData(data *ConfigFileRelease) *model.ConfigFileRelease { @@ -398,6 +401,7 @@ func (cfr *configFileReleaseStore) toModelData(data *ConfigFileRelease) *model.C Namespace: data.Namespace, Group: data.Group, FileName: data.FileName, + Typ: model.ReleaseType(data.Typ), }, Comment: data.Comment, Md5: data.Md5, @@ -436,5 +440,6 @@ func (cfr *configFileReleaseStore) toStoreData(data *model.ConfigFileRelease) *C ModifyTime: data.ModifyTime, ModifyBy: data.ModifyBy, Content: data.Content, + Typ: uint32(data.Typ), } } diff --git a/store/boltdb/default.go b/store/boltdb/default.go index c10eb4bfa..a5069fff7 100644 --- a/store/boltdb/default.go +++ b/store/boltdb/default.go @@ -88,6 +88,7 @@ type boltStore struct { *userStore *groupStore *strategyStore + *grayStore handler BoltHandler start bool @@ -312,7 +313,7 @@ func (m *boltStore) newStore() error { return err } m.clientStore = &clientStore{handler: m.handler} - + m.grayStore = &grayStore{handler: m.handler} m.newDiscoverModuleStore() m.newAuthModuleStore() m.newConfigModuleStore() diff --git a/store/boltdb/gray_resource.go b/store/boltdb/gray_resource.go new file mode 100644 index 000000000..191023217 --- /dev/null +++ b/store/boltdb/gray_resource.go @@ -0,0 +1,94 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 boltdb + +import ( + "time" + + bolt "go.etcd.io/bbolt" + "go.uber.org/zap" + + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/store" +) + +var _ store.GrayStore = (*grayStore)(nil) + +const ( + tblGrayResource string = "GrayResource" + + GrayResourceFieldModifyTime string = "ModifyTime" +) + +type grayStore struct { + handler BoltHandler +} + +func newGrayStore(handler BoltHandler) *configFileReleaseStore { + s := &configFileReleaseStore{handler: handler} + return s +} + +// CreateGrayResourceTx 新建灰度资源 +func (cfr *grayStore) CreateGrayResourceTx(proxyTx store.Tx, grayResource *model.GrayResource) error { + tx := proxyTx.GetDelegateTx().(*bolt.Tx) + + tN := time.Now() + grayResource.CreateTime = tN + grayResource.ModifyTime = tN + + err := saveValue(tx, tblGrayResource, grayResource.Name, grayResource) + if err != nil { + log.Error("[GrayResource] save info", zap.Error(err)) + return store.Error(err) + } + return nil +} + +// GetMoreGrayResouces Get the last update time more than a certain time point +func (cfr *grayStore) GetMoreGrayResouces(firstUpdate bool, + modifyTime time.Time) ([]*model.GrayResource, error) { + + if firstUpdate { + modifyTime = time.Time{} + } + + fields := []string{GrayResourceFieldModifyTime} + values, err := cfr.handler.LoadValuesByFilter(tblGrayResource, fields, &model.GrayResource{}, + func(m map[string]interface{}) bool { + saveMt, _ := m[GrayResourceFieldModifyTime].(time.Time) + return !saveMt.Before(modifyTime) + }) + + if err != nil { + return nil, err + } + + if len(values) == 0 { + return []*model.GrayResource{}, nil + } + + grayResources := make([]*model.GrayResource, 0, len(values)) + + for i := range values { + grayResource := values[i].(*model.GrayResource) + grayResources = append(grayResources, grayResource) + } + return grayResources, nil + +} diff --git a/store/mock/api_mock.go b/store/mock/api_mock.go index ed865ed41..2c620d0f1 100644 --- a/store/mock/api_mock.go +++ b/store/mock/api_mock.go @@ -2874,3 +2874,32 @@ func (mr *MockToolStoreMockRecorder) GetUnixSecond(maxWait interface{}) *gomock. mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnixSecond", reflect.TypeOf((*MockToolStore)(nil).GetUnixSecond), maxWait) } + +// CreateGrayResourceTx mocks base method. +func (m *MockStore) CreateGrayResourceTx(tx store.Tx, grayResource *model.GrayResource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGrayResourceTx", tx, grayResource) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGrayResourceTx indicates an expected call of CreategrayResourceTx. +func (mr *MockStoreMockRecorder) CreateGrayResourceTx(tx, grayResource interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGrayResourceTx", reflect.TypeOf((*MockStore)(nil).CreateGrayResourceTx), tx, grayResource) +} + +// GetMoreGrayResouces mocks base method. +func (m *MockStore) GetMoreGrayResouces(firstUpdate bool, modifyTime time.Time) ([]*model.GrayResource, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreGrayResouces", firstUpdate, modifyTime) + ret0, _ := ret[0].([]*model.GrayResource) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreGrayResouces indicates an expected call of GetMoreGrayResouces. +func (mr *MockStoreMockRecorder) GetMoreGrayResouces(firstUpdate, modifyTime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreGrayResouces", reflect.TypeOf((*MockStore)(nil).GetMoreGrayResouces), firstUpdate, modifyTime) +} \ No newline at end of file diff --git a/store/mysql/config_file_release.go b/store/mysql/config_file_release.go index 76a690efc..2bbf76656 100644 --- a/store/mysql/config_file_release.go +++ b/store/mysql/config_file_release.go @@ -178,13 +178,13 @@ func (cfr *configFileReleaseStore) GetConfigFileActiveReleaseTx(tx store.Tx, dbTx := tx.GetDelegateTx().(*BaseTx) querySql := cfr.baseQuerySql() + "WHERE namespace = ? AND `group` = ? AND " + - " file_name = ? AND active = 1 AND TYPE =? AND flag = 0 " + " file_name = ? AND active = 1 AND type =? AND flag = 0 " var ( rows *sql.Rows err error ) - rows, err = dbTx.Query(querySql, file.Namespace, file.Group, file.Name, model.ConfigeFileTypeFull) + rows, err = dbTx.Query(querySql, file.Namespace, file.Group, file.Name, model.ReleaseTypeFull) if err != nil { return nil, err } diff --git a/store/mysql/default.go b/store/mysql/default.go index 191f1682c..f01454589 100644 --- a/store/mysql/default.go +++ b/store/mysql/default.go @@ -72,6 +72,7 @@ type stableStore struct { *userStore *groupStore *strategyStore + *grayStore // 主数据库,可以进行读写 master *BaseDB @@ -278,6 +279,7 @@ func (s *stableStore) newStore() { s.userStore = &userStore{master: s.master, slave: s.slave} s.groupStore = &groupStore{master: s.master, slave: s.slave} s.strategyStore = &strategyStore{master: s.master, slave: s.slave} + s.grayStore = &grayStore{master: s.master, slave: s.slave} } func buildEtimeStr(enable bool) string { diff --git a/store/mysql/gray_resouce.go b/store/mysql/gray_resouce.go new file mode 100644 index 000000000..2edc20488 --- /dev/null +++ b/store/mysql/gray_resouce.go @@ -0,0 +1,108 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 sqldb + +import ( + "database/sql" + "time" + + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/store" +) + +type grayStore struct { + master *BaseDB + slave *BaseDB +} + +// CreateGrayResourceTx 创建灰度资源 +func (g *grayStore) CreateGrayResourceTx(tx store.Tx, data *model.GrayResource) error { + if tx == nil { + return ErrTxIsNil + } + dbTx := tx.GetDelegateTx().(*BaseTx) + s := "INSERT INTO gray_resource(name, match_rule, create_time, create_by , modify_time, modify_by) " + + " VALUES (?, ?, sysdate(), ? , sysdate(), ?) ON DUPLICATE KEY UPDATE " + + "match_rule = ?, create_time=sysdate(), create_by=? , modify_time=sysdate(), modify_by=?" + + args := []interface{}{ + data.Name, data.MatchRule, + data.CreateBy, data.ModifyBy, + data.MatchRule, data.CreateBy, data.ModifyBy, + } + if _, err := dbTx.Exec(s, args...); err != nil { + return store.Error(err) + } + return nil +} + +// GetMoreGrayResouces 获取最近更新的灰度资源, 此方法用于 cache 增量更新,需要注意 modifyTime 应为数据库时间戳 +func (g *grayStore) GetMoreGrayResouces(firstUpdate bool, + modifyTime time.Time) ([]*model.GrayResource, error) { + + if firstUpdate { + modifyTime = time.Time{} + } + + s := "SELECT name, match_rule, UNIX_TIMESTAMP(create_time), IFNULL(create_by, ''), " + + " UNIX_TIMESTAMP(modify_time), IFNULL(modify_by, '') FROM gray_resource WHERE modify_time > FROM_UNIXTIME(?)" + rows, err := g.slave.Query(s, timeToTimestamp(modifyTime)) + if err != nil { + return nil, err + } + grayResources, err := g.fetchGrayResourceRows(rows) + if err != nil { + return nil, err + } + return grayResources, nil +} + +func (g *grayStore) fetchGrayResourceRows(rows *sql.Rows) ([]*model.GrayResource, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + grayResources := make([]*model.GrayResource, 0, 32) + for rows.Next() { + var ctime, mtime int64 + grayResource := &model.GrayResource{} + if err := rows.Scan(&grayResource.Name, &grayResource.MatchRule, &ctime, + &grayResource.CreateBy, &mtime, &grayResource.ModifyBy); err != nil { + return nil, err + } + grayResource.CreateTime = time.Unix(ctime, 0) + grayResource.ModifyTime = time.Unix(mtime, 0) + grayResources = append(grayResources, grayResource) + } + + if err := rows.Err(); err != nil { + return nil, err + } + return grayResources, nil +} + +// DeleteGrayResource 删除灰度资源 +func (g *grayStore) DeleteGrayResource(tx store.Tx, data *model.GrayResource) error { + s := "DELETE FROM gray_resource WHERE name= ?" + _, err := g.master.Exec(s, data.Name) + if err != nil { + return store.Error(err) + } + return nil +} diff --git a/store/mysql/scripts/polaris_server.sql b/store/mysql/scripts/polaris_server.sql index 3c72fc0e3..34480baad 100644 --- a/store/mysql/scripts/polaris_server.sql +++ b/store/mysql/scripts/polaris_server.sql @@ -496,7 +496,7 @@ CREATE TABLE `config_file_release` `tags` TEXT COMMENT '文件标签', `active` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否处于使用中', `description` VARCHAR(512) DEFAULT NULL COMMENT '发布描述', - `type` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '文件类型:0:全量 1:灰度', + `type` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '文件类型:1:全量 2:灰度', PRIMARY KEY (`id`), UNIQUE KEY `uk_file` (`namespace`, `group`, `file_name`, `name`), KEY `idx_modify_time` (`modify_time`) @@ -917,31 +917,14 @@ CREATE TABLE service_contract_detail KEY (`contract_id`, `path`, `method`) ) ENGINE = InnoDB; -/* 灰度规则表 */ -CREATE TABLE `gray_rule` -( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', - `group` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属组', - `name` VARCHAR(128) NOT NULL COMMENT '规则名', - `match_rule` TEXT NOT NULL COMMENT '配置规则', - `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `create_by` VARCHAR(32) DEFAULT "" COMMENT '创建人', - `modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', - `modify_by` VARCHAR(32) DEFAULT "" COMMENT '最后更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tag` (`namespace`, `group`, `name`) -) ENGINE = InnoDB COMMENT = '灰度规则表'; - /* 灰度资源 */ CREATE TABLE `gray_resource` ( - `resource` VARCHAR(128) NOT NULL COMMENT '灰度资源', - `module` VARCHAR(64) NOT NULL COMMENT '所属的模块 config|circuitbreaker|routing|ratelimit', - `rule_id` BIGINT UNSIGNED NOT NULL COMMENT '灰度规则id', + `name` VARCHAR(128) NOT NULL COMMENT '灰度资源', + `match_rule` TEXT NOT NULL COMMENT '配置规则', `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_by` VARCHAR(32) DEFAULT "" COMMENT '创建人', `modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', `modify_by` VARCHAR(32) DEFAULT "" COMMENT '最后更新人', - PRIMARY KEY (`appid`), -) ENGINE = InnoDB COMMENT = '灰度资源表'; \ No newline at end of file + PRIMARY KEY (`name`) +) ENGINE = InnoDB COMMENT = '灰度资源表';