From dfa9741151733cc4430228ddfd5c794b5af35f26 Mon Sep 17 00:00:00 2001 From: liaochuntao Date: Tue, 19 Dec 2023 23:34:41 +0800 Subject: [PATCH] feat:xdsv3 support envoy odcds (#1304) --- .github/workflows/benchmark.yaml | 1 - .github/workflows/codecov.yaml | 4 +- .../workflows/integration-testing-mysql.yml | 2 - .github/workflows/integration-testing.yml | 1 - .golangci.yml | 2 + .licenserc.yaml | 2 + apiserver/apiserver.go | 10 +- apiserver/eurekaserver/access_test.go | 61 +- apiserver/eurekaserver/applications.go | 3 +- apiserver/eurekaserver/write_test.go | 8 + apiserver/grpcserver/config/client_access.go | 128 ++++- .../grpcserver/discover/v1/client_access.go | 14 +- apiserver/httpserver/config/client_access.go | 97 +++- apiserver/httpserver/config/console_access.go | 22 + apiserver/httpserver/config/server.go | 2 + .../httpserver/discover/v1/client_access.go | 14 +- apiserver/httpserver/discover/v1/server.go | 49 +- .../httpserver/discover/v2/console_access.go | 26 +- apiserver/httpserver/discover/v2/server.go | 4 +- .../httpserver/docs/config_server_apidoc.go | 16 + .../docs/naming_console_access_apidoc.go | 1 + apiserver/httpserver/server.go | 33 +- apiserver/httpserver/utils/handler.go | 6 +- apiserver/nacosserver/core/storage.go | 24 +- apiserver/nacosserver/logger/log.go | 5 + apiserver/nacosserver/model/constant.go | 8 + apiserver/nacosserver/model/error.go | 10 +- apiserver/nacosserver/v1/config/builder.go | 2 +- .../nacosserver/v1/config/config_file.go | 66 ++- apiserver/nacosserver/v1/config/watch.go | 29 +- apiserver/nacosserver/v1/discover/builder.go | 2 +- apiserver/nacosserver/v1/http/utils.go | 2 +- apiserver/nacosserver/v2/access.go | 21 +- .../nacosserver/v2/config/config_file.go | 62 +- apiserver/nacosserver/v2/config/watch.go | 8 +- apiserver/nacosserver/v2/discover/checker.go | 2 +- .../nacosserver/v2/discover/client_conn.go | 3 +- apiserver/nacosserver/v2/discover/instance.go | 57 ++ apiserver/nacosserver/v2/discover/server.go | 7 + .../nacosserver/v2/discover/subscribe.go | 48 +- apiserver/nacosserver/v2/option.go | 1 + apiserver/nacosserver/v2/pb/config_request.go | 5 +- apiserver/nacosserver/v2/pb/naming_request.go | 23 + apiserver/nacosserver/v2/pb/request.go | 31 +- .../nacosserver/v2/remote/client_conn.go | 7 +- apiserver/nacosserver/v2/remote/inflights.go | 9 +- apiserver/xdsserverv3/cache/cache.go | 26 +- apiserver/xdsserverv3/cache/linear.go | 260 +++++---- apiserver/xdsserverv3/cache/response.go | 117 +++- apiserver/xdsserverv3/cds.go | 2 - apiserver/xdsserverv3/debug.go | 30 +- apiserver/xdsserverv3/eds.go | 42 +- apiserver/xdsserverv3/generate.go | 328 ++++++----- apiserver/xdsserverv3/hds.go | 11 +- apiserver/xdsserverv3/hook.go | 16 + apiserver/xdsserverv3/lds.go | 4 +- apiserver/xdsserverv3/rds.go | 115 ++-- apiserver/xdsserverv3/resource/api.go | 2 + apiserver/xdsserverv3/resource/help.go | 113 +++- apiserver/xdsserverv3/resource/model.go | 14 +- apiserver/xdsserverv3/resource/mtls.go | 1 - apiserver/xdsserverv3/resource/node.go | 30 + apiserver/xdsserverv3/server.go | 52 +- auth/api.go | 8 +- auth/auth.go | 7 +- auth/defaultauth/auth_checker.go | 12 +- auth/defaultauth/auth_checker_test.go | 54 +- auth/defaultauth/group_test.go | 23 +- auth/defaultauth/server.go | 21 +- auth/defaultauth/strategy_authability.go | 5 +- auth/defaultauth/strategy_test.go | 15 +- auth/defaultauth/user_authability.go | 5 +- auth/defaultauth/user_test.go | 64 ++- auth/testexport.go | 4 +- bootstrap/server.go | 9 +- cache/api/types.go | 53 +- cache/cache.go | 4 +- cache/cache_test.go | 100 ---- cache/config.go | 6 - cache/config/config_file.go | 31 +- cache/config/config_group.go | 40 +- cache/default.go | 2 +- cache/gray/gray.go | 80 ++- cache/gray/log.go | 24 + cache/gray/match_test.go | 60 ++ cache/mock/cache_mock.go | 543 ++++++++++++++++++ cache/service/instance.go | 167 +----- cache/service/router_rule_query.go | 29 +- cache/test_export.go | 8 +- common/api/v1/codeinfo.go | 2 + common/api/v1/naming_response.go | 23 + common/batchjob/batch.go | 281 --------- common/batchjob/batch_test.go | 234 -------- common/batchjob/future.go | 125 ---- common/metrics/types.go | 30 + common/model/auth.go | 14 + common/model/client.go | 11 + common/model/config_file.go | 46 +- common/model/gray.go | 3 +- common/model/http.go | 1 + common/model/instance.go | 170 ++++++ common/model/metadata.go | 16 + common/utils/collection.go | 4 +- common/utils/common.go | 15 + common/utils/config_file.go | 8 +- common/utils/const.go | 9 - common/utils/match.go | 91 ++- common/utils/match_test.go | 339 ++++++++++- config/api.go | 25 +- config/client.go | 152 ++--- config/client_test.go | 21 +- config/common.go | 49 ++ config/config_chain.go | 31 +- config/config_file.go | 5 +- config/config_file_release.go | 330 ++++++++--- config/config_file_release_test.go | 191 +++++- config/config_file_test.go | 29 +- .../auth}/client_authibility.go | 75 ++- .../auth}/config_file_authibility.go | 36 +- .../auth}/config_file_group_authibility.go | 20 +- .../auth}/config_file_release_authibility.go | 45 +- ...config_file_release_history_authibility.go | 6 +- .../auth}/config_file_template_authibility.go | 14 +- config/interceptor/auth/log.go | 25 + .../auth}/resource_listener.go | 23 +- .../auth}/server_authability.go | 51 +- .../interceptor/register.go | 43 +- config/options.go | 9 +- config/server.go | 102 ++-- config/test_export.go | 23 +- config/watcher.go | 74 ++- go.mod | 5 +- go.sum | 14 +- import-format.sh | 4 +- namespace/default.go | 6 +- namespace/test_export.go | 5 + plugin.go | 2 + plugin/cmdb/memory/memory.go | 4 +- plugin/healthchecker.go | 4 +- plugin/healthchecker/leader/checker_leader.go | 86 +-- .../leader/checker_leader_test.go | 8 +- plugin/healthchecker/leader/config.go | 15 +- plugin/healthchecker/leader/peer.go | 40 +- plugin/healthchecker/leader/peer_test.go | 79 +-- plugin/healthchecker/memory/checker_memory.go | 5 +- plugin/healthchecker/redis/checker_redis.go | 5 +- plugin/plugin.go | 6 - plugin/statis/base/apicall.go | 12 +- plugin/statis/logger/log.go | 2 +- plugin/statis/logger/statis.go | 21 +- release/build_docker.sh | 4 +- .../helm/templates/config-polaris-server.yaml | 31 +- release/cluster/helm/values.yaml | 11 +- .../kubernetes/02-polaris-server-config.yaml | 55 +- release/conf/polaris-server.yaml | 27 +- release/standalone/docker-compose/README.md | 3 - .../docker-compose/docker-compose.yaml | 15 - .../docker-compose/server/polaris-server.yaml | 108 +--- release/tool/include | 2 +- service/api.go | 27 +- service/batch/future.go | 5 +- service/batch/instance.go | 1 + service/client_check_test.go | 5 +- service/client_test.go | 20 +- service/default.go | 49 +- service/default_test.go | 9 +- .../circuitbreaker_config_authability.go | 26 +- .../auth}/circuitbreaker_rule_authability.go | 12 +- .../auth}/client_v1_authability.go | 24 +- .../auth}/faultdetect_config_authability.go | 10 +- .../auth}/instance_authability.go | 18 +- .../auth}/l5_service_authability.go | 6 +- service/interceptor/auth/log.go | 27 + .../auth}/ratelimit_config_authability.go | 12 +- .../{ => interceptor/auth}/resource_listen.go | 32 +- .../auth}/routing_config_v1_authability.go | 10 +- .../auth}/routing_config_v2_authability.go | 12 +- .../auth}/server_authability.go | 69 +-- .../auth}/service_alias_authability.go | 10 +- .../auth}/service_authability.go | 20 +- .../auth}/service_contract_authability.go | 16 +- service/interceptor/register.go | 42 ++ service/l5_service_test.go | 2 +- service/options.go | 34 +- service/ratelimit_config_test.go | 1 + service/server.go | 5 +- service/service_contract.go | 2 +- service/service_test.go | 5 +- service/test_export.go | 44 +- store/api.go | 4 + store/boltdb/circuitbreaker_rule.go | 21 +- store/boltdb/config_file_release.go | 68 ++- store/boltdb/faultdetector.go | 3 +- store/boltdb/gray_resource.go | 19 +- store/boltdb/instance.go | 3 +- store/boltdb/ratelimit.go | 3 +- store/boltdb/routing.go | 3 +- store/boltdb/service.go | 10 +- store/config_file_api.go | 2 + store/mock/api_mock.go | 153 ++++- store/mysql/circuitbreaker_config_v2.go | 8 +- store/mysql/config_file_release.go | 58 +- store/mysql/gray_resouce.go | 24 +- store/mysql/ratelimit_config.go | 1 + store/mysql/scripts/delta/v1_17_3-v1_18_0.sql | 18 +- store/mysql/scripts/polaris_server.sql | 40 +- store/mysql/service_contract.go | 4 +- test/data/service_test.yaml | 16 +- test/data/service_test_sqldb.yaml | 32 +- test/outlier/backend/main.go | 6 +- test/outlier/frontend/main.go | 2 +- test/suit/test_suit.go | 69 ++- version | 2 +- 213 files changed, 5095 insertions(+), 2884 deletions(-) delete mode 100644 cache/cache_test.go create mode 100644 cache/gray/log.go create mode 100644 cache/gray/match_test.go delete mode 100644 common/batchjob/batch.go delete mode 100644 common/batchjob/batch_test.go delete mode 100644 common/batchjob/future.go create mode 100644 common/model/instance.go rename config/{ => interceptor/auth}/client_authibility.go (67%) rename config/{ => interceptor/auth}/config_file_authibility.go (80%) rename config/{ => interceptor/auth}/config_file_group_authibility.go (85%) rename config/{ => interceptor/auth}/config_file_release_authibility.go (73%) rename config/{ => interceptor/auth}/config_file_release_history_authibility.go (89%) rename config/{ => interceptor/auth}/config_file_template_authibility.go (84%) create mode 100644 config/interceptor/auth/log.go rename config/{ => interceptor/auth}/resource_listener.go (75%) rename config/{ => interceptor/auth}/server_authability.go (86%) rename common/batchjob/config.go => config/interceptor/register.go (50%) rename service/{ => interceptor/auth}/circuitbreaker_config_authability.go (80%) rename service/{ => interceptor/auth}/circuitbreaker_rule_authability.go (92%) rename service/{ => interceptor/auth}/client_v1_authability.go (91%) rename service/{ => interceptor/auth}/faultdetect_config_authability.go (92%) rename service/{ => interceptor/auth}/instance_authability.go (91%) rename service/{ => interceptor/auth}/l5_service_authability.go (89%) create mode 100644 service/interceptor/auth/log.go rename service/{ => interceptor/auth}/ratelimit_config_authability.go (93%) rename service/{ => interceptor/auth}/resource_listen.go (72%) rename service/{ => interceptor/auth}/routing_config_v1_authability.go (93%) rename service/{ => interceptor/auth}/routing_config_v2_authability.go (91%) rename service/{ => interceptor/auth}/server_authability.go (89%) rename service/{ => interceptor/auth}/service_alias_authability.go (94%) rename service/{ => interceptor/auth}/service_authability.go (93%) rename service/{ => interceptor/auth}/service_contract_authability.go (92%) create mode 100644 service/interceptor/register.go diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index c140b8a5b..19fa7b0ad 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -100,7 +100,6 @@ jobs: sleep 120s ls -alR - cat ./log/stdout 2>&1 cd .. ls -lstrh diff --git a/.github/workflows/codecov.yaml b/.github/workflows/codecov.yaml index 9d8ab194c..282658436 100644 --- a/.github/workflows/codecov.yaml +++ b/.github/workflows/codecov.yaml @@ -72,8 +72,8 @@ jobs: mysql -e "ALTER USER '${{ env.MYSQL_DB_USER }}'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" -u${{ env.MYSQL_DB_USER }} -p${{ env.MYSQL_DB_PWD }} # Execute vert check - - name: Vert check - run: bash vert.sh -install && bash vert.sh + # - name: Vert check + # run: bash vert.sh -install && bash vert.sh - name: Standalone Test env: diff --git a/.github/workflows/integration-testing-mysql.yml b/.github/workflows/integration-testing-mysql.yml index aa1fd785c..8fb18b814 100644 --- a/.github/workflows/integration-testing-mysql.yml +++ b/.github/workflows/integration-testing-mysql.yml @@ -145,8 +145,6 @@ jobs: sleep 120s ls -alR - cat ./log/stdout 2>&1 - cd .. ls -lstrh # 先测试普通的集成测试 diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index a331802cb..754204e67 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -100,7 +100,6 @@ jobs: sleep 120s ls -alR - cat ./log/stdout 2>&1 cd .. ls -lstrh diff --git a/.golangci.yml b/.golangci.yml index d18233716..fe88535f8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,6 +38,7 @@ run: - pkg/model/pb - .*~ - test + - "apiserver/nacosserver/v2/pb" # Which files to skip: they will be analyzed, but issues from them won't be reported. # Default value is empty list, @@ -51,6 +52,7 @@ run: - ".*_test\\.go$" - ".*\\.yaml$" - ".*\\.yml$" + - "apiserver/xdsserverv3/cache/linear.go" # Main linters configurations. diff --git a/.licenserc.yaml b/.licenserc.yaml index 98ff06bd6..c6f3dbeb1 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -58,6 +58,7 @@ header: # `header` section is configurations for source codes license header. - "deploy" - "release" - "test/data/xds" + - "apiserver/nacosserver/v2/pb" # single file - "LICENSE" @@ -74,6 +75,7 @@ header: # `header` section is configurations for source codes license header. - "**/*.md" - "**/go.mod" - "**/go.sum" + - "apiserver/xdsserverv3/cache/linear.go" comment: on-failure # on what condition license-eye will comment on the pull request, `on-failure`, `always`, `never`. # license-location-threshold specifies the index threshold where the license header can be located, diff --git a/apiserver/apiserver.go b/apiserver/apiserver.go index 2e0611c16..1388ae906 100644 --- a/apiserver/apiserver.go +++ b/apiserver/apiserver.go @@ -20,7 +20,8 @@ package apiserver import ( "context" "fmt" - "net/http" + + "github.com/polarismesh/polaris/common/model" ) const ( @@ -61,12 +62,7 @@ type Apiserver interface { type EnrichApiserver interface { Apiserver - DebugHandlers() []DebugHandler -} - -type DebugHandler struct { - Path string - Handler http.HandlerFunc + DebugHandlers() []model.DebugHandler } var ( diff --git a/apiserver/eurekaserver/access_test.go b/apiserver/eurekaserver/access_test.go index 201c8450d..24964f2a3 100644 --- a/apiserver/eurekaserver/access_test.go +++ b/apiserver/eurekaserver/access_test.go @@ -53,6 +53,8 @@ func createEurekaServerForTest( if err != nil { return nil, err } + // 注册实例信息修改 chain 数据 + eurekaSrv.registerInstanceChain() return eurekaSrv, nil } @@ -88,7 +90,7 @@ func batchBuildInstances(appId string, host string, port int, lease *LeaseInfo, func batchCreateInstance(t *testing.T, eurekaSvr *EurekaServer, namespace string, instances []*InstanceInfo) { for _, instance := range instances { code := eurekaSvr.registerInstances(context.Background(), namespace, instance.AppName, instance, false) - assert.Equal(t, api.ExecuteSuccess, code) + assert.Equal(t, api.ExecuteSuccess, code, fmt.Sprintf("%+v", code)) } } @@ -223,36 +225,34 @@ func Test_EurekaWrite(t *testing.T) { mockIns := genMockEurekaInstance() - t.Run("RegisterInstance", func(t *testing.T) { - // pretty output must be created and written explicitly - output, err := xml.MarshalIndent(mockIns, " ", " ") - assert.NoError(t, err) - - var body bytes.Buffer - _, err = body.Write([]byte(xml.Header)) - assert.NoError(t, err) - _, err = body.Write(output) - assert.NoError(t, err) - - mockReq := httptest.NewRequest("", fmt.Sprintf("http://127.0.0.1:8761/eureka/v2/apps/%s", mockIns.AppName), &body) - mockReq.Header.Add(restful.HEADER_Accept, restful.MIME_XML) - mockReq.Header.Add(restful.HEADER_ContentType, restful.MIME_XML) - mockRsp := newMockResponseWriter() - - restfulReq := restful.NewRequest(mockReq) - injectRestfulReqPathParameters(t, restfulReq, map[string]string{ - ParamAppId: mockIns.AppName, - }) - // 这里是异步注册 - eurekaSrv.RegisterApplication(restfulReq, restful.NewResponse(mockRsp)) - assert.Equal(t, http.StatusNoContent, mockRsp.statusCode) - assert.Equal(t, restfulReq.Attribute(statusCodeHeader), uint32(apimodel.Code_ExecuteSuccess)) - - time.Sleep(5 * time.Second) - saveIns, err := eurekaSrv.originDiscoverSvr.Cache().GetStore().GetInstance(mockIns.InstanceId) - assert.NoError(t, err) - assert.NotNil(t, saveIns) + // pretty output must be created and written explicitly + output, err := xml.MarshalIndent(mockIns, " ", " ") + assert.NoError(t, err) + + var body bytes.Buffer + _, err = body.Write([]byte(xml.Header)) + assert.NoError(t, err) + _, err = body.Write(output) + assert.NoError(t, err) + + mockReq := httptest.NewRequest("", fmt.Sprintf("http://127.0.0.1:8761/eureka/v2/apps/%s", mockIns.AppName), &body) + mockReq.Header.Add(restful.HEADER_Accept, restful.MIME_XML) + mockReq.Header.Add(restful.HEADER_ContentType, restful.MIME_XML) + mockRsp := newMockResponseWriter() + + restfulReq := restful.NewRequest(mockReq) + injectRestfulReqPathParameters(t, restfulReq, map[string]string{ + ParamAppId: mockIns.AppName, }) + // 这里是异步注册 + eurekaSrv.RegisterApplication(restfulReq, restful.NewResponse(mockRsp)) + assert.Equal(t, http.StatusNoContent, mockRsp.statusCode) + assert.Equal(t, restfulReq.Attribute(statusCodeHeader), uint32(apimodel.Code_ExecuteSuccess)) + + time.Sleep(5 * time.Second) + saveIns, err := eurekaSrv.originDiscoverSvr.Cache().GetStore().GetInstance(mockIns.InstanceId) + assert.NoError(t, err) + assert.NotNil(t, saveIns) t.Run("UpdateStatus", func(t *testing.T) { t.Run("StatusUnknown", func(t *testing.T) { @@ -274,6 +274,7 @@ func Test_EurekaWrite(t *testing.T) { // saveIns, err := discoverSuit.Storage.GetInstance(mockIns.InstanceId) assert.NoError(t, err) + assert.NotNil(t, saveIns) assert.False(t, saveIns.Isolate()) }) diff --git a/apiserver/eurekaserver/applications.go b/apiserver/eurekaserver/applications.go index 4aff063cb..7d6b0b940 100644 --- a/apiserver/eurekaserver/applications.go +++ b/apiserver/eurekaserver/applications.go @@ -393,9 +393,8 @@ func buildDataCenterInfo() *DataCenterInfo { Clazz: DefaultDciClazz, Name: customDciName, } - } else { - return DefaultDataCenterInfo } + return DefaultDataCenterInfo } func buildLocationInfo(instanceInfo *InstanceInfo, instance *apiservice.Instance) { diff --git a/apiserver/eurekaserver/write_test.go b/apiserver/eurekaserver/write_test.go index fa9b72ea9..67e1a1127 100644 --- a/apiserver/eurekaserver/write_test.go +++ b/apiserver/eurekaserver/write_test.go @@ -32,6 +32,7 @@ import ( "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/service" "github.com/polarismesh/polaris/store" "github.com/polarismesh/polaris/store/mock" testsuit "github.com/polarismesh/polaris/test/suit" @@ -120,10 +121,14 @@ func TestEurekaServer_renew(t *testing.T) { }, }, nil) + mockStore.EXPECT().GetMoreClients(gomock.Any(), gomock.Any()).Return(map[string]*model.Client{}, nil).AnyTimes() + mockStore.EXPECT().GetMoreGrayResouces(gomock.Any(), gomock.Any()).Return([]*model.GrayResource{}, nil).AnyTimes() mockStore.EXPECT().GetInstancesCountTx(gomock.Any()).AnyTimes().Return(uint32(1), nil) mockStore.EXPECT().GetUnixSecond(gomock.Any()).AnyTimes().Return(time.Now().Unix(), nil) mockStore.EXPECT().GetServicesCount().Return(uint32(1), nil).AnyTimes() mockStore.EXPECT().StartLeaderElection(gomock.Any()).AnyTimes() + mockStore.EXPECT().GetMoreServiceContracts(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockStore.EXPECT().GetMoreNamespaces(gomock.Any()).Return(nil, nil).AnyTimes() mockStore.EXPECT().Destroy().Return(nil) mockStore.EXPECT().Initialize(gomock.Any()).Return(nil).AnyTimes() mockStore.EXPECT().Name().Return("eureka_store_test").AnyTimes() @@ -135,7 +140,10 @@ func TestEurekaServer_renew(t *testing.T) { return mockStore }) eurekaSuit.Initialize(func(conf *testsuit.TestConfig) { + conf.DisableAuth = true conf.Cache = cache.Config{} + conf.DisableConfig = true + conf.ServiceCacheEntries = service.GetRegisterCaches() store.TestInjectConfig(store.Config{ Name: "eureka_store_test", }) diff --git a/apiserver/grpcserver/config/client_access.go b/apiserver/grpcserver/config/client_access.go index 6279d02e3..f538b496c 100644 --- a/apiserver/grpcserver/config/client_access.go +++ b/apiserver/grpcserver/config/client_access.go @@ -20,33 +20,48 @@ package config import ( "context" "fmt" + "io" + "strconv" 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" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/wrapperspb" + api "github.com/polarismesh/polaris/common/api/v1" + commonlog "github.com/polarismesh/polaris/common/log" "github.com/polarismesh/polaris/common/metrics" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/plugin" ) +var ( + accesslog = commonlog.GetScopeOrDefaultByName(commonlog.APIServerLoggerName) +) + // GetConfigFile 拉取配置 func (g *ConfigGRPCServer) GetConfigFile(ctx context.Context, req *apiconfig.ClientConfigFileInfo) (*apiconfig.ConfigClientResponse, error) { ctx = utils.ConvertGRPCContext(ctx) startTime := commontime.CurrentMillisecond() + var ret *apiconfig.ConfigClientResponse defer func() { plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: metrics.ActionGetConfigFile, ClientIP: utils.ParseClientAddress(ctx), Namespace: req.GetNamespace().GetValue(), - Resource: fmt.Sprintf("CONFIG_FILE:%s|%s|%d", req.GetGroup().GetValue(), - req.GetFileName().GetValue(), req.GetVersion().GetValue()), + Resource: metrics.ResourceOfConfigFile(req.GetGroup().GetValue(), req.GetFileName().GetValue()), Timestamp: startTime, CostTime: commontime.CurrentMillisecond() - startTime, + Revision: strconv.FormatUint(ret.GetConfigFile().GetVersion().GetValue(), 10), + Success: ret.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), }) }() - response := g.configServer.GetConfigFileForClient(ctx, req) - return response, nil + ret = g.configServer.GetConfigFileWithCache(ctx, req) + return ret, nil } // CreateConfigFile 创建或更新配置 @@ -90,17 +105,116 @@ func (g *ConfigGRPCServer) GetConfigFileMetadataList(ctx context.Context, req *apiconfig.ConfigFileGroupRequest) (*apiconfig.ConfigClientListResponse, error) { startTime := commontime.CurrentMillisecond() + var ret *apiconfig.ConfigClientListResponse defer func() { plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: metrics.ActionListConfigFiles, ClientIP: utils.ParseClientAddress(ctx), Namespace: req.GetConfigFileGroup().GetNamespace().GetValue(), - Resource: fmt.Sprintf("CONFIG_FILE_LIST:%s|%s", req.GetConfigFileGroup().GetName().GetValue(), - req.GetRevision().GetValue()), + Resource: metrics.ResourceOfConfigFileList(req.GetConfigFileGroup().GetName().GetValue()), Timestamp: startTime, CostTime: commontime.CurrentMillisecond() - startTime, + Revision: ret.GetRevision().GetValue(), + Success: ret.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), }) }() ctx = utils.ConvertGRPCContext(ctx) - return g.configServer.GetConfigFileNamesWithCache(ctx, req), nil + ret = g.configServer.GetConfigFileNamesWithCache(ctx, req) + return ret, nil +} + +func (g *ConfigGRPCServer) Discover(svr apiconfig.PolarisConfigGRPC_DiscoverServer) error { + ctx := utils.ConvertGRPCContext(svr.Context()) + clientIP, _ := ctx.Value(utils.StringContext("client-ip")).(string) + clientAddress, _ := ctx.Value(utils.StringContext("client-address")).(string) + requestID, _ := ctx.Value(utils.StringContext("request-id")).(string) + userAgent, _ := ctx.Value(utils.StringContext("user-agent")).(string) + method, _ := grpc.MethodFromServerStream(svr) + + for { + in, err := svr.Recv() + if err != nil { + if io.EOF == err { + return nil + } + return err + } + + msg := fmt.Sprintf("receive grpc discover request: %s", in.String()) + accesslog.Info(msg, + zap.String("type", apiconfig.ConfigDiscoverRequest_ConfigDiscoverRequestType_name[int32(in.Type)]), + zap.String("client-address", clientAddress), + zap.String("user-agent", userAgent), + utils.ZapRequestID(requestID), + ) + + // 是否允许访问 + if ok := g.allowAccess(method); !ok { + resp := api.NewConfigDiscoverResponse(apimodel.Code_ClientAPINotOpen) + if sendErr := svr.Send(resp); sendErr != nil { + return sendErr + } + continue + } + + // stream模式,需要对每个包进行检测 + if code := g.enterRateLimit(clientIP, method); code != uint32(apimodel.Code_ExecuteSuccess) { + resp := api.NewConfigDiscoverResponse(apimodel.Code(code)) + if err = svr.Send(resp); err != nil { + return err + } + continue + } + + var out *apiconfig.ConfigDiscoverResponse + var action string + startTime := commontime.CurrentMillisecond() + defer func() { + plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: action, + ClientIP: utils.ParseClientAddress(ctx), + Namespace: in.GetConfigFile().GetNamespace().GetValue(), + Resource: metrics.ResourceOfConfigFile(in.GetConfigFile().GetGroup().GetValue(), in.GetConfigFile().GetFileName().GetValue()), + Timestamp: startTime, + CostTime: commontime.CurrentMillisecond() - startTime, + Revision: out.GetRevision(), + Success: out.GetCode() > uint32(apimodel.Code_DataNoChange), + }) + }() + + switch in.Type { + case apiconfig.ConfigDiscoverRequest_CONFIG_FILE: + action = metrics.ActionGetConfigFile + ret := g.configServer.GetConfigFileWithCache(ctx, &apiconfig.ClientConfigFileInfo{}) + out = api.NewConfigDiscoverResponse(apimodel.Code(ret.GetCode().GetValue())) + out.ConfigFile = ret.GetConfigFile() + out.Type = apiconfig.ConfigDiscoverResponse_CONFIG_FILE + out.Revision = strconv.Itoa(int(out.GetConfigFile().GetVersion().GetValue())) + case apiconfig.ConfigDiscoverRequest_CONFIG_FILE_Names: + action = metrics.ActionListConfigFiles + ret := g.configServer.GetConfigFileNamesWithCache(ctx, &apiconfig.ConfigFileGroupRequest{ + Revision: wrapperspb.String(in.GetRevision()), + ConfigFileGroup: &apiconfig.ConfigFileGroup{ + Namespace: in.GetConfigFile().GetNamespace(), + Name: in.GetConfigFile().GetGroup(), + }, + }) + out = api.NewConfigDiscoverResponse(apimodel.Code(ret.GetCode().GetValue())) + out.ConfigFileNames = ret.GetConfigFileInfos() + out.Type = apiconfig.ConfigDiscoverResponse_CONFIG_FILE_Names + out.Revision = ret.GetRevision().GetValue() + case apiconfig.ConfigDiscoverRequest_CONFIG_FILE_GROUPS: + action = metrics.ActionListConfigGroups + req := in.GetConfigFile() + req.Md5 = wrapperspb.String(in.GetRevision()) + out = g.configServer.GetConfigGroupsWithCache(ctx, req) + default: + out = api.NewConfigDiscoverResponse(apimodel.Code_InvalidDiscoverResource) + } + + if err := svr.Send(out); err != nil { + return err + } + } } diff --git a/apiserver/grpcserver/discover/v1/client_access.go b/apiserver/grpcserver/discover/v1/client_access.go index f77ea2f88..fb4cf3979 100644 --- a/apiserver/grpcserver/discover/v1/client_access.go +++ b/apiserver/grpcserver/discover/v1/client_access.go @@ -128,30 +128,40 @@ func (g *DiscoverServer) Discover(server apiservice.PolarisGRPC_DiscoverServer) continue } + var out *apiservice.DiscoverResponse + var action string startTime := commontime.CurrentMillisecond() defer func() { plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: action, ClientIP: utils.ParseClientAddress(ctx), Namespace: in.GetService().GetNamespace().GetValue(), - Resource: in.Type.String() + ":" + in.GetService().GetName().GetValue(), + Resource: in.GetType().String() + ":" + in.GetService().GetName().GetValue(), Timestamp: startTime, CostTime: commontime.CurrentMillisecond() - startTime, + Revision: out.GetService().GetRevision().GetValue(), + Success: out.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), }) }() - var out *apiservice.DiscoverResponse switch in.Type { case apiservice.DiscoverRequest_INSTANCE: + action = metrics.ActionDiscoverInstance out = g.namingServer.ServiceInstancesCache(ctx, &apiservice.DiscoverFilter{}, in.Service) case apiservice.DiscoverRequest_ROUTING: + action = metrics.ActionDiscoverRouterRule out = g.namingServer.GetRoutingConfigWithCache(ctx, in.Service) case apiservice.DiscoverRequest_RATE_LIMIT: + action = metrics.ActionDiscoverRateLimit out = g.namingServer.GetRateLimitWithCache(ctx, in.Service) case apiservice.DiscoverRequest_CIRCUIT_BREAKER: + action = metrics.ActionDiscoverCircuitBreaker out = g.namingServer.GetCircuitBreakerWithCache(ctx, in.Service) case apiservice.DiscoverRequest_SERVICES: + action = metrics.ActionDiscoverServices out = g.namingServer.GetServiceWithCache(ctx, in.Service) case apiservice.DiscoverRequest_FAULT_DETECTOR: + action = metrics.ActionDiscoverFaultDetect out = g.namingServer.GetFaultDetectWithCache(ctx, in.Service) default: out = api.NewDiscoverRoutingResponse(apimodel.Code_InvalidDiscoverResource, in.Service) diff --git a/apiserver/httpserver/config/client_access.go b/apiserver/httpserver/config/client_access.go index 232013745..7de65ebc3 100644 --- a/apiserver/httpserver/config/client_access.go +++ b/apiserver/httpserver/config/client_access.go @@ -18,8 +18,8 @@ package config import ( - "fmt" "strconv" + "strings" "github.com/emicklei/go-restful/v3" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" @@ -46,23 +46,38 @@ func (h *HTTPServer) ClientGetConfigFile(req *restful.Request, rsp *restful.Resp Group: &wrapperspb.StringValue{Value: handler.Request.QueryParameter("group")}, FileName: &wrapperspb.StringValue{Value: handler.Request.QueryParameter("fileName")}, Version: &wrapperspb.UInt64Value{Value: version}, + Tags: func() []*apiconfig.ConfigFileTag { + tags := handler.Request.QueryParameters("tags") + ret := make([]*apiconfig.ConfigFileTag, 0, len(tags)) + for i := range tags { + kv := strings.Split(tags[i], "=") + ret = append(ret, &apiconfig.ConfigFileTag{ + Key: &wrapperspb.StringValue{Value: strings.TrimSpace(kv[0])}, + Value: &wrapperspb.StringValue{Value: strings.TrimSpace(kv[1])}, + }) + } + return ret + }(), } ctx := handler.ParseHeaderContext() startTime := commontime.CurrentMillisecond() + var ret *apiconfig.ConfigClientResponse defer func() { plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: metrics.ActionGetConfigFile, ClientIP: utils.ParseClientAddress(ctx), Namespace: configFile.GetNamespace().GetValue(), - Resource: fmt.Sprintf("CONFIG_FILE:%s|%s|%d", configFile.GetGroup().GetValue(), - configFile.GetFileName().GetValue(), version), + Resource: metrics.ResourceOfConfigFile(configFile.GetGroup().GetValue(), configFile.GetFileName().GetValue()), Timestamp: startTime, CostTime: commontime.CurrentMillisecond() - startTime, + Revision: strconv.FormatUint(ret.GetConfigFile().GetVersion().GetValue(), 10), + Success: ret.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), }) }() - response := h.configServer.GetConfigFileForClient(ctx, configFile) - handler.WriteHeaderAndProto(response) + ret = h.configServer.GetConfigFileWithCache(ctx, configFile) + handler.WriteHeaderAndProto(ret) } func (h *HTTPServer) ClientWatchConfigFile(req *restful.Request, rsp *restful.Response) { @@ -101,18 +116,84 @@ func (h *HTTPServer) GetConfigFileMetadataList(req *restful.Request, rsp *restfu return } + var out *apiconfig.ConfigClientListResponse startTime := commontime.CurrentMillisecond() defer func() { plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: metrics.ActionListConfigFiles, ClientIP: utils.ParseClientAddress(ctx), Namespace: in.GetConfigFileGroup().GetNamespace().GetValue(), - Resource: fmt.Sprintf("CONFIG_FILE_LIST:%s|%s", in.GetConfigFileGroup().GetName().GetValue(), - in.GetRevision().GetValue()), + Resource: metrics.ResourceOfConfigFileList(in.GetConfigFileGroup().GetName().GetValue()), Timestamp: startTime, CostTime: commontime.CurrentMillisecond() - startTime, + Revision: out.GetRevision().GetValue(), + Success: out.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), }) }() - out := h.configServer.GetConfigFileNamesWithCache(ctx, in) + out = h.configServer.GetConfigFileNamesWithCache(ctx, in) handler.WriteHeaderAndProto(out) } + +// Discover 统一发现接口 +func (h *HTTPServer) Discover(req *restful.Request, rsp *restful.Response) { + handler := &httpcommon.Handler{ + Request: req, + Response: rsp, + } + + in := &apiconfig.ConfigDiscoverRequest{} + ctx, err := handler.Parse(in) + if err != nil { + handler.WriteHeaderAndProto(api.NewResponseWithMsg(apimodel.Code_ParseException, err.Error())) + return + } + + var out *apiconfig.ConfigDiscoverResponse + var action string + startTime := commontime.CurrentMillisecond() + defer func() { + plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: action, + ClientIP: utils.ParseClientAddress(ctx), + Namespace: in.GetConfigFile().GetNamespace().GetValue(), + Resource: metrics.ResourceOfConfigFile(in.GetConfigFile().GetGroup().GetValue(), in.GetConfigFile().GetFileName().GetValue()), + Timestamp: startTime, + CostTime: commontime.CurrentMillisecond() - startTime, + Revision: out.GetRevision(), + Success: out.GetCode() > uint32(apimodel.Code_DataNoChange), + }) + }() + + switch in.Type { + case apiconfig.ConfigDiscoverRequest_CONFIG_FILE: + action = metrics.ActionGetConfigFile + ret := h.configServer.GetConfigFileWithCache(ctx, &apiconfig.ClientConfigFileInfo{}) + out = api.NewConfigDiscoverResponse(apimodel.Code(ret.GetCode().GetValue())) + out.ConfigFile = ret.GetConfigFile() + out.Type = apiconfig.ConfigDiscoverResponse_CONFIG_FILE + out.Revision = strconv.Itoa(int(out.GetConfigFile().GetVersion().GetValue())) + case apiconfig.ConfigDiscoverRequest_CONFIG_FILE_Names: + action = metrics.ActionListConfigFiles + ret := h.configServer.GetConfigFileNamesWithCache(ctx, &apiconfig.ConfigFileGroupRequest{ + Revision: wrapperspb.String(in.GetRevision()), + ConfigFileGroup: &apiconfig.ConfigFileGroup{ + Namespace: in.GetConfigFile().GetNamespace(), + Name: in.GetConfigFile().GetGroup(), + }, + }) + out = api.NewConfigDiscoverResponse(apimodel.Code(ret.GetCode().GetValue())) + out.ConfigFileNames = ret.GetConfigFileInfos() + out.Type = apiconfig.ConfigDiscoverResponse_CONFIG_FILE_Names + out.Revision = ret.GetRevision().GetValue() + case apiconfig.ConfigDiscoverRequest_CONFIG_FILE_GROUPS: + action = metrics.ActionListConfigGroups + req := in.GetConfigFile() + req.Md5 = wrapperspb.String(in.GetRevision()) + out = h.configServer.GetConfigGroupsWithCache(ctx, req) + default: + out = api.NewConfigDiscoverResponse(apimodel.Code_InvalidDiscoverResource) + } + + handler.WriteHeaderAndProtoV2(out) +} diff --git a/apiserver/httpserver/config/console_access.go b/apiserver/httpserver/config/console_access.go index d641a7b00..8d927f15c 100644 --- a/apiserver/httpserver/config/console_access.go +++ b/apiserver/httpserver/config/console_access.go @@ -475,3 +475,25 @@ func (h *HTTPServer) UpsertAndReleaseConfigFile(req *restful.Request, rsp *restf handler.WriteHeaderAndProto(h.configServer.UpsertAndReleaseConfigFile(ctx, configFile)) } + +// StopGrayConfigFileReleases . +func (h *HTTPServer) StopGrayConfigFileReleases(req *restful.Request, rsp *restful.Response) { + handler := &httpcommon.Handler{ + Request: req, + Response: rsp, + } + + var releases []*apiconfig.ConfigFileRelease + ctx, err := handler.ParseArray(func() proto.Message { + msg := &apiconfig.ConfigFileRelease{} + releases = append(releases, msg) + return msg + }) + if err != nil { + handler.WriteHeaderAndProto(api.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + return + } + + response := h.configServer.StopGrayConfigFileReleases(ctx, releases) + handler.WriteHeaderAndProto(response) +} diff --git a/apiserver/httpserver/config/server.go b/apiserver/httpserver/config/server.go index 4b025e9f7..be8e13911 100644 --- a/apiserver/httpserver/config/server.go +++ b/apiserver/httpserver/config/server.go @@ -120,6 +120,7 @@ func (h *HTTPServer) addDefaultAccess(ws *restful.WebService) { ws.Route(docs.EnrichGetConfigFileReleaseApiDocs(ws.POST("/configfiles/releases/delete").To(h.DeleteConfigFileReleases))) ws.Route(docs.EnrichGetConfigFileReleaseApiDocs(ws.GET("/configfiles/release/versions").To(h.GetConfigFileReleaseVersions))) ws.Route(docs.EnrichUpsertAndReleaseConfigFileApiDocs(ws.POST("/configfiles/createandpub").To(h.UpsertAndReleaseConfigFile))) + ws.Route(docs.EnrichStopBetaReleaseConfigFileApiDocs(ws.POST("/configfiles/releases/stopbeta").To(h.StopGrayConfigFileReleases))) // 配置文件发布历史 ws.Route(docs.EnrichGetConfigFileReleaseHistoryApiDocs(ws.GET("/configfiles/releasehistory"). @@ -151,6 +152,7 @@ func (h *HTTPServer) GetClientAccessServer(ws *restful.WebService, include []str } func (h *HTTPServer) addDiscover(ws *restful.WebService) { + ws.Route(docs.EnrichConfigDiscoverApiDocs(ws.POST("/ConfigDiscover").To(h.Discover))) ws.Route(docs.EnrichGetConfigFileForClientApiDocs(ws.GET("/GetConfigFile").To(h.ClientGetConfigFile))) ws.Route(docs.EnrichWatchConfigFileForClientApiDocs(ws.POST("/WatchConfigFile").To(h.ClientWatchConfigFile))) ws.Route(docs.EnrichGetConfigFileMetadataList(ws.POST("/GetConfigFileMetadataList").To(h.GetConfigFileMetadataList))) diff --git a/apiserver/httpserver/discover/v1/client_access.go b/apiserver/httpserver/discover/v1/client_access.go index 9b971ea23..945fbdc15 100644 --- a/apiserver/httpserver/discover/v1/client_access.go +++ b/apiserver/httpserver/discover/v1/client_access.go @@ -104,29 +104,39 @@ func (h *HTTPServerV1) Discover(req *restful.Request, rsp *restful.Response) { } startTime := commontime.CurrentMillisecond() + var ret *apiservice.DiscoverResponse + var action string defer func() { plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: action, ClientIP: utils.ParseClientAddress(ctx), Namespace: discoverRequest.GetService().GetNamespace().GetValue(), - Resource: discoverRequest.Type.String() + ":" + discoverRequest.GetService().GetName().GetValue(), + Resource: discoverRequest.GetType().String() + ":" + discoverRequest.GetService().GetName().GetValue(), Timestamp: startTime, CostTime: commontime.CurrentMillisecond() - startTime, + Revision: ret.GetService().GetRevision().GetValue(), + Success: ret.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), }) }() - var ret *apiservice.DiscoverResponse switch discoverRequest.Type { case apiservice.DiscoverRequest_INSTANCE: + action = metrics.ActionDiscoverInstance ret = h.namingServer.ServiceInstancesCache(ctx, discoverRequest.Filter, discoverRequest.Service) case apiservice.DiscoverRequest_ROUTING: + action = metrics.ActionDiscoverRouterRule ret = h.namingServer.GetRoutingConfigWithCache(ctx, discoverRequest.Service) case apiservice.DiscoverRequest_RATE_LIMIT: + action = metrics.ActionDiscoverRateLimit ret = h.namingServer.GetRateLimitWithCache(ctx, discoverRequest.Service) case apiservice.DiscoverRequest_CIRCUIT_BREAKER: + action = metrics.ActionDiscoverCircuitBreaker ret = h.namingServer.GetCircuitBreakerWithCache(ctx, discoverRequest.Service) case apiservice.DiscoverRequest_SERVICES: + action = metrics.ActionDiscoverServices ret = h.namingServer.GetServiceWithCache(ctx, discoverRequest.Service) case apiservice.DiscoverRequest_FAULT_DETECTOR: + action = metrics.ActionDiscoverFaultDetect ret = h.namingServer.GetFaultDetectWithCache(ctx, discoverRequest.Service) default: ret = api.NewDiscoverRoutingResponse(apimodel.Code_InvalidDiscoverResource, discoverRequest.Service) diff --git a/apiserver/httpserver/discover/v1/server.go b/apiserver/httpserver/discover/v1/server.go index 02f9c360c..8544e34d9 100644 --- a/apiserver/httpserver/discover/v1/server.go +++ b/apiserver/httpserver/discover/v1/server.go @@ -53,8 +53,8 @@ const ( rateLimitAccess string = "ratelimit" ) -// GetNamingConsoleAccessServer 注册管理端接口 -func (h *HTTPServerV1) GetNamingConsoleAccessServer(include []string) (*restful.WebService, error) { +// GetConsoleAccessServer 注册管理端接口 +func (h *HTTPServerV1) GetConsoleAccessServer(include []string) (*restful.WebService, error) { consoleAccess := []string{defaultAccess} ws := new(restful.WebService) @@ -111,11 +111,14 @@ func (h *HTTPServerV1) addDefaultReadAccess(ws *restful.WebService) { ws.Route(docs.EnrichGetInstancesApiDocs(ws.GET("/instances").To(h.GetInstances))) ws.Route(docs.EnrichGetInstancesCountApiDocs(ws.GET("/instances/count").To(h.GetInstancesCount))) ws.Route(docs.EnrichGetRateLimitsApiDocs(ws.GET("/ratelimits").To(h.GetRateLimits))) - ws.Route(docs.EnrichGetCircuitBreakerRulesApiDocs(ws.GET("/circuitbreaker/rules").To(h.GetCircuitBreakerRules))) + ws.Route(docs.EnrichGetCircuitBreakerRulesApiDocs( + ws.GET("/circuitbreaker/rules").To(h.GetCircuitBreakerRules))) ws.Route(docs.EnrichGetFaultDetectRulesApiDocs(ws.GET("/faultdetectors").To(h.GetFaultDetectRules))) - ws.Route(docs.EnrichGetServiceContractsApiDocs(ws.GET("/service/contracts").To(h.GetServiceContracts))) - ws.Route(docs.EnrichGetServiceContractsApiDocs(ws.GET("/service/contract/versions").To(h.GetServiceContractVersions))) + ws.Route(docs.EnrichGetServiceContractsApiDocs( + ws.GET("/service/contracts").To(h.GetServiceContracts))) + ws.Route(docs.EnrichGetServiceContractsApiDocs( + ws.GET("/service/contract/versions").To(h.GetServiceContractVersions))) // Deprecate -- start ws.Route(ws.GET("/namespace/token").To(h.GetNamespaceToken)) @@ -147,7 +150,8 @@ func (h *HTTPServerV1) addServiceAccess(ws *restful.WebService) { ws.Route(docs.EnrichUpdateNamespacesApiDocsOld(ws.PUT("/namespaces").To(h.UpdateNamespaces))) ws.Route(docs.EnrichGetNamespacesApiDocsOld(ws.GET("/namespaces").To(h.GetNamespaces))) ws.Route(docs.EnrichGetNamespaceTokenApiDocsOld(ws.GET("/namespace/token").To(h.GetNamespaceToken))) - ws.Route(docs.EnrichUpdateNamespaceTokenApiDocsOld(ws.PUT("/namespace/token").To(h.UpdateNamespaceToken))) + ws.Route(docs.EnrichUpdateNamespaceTokenApiDocsOld( + ws.PUT("/namespace/token").To(h.UpdateNamespaceToken))) ws.Route(docs.EnrichCreateServicesApiDocs(ws.POST("/services").To(h.CreateServices))) ws.Route(docs.EnrichDeleteServicesApiDocs(ws.POST("/services/delete").To(h.DeleteServices))) @@ -160,7 +164,8 @@ func (h *HTTPServerV1) addServiceAccess(ws *restful.WebService) { ws.Route(docs.EnrichCreateServiceAliasApiDocs(ws.POST("/service/alias").To(h.CreateServiceAlias))) ws.Route(docs.EnrichUpdateServiceAliasApiDocs(ws.PUT("/service/alias").To(h.UpdateServiceAlias))) ws.Route(docs.EnrichGetServiceAliasesApiDocs(ws.GET("/service/aliases").To(h.GetServiceAliases))) - ws.Route(docs.EnrichDeleteServiceAliasesApiDocs(ws.POST("/service/aliases/delete").To(h.DeleteServiceAliases))) + ws.Route(docs.EnrichDeleteServiceAliasesApiDocs( + ws.POST("/service/aliases/delete").To(h.DeleteServiceAliases))) ws.Route(docs.EnrichCreateInstancesApiDocs(ws.POST("/instances").To(h.CreateInstances))) ws.Route(docs.EnrichDeleteInstancesApiDocs(ws.POST("/instances/delete").To(h.DeleteInstances))) @@ -174,13 +179,20 @@ func (h *HTTPServerV1) addServiceAccess(ws *restful.WebService) { ws.Route(docs.EnrichGetInstanceLabelsApiDocs(ws.GET("/instances/labels").To(h.GetInstanceLabels))) // 服务契约相关 - ws.Route(docs.EnrichCreateServiceContractsApiDocs(ws.POST("/service/contracts").To(h.CreateServiceContract))) - ws.Route(docs.EnrichGetServiceContractsApiDocs(ws.GET("/service/contracts").To(h.GetServiceContracts))) - ws.Route(docs.EnrichDeleteServiceContractsApiDocs(ws.POST("/service/contracts/delete").To(h.DeleteServiceContracts))) - ws.Route(docs.EnrichGetServiceContractsApiDocs(ws.GET("/service/contract/versions").To(h.GetServiceContractVersions))) - ws.Route(docs.EnrichAddServiceContractInterfacesApiDocs(ws.POST("/service/contract/methods").To(h.CreateServiceContractInterfaces))) - ws.Route(docs.EnrichAppendServiceContractInterfacesApiDocs(ws.PUT("/service/contract/methods/append").To(h.AppendServiceContractInterfaces))) - ws.Route(docs.EnrichDeleteServiceContractsApiDocs(ws.POST("/service/contract/methods/delete").To(h.DeleteServiceContractInterfaces))) + ws.Route(docs.EnrichCreateServiceContractsApiDocs( + ws.POST("/service/contracts").To(h.CreateServiceContract))) + ws.Route(docs.EnrichGetServiceContractsApiDocs( + ws.GET("/service/contracts").To(h.GetServiceContracts))) + ws.Route(docs.EnrichDeleteServiceContractsApiDocs( + ws.POST("/service/contracts/delete").To(h.DeleteServiceContracts))) + ws.Route(docs.EnrichGetServiceContractsApiDocs( + ws.GET("/service/contract/versions").To(h.GetServiceContractVersions))) + ws.Route(docs.EnrichAddServiceContractInterfacesApiDocs( + ws.POST("/service/contract/methods").To(h.CreateServiceContractInterfaces))) + ws.Route(docs.EnrichAppendServiceContractInterfacesApiDocs( + ws.PUT("/service/contract/methods/append").To(h.AppendServiceContractInterfaces))) + ws.Route(docs.EnrichDeleteServiceContractsApiDocs( + ws.POST("/service/contract/methods/delete").To(h.DeleteServiceContractInterfaces))) ws.Route(ws.POST("/service/owner").To(h.GetServiceOwner)) } @@ -228,9 +240,12 @@ func (h *HTTPServerV1) addCircuitBreakerRuleAccess(ws *restful.WebService) { ws.POST("/circuitbreaker/rules/delete").To(h.DeleteCircuitBreakerRules))) ws.Route(docs.EnrichEnableCircuitBreakerRulesApiDocs( ws.PUT("/circuitbreaker/rules/enable").To(h.EnableCircuitBreakerRules))) - ws.Route(docs.EnrichGetFaultDetectRulesApiDocs(ws.GET("/faultdetectors").To(h.GetFaultDetectRules))) - ws.Route(docs.EnrichCreateFaultDetectRulesApiDocs(ws.POST("/faultdetectors").To(h.CreateFaultDetectRules))) - ws.Route(docs.EnrichUpdateFaultDetectRulesApiDocs(ws.PUT("/faultdetectors").To(h.UpdateFaultDetectRules))) + ws.Route(docs.EnrichGetFaultDetectRulesApiDocs( + ws.GET("/faultdetectors").To(h.GetFaultDetectRules))) + ws.Route(docs.EnrichCreateFaultDetectRulesApiDocs( + ws.POST("/faultdetectors").To(h.CreateFaultDetectRules))) + ws.Route(docs.EnrichUpdateFaultDetectRulesApiDocs( + ws.PUT("/faultdetectors").To(h.UpdateFaultDetectRules))) ws.Route(docs.EnrichDeleteFaultDetectRulesApiDocs( ws.POST("/faultdetectors/delete").To(h.DeleteFaultDetectRules))) } diff --git a/apiserver/httpserver/discover/v2/console_access.go b/apiserver/httpserver/discover/v2/console_access.go index 08dacc30e..932b86317 100644 --- a/apiserver/httpserver/discover/v2/console_access.go +++ b/apiserver/httpserver/discover/v2/console_access.go @@ -54,7 +54,7 @@ func (h *HTTPServerV2) CreateRoutings(req *restful.Request, rsp *restful.Respons requestText, err := h.replaceV2TypeUrl(req) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } var routings v1.RouterArr @@ -64,12 +64,12 @@ func (h *HTTPServerV2) CreateRoutings(req *restful.Request, rsp *restful.Respons return msg }, requestText) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } ret := h.namingServer.CreateRoutingConfigsV2(ctx, routings) - handler.WriteHeaderAndProtoV2(ret) + handler.WriteHeaderAndProto(ret) } // DeleteRoutings 删除规则路由 @@ -80,7 +80,7 @@ func (h *HTTPServerV2) DeleteRoutings(req *restful.Request, rsp *restful.Respons } requestText, err := h.replaceV2TypeUrl(req) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } var routings v1.RouterArr @@ -90,12 +90,12 @@ func (h *HTTPServerV2) DeleteRoutings(req *restful.Request, rsp *restful.Respons return msg }, requestText) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } ret := h.namingServer.DeleteRoutingConfigsV2(ctx, routings) - handler.WriteHeaderAndProtoV2(ret) + handler.WriteHeaderAndProto(ret) } // UpdateRoutings 修改规则路由 @@ -106,7 +106,7 @@ func (h *HTTPServerV2) UpdateRoutings(req *restful.Request, rsp *restful.Respons } requestText, err := h.replaceV2TypeUrl(req) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } var routings v1.RouterArr @@ -116,12 +116,12 @@ func (h *HTTPServerV2) UpdateRoutings(req *restful.Request, rsp *restful.Respons return msg }, requestText) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } ret := h.namingServer.UpdateRoutingConfigsV2(ctx, routings) - handler.WriteHeaderAndProtoV2(ret) + handler.WriteHeaderAndProto(ret) } // GetRoutings 查询规则路由 @@ -133,7 +133,7 @@ func (h *HTTPServerV2) GetRoutings(req *restful.Request, rsp *restful.Response) queryParams := httpcommon.ParseQueryParams(req) ret := h.namingServer.QueryRoutingConfigsV2(handler.ParseHeaderContext(), queryParams) - handler.WriteHeaderAndProtoV2(ret) + handler.WriteHeaderAndProto(ret) } // EnableRoutings 启用规则路由 @@ -144,7 +144,7 @@ func (h *HTTPServerV2) EnableRoutings(req *restful.Request, rsp *restful.Respons } requestText, err := h.replaceV2TypeUrl(req) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } var routings v1.RouterArr @@ -154,10 +154,10 @@ func (h *HTTPServerV2) EnableRoutings(req *restful.Request, rsp *restful.Respons return msg }, requestText) if err != nil { - handler.WriteHeaderAndProtoV2(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) + handler.WriteHeaderAndProto(apiv1.NewBatchWriteResponseWithMsg(apimodel.Code_ParseException, err.Error())) return } ret := h.namingServer.EnableRoutings(ctx, routings) - handler.WriteHeaderAndProtoV2(ret) + handler.WriteHeaderAndProto(ret) } diff --git a/apiserver/httpserver/discover/v2/server.go b/apiserver/httpserver/discover/v2/server.go index f992d370c..c5d0883ea 100644 --- a/apiserver/httpserver/discover/v2/server.go +++ b/apiserver/httpserver/discover/v2/server.go @@ -53,8 +53,8 @@ const ( rateLimitAccess string = "ratelimit" ) -// GetNamingConsoleAccessServer 注册管理端接口 -func (h *HTTPServerV2) GetNamingConsoleAccessServer(include []string) (*restful.WebService, error) { +// GetConsoleAccessServer 注册管理端接口 +func (h *HTTPServerV2) GetConsoleAccessServer(include []string) (*restful.WebService, error) { consoleAccess := []string{defaultAccess} ws := new(restful.WebService) ws.Path("/naming/v2").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON) diff --git a/apiserver/httpserver/docs/config_server_apidoc.go b/apiserver/httpserver/docs/config_server_apidoc.go index 7223d2631..c00f6eecc 100644 --- a/apiserver/httpserver/docs/config_server_apidoc.go +++ b/apiserver/httpserver/docs/config_server_apidoc.go @@ -92,6 +92,14 @@ func EnrichUpsertAndReleaseConfigFileApiDocs(r *restful.RouteBuilder) *restful.R Returns(0, "", BaseResponse{}) } +func EnrichStopBetaReleaseConfigFileApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { + return r. + Doc("停止灰度发布配置文件"). + Metadata(restfulspec.KeyOpenAPITags, configConsoleApiTags). + Reads(apiconfig.ConfigFileRelease{}). + Returns(0, "", BaseResponse{}) +} + func EnrichGetConfigFileApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { return r. Doc("拉取配置"). @@ -256,6 +264,14 @@ func EnrichCreateConfigFileTemplateApiDocs(r *restful.RouteBuilder) *restful.Rou Returns(0, "", BaseResponse{}) } +func EnrichConfigDiscoverApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { + return r. + Doc("配置数据发现"). + Metadata(restfulspec.KeyOpenAPITags, configClientApiTags). + Reads(config_manage.ConfigDiscoverResponse{}). + Returns(0, "", apiconfig.ConfigDiscoverResponse{}) +} + func EnrichGetConfigFileForClientApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { return r. Doc("拉取配置"). diff --git a/apiserver/httpserver/docs/naming_console_access_apidoc.go b/apiserver/httpserver/docs/naming_console_access_apidoc.go index cc0423199..e2eabc971 100644 --- a/apiserver/httpserver/docs/naming_console_access_apidoc.go +++ b/apiserver/httpserver/docs/naming_console_access_apidoc.go @@ -750,6 +750,7 @@ func EnrichAppendServiceContractInterfacesApiDocs(r *restful.RouteBuilder) *rest return r.Doc("追加服务契约接口描述"). Metadata(restfulspec.KeyOpenAPITags, serviceContractApiTags) } + func EnrichDeleteServiceContractInterfacesApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { return r.Doc("删除服务契约接口描述"). Metadata(restfulspec.KeyOpenAPITags, serviceContractApiTags) diff --git a/apiserver/httpserver/server.go b/apiserver/httpserver/server.go index a49aa1dbf..611dbb77a 100644 --- a/apiserver/httpserver/server.go +++ b/apiserver/httpserver/server.go @@ -380,13 +380,13 @@ func (h *HTTPServer) createRestfulContainer() (*restful.Container, error) { } case "console": if apiConfig.Enable { - namingServiceV1, err := h.discoverV1.GetNamingConsoleAccessServer(apiConfig.Include) + namingServiceV1, err := h.discoverV1.GetConsoleAccessServer(apiConfig.Include) if err != nil { return nil, err } wsContainer.Add(namingServiceV1) - namingServiceV2, err := h.discoverV2.GetNamingConsoleAccessServer(apiConfig.Include) + namingServiceV2, err := h.discoverV2.GetConsoleAccessServer(apiConfig.Include) if err != nil { return nil, err } @@ -468,6 +468,35 @@ func (h *HTTPServer) enablePrometheusAccess(wsContainer *restful.Container) { // enablePluginDebugAccess . func (h *HTTPServer) enablePluginDebugAccess(wsContainer *restful.Container) { log.Infof("open http access for plugin") + + wsContainer.Handle("/debug/endpoints", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + endpints := make([]map[string]string, 0, 8) + for _, checker := range h.healthCheckServer.Checkers() { + handlers := checker.DebugHandlers() + for i := range handlers { + endpints = append(endpints, map[string]string{ + "path": handlers[i].Path, + "desc": handlers[i].Desc, + }) + } + } + + for _, item := range h.apiserverSlots { + if val, ok := item.(apiserver.EnrichApiserver); ok { + handlers := val.DebugHandlers() + for i := range handlers { + endpints = append(endpints, map[string]string{ + "path": handlers[i].Path, + "desc": handlers[i].Desc, + }) + } + } + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(utils.MustJson(endpints))) + })) + for _, checker := range h.healthCheckServer.Checkers() { handlers := checker.DebugHandlers() for i := range handlers { diff --git a/apiserver/httpserver/utils/handler.go b/apiserver/httpserver/utils/handler.go index 78b9604e7..e8d1c91d9 100644 --- a/apiserver/httpserver/utils/handler.go +++ b/apiserver/httpserver/utils/handler.go @@ -332,15 +332,15 @@ func (h *Handler) WriteHeaderAndProto(obj api.ResponseMessage) { } // WriteHeaderAndProtoV2 返回Code和Proto -func (h *Handler) WriteHeaderAndProtoV2(obj api.ResponseMessage) { +func (h *Handler) WriteHeaderAndProtoV2(obj api.ResponseMessageV2) { requestID := h.Request.HeaderParameter(utils.PolarisRequestID) h.Request.SetAttribute(utils.PolarisCode, obj.GetCode()) - status := api.CalcCode(obj) + status := api.CalcCodeV2(obj) if status != http.StatusOK { accesslog.Error(obj.String(), utils.ZapRequestID(requestID)) } - if code := obj.GetCode().GetValue(); code != api.ExecuteSuccess { + if code := obj.GetCode(); code != api.ExecuteSuccess { h.Response.AddHeader(utils.PolarisCode, fmt.Sprintf("%d", code)) h.Response.AddHeader(utils.PolarisMessage, api.Code2Info(code)) } diff --git a/apiserver/nacosserver/core/storage.go b/apiserver/nacosserver/core/storage.go index 8d358acb4..3347a23df 100644 --- a/apiserver/nacosserver/core/storage.go +++ b/apiserver/nacosserver/core/storage.go @@ -36,7 +36,7 @@ import ( ) type ( - // FilterContext + // FilterContext nacos 实例列表过滤上下文 FilterContext struct { Service *nacosmodel.ServiceMetadata Clusters []string @@ -45,7 +45,7 @@ type ( SubscriberIP string } - // InstanceFilter + // InstanceFilter 实例过滤器 InstanceFilter func(ctx *FilterContext, svcInfo *nacosmodel.ServiceInfo, ins []*nacosmodel.Instance, healthyCount int32) *nacosmodel.ServiceInfo ) @@ -209,7 +209,7 @@ func (n *NacosDataStorage) syncTask() { zap.String("old-reversion", oldRevision), zap.String("reversion", revision)) svcData := n.loadNacosService(revision, svc) svcInfos = append(svcInfos, svcData.specService) - instances := n.cacheMgr.Instance().GetInstancesByServiceID(svc.ID) + instances := n.cacheMgr.Instance().GetInstances(svc.ID) svcData.loadInstances(instances) } n.revisions[svc.ID] = revision @@ -220,7 +220,7 @@ func (n *NacosDataStorage) syncTask() { return } // 发布服务信息变更事件 - eventhub.Publish(nacosmodel.NacosServicesChangeEventTopic, &nacosmodel.NacosServicesChangeEvent{ + _ = eventhub.Publish(nacosmodel.NacosServicesChangeEventTopic, &nacosmodel.NacosServicesChangeEvent{ Services: svcInfos, }) } @@ -272,19 +272,21 @@ func (n *NacosDataStorage) loadNacosService(reversion string, svc *model.Service // ServiceData nacos 的服务数据模型 type ServiceData struct { - specService *nacosmodel.ServiceMetadata - name string - group string - lock sync.RWMutex - reversion string - instances map[string]*nacosmodel.Instance + reachProtectionThreshold bool + specService *nacosmodel.ServiceMetadata + name string + group string + lock sync.RWMutex + reversion string + instances map[string]*nacosmodel.Instance } -func (s *ServiceData) loadInstances(instances []*model.Instance) { +func (s *ServiceData) loadInstances(svcIns *model.ServiceInstances) { var ( finalInstances = map[string]*nacosmodel.Instance{} ) + instances := svcIns.GetInstances(false) healthCount := 0 for i := range instances { ins := &nacosmodel.Instance{} diff --git a/apiserver/nacosserver/logger/log.go b/apiserver/nacosserver/logger/log.go index 80f1fb558..300c50cef 100644 --- a/apiserver/nacosserver/logger/log.go +++ b/apiserver/nacosserver/logger/log.go @@ -23,8 +23,13 @@ import ( var ( nacoslog = commonlog.RegisterScope("nacos-apiserver", "nacos apiserver plugin", 0) + tracelog = commonlog.RegisterScope("nacos-trace", "nacos trace", 0) ) func GetNacosLog() *commonlog.Scope { return nacoslog } + +func GetTraceLog() *commonlog.Scope { + return tracelog +} diff --git a/apiserver/nacosserver/model/constant.go b/apiserver/nacosserver/model/constant.go index cd9e1098b..2a239da58 100644 --- a/apiserver/nacosserver/model/constant.go +++ b/apiserver/nacosserver/model/constant.go @@ -29,6 +29,10 @@ const ( DefaultListenPort = 8848 ) +const ( + NacosClientAuthHeader = "accessToken" +) + const ( ParamCode = "code" ParamServiceName = "serviceName" @@ -139,3 +143,7 @@ func ToNacosConfigNamespace(ns string) string { } return ns } + +const ( + ActionGetConfigFile = "NACOS_GET_CONFIG" +) diff --git a/apiserver/nacosserver/model/error.go b/apiserver/nacosserver/model/error.go index de80052e6..485392560 100644 --- a/apiserver/nacosserver/model/error.go +++ b/apiserver/nacosserver/model/error.go @@ -17,7 +17,9 @@ package model -import "encoding/json" +import ( + "github.com/polarismesh/polaris/common/utils" +) type NacosApiError struct { DetailErrCode int32 @@ -26,8 +28,7 @@ type NacosApiError struct { } func (e *NacosApiError) Error() string { - desc, _ := json.Marshal(e) - return string(desc) + return utils.MustJson(e) } type NacosError struct { @@ -37,6 +38,5 @@ type NacosError struct { } func (e *NacosError) Error() string { - desc, _ := json.Marshal(e) - return string(desc) + return utils.MustJson(e) } diff --git a/apiserver/nacosserver/v1/config/builder.go b/apiserver/nacosserver/v1/config/builder.go index c973257ec..2d3bfcad4 100644 --- a/apiserver/nacosserver/v1/config/builder.go +++ b/apiserver/nacosserver/v1/config/builder.go @@ -9,7 +9,7 @@ * * https://opensource.org/licenses/BSD-3-Clause * - * Unless nacoshttp.Required by applicable law or agreed to in writing, software distributed + * 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. diff --git a/apiserver/nacosserver/v1/config/config_file.go b/apiserver/nacosserver/v1/config/config_file.go index f131d9d1c..e689f52b9 100644 --- a/apiserver/nacosserver/v1/config/config_file.go +++ b/apiserver/nacosserver/v1/config/config_file.go @@ -31,9 +31,12 @@ import ( "github.com/polarismesh/polaris/apiserver/nacosserver/model" api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/metrics" + commonmodel "github.com/polarismesh/polaris/common/model" + commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/config" - commonmodel "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/plugin" ) func (n *ConfigServer) handlePublishConfig(ctx context.Context, req *model.ConfigFile) (bool, error) { @@ -63,7 +66,22 @@ func (n *ConfigServer) handleDeleteConfig(ctx context.Context, req *model.Config } func (n *ConfigServer) handleGetConfig(ctx context.Context, req *model.ConfigFile, rsp *restful.Response) (string, error) { - queryResp := n.configSvr.GetConfigFileForClient(ctx, req.ToQuerySpec()) + var queryResp *config_manage.ConfigClientResponse + startTime := commontime.CurrentMillisecond() + defer func() { + plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: model.ActionGetConfigFile, + ClientIP: utils.ParseClientAddress(ctx), + Namespace: req.Namespace, + Resource: metrics.ResourceOfConfigFile(req.Group, req.DataId), + Timestamp: startTime, + CostTime: commontime.CurrentMillisecond() - startTime, + Revision: queryResp.GetConfigFile().GetMd5().GetValue(), + Success: queryResp.GetCode().GetValue() > uint32(apimodel.Code_DataNoChange), + }) + }() + + queryResp = n.configSvr.GetConfigFileWithCache(ctx, req.ToQuerySpec()) if queryResp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { nacoslog.Error("[NACOS-V1][Config] query config file fail", zap.Uint32("code", queryResp.GetCode().GetValue()), zap.String("msg", queryResp.GetInfo().GetValue())) @@ -95,7 +113,7 @@ func (n *ConfigServer) handleWatch(ctx context.Context, listenCtx *model.ConfigW specWatchReq := listenCtx.ToSpecWatch() timeout, ok := listenCtx.IsSupportLongPolling() if !ok { - changeKeys := n.diffChangeFiles(specWatchReq) + changeKeys := n.diffChangeFiles(ctx, specWatchReq) oldResult := md5OldResult(changeKeys) newResult := md5ResultString(changeKeys) @@ -106,12 +124,12 @@ func (n *ConfigServer) handleWatch(ctx context.Context, listenCtx *model.ConfigW listenCtx.Request.SetAttribute(model.HeaderContent, newResult) return } - if changeKeys := n.diffChangeFiles(specWatchReq); len(changeKeys) > 0 { + if changeKeys := n.diffChangeFiles(ctx, specWatchReq); len(changeKeys) > 0 { newResult := md5ResultString(changeKeys) nacoslog.Info("[NACOS-V1][Config] client quick compare file result.", zap.String("result", newResult)) rsp.WriteHeader(http.StatusOK) disableCache(rsp) - rsp.Write([]byte(newResult)) + _, _ = rsp.Write([]byte(newResult)) return } if listenCtx.IsNoHangUp() { @@ -123,7 +141,7 @@ func (n *ConfigServer) handleWatch(ctx context.Context, listenCtx *model.ConfigW clientId := utils.ParseClientAddress(ctx) + "@" + utils.NewUUID()[0:8] configSvr := n.originConfigSvr.(*config.Server) watchCtx := configSvr.WatchCenter().AddWatcher(clientId, specWatchReq.GetWatchFiles(), - BuildTimeoutWatchCtx(timeout)) + n.BuildTimeoutWatchCtx(ctx, timeout)) nacoslog.Info("[NACOS-V1][Config] client start waitting server send notify message") notifyRet := (watchCtx.(*LongPollWatchContext)).GetNotifieResult() notifyCode := notifyRet.GetCode().GetValue() @@ -138,7 +156,7 @@ func (n *ConfigServer) handleWatch(ctx context.Context, listenCtx *model.ConfigW var changeKeys []*model.ConfigListenItem if notifyCode == uint32(apimodel.Code_DataNoChange) { // 按照 Nacos 原本的设计,只有 WatchClient 超时后才会再次全部 diff 比较 - changeKeys = n.diffChangeFiles(specWatchReq) + changeKeys = n.diffChangeFiles(ctx, specWatchReq) } else { // 如果收到一个事件变化,就立即通知这个文件的变化信息 changeKeys = []*model.ConfigListenItem{ @@ -150,7 +168,7 @@ func (n *ConfigServer) handleWatch(ctx context.Context, listenCtx *model.ConfigW } } if len(changeKeys) == 0 { - nacoslog.Info("[NACOS-V1][Config] client receive empty watch result.", zap.Any("ret", notifyRet)) + nacoslog.Debug("[NACOS-V1][Config] client receive empty watch result.", zap.Any("ret", notifyRet)) rsp.WriteHeader(http.StatusOK) return } @@ -158,11 +176,15 @@ func (n *ConfigServer) handleWatch(ctx context.Context, listenCtx *model.ConfigW nacoslog.Info("[NACOS-V1][Config] client receive watch result.", zap.String("result", newResult)) rsp.WriteHeader(http.StatusOK) disableCache(rsp) - rsp.Write([]byte(newResult)) + _, _ = rsp.Write([]byte(newResult)) return } -func (n *ConfigServer) diffChangeFiles(listenCtx *config_manage.ClientWatchConfigFileRequest) []*model.ConfigListenItem { +func (n *ConfigServer) diffChangeFiles(ctx context.Context, + listenCtx *config_manage.ClientWatchConfigFileRequest) []*model.ConfigListenItem { + clientLabels := map[string]string{ + commonmodel.ClientLabel_IP: utils.ParseClientIP(ctx), + } changeKeys := make([]*model.ConfigListenItem, 0, 4) // quick get file and compare for _, item := range listenCtx.WatchFiles { @@ -171,7 +193,18 @@ func (n *ConfigServer) diffChangeFiles(listenCtx *config_manage.ClientWatchConfi dataId := item.GetFileName().GetValue() mdval := item.GetMd5().GetValue() - active := n.cacheSvr.ConfigFile().GetActiveRelease(namespace, group, dataId, commonmodel.ReleaseTypeFull) + if beta := n.cacheSvr.ConfigFile().GetGrayRelease(namespace, group, dataId); beta != nil { + if n.cacheSvr.Gray().HitGrayRule(beta.FileKey(), clientLabels) { + changeKeys = append(changeKeys, &model.ConfigListenItem{ + Tenant: model.ToNacosConfigNamespace(beta.Namespace), + Group: beta.Group, + DataId: dataId, + }) + continue + } + } + + active := n.cacheSvr.ConfigFile().GetActiveRelease(namespace, group, dataId) if (active == nil && mdval != "") || (active != nil && active.Md5 != mdval) { changeKeys = append(changeKeys, &model.ConfigListenItem{ Tenant: model.ToNacosConfigNamespace(namespace), @@ -183,13 +216,20 @@ func (n *ConfigServer) diffChangeFiles(listenCtx *config_manage.ClientWatchConfi return changeKeys } -func BuildTimeoutWatchCtx(watchTimeOut time.Duration) config.WatchContextFactory { - return func(clientId string) config.WatchContext { +func (n *ConfigServer) BuildTimeoutWatchCtx(ctx context.Context, watchTimeOut time.Duration) config.WatchContextFactory { + labels := map[string]string{} + labels[commonmodel.ClientLabel_IP] = utils.ParseClientIP(ctx) + + return func(clientId string, matcher config.BetaReleaseMatcher) config.WatchContext { watchCtx := &LongPollWatchContext{ clientId: clientId, + labels: labels, finishTime: time.Now().Add(watchTimeOut), finishChan: make(chan *config_manage.ConfigClientResponse), watchConfigFiles: map[string]*config_manage.ClientConfigFileInfo{}, + betaMatcher: func(clientLabels map[string]string, event *commonmodel.SimpleConfigFileRelease) bool { + return n.cacheSvr.Gray().HitGrayRule(commonmodel.GetGrayConfigRealseKey(event), clientLabels) + }, } return watchCtx } diff --git a/apiserver/nacosserver/v1/config/watch.go b/apiserver/nacosserver/v1/config/watch.go index 80d325427..42365ca85 100644 --- a/apiserver/nacosserver/v1/config/watch.go +++ b/apiserver/nacosserver/v1/config/watch.go @@ -1,3 +1,20 @@ +/** + * 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 config import ( @@ -5,16 +22,24 @@ import ( "sync" "time" - "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/specification/source/go/api/v1/config_manage" + + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/config" ) type LongPollWatchContext struct { clientId string + labels map[string]string once sync.Once finishTime time.Time finishChan chan *config_manage.ConfigClientResponse watchConfigFiles map[string]*config_manage.ClientConfigFileInfo + betaMatcher config.BetaReleaseMatcher +} + +func (c *LongPollWatchContext) ClientLabels() map[string]string { + return c.labels } // GetNotifieResult . @@ -51,7 +76,7 @@ func (c *LongPollWatchContext) ClientID() string { // ShouldNotify . func (c *LongPollWatchContext) ShouldNotify(event *model.SimpleConfigFileRelease) bool { - key := event.ActiveKey() + key := event.FileKey() watchFile, ok := c.watchConfigFiles[key] if !ok { return false diff --git a/apiserver/nacosserver/v1/discover/builder.go b/apiserver/nacosserver/v1/discover/builder.go index fe4998793..fec782f73 100644 --- a/apiserver/nacosserver/v1/discover/builder.go +++ b/apiserver/nacosserver/v1/discover/builder.go @@ -9,7 +9,7 @@ * * https://opensource.org/licenses/BSD-3-Clause * - * Unless nacoshttp.Required by applicable law or agreed to in writing, software distributed + * 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. diff --git a/apiserver/nacosserver/v1/http/utils.go b/apiserver/nacosserver/v1/http/utils.go index 3b88a2151..bf4a0731f 100644 --- a/apiserver/nacosserver/v1/http/utils.go +++ b/apiserver/nacosserver/v1/http/utils.go @@ -52,7 +52,7 @@ func WrirteNacosResponseWithCode(code int, data interface{}, resp *restful.Respo func WrirteNacosErrorResponse(data error, resp *restful.Response) { if nerr, ok := data.(*model.NacosError); ok { resp.WriteHeader(int(nerr.ErrCode)) - resp.Write([]byte(nerr.Error())) + _, _ = resp.Write([]byte(nerr.Error())) return } _ = resp.WriteError(http.StatusInternalServerError, data) diff --git a/apiserver/nacosserver/v2/access.go b/apiserver/nacosserver/v2/access.go index 82ad9472a..40d82d45b 100644 --- a/apiserver/nacosserver/v2/access.go +++ b/apiserver/nacosserver/v2/access.go @@ -55,6 +55,7 @@ var ( func (h *NacosV2Server) Request(ctx context.Context, payload *nacospb.Payload) (*nacospb.Payload, error) { h.connectionManager.RefreshClient(ctx) + ctx = injectPayloadHeader(ctx, payload) handle, val, err := h.UnmarshalPayload(payload) if err != nil { return nil, err @@ -150,7 +151,7 @@ func (h *NacosV2Server) RequestBiStream(svr nacospb.BiRequestStream_RequestBiStr } return err } - + ctx = injectPayloadHeader(ctx, req) _, val, err := h.UnmarshalPayload(req) if err != nil { return err @@ -194,3 +195,21 @@ func (h *NacosV2Server) UnmarshalPayload(payload *nacospb.Payload) (remote.Reque } return handler.Handler, msg, nil } + +func injectPayloadHeader(ctx context.Context, payload *nacospb.Payload) context.Context { + metadata := payload.GetMetadata() + if metadata == nil { + return ctx + } + if len(metadata.Headers) == 0 { + return ctx + } + token, exist := metadata.Headers[nacosmodel.NacosClientAuthHeader] + if exist { + ctx = context.WithValue(ctx, utils.ContextAuthTokenKey, token) + } + for k, v := range metadata.Headers { + ctx = context.WithValue(ctx, utils.StringContext(k), v) + } + return ctx +} diff --git a/apiserver/nacosserver/v2/config/config_file.go b/apiserver/nacosserver/v2/config/config_file.go index eee2b994b..b1615ed21 100644 --- a/apiserver/nacosserver/v2/config/config_file.go +++ b/apiserver/nacosserver/v2/config/config_file.go @@ -28,9 +28,12 @@ import ( nacosmodel "github.com/polarismesh/polaris/apiserver/nacosserver/model" nacospb "github.com/polarismesh/polaris/apiserver/nacosserver/v2/pb" "github.com/polarismesh/polaris/apiserver/nacosserver/v2/remote" + "github.com/polarismesh/polaris/common/metrics" + "github.com/polarismesh/polaris/common/model" + commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/config" - commonmodel "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/plugin" ) const ( @@ -45,7 +48,7 @@ func (h *ConfigServer) handlePublishConfigRequest(ctx context.Context, req nacos return nil, remote.ErrorInvalidRequestBodyType } - resp := h.configSvr.UpsertAndReleaseConfigFileFromClient(ctx, configReq.ToSpec()) + resp := h.configSvr.CasUpsertAndReleaseConfigFileFromClient(ctx, configReq.ToSpec()) if resp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { nacoslog.Error("[NACOS-V2][Config] publish config file fail", zap.String("tenant", configReq.Tenant), utils.ZapGroup(configReq.Group), utils.ZapFileName(configReq.DataId), @@ -75,9 +78,24 @@ func (h *ConfigServer) handleGetConfigRequest(ctx context.Context, req nacospb.B if !ok { return nil, remote.ErrorInvalidRequestBodyType } + var rsp *nacospb.ConfigQueryResponse + + startTime := commontime.CurrentMillisecond() + defer func() { + plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + Action: nacosmodel.ActionGetConfigFile, + ClientIP: utils.ParseClientAddress(ctx), + Namespace: configReq.Tenant, + Resource: metrics.ResourceOfConfigFile(configReq.Group, configReq.DataId), + Timestamp: startTime, + CostTime: commontime.CurrentMillisecond() - startTime, + Revision: rsp.Md5, + Success: rsp.Success, + }) + }() queryReq := configReq.ToQuerySpec() - queryResp := h.configSvr.GetConfigFileForClient(ctx, queryReq) + queryResp := h.configSvr.GetConfigFileWithCache(ctx, queryReq) if queryResp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { nacoslog.Error("[NACOS-V2][Config] query config file fail", zap.String("tenant", configReq.Tenant), utils.ZapNamespace(queryReq.GetNamespace().GetValue()), utils.ZapGroup(configReq.Group), @@ -85,27 +103,29 @@ func (h *ConfigServer) handleGetConfigRequest(ctx context.Context, req nacospb.B zap.String("msg", queryResp.GetInfo().GetValue())) switch queryResp.GetCode().GetValue() { case uint32(apimodel.Code_NotFoundResource): - return &nacospb.ConfigQueryResponse{ + rsp = &nacospb.ConfigQueryResponse{ Response: &nacospb.Response{ ResultCode: int(nacosmodel.Response_Fail.Code), ErrorCode: ErrorConfigNotFound, Message: "config data not exist", }, - }, nil + } + return rsp, nil default: - return &nacospb.ConfigQueryResponse{ + rsp = &nacospb.ConfigQueryResponse{ Response: &nacospb.Response{ ResultCode: int(nacosmodel.Response_Fail.Code), ErrorCode: int(queryResp.GetCode().GetValue()), Message: queryResp.GetInfo().GetValue(), }, - }, nil + } + return rsp, nil } } viewRelease := queryResp.GetConfigFile() - ret := &nacospb.ConfigQueryResponse{ + rsp = &nacospb.ConfigQueryResponse{ Response: &nacospb.Response{ ResultCode: int(nacosmodel.Response_Success.Code), Success: true, @@ -114,7 +134,7 @@ func (h *ConfigServer) handleGetConfigRequest(ctx context.Context, req nacospb.B Md5: viewRelease.GetMd5().GetValue(), LastModified: stringToTimestamp(viewRelease.GetReleaseTime().GetValue()), } - return ret, nil + return rsp, nil } func (h *ConfigServer) handleDeleteConfigRequest(ctx context.Context, req nacospb.BaseRequest, @@ -159,7 +179,7 @@ func (h *ConfigServer) handleWatchConfigRequest(ctx context.Context, req nacospb clientId := meta.ConnectionID specReq := watchReq.ToSpec() if watchReq.Listen { - _ = configSvr.WatchCenter().AddWatcher(clientId, specReq.GetWatchFiles(), h.BuildGrpcWatchCtx()) + watchCtx := configSvr.WatchCenter().AddWatcher(clientId, specReq.GetWatchFiles(), h.BuildGrpcWatchCtx(ctx)) for i := range specReq.GetWatchFiles() { item := specReq.GetWatchFiles()[i] namespace := item.GetNamespace().GetValue() @@ -167,7 +187,16 @@ func (h *ConfigServer) handleWatchConfigRequest(ctx context.Context, req nacospb dataId := item.GetFileName().GetValue() mdval := item.GetMd5().GetValue() - active := h.cacheSvr.ConfigFile().GetActiveRelease(namespace, group, dataId, commonmodel.ReleaseTypeFull) + var active *model.ConfigFileRelease + var match bool + if betaActive := h.cacheSvr.ConfigFile().GetGrayRelease(namespace, group, dataId); betaActive != nil { + match = h.cacheSvr.Gray().HitGrayRule(model.GetGrayConfigRealseKey(betaActive.SimpleConfigFileRelease), watchCtx.ClientLabels()) + active = betaActive + } + if !match { + active = h.cacheSvr.ConfigFile().GetActiveRelease(namespace, group, dataId) + } + // 如果 client 过来的 MD5 是一个空字符串 if (active == nil && mdval != "") || (active != nil && active.Md5 != mdval) { listenResp.ChangedConfigs = append(listenResp.ChangedConfigs, nacospb.ConfigContext{ @@ -190,12 +219,19 @@ func (h *ConfigServer) handleWatchConfigRequest(ctx context.Context, req nacospb } // BuildGrpcWatchCtx . -func (h *ConfigServer) BuildGrpcWatchCtx() config.WatchContextFactory { - return func(clientId string) config.WatchContext { +func (h *ConfigServer) BuildGrpcWatchCtx(ctx context.Context) config.WatchContextFactory { + labels := map[string]string{} + labels[model.ClientLabel_IP] = utils.ParseClientIP(ctx) + + return func(clientId string, matcher config.BetaReleaseMatcher) config.WatchContext { watchCtx := &StreamWatchContext{ clientId: clientId, connMgr: h.connMgr, + labels: labels, watchConfigFiles: utils.NewSyncMap[string, *config_manage.ClientConfigFileInfo](), + betaMatcher: func(clientLabels map[string]string, event *model.SimpleConfigFileRelease) bool { + return h.cacheSvr.Gray().HitGrayRule(model.GetGrayConfigRealseKey(event), clientLabels) + }, } return watchCtx } diff --git a/apiserver/nacosserver/v2/config/watch.go b/apiserver/nacosserver/v2/config/watch.go index c3df8c8b5..21faab30d 100644 --- a/apiserver/nacosserver/v2/config/watch.go +++ b/apiserver/nacosserver/v2/config/watch.go @@ -73,8 +73,14 @@ func (c *ConnectionClientManager) OnEvent(ctx context.Context, a any) error { type StreamWatchContext struct { clientId string + labels map[string]string connMgr *remote.ConnectionManager watchConfigFiles *utils.SyncMap[string, *apiconfig.ClientConfigFileInfo] + betaMatcher config.BetaReleaseMatcher +} + +func (c *StreamWatchContext) ClientLabels() map[string]string { + return c.labels } // IsOnce @@ -93,7 +99,7 @@ func (c *StreamWatchContext) ClientID() string { // ShouldNotify . func (c *StreamWatchContext) ShouldNotify(event *model.SimpleConfigFileRelease) bool { - key := event.ActiveKey() + key := event.FileKey() watchFile, ok := c.watchConfigFiles.Load(key) if !ok { return false diff --git a/apiserver/nacosserver/v2/discover/checker.go b/apiserver/nacosserver/v2/discover/checker.go index 8d056afc6..b8331b209 100644 --- a/apiserver/nacosserver/v2/discover/checker.go +++ b/apiserver/nacosserver/v2/discover/checker.go @@ -112,7 +112,7 @@ func (c *Checker) syncCacheData(cancel context.CancelFunc) { c.OnUpsert(val) } - c.cacheMgr.Instance().IteratorInstances(func(key string, value *model.Instance) (bool, error) { + _ = c.cacheMgr.Instance().IteratorInstances(func(key string, value *model.Instance) (bool, error) { handle(key, value) return true, nil }) diff --git a/apiserver/nacosserver/v2/discover/client_conn.go b/apiserver/nacosserver/v2/discover/client_conn.go index db688172e..0cb3a4aef 100644 --- a/apiserver/nacosserver/v2/discover/client_conn.go +++ b/apiserver/nacosserver/v2/discover/client_conn.go @@ -22,10 +22,11 @@ import ( "sync" "sync/atomic" + "go.uber.org/zap" + "github.com/polarismesh/polaris/apiserver/nacosserver/v2/remote" "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/model" - "go.uber.org/zap" ) type ( diff --git a/apiserver/nacosserver/v2/discover/instance.go b/apiserver/nacosserver/v2/discover/instance.go index 62f9f49e1..160467cb2 100644 --- a/apiserver/nacosserver/v2/discover/instance.go +++ b/apiserver/nacosserver/v2/discover/instance.go @@ -111,6 +111,63 @@ func (h *DiscoverServer) handleInstanceRequest(ctx context.Context, req nacospb. }, nil } +func (h *DiscoverServer) handlePersistentInstanceRequest(ctx context.Context, req nacospb.BaseRequest, + meta nacospb.RequestMeta) (nacospb.BaseResponse, error) { + insReq, ok := req.(*nacospb.PersistentInstanceRequest) + if !ok { + return nil, remote.ErrorInvalidRequestBodyType + } + + namespace := nacosmodel.DefaultNacosNamespace + if len(insReq.Namespace) != 0 { + namespace = insReq.Namespace + } + namespace = nacosmodel.ToPolarisNamespace(namespace) + svcName := nacosmodel.BuildServiceName(insReq.ServiceName, insReq.GroupName) + ins := nacosmodel.PrepareSpecInstance(namespace, svcName, &insReq.Instance) + ins.EnableHealthCheck = wrapperspb.Bool(false) + ins.HealthCheck = nil + + var resp *service_manage.Response + var respType string + + errCode := int(nacosmodel.ErrorCode_Success.Code) + resultCode := int(nacosmodel.Response_Success.Code) + success := true + + switch insReq.Type { + case "registerInstance": + respType = "registerInstance" + resp = h.discoverSvr.RegisterInstance(ctx, ins) + case "deregisterInstance": + respType = "deregisterInstance" + insID, errRsp := utils.CheckInstanceTetrad(ins) + if errRsp != nil { + return nil, &nacosmodel.NacosError{ + ErrCode: int32(errRsp.GetCode().GetValue()), + ErrMsg: errRsp.GetInfo().GetValue(), + } + } + ins.Id = utils.NewStringValue(insID) + resp = h.discoverSvr.DeregisterInstance(ctx, ins) + default: + return nil, &nacosmodel.NacosError{ + ErrCode: int32(nacosmodel.ExceptionCode_InvalidParam), + ErrMsg: fmt.Sprintf("Unsupported request type %s", insReq.Type), + } + } + + return &nacospb.InstanceResponse{ + Response: &nacospb.Response{ + ResultCode: resultCode, + ErrorCode: errCode, + Success: success, + Message: resp.GetInfo().GetValue(), + }, + Type: respType, + }, nil +} + func (h *DiscoverServer) handleBatchInstanceRequest(ctx context.Context, req nacospb.BaseRequest, meta nacospb.RequestMeta) (nacospb.BaseResponse, error) { batchInsReq, ok := req.(*nacospb.BatchInstanceRequest) diff --git a/apiserver/nacosserver/v2/discover/server.go b/apiserver/nacosserver/v2/discover/server.go index 561c57ea2..263f9c714 100644 --- a/apiserver/nacosserver/v2/discover/server.go +++ b/apiserver/nacosserver/v2/discover/server.go @@ -91,6 +91,13 @@ func (h *DiscoverServer) initGRPCHandlers() { return nacospb.NewInstanceRequest() }, }, + // Request + nacospb.TypePersistentInstanceRequest: { + Handler: h.handleInstanceRequest, + PayloadBuilder: func() nacospb.CustomerPayload { + return nacospb.NewPersistentInstanceRequest() + }, + }, nacospb.TypeBatchInstanceRequest: { Handler: h.handleBatchInstanceRequest, PayloadBuilder: func() nacospb.CustomerPayload { diff --git a/apiserver/nacosserver/v2/discover/subscribe.go b/apiserver/nacosserver/v2/discover/subscribe.go index 6fccdb9d3..dc5baf285 100644 --- a/apiserver/nacosserver/v2/discover/subscribe.go +++ b/apiserver/nacosserver/v2/discover/subscribe.go @@ -28,7 +28,10 @@ import ( nacosmodel "github.com/polarismesh/polaris/apiserver/nacosserver/model" nacospb "github.com/polarismesh/polaris/apiserver/nacosserver/v2/pb" "github.com/polarismesh/polaris/apiserver/nacosserver/v2/remote" + "github.com/polarismesh/polaris/common/metrics" + commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/plugin" ) func (h *DiscoverServer) handleSubscribeServiceReques(ctx context.Context, req nacospb.BaseRequest, @@ -100,37 +103,62 @@ func (h *DiscoverServer) sendPushData(sub core.Subscriber, data *core.PushData) } connCtx := context.WithValue(context.TODO(), remote.ConnIDKey{}, watcher.Key) - callback := func(resp nacospb.BaseResponse, err error) { + callback := func(attachment map[string]interface{}, resp nacospb.BaseResponse, err error) { if err != nil { nacoslog.Error("[NACOS-V2][PushCenter] receive client push error", zap.String("req-id", req.RequestId), zap.String("namespace", data.Service.Namespace), zap.String("svc", data.Service.Name), zap.Error(err)) } else { + // 刷新连接的存活时间 h.connMgr.RefreshClient(connCtx) nacoslog.Info("[NACOS-V2][PushCenter] receive client push ack", zap.String("req-id", req.RequestId), zap.String("namespace", data.Service.Namespace), zap.String("svc", data.Service.Name), zap.Any("resp", resp)) } + plugin.GetStatis().ReportDiscoverCall(metrics.ClientDiscoverMetric{ + ClientIP: client.Addr.String(), + Action: attachment["action"].(string), + Namespace: attachment["namespace"].(string), + Resource: attachment["resource"].(string), + Revision: attachment["revision"].(string), + Timestamp: commontime.CurrentMillisecond(), + CostTime: commontime.CurrentMillisecond() - attachment["start"].(int64), + Success: err == nil, + }) + } + clientResp, err := remote.MarshalPayload(req) + if err != nil { + return err } - // add inflight first - err := h.connMgr.InFlights().AddInFlight(&remote.InFlight{ + if err := h.connMgr.InFlights().AddInFlight(&remote.InFlight{ ConnID: watcher.Key, RequestID: req.RequestId, Callback: callback, ExpireTime: time.Now().Add(5 * time.Second), - }) - if err != nil { + Attachment: map[string]interface{}{ + "start": commontime.CurrentMillisecond(), + "action": "NACOS_SERVICE_PUSH", + "namespace": namespace, + "resource": "INSTANCE:" + data.Service.Group + "/" + data.Service.Name, + "revision": data.ServiceInfo.Checksum, + }, + }); err != nil { nacoslog.Error("[NACOS-V2][PushCenter] add inflight client error", zap.String("conn-id", watcher.Key), zap.String("req-id", req.RequestId), zap.String("namespace", data.Service.Namespace), zap.String("svc", data.Service.Name), zap.Error(err)) } - - clientResp, err := remote.MarshalPayload(req) - if err != nil { - return err + // 发送通知失败,直接触发 Inflight 结束 + if err = svr.SendMsg(clientResp); err != nil { + h.connMgr.InFlights().NotifyInFlight(client.ID, &nacospb.NotifySubscriberResponse{ + Response: &nacospb.Response{ + ResultCode: int(nacosmodel.Response_Fail.Code), + Message: err.Error(), + RequestId: req.RequestId, + }, + }) } - return svr.SendMsg(clientResp) + return err } diff --git a/apiserver/nacosserver/v2/option.go b/apiserver/nacosserver/v2/option.go index 4ee6f2fd3..567cbfada 100644 --- a/apiserver/nacosserver/v2/option.go +++ b/apiserver/nacosserver/v2/option.go @@ -64,6 +64,7 @@ func WithConfigSvr(configSvr config.ConfigCenterServer, originSvr config.ConfigC } } +// WithAuthSvr 设置鉴权 Server func WithAuthSvr(userSvr auth.UserServer, checkerSvr auth.StrategyServer) option { return func(svr *NacosV2Server) { svr.discoverOpt.UserSvr = userSvr diff --git a/apiserver/nacosserver/v2/pb/config_request.go b/apiserver/nacosserver/v2/pb/config_request.go index 2e5f88a53..a3bd5a5f8 100644 --- a/apiserver/nacosserver/v2/pb/config_request.go +++ b/apiserver/nacosserver/v2/pb/config_request.go @@ -17,10 +17,11 @@ package nacos_grpc_service import ( - "github.com/polarismesh/polaris/apiserver/nacosserver/model" - "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/specification/source/go/api/v1/config_manage" "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/polarismesh/polaris/apiserver/nacosserver/model" + "github.com/polarismesh/polaris/common/utils" ) type ConfigListenContext struct { diff --git a/apiserver/nacosserver/v2/pb/naming_request.go b/apiserver/nacosserver/v2/pb/naming_request.go index 26c68cf4d..673381e66 100644 --- a/apiserver/nacosserver/v2/pb/naming_request.go +++ b/apiserver/nacosserver/v2/pb/naming_request.go @@ -98,6 +98,29 @@ func (r *InstanceRequest) GetRequestType() string { return TypeInstanceRequest } +type PersistentInstanceRequest struct { + *NamingRequest + Type string `json:"type"` + Instance model.Instance `json:"instance"` +} + +func (n *PersistentInstanceRequest) RequestMeta() interface{} { + return n.NamingRequest +} + +// NewInstanceRequest +func NewPersistentInstanceRequest() *PersistentInstanceRequest { + return &PersistentInstanceRequest{ + NamingRequest: NewNamingRequest(), + Type: TypePersistentInstanceRequest, + Instance: model.Instance{}, + } +} + +func (r *PersistentInstanceRequest) GetRequestType() string { + return TypePersistentInstanceRequest +} + // BatchInstanceRequest . type BatchInstanceRequest struct { *NamingRequest diff --git a/apiserver/nacosserver/v2/pb/request.go b/apiserver/nacosserver/v2/pb/request.go index 417731e5d..680f2965e 100644 --- a/apiserver/nacosserver/v2/pb/request.go +++ b/apiserver/nacosserver/v2/pb/request.go @@ -21,21 +21,22 @@ import ( ) const ( - TypeConnectionSetupRequest = "ConnectionSetupRequest" - TypeConnectResetRequest = "ConnectResetRequest" - TypeClientDetectionRequest = "ClientDetectionRequest" - TypeHealthCheckRequest = "HealthCheckRequest" - TypeServerCheckRequest = "ServerCheckRequest" - TypeInstanceRequest = "InstanceRequest" - TypeBatchInstanceRequest = "BatchInstanceRequest" - TypeNotifySubscriberRequest = "NotifySubscriberRequest" - TypeSubscribeServiceRequest = "SubscribeServiceRequest" - TypeServiceListRequest = "ServiceListRequest" - TypeServiceQueryRequest = "ServiceQueryRequest" - TypeConfigPublishRequest = "ConfigPublishRequest" - TypeConfigQueryRequest = "ConfigQueryRequest" - TypeConfigRemoveRequest = "ConfigRemoveRequest" - TypeConfigBatchListenRequest = "ConfigBatchListenRequest" + TypeConnectionSetupRequest = "ConnectionSetupRequest" + TypeConnectResetRequest = "ConnectResetRequest" + TypeClientDetectionRequest = "ClientDetectionRequest" + TypeHealthCheckRequest = "HealthCheckRequest" + TypeServerCheckRequest = "ServerCheckRequest" + TypeInstanceRequest = "InstanceRequest" + TypePersistentInstanceRequest = "PersistentInstanceRequest" + TypeBatchInstanceRequest = "BatchInstanceRequest" + TypeNotifySubscriberRequest = "NotifySubscriberRequest" + TypeSubscribeServiceRequest = "SubscribeServiceRequest" + TypeServiceListRequest = "ServiceListRequest" + TypeServiceQueryRequest = "ServiceQueryRequest" + TypeConfigPublishRequest = "ConfigPublishRequest" + TypeConfigQueryRequest = "ConfigQueryRequest" + TypeConfigRemoveRequest = "ConfigRemoveRequest" + TypeConfigBatchListenRequest = "ConfigBatchListenRequest" ) // CustomerPayload diff --git a/apiserver/nacosserver/v2/remote/client_conn.go b/apiserver/nacosserver/v2/remote/client_conn.go index da6159a1a..82ef1b3be 100644 --- a/apiserver/nacosserver/v2/remote/client_conn.go +++ b/apiserver/nacosserver/v2/remote/client_conn.go @@ -212,7 +212,7 @@ func (h *ConnectionManager) RegisterConnection(ctx context.Context, payload *nac func (h *ConnectionManager) UnRegisterConnection(connID string) { h.lock.Lock() defer h.lock.Unlock() - eventhub.Publish(ClientConnectionEvent, &ConnectionEvent{ + _ = eventhub.Publish(ClientConnectionEvent, &ConnectionEvent{ EventType: EventClientDisConnected, ConnID: connID, Client: h.clients[connID], @@ -299,7 +299,7 @@ func (h *ConnectionManager) HandleConn(ctx context.Context, s stats.ConnStats) { defer h.lock.RUnlock() connID, _ := ctx.Value(ConnIDKey{}).(string) nacoslog.Info("[NACOS-V2][ConnMgr] grpc conn begin", zap.String("conn-id", connID)) - eventhub.Publish(ClientConnectionEvent, &ConnectionEvent{ + _ = eventhub.Publish(ClientConnectionEvent, &ConnectionEvent{ EventType: EventClientConnected, ConnID: connID, Client: h.clients[connID], @@ -409,7 +409,7 @@ func (h *ConnectionManager) ejectOutdateConnection() { ConnID: connID, RequestID: req.RequestId, ExpireTime: time.Now().Add(5 * time.Second), - Callback: func(resp nacospb.BaseResponse, err error) { + Callback: func(attachment map[string]interface{}, resp nacospb.BaseResponse, err error) { defer wait.Done() select { case <-ctx.Done(): @@ -431,7 +431,6 @@ func (h *ConnectionManager) ejectOutdateConnection() { <-ctx.Done() if errors.Is(ctx.Err(), context.DeadlineExceeded) { // TODO log - wait.Done() } for connID := range outDatedConnections { if _, ok := successConnections.Load(connID); !ok { diff --git a/apiserver/nacosserver/v2/remote/inflights.go b/apiserver/nacosserver/v2/remote/inflights.go index 63bf312fb..7e35ebecd 100644 --- a/apiserver/nacosserver/v2/remote/inflights.go +++ b/apiserver/nacosserver/v2/remote/inflights.go @@ -43,8 +43,9 @@ type ( once sync.Once ConnID string RequestID string - Callback func(nacospb.BaseResponse, error) + Callback func(map[string]interface{}, nacospb.BaseResponse, error) ExpireTime time.Time + Attachment map[string]interface{} } ) @@ -72,14 +73,14 @@ func (i *InFlights) NotifyInFlight(connID string, resp nacospb.BaseResponse) { } if resp.GetResultCode() != int(nacosmodel.Response_Success.Code) { - inflight.Callback(resp, &nacosmodel.NacosError{ + inflight.Callback(inflight.Attachment, resp, &nacosmodel.NacosError{ ErrCode: int32(resp.GetErrorCode()), ErrMsg: resp.GetMessage(), }) return } inflight.once.Do(func() { - inflight.Callback(resp, nil) + inflight.Callback(inflight.Attachment, resp, nil) }) } @@ -121,7 +122,7 @@ func (i *InFlights) notifyOutDateInFlight(ctx context.Context) { } val.inFlights.Delete(reqId) inFlight.once.Do(func() { - inFlight.Callback(nil, context.DeadlineExceeded) + inFlight.Callback(inFlight.Attachment, nil, context.DeadlineExceeded) }) }) }) diff --git a/apiserver/xdsserverv3/cache/cache.go b/apiserver/xdsserverv3/cache/cache.go index a718b7547..9ca4f1d96 100644 --- a/apiserver/xdsserverv3/cache/cache.go +++ b/apiserver/xdsserverv3/cache/cache.go @@ -98,6 +98,15 @@ func (sc *XDSCache) Fetch(ctx context.Context, request *cachev3.Request) (cachev return nil, errors.New("not implemented") } +// DeltaUpdateNodeResource . +func (sc *XDSCache) DeltaUpdateNodeResource(client *resource.XDSClient, key, typeUrl string, current map[string]types.Resource) error { + val, _ := sc.Caches.ComputeIfAbsent(key, func(_ string) cachev3.Cache { + return NewLinearCache(typeUrl) + }) + linearCache, _ := val.(*LinearCache) + return linearCache.UpdateNodeResource(client, current) +} + // DeltaUpdateResource . func (sc *XDSCache) DeltaUpdateResource(key, typeUrl string, current map[string]types.Resource) error { val, _ := sc.Caches.ComputeIfAbsent(key, func(_ string) cachev3.Cache { @@ -107,6 +116,20 @@ func (sc *XDSCache) DeltaUpdateResource(key, typeUrl string, current map[string] return linearCache.UpdateResources(current, []string{}) } +// DeltaRemoveResource . +func (sc *XDSCache) DeltaRemoveResource(key, typeUrl string, current map[string]types.Resource) error { + val, _ := sc.Caches.ComputeIfAbsent(key, func(_ string) cachev3.Cache { + return NewLinearCache(typeUrl) + }) + linearCache, _ := val.(*LinearCache) + + waitRemove := make([]string, 0, len(current)) + for k := range current { + waitRemove = append(waitRemove, k) + } + return linearCache.UpdateResources(nil, waitRemove) +} + func classify(typeUrl string, resources []string, client *resource.XDSClient) []string { isAllowNode := false _, isAllowTls := allowTlsResource[typeUrl] @@ -153,8 +176,9 @@ var ( return false }, resourcev3.RouteType: func(typeUrl string, resources []string, client *resource.XDSClient) bool { + selfSvc := resource.MakeInBoundRouteConfigName(client.GetSelfServiceKey()) for i := range resources { - if resources[i] == resource.InBoundRouteConfigName { + if resources[i] == selfSvc { return true } } diff --git a/apiserver/xdsserverv3/cache/linear.go b/apiserver/xdsserverv3/cache/linear.go index d40ec233a..6cbe3fc00 100644 --- a/apiserver/xdsserverv3/cache/linear.go +++ b/apiserver/xdsserverv3/cache/linear.go @@ -77,12 +77,16 @@ var _ cachev3.Cache = &LinearCache{} // NewLinearCache creates a new cache. See the comments on the struct definition. func NewLinearCache(typeURL string) *LinearCache { out := &LinearCache{ - typeURL: typeURL, - resources: make(map[string]types.Resource), - watchClients: make(map[string]*watchClient), - versionMap: nil, - version: 0, - versionVector: make(map[string]uint64), + typeURL: typeURL, + resources: make(map[string]types.Resource), + watchClients: make(map[string]*watchClient), + versionMap: nil, + version: 0, + versionVector: make(map[string]uint64), + nodeResources: map[string]map[string]types.Resource{}, + nodeVersionMap: map[string]map[string]string{}, + nodeVersion: map[string]uint64{}, + nodeVersionVector: map[string]map[string]uint64{}, } return out } @@ -92,15 +96,15 @@ func (cache *LinearCache) respond(value chan<- cachev3.Response, staleResources // TODO: optimize the resources slice creations across different clients if len(staleResources) == 0 { resources = make([]types.ResourceWithTTL, 0, len(cache.resources)) - for _, resource := range cache.resources { - resources = append(resources, types.ResourceWithTTL{Resource: resource}) + for _, resItem := range cache.resources { + resources = append(resources, types.ResourceWithTTL{Resource: resItem}) } } else { resources = make([]types.ResourceWithTTL, 0, len(staleResources)) for _, name := range staleResources { - resource := cache.resources[name] - if resource != nil { - resources = append(resources, types.ResourceWithTTL{Resource: resource}) + resItem := cache.resources[name] + if resItem != nil { + resources = append(resources, types.ResourceWithTTL{Resource: resItem}) } } } @@ -138,9 +142,18 @@ func (cache *LinearCache) notifyAll(watchClients map[string]*watchClient, modifi // Building the version map has a very high cost when using SetResources to do full updates. // As it is only used with delta watches, it is only maintained when applicable. - if cache.versionMap != nil { - if err := cache.updateVersionMap(modified); err != nil { - log.Errorf("failed to update version map: %v", err) + if cache.versionMap != nil || len(cache.nodeVersionMap) > 0 { + if cache.versionMap != nil { + if err := cache.updateVersionMap(modified); err != nil { + log.Errorf("failed to update version map: %v", err) + } + } + if len(cache.nodeVersionMap) > 0 { + for _, client := range watchClients { + if err := cache.updateNodeVersionMap(client.client.GetNodeID(), modified); err != nil { + log.Errorf("failed to update node version map: %v", err) + } + } } for _, client := range watchClients { @@ -204,30 +217,33 @@ func (cache *LinearCache) DeleteNodeResources(client *resource.XDSClient) error } // UpdateResource updates a resource in the collection. -func (cache *LinearCache) UpdateNodeResource(client *resource.XDSClient, name string, res types.Resource) error { - if res == nil { - return errors.New("nil resource") - } - cache.mu.Lock() - defer cache.mu.Unlock() +func (cache *LinearCache) UpdateNodeResource(client *resource.XDSClient, toUpdate map[string]types.Resource) error { + waitNotify := map[string]struct{}{} + for name, res := range toUpdate { + if res == nil { + return errors.New("nil resource") + } + cache.mu.Lock() + defer cache.mu.Unlock() - if _, ok := cache.nodeVersion[client.GetNodeID()]; !ok { - cache.nodeVersion[client.GetNodeID()] = 0 - } - cache.nodeVersion[client.GetNodeID()] = cache.nodeVersion[client.GetNodeID()] + 1 + if _, ok := cache.nodeVersion[client.GetNodeID()]; !ok { + cache.nodeVersion[client.GetNodeID()] = 0 + } + cache.nodeVersion[client.GetNodeID()] = cache.nodeVersion[client.GetNodeID()] + 1 - if _, ok := cache.nodeVersionVector[client.GetNodeID()]; !ok { - cache.nodeVersionVector[client.GetNodeID()] = map[string]uint64{} - } - cache.nodeVersionVector[client.GetNodeID()][name] = cache.nodeVersion[client.GetNodeID()] + if _, ok := cache.nodeVersionVector[client.GetNodeID()]; !ok { + cache.nodeVersionVector[client.GetNodeID()] = map[string]uint64{} + } + cache.nodeVersionVector[client.GetNodeID()][name] = cache.nodeVersion[client.GetNodeID()] - if _, ok := cache.nodeResources[client.GetNodeID()]; !ok { - cache.nodeResources[client.GetNodeID()] = map[string]types.Resource{} + if _, ok := cache.nodeResources[client.GetNodeID()]; !ok { + cache.nodeResources[client.GetNodeID()] = map[string]types.Resource{} + } + cache.nodeResources[client.GetNodeID()][name] = res + waitNotify[name] = struct{}{} } - cache.nodeResources[client.GetNodeID()][name] = res - // TODO: batch watch closures to prevent rapid updates - cache.notifyClient(client, map[string]struct{}{name: {}}) + cache.notifyClient(client, waitNotify) return nil } @@ -255,9 +271,9 @@ func (cache *LinearCache) UpdateResources(toUpdate map[string]types.Resource, to cache.version++ modified := make(map[string]struct{}, len(toUpdate)+len(toDelete)) - for name, resource := range toUpdate { + for name, resItem := range toUpdate { cache.versionVector[name] = cache.version - cache.resources[name] = resource + cache.resources[name] = resItem modified[name] = struct{}{} } for _, name := range toDelete { @@ -270,37 +286,6 @@ func (cache *LinearCache) UpdateResources(toUpdate map[string]types.Resource, to return nil } -// SetResources replaces current resources with a new set of resources. -// This function is useful for wildcard xDS subscriptions. -// This way watches that are subscribed to all resources are triggered only once regardless of how many resources are changed. -func (cache *LinearCache) SetResources(resources map[string]types.Resource) { - cache.mu.Lock() - defer cache.mu.Unlock() - - cache.version++ - - modified := map[string]struct{}{} - // Collect deleted resource names. - for name := range cache.resources { - if _, found := resources[name]; !found { - delete(cache.versionVector, name) - modified[name] = struct{}{} - } - } - - cache.resources = resources - - // Collect changed resource names. - // We assume all resources passed to SetResources are changed. - // Otherwise we would have to do proto.Equal on resources which is pretty expensive operation - for name := range resources { - cache.versionVector[name] = cache.version - modified[name] = struct{}{} - } - - cache.notifyAll(cache.watchClients, modified) -} - // GetResources returns current resources stored in the cache func (cache *LinearCache) GetResources() map[string]types.Resource { cache.mu.RLock() @@ -315,6 +300,23 @@ func (cache *LinearCache) GetResources() map[string]types.Resource { return resources } +// GetResources returns current resources stored in the cache +func (cache *LinearCache) GetNodeResources() map[string]map[string]types.Resource { + cache.mu.RLock() + defer cache.mu.RUnlock() + + // create a copy of our internal storage to avoid data races + // involving mutations of our backing map + resources := make(map[string]map[string]types.Resource, len(cache.resources)) + for k, v := range cache.nodeResources { + resources[k] = make(map[string]types.Resource) + for n, r := range v { + resources[k][n] = r + } + } + return resources +} + func parseReceiveVersion(version string) (string, string) { ret := strings.Split(version, "~") resVer, nodeVer := ret[0], ret[1] @@ -364,17 +366,22 @@ func (cache *LinearCache) CreateWatch(request *cachev3.Request, _ stream.StreamS } else if len(request.ResourceNames) == 0 { stale = lastVersion != cache.version } else { + exist := map[string]struct{}{} for _, name := range request.ResourceNames { - if saveVer, ok := cache.versionVector[name]; ok { + if saveVer, ok := cache.nodeVersionVector[nodeId][name]; ok { + exist[name] = struct{}{} // When a resource is removed, its version defaults 0 and it is not considered stale. - if lastVersion < saveVer { + if lastNodeVersion < saveVer { stale = true staleResources = append(staleResources, name) } } - if saveVer, ok := cache.nodeVersionVector[nodeId][name]; ok { + if _, ok := exist[name]; ok { + continue + } + if saveVer, ok := cache.versionVector[name]; ok { // When a resource is removed, its version defaults 0 and it is not considered stale. - if lastNodeVersion < saveVer { + if lastVersion < saveVer { stale = true staleResources = append(staleResources, name) } @@ -424,6 +431,19 @@ func (cache *LinearCache) CreateDeltaWatch(request *cachev3.DeltaRequest, state log.Errorf("failed to update version map: %v", err) } } + if len(cache.nodeVersionMap[nodeId]) == 0 { + // If we had no previously open delta watches, we need to build the version map for the first time. + // The version map will not be destroyed when the last delta watch is removed. + // This avoids constantly rebuilding when only a few delta watches are open. + modified := map[string]struct{}{} + for name := range cache.nodeResources[nodeId] { + modified[name] = struct{}{} + } + err := cache.updateNodeVersionMap(nodeId, modified) + if err != nil { + log.Errorf("failed to update node version map: %v", err) + } + } response := cache.respondDelta(request, value, state) // if respondDelta returns nil this means that there is no change in any resource version @@ -465,6 +485,32 @@ func (cache *LinearCache) updateVersionMap(modified map[string]struct{}) error { return nil } +func (cache *LinearCache) updateNodeVersionMap(id string, modified map[string]struct{}) error { + if _, ok := cache.nodeVersionMap[id]; !ok { + cache.nodeVersionMap[id] = make(map[string]string, len(modified)) + } + for name := range modified { + r, ok := cache.nodeResources[id][name] + if !ok { + // The resource was deleted + delete(cache.nodeVersionMap[id], name) + continue + } + // hash our version in here and build the version map + marshaledResource, err := cachev3.MarshalResource(r) + if err != nil { + return err + } + v := cachev3.HashResource(marshaledResource) + if v == "" { + return errors.New("failed to build resource version") + } + + cache.nodeVersionMap[id][name] = v + } + return nil +} + func (cache *LinearCache) getVersion(nodeId string) string { return cache.versionPrefix + strconv.FormatUint(cache.version, 10) + "~" + strconv.FormatUint(cache.nodeVersion[nodeId], 10) } @@ -507,65 +553,6 @@ type resourceContainer struct { systemVersion string } -func createDeltaResponse(ctx context.Context, req *cachev3.DeltaRequest, state stream.StreamState, resources resourceContainer) *cachev3.RawDeltaResponse { - // variables to build our response with - var nextVersionMap map[string]string - var filtered []types.Resource - var toRemove []string - - // If we are handling a wildcard request, we want to respond with all resources - switch { - case state.IsWildcard(): - if len(state.GetResourceVersions()) == 0 { - filtered = make([]types.Resource, 0, len(resources.resourceMap)) - } - nextVersionMap = make(map[string]string, len(resources.resourceMap)) - for name, r := range resources.resourceMap { - // Since we've already precomputed the version hashes of the new snapshot, - // we can just set it here to be used for comparison later - version := resources.versionMap[name] - nextVersionMap[name] = version - prevVersion, found := state.GetResourceVersions()[name] - if !found || (prevVersion != version) { - filtered = append(filtered, r) - } - } - - // Compute resources for removal - // The resource version can be set to "" here to trigger a removal even if never returned before - for name := range state.GetResourceVersions() { - if _, ok := resources.resourceMap[name]; !ok { - toRemove = append(toRemove, name) - } - } - default: - nextVersionMap = make(map[string]string, len(state.GetSubscribedResourceNames())) - // state.GetResourceVersions() may include resources no longer subscribed - // In the current code this gets silently cleaned when updating the version map - for name := range state.GetSubscribedResourceNames() { - prevVersion, found := state.GetResourceVersions()[name] - if r, ok := resources.resourceMap[name]; ok { - nextVersion := resources.versionMap[name] - if prevVersion != nextVersion { - filtered = append(filtered, r) - } - nextVersionMap[name] = nextVersion - } else if found { - toRemove = append(toRemove, name) - } - } - } - - return &cachev3.RawDeltaResponse{ - DeltaRequest: req, - Resources: filtered, - RemovedResources: toRemove, - NextVersionMap: nextVersionMap, - SystemVersionInfo: resources.systemVersion, - Ctx: ctx, - } -} - // newWatchClient initializes a status info data structure. func newWatchClient(node *corev3.Node) *watchClient { out := watchClient{ @@ -682,8 +669,19 @@ func (info *watchClient) addResourcesWatch(resources []string, resp chan<- cache } } -func (info *watchClient) popResourceWatchChans(resource string) []chan<- cachev3.Response { - return nil +func (info *watchClient) popResourceWatchChans(resourceName string) []chan<- cachev3.Response { + info.mu.Lock() + defer info.mu.Unlock() + + ret := make([]chan<- cachev3.Response, 0, 4) + resps, ok := info.watches[resourceName] + if !ok { + return ret + } + for r := range resps { + ret = append(ret, r) + } + return ret } func (info *watchClient) removeResourcesWatch(resources []string, resp chan<- cachev3.Response) { diff --git a/apiserver/xdsserverv3/cache/response.go b/apiserver/xdsserverv3/cache/response.go index d2e074cf5..38c0ba1b9 100644 --- a/apiserver/xdsserverv3/cache/response.go +++ b/apiserver/xdsserverv3/cache/response.go @@ -1,20 +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 cache import ( + "context" "errors" discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + "github.com/envoyproxy/go-control-plane/pkg/cache/types" cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3" + "github.com/envoyproxy/go-control-plane/pkg/server/stream/v3" ) -type NoReadyXdsResponse struct{ +func createDeltaResponse(ctx context.Context, req *cachev3.DeltaRequest, state stream.StreamState, + resources resourceContainer) *cachev3.RawDeltaResponse { + // variables to build our response with + var nextVersionMap map[string]string + var filtered []types.Resource + var toRemove []string + + // If we are handling a wildcard request, we want to respond with all resources + switch { + case state.IsWildcard(): + if len(state.GetResourceVersions()) == 0 { + filtered = make([]types.Resource, 0, len(resources.resourceMap)) + } + nextVersionMap = make(map[string]string, len(resources.resourceMap)) + for name, r := range resources.nodeResourceMap { + // Since we've already precomputed the version hashes of the new snapshot, + // we can just set it here to be used for comparison later + version := resources.nodeVersionMap[name] + nextVersionMap[name] = version + prevVersion, found := state.GetResourceVersions()[name] + if !found || (prevVersion != version) { + filtered = append(filtered, r) + } + } + + for name, r := range resources.resourceMap { + if _, exist := nextVersionMap[name]; exist { + continue + } + // Since we've already precomputed the version hashes of the new snapshot, + // we can just set it here to be used for comparison later + version := resources.versionMap[name] + nextVersionMap[name] = version + prevVersion, found := state.GetResourceVersions()[name] + if !found || (prevVersion != version) { + filtered = append(filtered, r) + } + } + + // Compute resources for removal + // The resource version can be set to "" here to trigger a removal even if never returned before + for name := range state.GetResourceVersions() { + _, commonOk := resources.resourceMap[name] + _, nodeOk := resources.nodeResourceMap[name] + if !commonOk && !nodeOk { + toRemove = append(toRemove, name) + } + } + default: + nextVersionMap = make(map[string]string, len(state.GetSubscribedResourceNames())) + // state.GetResourceVersions() may include resources no longer subscribed + // In the current code this gets silently cleaned when updating the version map + for name := range state.GetSubscribedResourceNames() { + prevVersion, found := state.GetResourceVersions()[name] + var commonOk, nodeOk bool + if r, ok := resources.nodeResourceMap[name]; ok { + nodeOk = true + nextVersion := resources.nodeVersionMap[name] + if prevVersion != nextVersion { + filtered = append(filtered, r) + } + nextVersionMap[name] = nextVersion + } + if r, ok := resources.resourceMap[name]; ok { + if _, exist := nextVersionMap[name]; !exist { + commonOk = true + nextVersion := resources.versionMap[name] + if prevVersion != nextVersion { + filtered = append(filtered, r) + } + nextVersionMap[name] = nextVersion + } + } + if found && (!commonOk && !nodeOk) { + toRemove = append(toRemove, name) + } + } + } + + return &cachev3.RawDeltaResponse{ + DeltaRequest: req, + Resources: filtered, + RemovedResources: toRemove, + NextVersionMap: nextVersionMap, + SystemVersionInfo: resources.systemVersion, + Ctx: ctx, + } +} + +type NoReadyXdsResponse struct { cachev3.DeltaResponse } -func (r *NoReadyXdsResponse) GetDeltaRequest() *discovery.DeltaDiscoveryRequest{ +func (r *NoReadyXdsResponse) GetDeltaRequest() *discovery.DeltaDiscoveryRequest { return nil } -func (r *NoReadyXdsResponse) GetDeltaDiscoveryResponse() (*discovery.DeltaDiscoveryResponse, error){ +func (r *NoReadyXdsResponse) GetDeltaDiscoveryResponse() (*discovery.DeltaDiscoveryResponse, error) { return nil, errors.New("node xds not created yet") -} \ No newline at end of file +} diff --git a/apiserver/xdsserverv3/cds.go b/apiserver/xdsserverv3/cds.go index 720382b4d..91d774504 100644 --- a/apiserver/xdsserverv3/cds.go +++ b/apiserver/xdsserverv3/cds.go @@ -27,7 +27,6 @@ import ( rawbuffer "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/raw_buffer/v3" tlstrans "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/envoyproxy/go-control-plane/pkg/cache/types" - resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/structpb" @@ -157,7 +156,6 @@ func (cds *CDSBuilder) makeCluster(svcInfo *resource.ServiceInfo, EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ ServiceName: name, EdsConfig: &core.ConfigSource{ - ResourceApiVersion: resourcev3.DefaultAPIVersion, ConfigSourceSpecifier: &core.ConfigSource_Ads{ Ads: &core.AggregatedConfigSource{}, }, diff --git a/apiserver/xdsserverv3/debug.go b/apiserver/xdsserverv3/debug.go index 2b533d2d5..4d1748940 100644 --- a/apiserver/xdsserverv3/debug.go +++ b/apiserver/xdsserverv3/debug.go @@ -50,11 +50,21 @@ func (x *XDSServer) listXDSNodes(resp http.ResponseWriter, req *http.Request) { } func (x *XDSServer) listXDSResources(resp http.ResponseWriter, req *http.Request) { + cType := req.URL.Query().Get("type") + resources := map[string]interface{}{} x.cache.Caches.ReadRange(func(key string, val cachev3.Cache) { linearCache := val.(*cache.LinearCache) + + var retVal interface{} + if cType == "node" { + retVal = linearCache.GetNodeResources() + } else { + retVal = linearCache.GetResources() + } + resources[key] = map[string]interface{}{ - "resources": linearCache.GetResources(), + "resources": retVal, } }) @@ -69,3 +79,21 @@ func (x *XDSServer) listXDSResources(resp http.ResponseWriter, req *http.Request resp.WriteHeader(http.StatusOK) _, _ = resp.Write([]byte(ret)) } + +func (x *XDSServer) listXDSCaches(resp http.ResponseWriter, req *http.Request) { + resources := []string{} + x.cache.Caches.ReadRange(func(key string, val cachev3.Cache) { + resources = append(resources, key) + }) + + data := map[string]interface{}{ + "code": apimodel.Code_ExecuteSuccess, + "info": "execute success", + "data": resources, + "count": len(resources), + } + + ret := utils.MustJson(data) + resp.WriteHeader(http.StatusOK) + _, _ = resp.Write([]byte(ret)) +} diff --git a/apiserver/xdsserverv3/eds.go b/apiserver/xdsserverv3/eds.go index 49eaff7e3..598e85614 100644 --- a/apiserver/xdsserverv3/eds.go +++ b/apiserver/xdsserverv3/eds.go @@ -75,32 +75,34 @@ func (eds *EDSBuilder) makeBoundEndpoints(option *resource.BuildOption, } var lbEndpoints []*endpoint.LbEndpoint - for _, instance := range serviceInfo.Instances { - // 处于隔离状态或者权重为0的实例不进行下发 - if !resource.IsNormalEndpoint(instance) { - continue - } - ep := &endpoint.LbEndpoint{ - HostIdentifier: &endpoint.LbEndpoint_Endpoint{ - Endpoint: &endpoint.Endpoint{ - Address: &core.Address{ - Address: &core.Address_SocketAddress{ - SocketAddress: &core.SocketAddress{ - Protocol: core.SocketAddress_TCP, - Address: instance.Host.Value, - PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: instance.Port.Value, + if !option.ForceDelete { + for _, instance := range serviceInfo.Instances { + // 处于隔离状态或者权重为0的实例不进行下发 + if !resource.IsNormalEndpoint(instance) { + continue + } + ep := &endpoint.LbEndpoint{ + HostIdentifier: &endpoint.LbEndpoint_Endpoint{ + Endpoint: &endpoint.Endpoint{ + Address: &core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + Protocol: core.SocketAddress_TCP, + Address: instance.Host.Value, + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: instance.Port.Value, + }, }, }, }, }, }, - }, - HealthStatus: resource.FormatEndpointHealth(instance), - LoadBalancingWeight: utils.NewUInt32Value(instance.GetWeight().GetValue()), - Metadata: resource.GenEndpointMetaFromPolarisIns(instance), + HealthStatus: resource.FormatEndpointHealth(instance), + LoadBalancingWeight: utils.NewUInt32Value(instance.GetWeight().GetValue()), + Metadata: resource.GenEndpointMetaFromPolarisIns(instance), + } + lbEndpoints = append(lbEndpoints, ep) } - lbEndpoints = append(lbEndpoints, ep) } cla := &endpoint.ClusterLoadAssignment{ diff --git a/apiserver/xdsserverv3/generate.go b/apiserver/xdsserverv3/generate.go index 70a5278d7..fd29098ff 100644 --- a/apiserver/xdsserverv3/generate.go +++ b/apiserver/xdsserverv3/generate.go @@ -20,12 +20,12 @@ package xdsserverv3 import ( "errors" "strconv" + "sync" "time" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" "github.com/envoyproxy/go-control-plane/pkg/cache/types" cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3" - resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "go.uber.org/atomic" "go.uber.org/zap" @@ -37,51 +37,115 @@ import ( "github.com/polarismesh/polaris/service" ) +var ( + ErrorNoSupportXDSType = errors.New("unsupport xds build type") +) + +type ( + ServiceInfos map[string]map[model.ServiceKey]*resource.ServiceInfo + DemandConfs map[resource.RunType]map[string]map[string]struct{} + XDSGenerate func(xdsType resource.XDSType, opt *resource.BuildOption) +) + // XdsResourceGenerator is the xDS resource generator type XdsResourceGenerator struct { namingServer service.DiscoverServer cache *cache.XDSCache versionNum *atomic.Uint64 xdsNodesMgr *resource.XDSNodeManager + + beforeDemandConfs DemandConfs } func (x *XdsResourceGenerator) Generate(versionLocal string, - registryInfo map[string]map[model.ServiceKey]*resource.ServiceInfo) { + needUpdate, needRemove ServiceInfos) { // 如果没有任何一个 XDS Node 接入则不会生成与 Node 有关的 XDS Resource if x.xdsNodesMgr.HasEnvoyNodes() { - // 只构建 Sidecar 特有的 XDS 数据 - _ = x.buildSidecarXDSCache(registryInfo) + // 只构建 OnDemand 场景涉及的 XDS 数据 + _ = x.buildOnDemandXDSCache(needUpdate) + // 只构建 Envoy Node 特有的 XDS 数据 + _ = x.buildMoreEnvoyXDSCache(needUpdate, needRemove) } - // CDS/EDS/VHDS 一起构建 - for namespace, services := range registryInfo { - opt := &resource.BuildOption{ - RunType: resource.RunTypeSidecar, - Namespace: namespace, - Services: services, - TrafficDirection: corev3.TrafficDirection_OUTBOUND, - TLSMode: resource.TLSModeNone, + deltaOp := func(runType resource.RunType, infos ServiceInfos, f XDSGenerate) { + direction := corev3.TrafficDirection_OUTBOUND + if runType == resource.RunTypeGateway { + direction = corev3.TrafficDirection_INBOUND + } + // CDS/EDS/VHDS 一起构建 + for namespace, services := range infos { + opt := &resource.BuildOption{ + RunType: runType, + Namespace: namespace, + Services: services, + TrafficDirection: direction, + TLSMode: resource.TLSModeNone, + } + f(resource.RDS, opt) + f(resource.EDS, opt) + f(resource.VHDS, opt) + // 默认构建没有设置 TLS 的 CDS 资源 + f(resource.CDS, opt) + + // 构建设置了 TLS Mode == Strict 的 CDS 资源 + opt.TLSMode = resource.TLSModeStrict + f(resource.CDS, opt) + // 构建设置了 TLS Mode == Permissive 的 CDS 资源 + opt.TLSMode = resource.TLSModePermissive + f(resource.CDS, opt) } - x.buildAndDeltaUpdate(resource.RDS, opt) - x.buildAndDeltaUpdate(resource.EDS, opt) - x.buildAndDeltaUpdate(resource.VHDS, opt) - // 默认构建没有设置 TLS 的 CDS 资源 - x.buildAndDeltaUpdate(resource.CDS, opt) - - // 构建设置了 TLS Mode == Strict 的 CDS 资源 - opt.TLSMode = resource.TLSModeStrict - x.buildAndDeltaUpdate(resource.CDS, opt) - // 构建设置了 TLS Mode == Permissive 的 CDS 资源 - opt.TLSMode = resource.TLSModePermissive - x.buildAndDeltaUpdate(resource.CDS, opt) + } + + wg := &sync.WaitGroup{} + wg.Add(2) + go func() { + defer wg.Done() + + // 处理 Sideacr + deltaOp(resource.RunTypeSidecar, needUpdate, x.buildAndDeltaUpdate) + deltaOp(resource.RunTypeSidecar, needRemove, x.buildAndDeltaRemove) + }() + + go func() { + defer wg.Done() + + // 处理 Gateway + deltaOp(resource.RunTypeGateway, needUpdate, x.buildAndDeltaUpdate) + deltaOp(resource.RunTypeGateway, needRemove, x.buildAndDeltaRemove) + }() + + wg.Wait() +} + +func (x *XdsResourceGenerator) buildAndDeltaRemove(xdsType resource.XDSType, opt *resource.BuildOption) { + opt.ForceDelete = true + xxds, err := x.generateXDSResource(xdsType, opt) + if err != nil { + log.Error("[XDS][Envoy] generate xds resource fail", zap.String("type", xdsType.String()), zap.Error(err)) + return + } + + typeUrl := xdsType.ResourceType() + cacheKey := xdsType.ResourceType() + "~" + opt.Namespace + if opt.TLSMode != resource.TLSModeNone { + cacheKey = cacheKey + "~" + string(opt.TLSMode) + } + if opt.OpenOnDemand { + cacheKey = cacheKey + "~" + opt.OnDemandServer + } + if err := x.cache.DeltaRemoveResource(cacheKey, typeUrl, cachev3.IndexRawResourcesByName(xxds)); err != nil { + log.Error("[XDS][Envoy] delta update fail", zap.String("cache-key", cacheKey), + zap.String("type", xdsType.String()), zap.Error(err)) + return } } func (x *XdsResourceGenerator) buildAndDeltaUpdate(xdsType resource.XDSType, opt *resource.BuildOption) { + opt.ForceDelete = false xxds, err := x.generateXDSResource(xdsType, opt) if err != nil { - log.Error("[XDS][Sidecar] build common fail", zap.String("type", xdsType.String()), zap.Error(err)) + log.Error("[XDS][Envoy] generate xds resource fail", zap.String("type", xdsType.String()), zap.Error(err)) return } @@ -93,155 +157,116 @@ func (x *XdsResourceGenerator) buildAndDeltaUpdate(xdsType resource.XDSType, opt // 与 XDS Node 有关的全部都有单独的 Cache 缓存处理 if opt.Client != nil { cacheKey = xdsType.ResourceType() + "~" + opt.Client.Node.Id + err = x.cache.DeltaUpdateNodeResource(opt.Client, cacheKey, typeUrl, cachev3.IndexRawResourcesByName(xxds)) + } else { + if opt.OpenOnDemand { + cacheKey = cacheKey + "~" + opt.OnDemandServer + } + err = x.cache.DeltaUpdateResource(cacheKey, typeUrl, cachev3.IndexRawResourcesByName(xxds)) } - - if err := x.cache.DeltaUpdateResource(cacheKey, typeUrl, cachev3.IndexRawResourcesByName(xxds)); err != nil { - log.Error("[XDS][Sidecar] delta update fail", zap.String("cache-key", cacheKey), + if err != nil { + log.Error("[XDS][Envoy] delta update fail", zap.String("cache-key", cacheKey), zap.String("type", xdsType.String()), zap.Error(err)) - return } } -func (x *XdsResourceGenerator) buildSidecarXDSCache(registryInfo map[string]map[model.ServiceKey]*resource.ServiceInfo) error { +// buildOnDemandXDSCache 只构建 OnDemand XDS Resource +func (x *XdsResourceGenerator) buildOnDemandXDSCache(needUpdate ServiceInfos) error { + // runType -> []ns -> []demand-server + demandConfs := x.xdsNodesMgr.ListDemandConfs() - nodes := x.xdsNodesMgr.ListSidecarNodes() - if len(nodes) == 0 || len(registryInfo) == 0 { - // 如果没有任何一个 XDS Sidecar Node 客户端,不做任何操作 - log.Info("[XDS][Sidecar] xds nodes or registryInfo is empty", zap.Int("nodes", len(nodes)), - zap.Int("register", len(registryInfo))) - return nil - } + deltaOp := func(demandConfs DemandConfs, needUpdate ServiceInfos, f XDSGenerate) { + for runType := range demandConfs { + for ns, svrs := range demandConfs[runType] { + svcInfos := needUpdate[ns] + for demandSvr := range svrs { + opt := &resource.BuildOption{ + RunType: resource.RunType(runType), + Namespace: ns, + Services: svcInfos, + OpenOnDemand: true, + OnDemandServer: demandSvr, + } - for i := range nodes { - node := nodes[i] - xdsNode := node - opt := &resource.BuildOption{ - RunType: resource.RunTypeSidecar, - Client: xdsNode, - TLSMode: node.TLSMode, - Namespace: xdsNode.GetSelfNamespace(), - OpenOnDemand: xdsNode.OpenOnDemand, - OnDemandServer: xdsNode.OnDemandServer, - SelfService: model.ServiceKey{ - Namespace: xdsNode.GetSelfNamespace(), - Name: xdsNode.GetSelfService(), - }, + opt.TrafficDirection = corev3.TrafficDirection_OUTBOUND + // 构建 OUTBOUND RDS 资源 + f(resource.RDS, opt) + } + } } - if services,ok:=registryInfo[xdsNode.GetSelfNamespace()];ok{ - opt.Services=services - } - - opt.TrafficDirection = corev3.TrafficDirection_OUTBOUND - // 构建 OUTBOUND LDS 资源 - x.buildAndDeltaUpdate(resource.LDS, opt) - // 构建 OUTBOUND RDS 资源 - x.buildAndDeltaUpdate(resource.RDS, opt) - opt.TrafficDirection = corev3.TrafficDirection_INBOUND - // 构建 INBOUND LDS 资源 - x.buildAndDeltaUpdate(resource.LDS, opt) - // 构建 INBOUND EDS 资源 - x.buildAndDeltaUpdate(resource.EDS, opt) - // 构建 INBOUND RDS 资源 - x.buildAndDeltaUpdate(resource.RDS, opt) + } + + waitUpdate := demandConfs + waitRemove := findWaitRemoveDemandConfs(x.beforeDemandConfs, demandConfs) + + deltaOp(DemandConfs(waitUpdate), needUpdate, x.buildAndDeltaUpdate) + deltaOp(DemandConfs(waitRemove), needUpdate, x.buildAndDeltaUpdate) + + x.beforeDemandConfs = demandConfs return nil } -// buildGatewayXDSCache 网关场景是允许跨命名空间直接进行访问 -func (x *XdsResourceGenerator) buildGatewayXDSCache(versionLocal string, - registryInfo map[string]map[model.ServiceKey]*resource.ServiceInfo) error { - - nodes := x.xdsNodesMgr.ListGatewayNodes() - if len(nodes) == 0 || len(registryInfo) == 0 { - // 如果没有任何一个 XDS Gateway Node 客户端,不做任何操作 - log.Info("[XDS][Gateway][V2] xds nodes or registryInfo is empty", zap.Int("nodes", len(nodes)), - zap.Int("registr", len(registryInfo))) +func (x *XdsResourceGenerator) buildMoreEnvoyXDSCache(needUpdate, needRemove ServiceInfos) error { + nodes := x.xdsNodesMgr.ListEnvoyNodes() + if len(nodes) == 0 || len(needUpdate) == 0 { + // 如果没有任何一个 XDS Sidecar Node 客户端,不做任何操作 + log.Info("[XDS][Envoy] xds nodes or update info is empty", zap.Int("nodes", len(nodes)), + zap.Int("need-update", len(needUpdate))) return nil } - alreadyMakeCache := map[string]struct{}{} for i := range nodes { - node := nodes[i] - cacheKey := (resource.PolarisNodeHash{}).ID(node.Node) - if _, exist := alreadyMakeCache[cacheKey]; exist { - continue - } - alreadyMakeCache[cacheKey] = struct{}{} - if err := x.makeGatewaySnapshot(node, node.TLSMode, versionLocal, registryInfo); err != nil { - log.Error("[XDS][Gateway][V2] make snapshot fail", zap.String("cacheKey", cacheKey), - zap.Error(err)) + if err := x.buildOneEnvoyXDSCache(nodes[i], needUpdate, needRemove); err != nil { + return err } } return nil } -// makeGatewaySnapshot nodeId must be like gateway~namespace -func (x *XdsResourceGenerator) makeGatewaySnapshot(xdsNode *resource.XDSClient, tlsMode resource.TLSMode, - version string, registryInfo map[string]map[model.ServiceKey]*resource.ServiceInfo) error { - - opt := &resource.BuildOption{ - TLSMode: tlsMode, - } - var ( - allEndpoints []types.Resource - allClusters []types.Resource - allRouters []types.Resource - ) - for namespace, services := range registryInfo { - opt.Services = services - opt.Namespace = namespace - // 构建 endpoints XDS 资源缓存数据,这里不需要下发网关的自己的 - endpoints, err := x.generateXDSResource(resource.EDS, opt) - if err != nil { - return err - } - allEndpoints = append(allEndpoints, endpoints...) - // 构建 cluster XDS 资源缓存数据 - clusters, err := x.generateXDSResource(resource.CDS, opt) - if err != nil { - return err - } - allClusters = append(allClusters, clusters...) - // 构建 route XDS 资源缓存 - routers, err := x.generateXDSResource(resource.RDS, opt) - if err != nil { - return err +func (x *XdsResourceGenerator) buildOneEnvoyXDSCache(node *resource.XDSClient, needUpdate, needRemove ServiceInfos) error { + deltaOp := func(infos ServiceInfos, f XDSGenerate) { + opt := &resource.BuildOption{ + RunType: node.RunType, + Client: node, + TLSMode: node.TLSMode, + Namespace: node.GetSelfNamespace(), + Services: infos[node.GetSelfNamespace()], + OpenOnDemand: node.OpenOnDemand, + OnDemandServer: node.OnDemandServer, + SelfService: model.ServiceKey{ + Namespace: node.GetSelfNamespace(), + Name: node.GetSelfService(), + }, } - allRouters = append(allRouters, routers...) - } - // 构建 listener XDS 资源缓存 - listeners, err := x.generateXDSResource(resource.LDS, opt) - if err != nil { - return err + opt.TrafficDirection = corev3.TrafficDirection_OUTBOUND + // 构建 OUTBOUND LDS 资源 + f(resource.LDS, opt) + // 构建 OUTBOUND RDS 资源 + f(resource.RDS, opt) + opt.TrafficDirection = corev3.TrafficDirection_INBOUND + // 构建 INBOUND LDS 资源 + f(resource.LDS, opt) + // 构建 INBOUND EDS 资源 + f(resource.EDS, opt) + // 构建 INBOUND RDS 资源 + f(resource.RDS, opt) } - resources := make(map[resourcev3.Type][]types.Resource) - resources[resourcev3.EndpointType] = allEndpoints - resources[resourcev3.ClusterType] = allClusters - resources[resourcev3.RouteType] = allRouters - resources[resourcev3.ListenerType] = listeners - cacheKey := (resource.PolarisNodeHash{}).ID(xdsNode.Node) - - for typeUrl, resources := range resources { - if err := x.cache.DeltaUpdateResource(xdsNode.Node.Id, typeUrl, cachev3.IndexRawResourcesByName(resources)); err != nil { - // TODO: need log - } - } - // 为每个 nodeId 刷写 cache ,推送 xds 更新 - log.Info("[XDS][Gateway] upsert xds resource success", zap.String("cacheKey", cacheKey)) + deltaOp(needUpdate, x.buildAndDeltaUpdate) return nil } func (x *XdsResourceGenerator) generateXDSResource(xdsType resource.XDSType, opt *resource.BuildOption) ([]types.Resource, error) { - // TODO 需要预埋相关 XDS 资源生成时间开销 + // 需要预埋相关 XDS 资源生成时间开销 start := time.Now() defer func() { plugin.GetStatis().ReportCallMetrics(metrics.CallMetric{ Type: metrics.XDSResourceBuildCallMetric, - API: string(xdsType), + API: xdsType.String(), Protocol: "XDS", Times: 1, Duration: time.Since(start), @@ -267,7 +292,7 @@ func (x *XdsResourceGenerator) generateXDSResource(xdsType resource.XDSType, case resource.VHDS: xdsBuilder = &VHDSBuilder{} default: - return nil, errors.New("unsupport xds build type") + return nil, ErrorNoSupportXDSType } // 构建 XDS 资源缓存数据 @@ -278,3 +303,26 @@ func (x *XdsResourceGenerator) generateXDSResource(xdsType resource.XDSType, } return resources.([]types.Resource), nil } + +func findWaitRemoveDemandConfs(before, after DemandConfs) DemandConfs { + ret := map[resource.RunType]map[string]map[string]struct{}{ + resource.RunTypeSidecar: {}, + resource.RunTypeGateway: {}, + } + + for runT, nsSvrs := range before { + for ns, svrs := range nsSvrs { + if _, ok := after[runT][ns]; !ok { + ret[runT][ns] = svrs + continue + } + ret[runT][ns] = map[string]struct{}{} + for svr := range svrs { + if _, ok := after[runT][ns][svr]; !ok { + ret[runT][ns][svr] = struct{}{} + } + } + } + } + return ret +} diff --git a/apiserver/xdsserverv3/hds.go b/apiserver/xdsserverv3/hds.go index d494e1457..d99969831 100644 --- a/apiserver/xdsserverv3/hds.go +++ b/apiserver/xdsserverv3/hds.go @@ -123,8 +123,7 @@ func (x *XDSServer) StreamHealthCheck(checksvr healthservice.HealthDiscoveryServ return err } } - endpointHealthResponse := req.GetEndpointHealthResponse() - if nil != endpointHealthResponse { + if endpointHealthResponse := req.GetEndpointHealthResponse(); nil != endpointHealthResponse { // 处理心跳上报 if client == nil { return status.Errorf(codes.NotFound, "xds node info not found") @@ -409,14 +408,6 @@ func (x *XDSServer) processEndpointHealthResponse( code, namespaceName, serviceName, host, port) } default: - // 直接更新为不健康 - // code := x.doUpdateUnhealthy(ctx, namespace, serviceName, host, port) - // if code != apimodel.Code_ExecuteSuccess && code != apimodel.Code_NotFoundResource { - // err := status.Errorf(codes.InvalidArgument, - // "fail to do update unhealthy, code is %d, namespace %s, service %s, host %s, port %d", - // code, namespace, serviceName, host, port) - // log.Errorf("[XdsV2Server] fail to process endpoint health, err is %v", err) - // } } } } diff --git a/apiserver/xdsserverv3/hook.go b/apiserver/xdsserverv3/hook.go index 4c4804e61..4a0e240a9 100644 --- a/apiserver/xdsserverv3/hook.go +++ b/apiserver/xdsserverv3/hook.go @@ -28,15 +28,31 @@ import ( func (x *XDSServer) OnCreateWatch(request *cachev3.Request, streamState stream.StreamState, value chan cachev3.Response) { x.activeUpdateTask() + + client := x.nodeMgr.GetNode(request.GetNode().Id) + if client == nil { + return + } + _ = x.resourceGenerator.buildOneEnvoyXDSCache(client, x.registryInfo, nil) } // OnCreateDeltaWatch before call cachev3.SnapshotCache OnCreateDeltaWatch func (x *XDSServer) OnCreateDeltaWatch(request *cachev3.DeltaRequest, state stream.StreamState, value chan cachev3.DeltaResponse) { x.activeUpdateTask() + client := x.nodeMgr.GetNode(request.GetNode().Id) + if client == nil { + return + } + _ = x.resourceGenerator.buildOneEnvoyXDSCache(client, x.registryInfo, nil) } // OnFetch before call cachev3.SnapshotCache OnFetch func (x *XDSServer) OnFetch(ctx context.Context, request *cachev3.Request) { x.activeUpdateTask() + client := x.nodeMgr.GetNode(request.GetNode().Id) + if client == nil { + return + } + _ = x.resourceGenerator.buildOneEnvoyXDSCache(client, x.registryInfo, nil) } diff --git a/apiserver/xdsserverv3/lds.go b/apiserver/xdsserverv3/lds.go index f529bc34c..f9b9d8fc1 100644 --- a/apiserver/xdsserverv3/lds.go +++ b/apiserver/xdsserverv3/lds.go @@ -127,12 +127,12 @@ func (lds *LDSBuilder) makeListener(option *resource.BuildOption, var boundHCM *hcm.HttpConnectionManager selfService := option.SelfService if isGateway { - boundHCM = resource.MakeGatewayBoundHCM(selfService) + boundHCM = resource.MakeGatewayBoundHCM(selfService, option) } else { if option.OpenOnDemand && direction == core.TrafficDirection_OUTBOUND { boundHCM = resource.MakeSidecarOnDemandOutBoundHCM(selfService, option) } else { - boundHCM = resource.MakeSidecarBoundHCM(selfService, direction) + boundHCM = resource.MakeSidecarBoundHCM(selfService, direction, option) } } diff --git a/apiserver/xdsserverv3/rds.go b/apiserver/xdsserverv3/rds.go index 9cce9d3d5..fea4f6313 100644 --- a/apiserver/xdsserverv3/rds.go +++ b/apiserver/xdsserverv3/rds.go @@ -18,14 +18,14 @@ package xdsserverv3 import ( + "fmt" + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" - on_demandv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/on_demand/v3" v32 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" "github.com/envoyproxy/go-control-plane/pkg/cache/types" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" - "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" "github.com/polarismesh/polaris/apiserver/xdsserverv3/resource" @@ -76,18 +76,22 @@ func (rds *RDSBuilder) makeSidecarInBoundRouteConfiguration(option *resource.Bui if !selfService.IsExact() { return []types.Resource{} } - return []types.Resource{ - &route.RouteConfiguration{ - Name: resource.MakeInBoundRouteConfigName(selfService), - ValidateClusters: wrapperspb.Bool(false), - VirtualHosts: []*route.VirtualHost{ - { - Name: resource.MakeServiceName(selfService, corev3.TrafficDirection_INBOUND, option), - Domains: []string{"*"}, - Routes: rds.makeSidecarInBoundRoutes(selfService, corev3.TrafficDirection_INBOUND, option), - }, + routeConf := &route.RouteConfiguration{ + Name: resource.MakeInBoundRouteConfigName(selfService), + ValidateClusters: wrapperspb.Bool(false), + } + + if !option.ForceDelete { + routeConf.VirtualHosts = []*route.VirtualHost{ + { + Name: resource.MakeServiceName(selfService, corev3.TrafficDirection_INBOUND, option), + Domains: []string{"*"}, + Routes: rds.makeSidecarInBoundRoutes(selfService, corev3.TrafficDirection_INBOUND, option), }, - }, + } + } + return []types.Resource{ + routeConf, } } @@ -97,12 +101,8 @@ func (rds *RDSBuilder) makeSidecarOutBoundRouteConfiguration(option *resource.Bu routeConfs []types.Resource hosts []*route.VirtualHost ) - - routeConfiguration := &route.RouteConfiguration{ - Name: resource.OutBoundRouteConfigName, - ValidateClusters: wrapperspb.Bool(false), - } - if !option.OpenOnDemand { + baseRouteName := resource.OutBoundRouteConfigName + if !option.ForceDelete { // step 1: 生成服务的 OUTBOUND 规则 services := option.Services for svcKey, serviceInfo := range services { @@ -113,58 +113,37 @@ func (rds *RDSBuilder) makeSidecarOutBoundRouteConfiguration(option *resource.Bu } hosts = append(hosts, vHost) } - hosts = append(hosts, resource.BuildAllowAnyVHost()) - routeConfiguration.VirtualHosts = hosts } - + hosts = append(hosts, resource.BuildAllowAnyVHost()) if option.OpenOnDemand { - routeConfiguration.TypedPerFilterConfig = map[string]*anypb.Any{ - "envoy.filters.http.on_demand": resource.MustNewAny(&on_demandv3.PerRouteConfig{ - Odcds: &on_demandv3.OnDemandCds{ - Source: &corev3.ConfigSource{ - ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{ - ApiConfigSource: &corev3.ApiConfigSource{ - ApiType: corev3.ApiConfigSource_DELTA_GRPC, - TransportApiVersion: corev3.ApiVersion_V3, - GrpcServices: []*corev3.GrpcService{ - { - TargetSpecifier: &corev3.GrpcService_GoogleGrpc_{ - GoogleGrpc: &corev3.GrpcService_GoogleGrpc{ - TargetUri: option.OnDemandServer, - StatPrefix: "polaris_odcds", - }, - }, - }, - }, - }, - }, - ResourceApiVersion: corev3.ApiVersion_V3, - }, - }, - }), - } - routeConfiguration.Vhds = &route.Vhds{ - ConfigSource: &corev3.ConfigSource{ - ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{ - ApiConfigSource: &corev3.ApiConfigSource{ - ApiType: corev3.ApiConfigSource_DELTA_GRPC, - TransportApiVersion: corev3.ApiVersion_V3, - GrpcServices: []*corev3.GrpcService{ - { - TargetSpecifier: &corev3.GrpcService_GoogleGrpc_{ - GoogleGrpc: &corev3.GrpcService_GoogleGrpc{ - TargetUri: option.OnDemandServer, - StatPrefix: "polaris_vhds", - }, - }, - }, - }, - }, - }, - ResourceApiVersion: corev3.ApiVersion_V3, - }, - } + baseRouteName = fmt.Sprintf("%s|%s|Demand|%s", resource.OutBoundRouteConfigName, option.Namespace, option.OnDemandServer) + // routeConfiguration.Vhds = &route.Vhds{ + // ConfigSource: &corev3.ConfigSource{ + // ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{ + // ApiConfigSource: &corev3.ApiConfigSource{ + // ApiType: corev3.ApiConfigSource_DELTA_GRPC, + // TransportApiVersion: corev3.ApiVersion_V3, + // GrpcServices: []*corev3.GrpcService{ + // { + // TargetSpecifier: &corev3.GrpcService_GoogleGrpc_{ + // GoogleGrpc: &corev3.GrpcService_GoogleGrpc{ + // TargetUri: option.OnDemandServer, + // StatPrefix: "polaris_vhds", + // }, + // }, + // }, + // }, + // }, + // }, + // ResourceApiVersion: corev3.ApiVersion_V3, + // }, + // } + } + routeConfiguration := &route.RouteConfiguration{ + Name: baseRouteName, + ValidateClusters: wrapperspb.Bool(false), } + routeConfiguration.VirtualHosts = hosts routeConfs = append(routeConfs, routeConfiguration) return routeConfs } diff --git a/apiserver/xdsserverv3/resource/api.go b/apiserver/xdsserverv3/resource/api.go index 9fc451803..74928b90f 100644 --- a/apiserver/xdsserverv3/resource/api.go +++ b/apiserver/xdsserverv3/resource/api.go @@ -43,6 +43,8 @@ type BuildOption struct { // 不是比带,只有在 EDS 生成,并且是处理 INBOUND 的时候才会设置 Client *XDSClient TrafficDirection corev3.TrafficDirection + // ForceDelete 如果设置了该字段值为 true, 则不会真正执行 XDS 的构建工作, 仅仅生成对应资源的 Name 名称用于清理 + ForceDelete bool } func (opt *BuildOption) Clone() *BuildOption { diff --git a/apiserver/xdsserverv3/resource/help.go b/apiserver/xdsserverv3/resource/help.go index dfc76f9ef..1c0d85b67 100644 --- a/apiserver/xdsserverv3/resource/help.go +++ b/apiserver/xdsserverv3/resource/help.go @@ -44,7 +44,6 @@ import ( v32 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" - resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "github.com/golang/protobuf/ptypes" _struct "github.com/golang/protobuf/ptypes/struct" @@ -517,24 +516,6 @@ func BuildRateLimitActionHeaderValueMatch(key, value string, return headerValueMatch } -// 默认路由 -func MakeDefaultRoute(trafficDirection corev3.TrafficDirection, svcKey model.ServiceKey, opt *BuildOption) *route.Route { - return &route.Route{ - Match: &route.RouteMatch{ - PathSpecifier: &route.RouteMatch_Prefix{ - Prefix: "/", - }, - }, - Action: &route.Route_Route{ - Route: &route.RouteAction{ - ClusterSpecifier: &route.RouteAction_Cluster{ - Cluster: MakeServiceName(svcKey, trafficDirection, opt), - }, - }, - }, - } -} - func GenerateServiceDomains(serviceInfo *ServiceInfo) []string { // k8s dns 可解析的服务名 domain := serviceInfo.Name + "." + serviceInfo.Namespace @@ -593,6 +574,51 @@ func MakeGatewayRoute(trafficDirection corev3.TrafficDirection, routeMatch *rout return sidecarRoute } +// 默认路由 +func MakeDefaultRoute(trafficDirection corev3.TrafficDirection, svcKey model.ServiceKey, opt *BuildOption) *route.Route { + routeConf := &route.Route{ + Match: &route.RouteMatch{ + PathSpecifier: &route.RouteMatch_Prefix{ + Prefix: "/", + }, + }, + Action: &route.Route_Route{ + Route: &route.RouteAction{ + ClusterSpecifier: &route.RouteAction_Cluster{ + Cluster: MakeServiceName(svcKey, trafficDirection, opt), + }, + }, + }, + } + if opt.OpenOnDemand { + routeConf.TypedPerFilterConfig = map[string]*anypb.Any{ + "envoy.filters.http.on_demand": MustNewAny(&on_demandv3.PerRouteConfig{ + Odcds: &on_demandv3.OnDemandCds{ + Source: &corev3.ConfigSource{ + ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{ + ApiConfigSource: &corev3.ApiConfigSource{ + ApiType: corev3.ApiConfigSource_DELTA_GRPC, + TransportApiVersion: corev3.ApiVersion_V3, + GrpcServices: []*corev3.GrpcService{ + { + TargetSpecifier: &corev3.GrpcService_GoogleGrpc_{ + GoogleGrpc: &corev3.GrpcService_GoogleGrpc{ + TargetUri: opt.OnDemandServer, + StatPrefix: "polaris_odcds", + }, + }, + }, + }, + }, + }, + }, + }, + }), + } + } + return routeConf +} + func MakeSidecarRoute(trafficDirection corev3.TrafficDirection, routeMatch *route.RouteMatch, svcInfo *ServiceInfo, destinations []*traffic_manage.DestinationGroup, opt *BuildOption) *route.Route { weightClusters := BuildWeightClustersV2(trafficDirection, destinations, opt) @@ -609,6 +635,32 @@ func MakeSidecarRoute(trafficDirection corev3.TrafficDirection, routeMatch *rout }, }, } + if opt.OpenOnDemand { + currentRoute.TypedPerFilterConfig = map[string]*anypb.Any{ + "envoy.filters.http.on_demand": MustNewAny(&on_demandv3.PerRouteConfig{ + Odcds: &on_demandv3.OnDemandCds{ + Source: &corev3.ConfigSource{ + ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{ + ApiConfigSource: &corev3.ApiConfigSource{ + ApiType: corev3.ApiConfigSource_DELTA_GRPC, + TransportApiVersion: corev3.ApiVersion_V3, + GrpcServices: []*corev3.GrpcService{ + { + TargetSpecifier: &corev3.GrpcService_GoogleGrpc_{ + GoogleGrpc: &corev3.GrpcService_GoogleGrpc{ + TargetUri: opt.OnDemandServer, + StatPrefix: "polaris_odcds", + }, + }, + }, + }, + }, + }, + }, + }, + }), + } + } return currentRoute } @@ -641,7 +693,9 @@ func MakeServiceName(svcKey model.ServiceKey, trafficDirection corev3.TrafficDir return fmt.Sprintf("%s|%s|%s", corev3.TrafficDirection_name[int32(trafficDirection)], svcKey.Namespace, svcKey.Name) } - return svcKey.Name + "." + svcKey.Namespace + // return svcKey.Name + "." + svcKey.Namespace + return fmt.Sprintf("%s|%s|%s", corev3.TrafficDirection_name[int32(trafficDirection)], + svcKey.Namespace, svcKey.Name) } // MakeVHDSServiceName . @@ -741,7 +795,7 @@ func MakeSidecarOnDemandOutBoundHCM(svcKey model.ServiceKey, option *BuildOption manager := &hcm.HttpConnectionManager{ CodecType: hcm.HttpConnectionManager_AUTO, StatPrefix: corev3.TrafficDirection_name[int32(corev3.TrafficDirection_OUTBOUND)] + "_HTTP", - RouteSpecifier: routeSpecifier(core.TrafficDirection_OUTBOUND), + RouteSpecifier: routeSpecifier(core.TrafficDirection_OUTBOUND, option), AccessLog: accessLog(), HttpFilters: hcmFilters, HttpProtocolOptions: &core.Http1ProtocolOptions{AcceptHttp_10: true}, @@ -749,7 +803,7 @@ func MakeSidecarOnDemandOutBoundHCM(svcKey model.ServiceKey, option *BuildOption return manager } -func MakeSidecarBoundHCM(svcKey model.ServiceKey, trafficDirection corev3.TrafficDirection) *hcm.HttpConnectionManager { +func MakeSidecarBoundHCM(svcKey model.ServiceKey, trafficDirection corev3.TrafficDirection, opt *BuildOption) *hcm.HttpConnectionManager { hcmFilters := []*hcm.HttpFilter{} hcmFilters = append(hcmFilters, &hcm.HttpFilter{ Name: wellknown.Router, @@ -765,7 +819,7 @@ func MakeSidecarBoundHCM(svcKey model.ServiceKey, trafficDirection corev3.Traffi manager := &hcm.HttpConnectionManager{ CodecType: hcm.HttpConnectionManager_AUTO, StatPrefix: trafficDirectionName + "_HTTP", - RouteSpecifier: routeSpecifier(trafficDirection), + RouteSpecifier: routeSpecifier(trafficDirection, opt), AccessLog: accessLog(), HttpFilters: hcmFilters, HttpProtocolOptions: &core.Http1ProtocolOptions{AcceptHttp_10: true}, @@ -781,7 +835,7 @@ func MakeSidecarBoundHCM(svcKey model.ServiceKey, trafficDirection corev3.Traffi return manager } -func MakeGatewayBoundHCM(svcKey model.ServiceKey) *hcm.HttpConnectionManager { +func MakeGatewayBoundHCM(svcKey model.ServiceKey, opt *BuildOption) *hcm.HttpConnectionManager { hcmFilters := makeRateLimitHCMFilter(svcKey) hcmFilters = append(hcmFilters, &hcm.HttpFilter{ Name: wellknown.Router, @@ -793,7 +847,7 @@ func MakeGatewayBoundHCM(svcKey model.ServiceKey) *hcm.HttpConnectionManager { manager := &hcm.HttpConnectionManager{ CodecType: hcm.HttpConnectionManager_AUTO, StatPrefix: trafficDirectionName + "_HTTP", - RouteSpecifier: routeSpecifier(corev3.TrafficDirection_OUTBOUND), + RouteSpecifier: routeSpecifier(corev3.TrafficDirection_OUTBOUND, opt), AccessLog: accessLog(), HttpFilters: hcmFilters, HttpProtocolOptions: &core.Http1ProtocolOptions{AcceptHttp_10: true}, @@ -801,16 +855,19 @@ func MakeGatewayBoundHCM(svcKey model.ServiceKey) *hcm.HttpConnectionManager { return manager } -func routeSpecifier(trafficDirection corev3.TrafficDirection) *hcm.HttpConnectionManager_Rds { +func routeSpecifier(trafficDirection corev3.TrafficDirection, opt *BuildOption) *hcm.HttpConnectionManager_Rds { + baseRouteName := TrafficBoundRoute[trafficDirection] + if opt.OpenOnDemand { + baseRouteName = fmt.Sprintf("%s|%s|Demand|%s", TrafficBoundRoute[trafficDirection], opt.Namespace, opt.OnDemandServer) + } return &hcm.HttpConnectionManager_Rds{ Rds: &hcm.Rds{ ConfigSource: &core.ConfigSource{ - ResourceApiVersion: resourcev3.DefaultAPIVersion, ConfigSourceSpecifier: &core.ConfigSource_Ads{ Ads: &core.AggregatedConfigSource{}, }, }, - RouteConfigName: TrafficBoundRoute[trafficDirection], + RouteConfigName: baseRouteName, }, } } diff --git a/apiserver/xdsserverv3/resource/model.go b/apiserver/xdsserverv3/resource/model.go index 41dd6a823..f7e46537d 100644 --- a/apiserver/xdsserverv3/resource/model.go +++ b/apiserver/xdsserverv3/resource/model.go @@ -18,8 +18,6 @@ package resource import ( - "os" - corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/polarismesh/specification/source/go/api/v1/fault_tolerance" @@ -55,12 +53,12 @@ var ( ) func Init() { - if val := os.Getenv("ENVOY_ODCDS_LUA_SCRIPT"); val != "" { - defaultOdcdsLuaScriptFile = val - } - code, _ := os.ReadFile(defaultOdcdsLuaScriptFile) - odcdsLuaCode = string(code) - log.Infof("[XDSV3][ODCDS] lua script path :%s content\n%s\n", defaultOdcdsLuaScriptFile, odcdsLuaCode) + // if val := os.Getenv("ENVOY_ODCDS_LUA_SCRIPT"); val != "" { + // defaultOdcdsLuaScriptFile = val + // } + // code, _ := os.ReadFile(defaultOdcdsLuaScriptFile) + // odcdsLuaCode = string(code) + // log.Infof("[XDSV3][ODCDS] lua script path :%s content\n%s\n", defaultOdcdsLuaScriptFile, odcdsLuaCode) } var ( diff --git a/apiserver/xdsserverv3/resource/mtls.go b/apiserver/xdsserverv3/resource/mtls.go index e9af7014f..59e8970ef 100644 --- a/apiserver/xdsserverv3/resource/mtls.go +++ b/apiserver/xdsserverv3/resource/mtls.go @@ -44,7 +44,6 @@ var DefaultSdsConfig = &core.ConfigSource{ }, }, InitialFetchTimeout: &duration.Duration{}, - ResourceApiVersion: core.ApiVersion_V3, } var MTLSTransportSocketMatch = &structpb.Struct{ diff --git a/apiserver/xdsserverv3/resource/node.go b/apiserver/xdsserverv3/resource/node.go index ed6423afb..b4c1c1985 100644 --- a/apiserver/xdsserverv3/resource/node.go +++ b/apiserver/xdsserverv3/resource/node.go @@ -30,6 +30,8 @@ import ( _struct "github.com/golang/protobuf/ptypes/struct" structpb "github.com/golang/protobuf/ptypes/struct" "go.uber.org/zap" + + "github.com/polarismesh/polaris/common/model" ) type RunType string @@ -85,6 +87,8 @@ type XDSNodeManager struct { sidecarNodes map[string]*XDSClient // gatewayNodes The XDS client is the node list of the Gateway run mode gatewayNodes map[string]*XDSClient + // demandConfs . + demandConfs map[RunType]map[string]map[string]struct{} } func (x *XDSNodeManager) AddNodeIfAbsent(streamId int64, node *core.Node) { @@ -152,6 +156,20 @@ func (x *XDSNodeManager) HasEnvoyNodes() bool { return len(x.gatewayNodes) != 0 || len(x.sidecarNodes) != 0 } +func (x *XDSNodeManager) ListEnvoyNodes() []*XDSClient { + x.lock.RLock() + defer x.lock.RUnlock() + + ret := make([]*XDSClient, 0, len(x.sidecarNodes)) + for i := range x.sidecarNodes { + ret = append(ret, x.sidecarNodes[i]) + } + for i := range x.gatewayNodes { + ret = append(ret, x.gatewayNodes[i]) + } + return ret +} + func (x *XDSNodeManager) ListGatewayNodes() []*XDSClient { x.lock.RLock() defer x.lock.RUnlock() @@ -174,6 +192,10 @@ func (x *XDSNodeManager) ListSidecarNodes() []*XDSClient { return ret } +func (x *XDSNodeManager) ListDemandConfs() map[RunType]map[string]map[string]struct{} { + return x.demandConfs +} + // ID id 的格式是 ${sidecar|gateway}~namespace/uuid~hostIp // case 1: envoy 为 sidecar 模式时,则 NodeID 的格式为以下两种 // @@ -318,6 +340,14 @@ func (n *XDSClient) GetRegisterServices() []*RegisterService { return ret } +// GetSelfServiceKey 获取 envoy 对应的 service 信息 +func (n *XDSClient) GetSelfServiceKey() model.ServiceKey { + return model.ServiceKey{ + Namespace: n.GetSelfNamespace(), + Name: n.GetSelfService(), + } +} + // GetSelfService 获取 envoy 对应的 service 信息 func (n *XDSClient) GetSelfService() string { if n.IsGateway() { diff --git a/apiserver/xdsserverv3/server.go b/apiserver/xdsserverv3/server.go index 2dff59ed1..c2c09ab3c 100644 --- a/apiserver/xdsserverv3/server.go +++ b/apiserver/xdsserverv3/server.go @@ -220,6 +220,7 @@ func (x *XDSServer) GetPort() uint32 { func (x *XDSServer) activeUpdateTask() { if !x.active.CompareAndSwap(false, true) { + <-x.activeNotifier.Done() return } log.Info("active update xds resource snapshot task") @@ -233,7 +234,9 @@ func (x *XDSServer) activeUpdateTask() { log.Errorf("getRegistryInfoWithCache %v", err) return } - x.Generate(x.registryInfo) + // 首次更新没有需要移除的 XDS 资源信息 + x.Generate(x.registryInfo, nil) + x.activeFinish() go x.startSynTask(x.ctx) } @@ -250,6 +253,7 @@ func (x *XDSServer) startSynTask(ctx context.Context) { } needPush := make(map[string]map[model.ServiceKey]*resource.ServiceInfo) + needRemove := make(map[string]map[model.ServiceKey]*resource.ServiceInfo) // 处理删除 ns 中最后一个 service for ns, infos := range x.registryInfo { @@ -261,6 +265,21 @@ func (x *XDSServer) startSynTask(ctx context.Context) { } } + for ns, infos := range x.registryInfo { + if _, exist := registryInfo[ns]; !exist { + needRemove[ns] = infos + continue + } + + for _, info := range infos { + cacheServiceInfos := registryInfo[ns] + if _, ok := cacheServiceInfos[info.ServiceKey]; !ok { + needRemove[ns][info.ServiceKey] = info + continue + } + } + } + // 与本地缓存对比,是否发生了变化,对发生变化的命名空间,推送配置 for ns, infos := range registryInfo { cacheServiceInfos, ok := x.registryInfo[ns] @@ -279,9 +298,10 @@ func (x *XDSServer) startSynTask(ctx context.Context) { } } - if len(needPush) > 0 { - log.Info("start update xds resource snapshot ticker task", zap.Int("need-push", len(needPush))) - x.Generate(needPush) + if len(needPush) > 0 || len(needRemove) > 0 { + log.Info("start update xds resource snapshot ticker task", zap.Int("need-push", len(needPush)), + zap.Int("need-remove", len(needRemove))) + x.Generate(needPush, needRemove) } } @@ -349,7 +369,7 @@ func (x *XDSServer) getRegistryInfoWithCache(ctx context.Context, } // 获取routing配置 - routerRule, err := x.namingServer.Cache().RoutingConfig().GetRouterConfig("", svc.Name, svc.Namespace) + routerRule, err := x.namingServer.Cache().RoutingConfig().GetRouterConfigV2("", svc.Name, svc.Namespace) if err != nil { log.Errorf("error sync routing for namespace(%s) service(%s), info : %s", svc.Namespace, svc.Name, err.Error()) @@ -412,13 +432,20 @@ func (x *XDSServer) getRegistryInfoWithCache(ctx context.Context, } } } + + // 清理 namespace 下没有数据的记录 + for k, v := range registryInfo { + if len(v) == 0 { + delete(registryInfo, k) + } + } return nil } -func (x *XDSServer) Generate(needPush map[string]map[model.ServiceKey]*resource.ServiceInfo) { +func (x *XDSServer) Generate(needPush, needRemove map[string]map[model.ServiceKey]*resource.ServiceInfo) { defer x.activeFinish() versionLocal := time.Now().Format(time.RFC3339) + "/" + strconv.FormatUint(x.versionNum.Inc(), 10) - x.resourceGenerator.Generate(versionLocal, needPush) + x.resourceGenerator.Generate(versionLocal, needPush, needRemove) } func (x *XDSServer) checkUpdate(curServiceInfo, cacheServiceInfo map[model.ServiceKey]*resource.ServiceInfo) bool { @@ -455,15 +482,22 @@ func (x *XDSServer) checkUpdate(curServiceInfo, cacheServiceInfo map[model.Servi return false } -func (x *XDSServer) DebugHandlers() []apiserver.DebugHandler { - return []apiserver.DebugHandler{ +func (x *XDSServer) DebugHandlers() []model.DebugHandler { + return []model.DebugHandler{ { Path: "/debug/apiserver/xds/envoy_nodes", + Desc: "Query the list of Envoy nodes, query parameter name is 'type', value is [sidecar, gateway]", Handler: x.listXDSNodes, }, { Path: "/debug/apiserver/xds/resources", + Desc: "Query XDS Resource List, query parameter name is 'type', value is [node, common]", Handler: x.listXDSResources, }, + { + Path: "/debug/apiserver/xds/cache_names", + Desc: "Query XDS cache name list", + Handler: x.listXDSCaches, + }, } } diff --git a/auth/api.go b/auth/api.go index 27c5219e6..25b2f4ba2 100644 --- a/auth/api.go +++ b/auth/api.go @@ -23,7 +23,7 @@ import ( apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" - "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/store" ) @@ -31,7 +31,7 @@ import ( // AuthChecker 权限管理通用接口定义 type AuthChecker interface { // Initialize 执行初始化动作 - Initialize(options *Config, storage store.Store, cacheMgn *cache.CacheManager) error + Initialize(options *Config, storage store.Store, cacheMgn cachetypes.CacheManager) error // VerifyCredential 验证令牌 VerifyCredential(preCtx *model.AcquireContext) error // CheckClientPermission 执行检查客户端动作判断是否有权限,并且对 RequestContext 注入操作者数据 @@ -47,7 +47,7 @@ type AuthChecker interface { // UserServer 用户数据管理 server type UserServer interface { // Initialize 初始化 - Initialize(authOpt *Config, storage store.Store, cacheMgn *cache.CacheManager) error + Initialize(authOpt *Config, storage store.Store, cacheMgn cachetypes.CacheManager) error // Name 用户数据管理server名称 Name() string // CreateUsers 批量创建用户 @@ -94,7 +94,7 @@ type GroupOperator interface { // StrategyServer 策略相关操作 type StrategyServer interface { // Initialize 初始化 - Initialize(authOpt *Config, storage store.Store, cacheMgn *cache.CacheManager) error + Initialize(authOpt *Config, storage store.Store, cacheMgn cachetypes.CacheManager) error // Name 策略管理server名称 Name() string // CreateStrategy 创建策略 diff --git a/auth/auth.go b/auth/auth.go index 9848e3a8f..45e984ebf 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -25,6 +25,7 @@ import ( "sync" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/store" ) @@ -143,7 +144,7 @@ func Initialize(ctx context.Context, authOpt *Config, storage store.Store, cache // initialize 包裹了初始化函数,在 Initialize 的时候会在自动调用,全局初始化一次 func initialize(_ context.Context, authOpt *Config, storage store.Store, - cacheMgn *cache.CacheManager) (UserServer, StrategyServer, error) { + cacheMgr cachetypes.CacheManager) (UserServer, StrategyServer, error) { authOpt.SetDefault() name := authOpt.User.Name if name == "" { @@ -154,7 +155,7 @@ func initialize(_ context.Context, authOpt *Config, storage store.Store, if !ok { return nil, nil, fmt.Errorf("no such UserServer plugin. name(%s)", name) } - if err := namedUserMgn.Initialize(authOpt, storage, cacheMgn); err != nil { + if err := namedUserMgn.Initialize(authOpt, storage, cacheMgr); err != nil { log.Printf("UserServer do initialize err: %s", err.Error()) return nil, nil, err } @@ -168,7 +169,7 @@ func initialize(_ context.Context, authOpt *Config, storage store.Store, if !ok { return nil, nil, fmt.Errorf("no such StrategyServer plugin. name(%s)", name) } - if err := namedStrategyMgn.Initialize(authOpt, storage, cacheMgn); err != nil { + if err := namedStrategyMgn.Initialize(authOpt, storage, cacheMgr); err != nil { log.Printf("StrategyServer do initialize err: %s", err.Error()) return nil, nil, err } diff --git a/auth/defaultauth/auth_checker.go b/auth/defaultauth/auth_checker.go index f40b306de..36da65d4d 100644 --- a/auth/defaultauth/auth_checker.go +++ b/auth/defaultauth/auth_checker.go @@ -27,7 +27,7 @@ import ( "go.uber.org/zap" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" @@ -45,15 +45,15 @@ var ( // DefaultAuthChecker 北极星自带的默认鉴权中心 type DefaultAuthChecker struct { - cacheMgn *cache.CacheManager + cacheMgn cachetypes.CacheManager } -func (d *DefaultAuthChecker) SetCacheMgr(mgr *cache.CacheManager) { +func (d *DefaultAuthChecker) SetCacheMgr(mgr cachetypes.CacheManager) { d.cacheMgn = mgr } // Initialize 执行初始化动作 -func (d *DefaultAuthChecker) Initialize(options *auth.Config, s store.Store, cacheMgn *cache.CacheManager) error { +func (d *DefaultAuthChecker) Initialize(options *auth.Config, s store.Store, cacheMgr cachetypes.CacheManager) error { // 新版本鉴权策略配置均从auth.Option中迁移至auth.user.option及auth.strategy.option中 var ( strategyContentBytes []byte @@ -103,12 +103,12 @@ func (d *DefaultAuthChecker) Initialize(options *auth.Config, s store.Store, cac cfg.ConsoleOpen = cfg.Strict } AuthOption = cfg - d.cacheMgn = cacheMgn + d.cacheMgn = cacheMgr return nil } // Cache 获取缓存统一管理 -func (d *DefaultAuthChecker) Cache() *cache.CacheManager { +func (d *DefaultAuthChecker) Cache() cachetypes.CacheManager { return d.cacheMgn } diff --git a/auth/defaultauth/auth_checker_test.go b/auth/defaultauth/auth_checker_test.go index ae27c5ad9..1ef2e76d5 100644 --- a/auth/defaultauth/auth_checker_test.go +++ b/auth/defaultauth/auth_checker_test.go @@ -30,6 +30,7 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/auth/defaultauth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" storemock "github.com/polarismesh/polaris/store/mock" @@ -69,12 +70,14 @@ func Test_DefaultAuthChecker_VerifyCredential(t *testing.T) { if err != nil { t.Fatal(err) } - cacheMgn.OpenResourceCache([]cache.ConfigEntry{ + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ { - Name: "users", + Name: cachetypes.UsersName, }, }...) + _ = cacheMgn.TestUpdate() + t.Cleanup(func() { cancel() cacheMgn.Close() @@ -231,14 +234,21 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { if err != nil { t.Fatal(err) } + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + { + Name: cachetypes.UsersName, + }, + { + Name: cachetypes.StrategyRuleName, + }, + }...) + _ = cacheMgn.TestUpdate() t.Cleanup(func() { cancel() cacheMgn.Close() }) - time.Sleep(time.Second) - checker := &defaultauth.DefaultAuthChecker{} checker.SetCacheMgr(cacheMgn) @@ -476,7 +486,15 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { cacheMgn.Close() }) - time.Sleep(time.Second) + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + { + Name: cachetypes.UsersName, + }, + { + Name: cachetypes.StrategyRuleName, + }, + }...) + _ = cacheMgn.TestUpdate() checker := &defaultauth.DefaultAuthChecker{} checker.SetCacheMgr(cacheMgn) @@ -670,8 +688,15 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { cancel() cacheMgn.Close() }) - - time.Sleep(time.Second) + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + { + Name: cachetypes.UsersName, + }, + { + Name: cachetypes.StrategyRuleName, + }, + }...) + _ = cacheMgn.TestUpdate() checker := &defaultauth.DefaultAuthChecker{} checker.SetCacheMgr(cacheMgn) @@ -886,8 +911,15 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { cancel() cacheMgn.Close() }) - - time.Sleep(time.Second) + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + { + Name: cachetypes.UsersName, + }, + { + Name: cachetypes.StrategyRuleName, + }, + }...) + _ = cacheMgn.TestUpdate() checker := &defaultauth.DefaultAuthChecker{} checker.SetCacheMgr(cacheMgn) @@ -1090,9 +1122,9 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { t.Fatal(err) } - cacheMgn.OpenResourceCache([]cache.ConfigEntry{ + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ { - Name: "users", + Name: cachetypes.UsersName, }, }...) t.Cleanup(func() { diff --git a/auth/defaultauth/group_test.go b/auth/defaultauth/group_test.go index 61b7a8090..8757ee946 100644 --- a/auth/defaultauth/group_test.go +++ b/auth/defaultauth/group_test.go @@ -30,6 +30,7 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/auth/defaultauth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" v1 "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" @@ -84,32 +85,16 @@ func newGroupTest(t *testing.T) *GroupTest { if err != nil { t.Error(err) } - cacheMgn.OpenResourceCache([]cache.ConfigEntry{ + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ { - Name: "service", - Option: map[string]interface{}{ - "disableBusiness": false, - "needMeta": true, - }, - }, - { - Name: "instance", - }, - { - Name: "users", - }, - { - Name: "strategyRule", - }, - { - Name: "namespace", + Name: cachetypes.UsersName, }, }...) t.Cleanup(func() { _ = cacheMgn.Close() }) - time.Sleep(time.Second) + _ = cacheMgn.TestUpdate() checker := &defaultauth.DefaultAuthChecker{} checker.SetCacheMgr(cacheMgn) diff --git a/auth/defaultauth/server.go b/auth/defaultauth/server.go index 04d245755..6e2ec3fd7 100644 --- a/auth/defaultauth/server.go +++ b/auth/defaultauth/server.go @@ -19,6 +19,8 @@ package defaultauth import ( "errors" + "fmt" + "time" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" @@ -27,6 +29,7 @@ import ( "golang.org/x/crypto/bcrypt" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" @@ -49,7 +52,7 @@ func NewServer(storage store.Store, type Server struct { storage store.Store history plugin.History - cacheMgn *cache.CacheManager + cacheMgn cachetypes.CacheManager authMgn *DefaultAuthChecker } @@ -228,7 +231,6 @@ func (svr *Server) handlerModifyDefaultStrategy(id, ownerId string, uType model. zap.String("owner", ownerId), zap.String("id", id), zap.Error(err)) return err } - if strategy == nil { return errors.New("not found default strategy rule") } @@ -256,6 +258,14 @@ func (svr *Server) handlerModifyDefaultStrategy(id, ownerId string, uType model. } } + entry := &model.RecordEntry{ + ResourceType: model.RAuthStrategy, + ResourceName: fmt.Sprintf("%s(%s)", strategy.Name, strategy.ID), + Operator: utils.ParseOperator(afterCtx.GetRequestContext()), + Detail: utils.MustJson(strategyResource), + HappenTime: time.Now(), + } + if afterCtx.GetOperation() == model.Delete || cleanRealtion { if err = svr.storage.RemoveStrategyResources(strategyResource); err != nil { log.Error("[Auth][Server] remove default strategy resource", @@ -263,7 +273,8 @@ func (svr *Server) handlerModifyDefaultStrategy(id, ownerId string, uType model. zap.String("type", model.PrincipalNames[uType]), zap.Error(err)) return err } - + entry.OperationType = model.ODelete + plugin.GetHistory().Record(entry) return nil } // 如果是写操作,那么采用松添加操作进行新增资源的添加操作(仅忽略主键冲突的错误) @@ -273,7 +284,8 @@ func (svr *Server) handlerModifyDefaultStrategy(id, ownerId string, uType model. zap.String("type", model.PrincipalNames[uType]), zap.Error(err)) return err } - + entry.OperationType = model.OUpdate + plugin.GetHistory().Record(entry) return nil } @@ -283,6 +295,5 @@ func checkHasPassAll(rule *model.StrategyDetail) bool { return true } } - return false } diff --git a/auth/defaultauth/strategy_authability.go b/auth/defaultauth/strategy_authability.go index 17bbefd71..b5bb26466 100644 --- a/auth/defaultauth/strategy_authability.go +++ b/auth/defaultauth/strategy_authability.go @@ -25,7 +25,6 @@ import ( apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" @@ -47,8 +46,8 @@ type StrategyAuthAbility struct { // Initialize 执行初始化动作 func (svr *StrategyAuthAbility) Initialize(authOpt *auth.Config, storage store.Store, - cacheMgn *cache.CacheManager) error { - cacheMgn.OpenResourceCache(cache.ConfigEntry{ + cacheMgn cachetypes.CacheManager) error { + _ = cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ Name: cachetypes.StrategyRuleName, }) var ( diff --git a/auth/defaultauth/strategy_test.go b/auth/defaultauth/strategy_test.go index bc50ff51b..510630daa 100644 --- a/auth/defaultauth/strategy_test.go +++ b/auth/defaultauth/strategy_test.go @@ -32,6 +32,7 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/auth/defaultauth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" @@ -97,28 +98,30 @@ func newStrategyTest(t *testing.T) *StrategyTest { if err != nil { t.Fatal(err) } - cacheMgn.OpenResourceCache([]cache.ConfigEntry{ + _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ { - Name: "service", + Name: cachetypes.ServiceName, Option: map[string]interface{}{ "disableBusiness": false, "needMeta": true, }, }, { - Name: "instance", + Name: cachetypes.InstanceName, }, { - Name: "users", + Name: cachetypes.UsersName, }, { - Name: "strategyRule", + Name: cachetypes.StrategyRuleName, }, { - Name: "namespace", + Name: cachetypes.NamespaceName, }, }...) + _ = cacheMgn.TestUpdate() + checker := &defaultauth.DefaultAuthChecker{} checker.Initialize(&auth.Config{ User: &auth.UserConfig{ diff --git a/auth/defaultauth/user_authability.go b/auth/defaultauth/user_authability.go index fdea0f9f7..e668a7391 100644 --- a/auth/defaultauth/user_authability.go +++ b/auth/defaultauth/user_authability.go @@ -25,7 +25,6 @@ import ( apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/plugin" @@ -47,8 +46,8 @@ type UserAuthAbility struct { // Initialize 执行初始化动作 func (svr *UserAuthAbility) Initialize(authOpt *auth.Config, storage store.Store, - cacheMgn *cache.CacheManager) error { - cacheMgn.OpenResourceCache(cache.ConfigEntry{ + cacheMgn cachetypes.CacheManager) error { + _ = cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ Name: cachetypes.UsersName, }) var ( diff --git a/auth/defaultauth/user_test.go b/auth/defaultauth/user_test.go index db52c1105..2bd902bec 100644 --- a/auth/defaultauth/user_test.go +++ b/auth/defaultauth/user_test.go @@ -30,6 +30,7 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/auth/defaultauth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" commonlog "github.com/polarismesh/polaris/common/log" "github.com/polarismesh/polaris/common/model" @@ -95,10 +96,10 @@ func newUserTest(t *testing.T) *UserTest { t.Fatal(err) } - cacheMgn.OpenResourceCache( - []cache.ConfigEntry{ + _ = cacheMgn.OpenResourceCache( + []cachetypes.ConfigEntry{ { - Name: "users", + Name: cachetypes.UsersName, }, }..., ) @@ -107,6 +108,7 @@ func newUserTest(t *testing.T) *UserTest { checker := &defaultauth.DefaultAuthChecker{} checker.SetCacheMgr(cacheMgn) + _ = cache.TestRun(ctx, cacheMgn) svr := defaultauth.NewUserAuthAbility( checker, defaultauth.NewServer(storage, nil, cacheMgn, checker), @@ -201,7 +203,7 @@ func Test_server_CreateUsers(t *testing.T) { }, } - userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerOne.ID)).Return(userTest.ownerOne, nil) + userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerOne.ID)).Return(userTest.ownerOne, nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) resp := userTest.svr.CreateUsers(reqCtx, createUsersReq) @@ -219,7 +221,7 @@ func Test_server_CreateUsers(t *testing.T) { }, } - userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerOne.ID)).Return(userTest.ownerOne, nil) + userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerOne.ID)).Return(userTest.ownerOne, nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.ownerOne.Token) resp := userTest.svr.CreateUsers(reqCtx, createUsersReq) @@ -567,10 +569,12 @@ func Test_server_UpdateUserPassword(t *testing.T) { } func Test_server_DeleteUser(t *testing.T) { - userTest := newUserTest(t) - defer userTest.Clean() - t.Run("主账户删除自己", func(t *testing.T) { + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[0], nil) reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) @@ -582,6 +586,11 @@ func Test_server_DeleteUser(t *testing.T) { }) t.Run("主账户删除另外一个主账户", func(t *testing.T) { + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + uid := utils.NewUUID() userTest.storage.EXPECT().GetUser(gomock.Any()).Return(&model.User{ ID: uid, @@ -598,7 +607,12 @@ func Test_server_DeleteUser(t *testing.T) { }) t.Run("主账户删除自己的子账户", func(t *testing.T) { - userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.users[1].ID)).Return(userTest.users[1], nil) + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + + userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.users[1].ID)).Return(userTest.users[1], nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ @@ -609,13 +623,18 @@ func Test_server_DeleteUser(t *testing.T) { }) t.Run("主账户删除不是自己的子账户", func(t *testing.T) { + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + uid := utils.NewUUID() oid := utils.NewUUID() userTest.storage.EXPECT().GetUser(gomock.Any()).Return(&model.User{ ID: uid, Type: model.OwnerUserRole, Owner: oid, - }, nil) + }, nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ @@ -626,8 +645,13 @@ func Test_server_DeleteUser(t *testing.T) { }) t.Run("管理员删除主账户-主账户下没有子账户", func(t *testing.T) { - userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[0], nil) - userTest.storage.EXPECT().GetSubCount(gomock.Any()).Return(uint32(0), nil) + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[0], nil).AnyTimes() + userTest.storage.EXPECT().GetSubCount(gomock.Any()).Return(uint32(0), nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.admin.Token) resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ @@ -638,8 +662,13 @@ func Test_server_DeleteUser(t *testing.T) { }) t.Run("管理员删除主账户-主账户下还有子账户", func(t *testing.T) { - userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[0], nil) - userTest.storage.EXPECT().GetSubCount(gomock.Any()).Return(uint32(1), nil) + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.ownerOne, nil).AnyTimes() + userTest.storage.EXPECT().GetSubCount(gomock.Any()).Return(uint32(1), nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.admin.Token) resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ @@ -650,6 +679,11 @@ func Test_server_DeleteUser(t *testing.T) { }) t.Run("子账户删除用户", func(t *testing.T) { + userTest := newUserTest(t) + t.Cleanup(func() { + userTest.Clean() + }) + reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[1].Token) resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.users[0].ID), @@ -781,7 +815,7 @@ func Test_server_UpdateUserToken(t *testing.T) { t.Run("主账户刷新自己的Token状态", func(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.ownerOne.Token) - userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.users[0].ID)).Return(userTest.users[0], nil) + userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.users[0].ID)).Return(userTest.users[0], nil).AnyTimes() resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.users[0].ID), diff --git a/auth/testexport.go b/auth/testexport.go index 719fc4452..cd1b2a660 100644 --- a/auth/testexport.go +++ b/auth/testexport.go @@ -20,13 +20,13 @@ package auth import ( "context" - "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/store" ) // TestInitialize 包裹了初始化函数,在 Initialize 的时候会在自动调用,全局初始化一次 func TestInitialize(ctx context.Context, authOpt *Config, storage store.Store, - cacheMgn *cache.CacheManager) (UserServer, StrategyServer, error) { + cacheMgn cachetypes.CacheManager) (UserServer, StrategyServer, error) { userSvr, strategySvr, err := initialize(ctx, authOpt, storage, cacheMgn) if err != nil { return nil, nil, err diff --git a/bootstrap/server.go b/bootstrap/server.go index e18174255..71e36acc8 100644 --- a/bootstrap/server.go +++ b/bootstrap/server.go @@ -36,6 +36,7 @@ import ( "github.com/polarismesh/polaris/auth" boot_config "github.com/polarismesh/polaris/bootstrap/config" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/log" @@ -164,6 +165,11 @@ func StartComponents(ctx context.Context, cfg *boot_config.Config) error { return err } + // 开启灰度规则缓存 + _ = cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ + Name: cachetypes.GrayName, + }) + // 初始化鉴权层 if err = auth.Initialize(ctx, &cfg.Auth, s, cacheMgn); err != nil { return err @@ -270,6 +276,7 @@ func StartDiscoverComponents(ctx context.Context, cfg *boot_config.Config, s sto service.WithNamespaceSvr(namespaceSvr), } + cfg.Naming.Interceptors = service.GetChainOrder() // 初始化服务模块 if err = service.Initialize(ctx, &cfg.Naming, opts...); err != nil { return err @@ -297,7 +304,7 @@ func StartConfigCenterComponents(ctx context.Context, cfg *boot_config.Config, s if err != nil { return err } - + cfg.Config.Interceptors = config_center.GetChainOrder() return config_center.Initialize(ctx, cfg.Config, s, cacheMgn, namespaceOperator, userMgn, strategyMgn) } diff --git a/cache/api/types.go b/cache/api/types.go index 054826407..f96cfc50c 100644 --- a/cache/api/types.go +++ b/cache/api/types.go @@ -22,10 +22,10 @@ import ( "sync" "time" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" 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" @@ -107,12 +107,50 @@ type Cache interface { Close() error } +// ConfigEntry 单个缓存资源配置 +type ConfigEntry struct { + Name string `yaml:"name"` + Option map[string]interface{} `yaml:"option"` +} + // CacheManager type CacheManager interface { // GetCacher GetCacher(cacheIndex CacheIndex) Cache // RegisterCacher RegisterCacher(cacheIndex CacheIndex, item Cache) + // + OpenResourceCache(entries ...ConfigEntry) error + // Service 获取Service缓存信息 + Service() ServiceCache + // Instance 获取Instance缓存信息 + Instance() InstanceCache + // RoutingConfig 获取路由配置的缓存信息 + RoutingConfig() RoutingConfigCache + // CL5 获取l5缓存信息 + CL5() L5Cache + // RateLimit 获取限流规则缓存信息 + RateLimit() RateLimitCache + // CircuitBreaker 获取熔断规则缓存信息 + CircuitBreaker() CircuitBreakerCache + // FaultDetector 获取探测规则缓存信息 + FaultDetector() FaultDetectCache + // ServiceContract 获取服务契约缓存 + ServiceContract() ServiceContractCache + // User Get user information cache information + User() UserCache + // AuthStrategy Get authentication cache information + AuthStrategy() StrategyCache + // Namespace Get namespace cache information + Namespace() NamespaceCache + // Client Get client cache information + Client() ClientCache + // ConfigFile get config file cache information + ConfigFile() ConfigFileCache + // ConfigGroup get config group cache information + ConfigGroup() ConfigGroupCache + // Gray get Gray cache information + Gray() GrayCache } type ( @@ -225,6 +263,8 @@ type ( GetInstance(instanceID string) *model.Instance // GetInstancesByServiceID 根据服务名获取实例,先查找服务名对应的服务ID,再找实例列表 GetInstancesByServiceID(serviceID string) []*model.Instance + // GetInstances 根据服务名获取实例,先查找服务名对应的服务ID,再找实例列表 + GetInstances(serviceID string) *model.ServiceInstances // IteratorInstances 迭代 IteratorInstances(iterProc InstanceIterProc) error // IteratorInstancesWithService 根据服务ID进行迭代 @@ -437,6 +477,8 @@ type ( GetGroupByName(namespace, name string) *model.ConfigFileGroup // GetGroupByID GetGroupByID(id uint64) *model.ConfigFileGroup + // ListGroups + ListGroups(namespace string) ([]*model.ConfigFileGroup, string) // Query Query(args *ConfigGroupArgs) (uint32, []*model.ConfigFileGroup, error) } @@ -446,7 +488,10 @@ type ( Cache // GetActiveRelease GetGroupActiveReleases(namespace, group string) ([]*model.ConfigFileRelease, string) - GetActiveRelease(namespace, group, fileName string, typ model.ReleaseType) *model.ConfigFileRelease + // GetActiveRelease + GetActiveRelease(namespace, group, fileName string) *model.ConfigFileRelease + // GetGrayRelease + GetGrayRelease(namespace, group, fileName string) *model.ConfigFileRelease // GetRelease GetRelease(key model.ConfigFileReleaseKey) *model.ConfigFileRelease // QueryReleases @@ -657,6 +702,8 @@ type ( // GrayCache 灰度 Cache 接口 GrayCache interface { Cache - GetGrayRule(name string) *apimodel.MatchTerm + GetGrayRule(name string) []*apimodel.ClientLabel + // HitGrayRule . + HitGrayRule(name string, labels map[string]string) bool } ) diff --git a/cache/cache.go b/cache/cache.go index f4f0e9acf..d06b31e99 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -56,9 +56,9 @@ func (nc *CacheManager) Initialize() error { } // OpenResourceCache 开启资源缓存 -func (nc *CacheManager) OpenResourceCache(entries ...ConfigEntry) error { +func (nc *CacheManager) OpenResourceCache(entries ...types.ConfigEntry) error { for _, obj := range nc.caches { - var entryItem *ConfigEntry + var entryItem *types.ConfigEntry for _, entry := range entries { if obj.Name() == entry.Name { entryItem = &entry diff --git a/cache/cache_test.go b/cache/cache_test.go deleted file mode 100644 index 491632e1d..000000000 --- a/cache/cache_test.go +++ /dev/null @@ -1,100 +0,0 @@ -/** - * 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 cache_test - -import ( - "context" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - - "github.com/polarismesh/polaris/cache" - types "github.com/polarismesh/polaris/cache/api" - "github.com/polarismesh/polaris/store/mock" -) - -// TestCacheManager_Start 测试cache函数是否正常 -func TestCacheManager_Start(t *testing.T) { - ctl := gomock.NewController(t) - storage := mock.NewMockStore(ctl) - storage.EXPECT().GetUnixSecond(gomock.Any()).AnyTimes().Return(time.Now().Unix(), nil) - defer ctl.Finish() - - conf := &cache.Config{} - entries := []cache.ConfigEntry{ - { - Name: "service", - }, - { - Name: "instance", - }, - { - Name: "routingConfig", - }, - { - Name: "rateLimitConfig", - }, - { - Name: "circuitBreakerConfig", - }, - { - Name: "l5", - }, - } - cache.SetCacheConfig(conf) - - t.Run("测试正常的更新缓存逻辑", func(t *testing.T) { - c, err := cache.TestCacheInitialize(context.Background(), &cache.Config{}, storage) - assert.Nil(t, err) - assert.NotNil(t, c) - err = c.OpenResourceCache(entries...) - assert.NotNil(t, c) - time.Sleep(time.Second) - beg := time.Unix(0, 0).Add(types.DefaultTimeDiff) - storage.EXPECT().GetUnixSecond(gomock.Any()).AnyTimes().Return(time.Now().Unix(), nil) - storage.EXPECT().GetMoreInstances(gomock.Any(), beg, true, false, nil).Return(nil, nil).MaxTimes(1) - storage.EXPECT().GetMoreInstances(gomock.Any(), beg, false, false, nil).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetMoreServices(beg, true, false, false).Return(nil, nil).MaxTimes(1) - storage.EXPECT().GetMoreServices(beg, false, false, false).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetRoutingConfigsForCache(beg, true).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetRoutingConfigsForCache(beg, false).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetMoreL5Routes(uint32(0)).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetMoreL5Policies(uint32(0)).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetMoreL5Sections(uint32(0)).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetMoreL5IPConfigs(uint32(0)).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetRateLimitsForCache(beg, true).Return(nil, nil).MaxTimes(1) - storage.EXPECT().GetRateLimitsForCache(beg, false).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetCircuitBreakerRulesForCache(beg, false).Return(nil, nil).MaxTimes(3) - storage.EXPECT().GetInstancesCountTx(gomock.Any()).Return(uint32(0), nil).MaxTimes(1) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err = c.Initialize() - assert.Nil(t, err) - - err = c.Start(ctx) - assert.Nil(t, err) - - // 等待cache更新 - time.Sleep(c.GetUpdateCacheInterval() + time.Second) - }) - -} diff --git a/cache/config.go b/cache/config.go index a62c7c2ab..11c30c64e 100644 --- a/cache/config.go +++ b/cache/config.go @@ -25,12 +25,6 @@ type Config struct { DiffTime time.Duration `yaml:"diffTime"` } -// ConfigEntry 单个缓存资源配置 -type ConfigEntry struct { - Name string `yaml:"name"` - Option map[string]interface{} `yaml:"option"` -} - var ( config *Config ) diff --git a/cache/config/config_file.go b/cache/config/config_file.go index 269ee5b13..01b5b8253 100644 --- a/cache/config/config_file.go +++ b/cache/config/config_file.go @@ -182,8 +182,9 @@ func (fc *fileCache) setReleases(releases []*model.ConfigFileRelease) (map[strin if item.Active { configLog.Info("[Config][Release][Cache] notify config release change", - zap.String("namespace", item.Namespace), zap.String("group", item.Group), - zap.String("file", item.FileName), zap.Uint64("version", item.Version), zap.Bool("valid", item.Valid)) + zap.String("namespace", item.Namespace), zap.String("group", item.Group), zap.String("release", item.Name), + zap.String("file", item.FileName), zap.Uint64("version", item.Version), zap.Bool("valid", item.Valid), + zap.String("type", string(item.ReleaseType))) fc.sendEvent(item) } } @@ -198,14 +199,15 @@ func (fc *fileCache) sendEvent(item *model.ConfigFileRelease) { if err != nil { configLog.Error("[Config][Release][Cache] notify config release change", zap.String("namespace", item.Namespace), zap.String("group", item.Group), - zap.String("file", item.FileName), zap.Uint64("version", item.Version), zap.Error(err)) + zap.String("file", item.FileName), zap.Uint64("version", item.Version), zap.String("type", string(item.ReleaseType)), + zap.Error(err)) } } // handleUpdateRelease func (fc *fileCache) handleUpdateRelease(oldVal *model.SimpleConfigFileRelease, item *model.ConfigFileRelease) error { // 如果ReleaseType类型变更, 先删除再保存 - if oldVal != nil && oldVal.Typ != item.Typ { + if oldVal != nil && oldVal.ReleaseType != item.ReleaseType { if err := fc.handleDeleteRelease(oldVal); err != nil { return err } @@ -403,7 +405,16 @@ func (fc *fileCache) GetGroupActiveReleases(namespace, group string) ([]*model.C } // GetActiveRelease -func (fc *fileCache) GetActiveRelease(namespace, group, fileName string, typ model.ReleaseType) *model.ConfigFileRelease { +func (fc *fileCache) GetActiveRelease(namespace, group, fileName string) *model.ConfigFileRelease { + return fc.handleGetActiveRelease(namespace, group, fileName, model.ReleaseTypeFull) +} + +// GetGrayRelease +func (fc *fileCache) GetGrayRelease(namespace, group, fileName string) *model.ConfigFileRelease { + return fc.handleGetActiveRelease(namespace, group, fileName, model.ReleaseTypeGray) +} + +func (fc *fileCache) handleGetActiveRelease(namespace, group, fileName string, typ model.ReleaseType) *model.ConfigFileRelease { nsBucket, ok := fc.activeReleases.Load(namespace) if !ok { return nil @@ -413,10 +424,10 @@ func (fc *fileCache) GetActiveRelease(namespace, group, fileName string, typ mod return nil } searchKey := &model.ConfigFileReleaseKey{ - Namespace: namespace, - Group: group, - FileName: fileName, - Typ: typ, + Namespace: namespace, + Group: group, + FileName: fileName, + ReleaseType: typ, } simple, ok := groupBucket.Load(searchKey.ActiveKey()) if !ok { @@ -487,7 +498,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.ReleaseTypeGray { + if !args.IncludeGray && item.ReleaseType == model.ReleaseTypeGray { return } if args.OnlyActive && !item.Active { diff --git a/cache/config/config_group.go b/cache/config/config_group.go index 587bc1d03..22f0d28c8 100644 --- a/cache/config/config_group.go +++ b/cache/config/config_group.go @@ -38,6 +38,8 @@ type configGroupCache struct { groups *utils.SyncMap[uint64, *model.ConfigFileGroup] // name2files config_file. -> model.ConfigFileGroup name2groups *utils.SyncMap[string, *utils.SyncMap[string, *model.ConfigFileGroup]] + // revisions namespace -> [revision] + revisions *utils.SyncMap[string, string] // singleGroup singleGroup *singleflight.Group } @@ -56,6 +58,7 @@ func (fc *configGroupCache) Initialize(opt map[string]interface{}) error { fc.groups = utils.NewSyncMap[uint64, *model.ConfigFileGroup]() fc.name2groups = utils.NewSyncMap[string, *utils.SyncMap[string, *model.ConfigFileGroup]]() fc.singleGroup = &singleflight.Group{} + fc.revisions = utils.NewSyncMap[string, string]() return nil } @@ -138,10 +141,22 @@ func (fc *configGroupCache) setConfigGroups(groups []*model.ConfigFileGroup) (ma func (fc *configGroupCache) postProcessUpdatedGroups(affect map[string]struct{}) { for ns := range affect { nsBucket, ok := fc.name2groups.Load(ns) - if ok { - count := nsBucket.Len() - fc.reportMetricsInfo(ns, count) + if !ok { + continue } + count := nsBucket.Len() + fc.reportMetricsInfo(ns, count) + + revisions := make([]string, 0, count) + nsBucket.Range(func(key string, val *model.ConfigFileGroup) { + revisions = append(revisions, val.Revision) + }) + + revision, err := types.CompositeComputeRevision(revisions) + if err != nil { + revision = utils.NewUUID() + } + fc.revisions.Store(ns, revision) } } @@ -150,6 +165,7 @@ func (fc *configGroupCache) Clear() error { fc.groups = utils.NewSyncMap[uint64, *model.ConfigFileGroup]() fc.name2groups = utils.NewSyncMap[string, *utils.SyncMap[string, *model.ConfigFileGroup]]() fc.singleGroup = &singleflight.Group{} + fc.revisions = utils.NewSyncMap[string, string]() return nil } @@ -158,6 +174,24 @@ func (fc *configGroupCache) Name() string { return types.ConfigGroupCacheName } +func (fc *configGroupCache) ListGroups(namespace string) ([]*model.ConfigFileGroup, string) { + nsBucket, ok := fc.name2groups.Load(namespace) + if !ok { + return nil, "" + } + ret := make([]*model.ConfigFileGroup, 0, nsBucket.Len()) + nsBucket.Range(func(key string, val *model.ConfigFileGroup) { + ret = append(ret, val) + }) + + revision, ok := fc.revisions.Load(namespace) + if !ok { + revision = utils.NewUUID() + } + + return ret, revision +} + // GetGroupByName func (fc *configGroupCache) GetGroupByName(namespace, name string) *model.ConfigFileGroup { nsBucket, ok := fc.name2groups.Load(namespace) diff --git a/cache/default.go b/cache/default.go index 4914c7aed..484678b13 100644 --- a/cache/default.go +++ b/cache/default.go @@ -26,9 +26,9 @@ import ( cacheauth "github.com/polarismesh/polaris/cache/auth" cacheclient "github.com/polarismesh/polaris/cache/client" cacheconfig "github.com/polarismesh/polaris/cache/config" + cachegray "github.com/polarismesh/polaris/cache/gray" 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/common/utils" "github.com/polarismesh/polaris/store" ) diff --git a/cache/gray/gray.go b/cache/gray/gray.go index c3c706cc3..9b86f5305 100644 --- a/cache/gray/gray.go +++ b/cache/gray/gray.go @@ -19,18 +19,19 @@ package gray import ( "bytes" + "encoding/json" "time" + regexp "github.com/dlclark/regexp2" + "github.com/golang/protobuf/jsonpb" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "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 ( @@ -40,7 +41,7 @@ var ( type grayCache struct { *types.BaseCache storage store.Store - grayResources *utils.SyncMap[string, *apimodel.MatchTerm] + grayResources *utils.SyncMap[string, []*apimodel.ClientLabel] updater *singleflight.Group } @@ -54,7 +55,7 @@ func NewGrayCache(storage store.Store, cacheMgr types.CacheManager) types.GrayCa // Initialize init gray cache func (gc *grayCache) Initialize(opt map[string]interface{}) error { - gc.grayResources = utils.NewSyncMap[string, *apimodel.MatchTerm]() + gc.grayResources = utils.NewSyncMap[string, []*apimodel.ClientLabel]() gc.updater = &singleflight.Group{} return nil } @@ -78,39 +79,46 @@ func (gc *grayCache) realUpdate() (map[string]time.Time, int64, error) { 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))) + lastMtimes, err := gc.setGrayResources(grayResources) + if err != nil { + return nil, 0, err + } + 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 { +func (gc *grayCache) setGrayResources(grayResources []*model.GrayResource) (map[string]time.Time, error) { 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 + clientLabels := []*apimodel.ClientLabel{} + jsonDecoder := json.NewDecoder(bytes.NewBuffer([]byte(grayResource.MatchRule))) + // read open bracket + if _, err := jsonDecoder.Token(); err != nil { + return nil, err + } + for jsonDecoder.More() { + protoMessage := &apimodel.ClientLabel{} + if err := jsonpb.UnmarshalNext(jsonDecoder, protoMessage); err != nil { + return nil, err + } + clientLabels = append(clientLabels, protoMessage) } - gc.grayResources.Store(grayResource.Name, grayRule) + gc.grayResources.Store(grayResource.Name, clientLabels) } return map[string]time.Time{ gc.Name(): time.Unix(lastMtime, 0), - } + }, nil } // Clear clear cache func (gc *grayCache) Clear() error { gc.BaseCache.Clear() - gc.grayResources = utils.NewSyncMap[string, *apimodel.MatchTerm]() + gc.grayResources = utils.NewSyncMap[string, []*apimodel.ClientLabel]() return nil } @@ -120,10 +128,42 @@ func (gc *grayCache) Name() string { } // GetGrayRule get gray rule -func (gc *grayCache) GetGrayRule(name string) *apimodel.MatchTerm { +func (gc *grayCache) GetGrayRule(name string) []*apimodel.ClientLabel { val, ok := gc.grayResources.Load(name) if !ok { return nil } return val } + +func (gc *grayCache) HitGrayRule(name string, labels map[string]string) bool { + rule, ok := gc.grayResources.Load(name) + if !ok { + return false + } + + return grayMatch(rule, labels) +} + +func grayMatch(rule []*apimodel.ClientLabel, labels map[string]string) bool { + for i := range rule { + clientLabel := rule[i] + labelKey := clientLabel.Key + actualVal, ok := labels[labelKey] + if !ok { + return false + } + isMatch := utils.MatchString(actualVal, clientLabel.Value, func(s string) *regexp.Regexp { + regex, err := regexp.Compile(s, regexp.RE2) + if err != nil { + log.Error("[Cache][Gray] compile regex failed", zap.Error(err)) + return nil + } + return regex + }) + if !isMatch { + return false + } + } + return true +} diff --git a/cache/gray/log.go b/cache/gray/log.go new file mode 100644 index 000000000..9e87b1444 --- /dev/null +++ b/cache/gray/log.go @@ -0,0 +1,24 @@ +/** + * 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 commonlog "github.com/polarismesh/polaris/common/log" + +var ( + log = commonlog.GetScopeOrDefaultByName(commonlog.CacheLoggerName) +) diff --git a/cache/gray/match_test.go b/cache/gray/match_test.go new file mode 100644 index 000000000..a4cacaa90 --- /dev/null +++ b/cache/gray/match_test.go @@ -0,0 +1,60 @@ +/** + * 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 ( + "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) { + // 1. 全匹配 + matchKv := []*apimodel.ClientLabel{ + { + Key: "ip", + Value: &apimodel.MatchString{ + Type: apimodel.MatchString_EXACT, + Value: &wrappers.StringValue{Value: "127.0.0.1"}, + }, + }, + } + + ok := grayMatch(matchKv, map[string]string{ + "ip": "127.0.0.1", + }) + assert.Equal(t, ok, true) + + // 2. in 匹配 + matchKv = []*apimodel.ClientLabel{ + { + Key: "ip", + Value: &apimodel.MatchString{ + Type: apimodel.MatchString_IN, + Value: &wrappers.StringValue{Value: "127.0.0.1,196.10.10.1"}, + }, + }, + } + + ok = grayMatch(matchKv, map[string]string{ + "ip": "127.0.0.1", + }) + assert.Equal(t, ok, true) +} diff --git a/cache/mock/cache_mock.go b/cache/mock/cache_mock.go index 6faf466bc..a3c83f4a3 100644 --- a/cache/mock/cache_mock.go +++ b/cache/mock/cache_mock.go @@ -11,6 +11,7 @@ import ( api "github.com/polarismesh/polaris/cache/api" model "github.com/polarismesh/polaris/common/model" store "github.com/polarismesh/polaris/store" + model0 "github.com/polarismesh/specification/source/go/api/v1/model" security "github.com/polarismesh/specification/source/go/api/v1/security" service_manage "github.com/polarismesh/specification/source/go/api/v1/service_manage" traffic_manage "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" @@ -132,6 +133,104 @@ func (m *MockCacheManager) EXPECT() *MockCacheManagerMockRecorder { return m.recorder } +// AuthStrategy mocks base method. +func (m *MockCacheManager) AuthStrategy() api.StrategyCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthStrategy") + ret0, _ := ret[0].(api.StrategyCache) + return ret0 +} + +// AuthStrategy indicates an expected call of AuthStrategy. +func (mr *MockCacheManagerMockRecorder) AuthStrategy() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthStrategy", reflect.TypeOf((*MockCacheManager)(nil).AuthStrategy)) +} + +// CL5 mocks base method. +func (m *MockCacheManager) CL5() api.L5Cache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CL5") + ret0, _ := ret[0].(api.L5Cache) + return ret0 +} + +// CL5 indicates an expected call of CL5. +func (mr *MockCacheManagerMockRecorder) CL5() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CL5", reflect.TypeOf((*MockCacheManager)(nil).CL5)) +} + +// CircuitBreaker mocks base method. +func (m *MockCacheManager) CircuitBreaker() api.CircuitBreakerCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CircuitBreaker") + ret0, _ := ret[0].(api.CircuitBreakerCache) + return ret0 +} + +// CircuitBreaker indicates an expected call of CircuitBreaker. +func (mr *MockCacheManagerMockRecorder) CircuitBreaker() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CircuitBreaker", reflect.TypeOf((*MockCacheManager)(nil).CircuitBreaker)) +} + +// Client mocks base method. +func (m *MockCacheManager) Client() api.ClientCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Client") + ret0, _ := ret[0].(api.ClientCache) + return ret0 +} + +// Client indicates an expected call of Client. +func (mr *MockCacheManagerMockRecorder) Client() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockCacheManager)(nil).Client)) +} + +// ConfigFile mocks base method. +func (m *MockCacheManager) ConfigFile() api.ConfigFileCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConfigFile") + ret0, _ := ret[0].(api.ConfigFileCache) + return ret0 +} + +// ConfigFile indicates an expected call of ConfigFile. +func (mr *MockCacheManagerMockRecorder) ConfigFile() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigFile", reflect.TypeOf((*MockCacheManager)(nil).ConfigFile)) +} + +// ConfigGroup mocks base method. +func (m *MockCacheManager) ConfigGroup() api.ConfigGroupCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConfigGroup") + ret0, _ := ret[0].(api.ConfigGroupCache) + return ret0 +} + +// ConfigGroup indicates an expected call of ConfigGroup. +func (mr *MockCacheManagerMockRecorder) ConfigGroup() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigGroup", reflect.TypeOf((*MockCacheManager)(nil).ConfigGroup)) +} + +// FaultDetector mocks base method. +func (m *MockCacheManager) FaultDetector() api.FaultDetectCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FaultDetector") + ret0, _ := ret[0].(api.FaultDetectCache) + return ret0 +} + +// FaultDetector indicates an expected call of FaultDetector. +func (mr *MockCacheManagerMockRecorder) FaultDetector() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FaultDetector", reflect.TypeOf((*MockCacheManager)(nil).FaultDetector)) +} + // GetCacher mocks base method. func (m *MockCacheManager) GetCacher(cacheIndex api.CacheIndex) api.Cache { m.ctrl.T.Helper() @@ -146,6 +245,80 @@ func (mr *MockCacheManagerMockRecorder) GetCacher(cacheIndex interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCacher", reflect.TypeOf((*MockCacheManager)(nil).GetCacher), cacheIndex) } +// Gray mocks base method. +func (m *MockCacheManager) Gray() api.GrayCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Gray") + ret0, _ := ret[0].(api.GrayCache) + return ret0 +} + +// Gray indicates an expected call of Gray. +func (mr *MockCacheManagerMockRecorder) Gray() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Gray", reflect.TypeOf((*MockCacheManager)(nil).Gray)) +} + +// Instance mocks base method. +func (m *MockCacheManager) Instance() api.InstanceCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Instance") + ret0, _ := ret[0].(api.InstanceCache) + return ret0 +} + +// Instance indicates an expected call of Instance. +func (mr *MockCacheManagerMockRecorder) Instance() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Instance", reflect.TypeOf((*MockCacheManager)(nil).Instance)) +} + +// Namespace mocks base method. +func (m *MockCacheManager) Namespace() api.NamespaceCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Namespace") + ret0, _ := ret[0].(api.NamespaceCache) + return ret0 +} + +// Namespace indicates an expected call of Namespace. +func (mr *MockCacheManagerMockRecorder) Namespace() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Namespace", reflect.TypeOf((*MockCacheManager)(nil).Namespace)) +} + +// OpenResourceCache mocks base method. +func (m *MockCacheManager) OpenResourceCache(entries ...api.ConfigEntry) error { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range entries { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "OpenResourceCache", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// OpenResourceCache indicates an expected call of OpenResourceCache. +func (mr *MockCacheManagerMockRecorder) OpenResourceCache(entries ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenResourceCache", reflect.TypeOf((*MockCacheManager)(nil).OpenResourceCache), entries...) +} + +// RateLimit mocks base method. +func (m *MockCacheManager) RateLimit() api.RateLimitCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RateLimit") + ret0, _ := ret[0].(api.RateLimitCache) + return ret0 +} + +// RateLimit indicates an expected call of RateLimit. +func (mr *MockCacheManagerMockRecorder) RateLimit() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RateLimit", reflect.TypeOf((*MockCacheManager)(nil).RateLimit)) +} + // RegisterCacher mocks base method. func (m *MockCacheManager) RegisterCacher(cacheIndex api.CacheIndex, item api.Cache) { m.ctrl.T.Helper() @@ -158,6 +331,62 @@ func (mr *MockCacheManagerMockRecorder) RegisterCacher(cacheIndex, item interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterCacher", reflect.TypeOf((*MockCacheManager)(nil).RegisterCacher), cacheIndex, item) } +// RoutingConfig mocks base method. +func (m *MockCacheManager) RoutingConfig() api.RoutingConfigCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RoutingConfig") + ret0, _ := ret[0].(api.RoutingConfigCache) + return ret0 +} + +// RoutingConfig indicates an expected call of RoutingConfig. +func (mr *MockCacheManagerMockRecorder) RoutingConfig() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoutingConfig", reflect.TypeOf((*MockCacheManager)(nil).RoutingConfig)) +} + +// Service mocks base method. +func (m *MockCacheManager) Service() api.ServiceCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Service") + ret0, _ := ret[0].(api.ServiceCache) + return ret0 +} + +// Service indicates an expected call of Service. +func (mr *MockCacheManagerMockRecorder) Service() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Service", reflect.TypeOf((*MockCacheManager)(nil).Service)) +} + +// ServiceContract mocks base method. +func (m *MockCacheManager) ServiceContract() api.ServiceContractCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceContract") + ret0, _ := ret[0].(api.ServiceContractCache) + return ret0 +} + +// ServiceContract indicates an expected call of ServiceContract. +func (mr *MockCacheManagerMockRecorder) ServiceContract() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceContract", reflect.TypeOf((*MockCacheManager)(nil).ServiceContract)) +} + +// User mocks base method. +func (m *MockCacheManager) User() api.UserCache { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "User") + ret0, _ := ret[0].(api.UserCache) + return ret0 +} + +// User indicates an expected call of User. +func (mr *MockCacheManagerMockRecorder) User() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "User", reflect.TypeOf((*MockCacheManager)(nil).User)) +} + // MockNamespaceCache is a mock of NamespaceCache interface. type MockNamespaceCache struct { ctrl *gomock.Controller @@ -251,6 +480,20 @@ func (mr *MockNamespaceCacheMockRecorder) GetNamespacesByName(names interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespacesByName", reflect.TypeOf((*MockNamespaceCache)(nil).GetNamespacesByName), names) } +// GetVisibleNamespaces mocks base method. +func (m *MockNamespaceCache) GetVisibleNamespaces(namespace string) []*model.Namespace { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVisibleNamespaces", namespace) + ret0, _ := ret[0].([]*model.Namespace) + return ret0 +} + +// GetVisibleNamespaces indicates an expected call of GetVisibleNamespaces. +func (mr *MockNamespaceCacheMockRecorder) GetVisibleNamespaces(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVisibleNamespaces", reflect.TypeOf((*MockNamespaceCache)(nil).GetVisibleNamespaces), namespace) +} + // Initialize mocks base method. func (m *MockNamespaceCache) Initialize(c map[string]interface{}) error { m.ctrl.T.Helper() @@ -484,6 +727,20 @@ func (mr *MockServiceCacheMockRecorder) GetServicesCount() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicesCount", reflect.TypeOf((*MockServiceCache)(nil).GetServicesCount)) } +// GetVisibleServicesInOtherNamespace mocks base method. +func (m *MockServiceCache) GetVisibleServicesInOtherNamespace(name, namespace string) []*model.Service { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVisibleServicesInOtherNamespace", name, namespace) + ret0, _ := ret[0].([]*model.Service) + return ret0 +} + +// GetVisibleServicesInOtherNamespace indicates an expected call of GetVisibleServicesInOtherNamespace. +func (mr *MockServiceCacheMockRecorder) GetVisibleServicesInOtherNamespace(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVisibleServicesInOtherNamespace", reflect.TypeOf((*MockServiceCache)(nil).GetVisibleServicesInOtherNamespace), name, namespace) +} + // Initialize mocks base method. func (m *MockServiceCache) Initialize(c map[string]interface{}) error { m.ctrl.T.Helper() @@ -647,6 +904,129 @@ func (mr *MockServiceRevisionWorkerMockRecorder) Notify(serviceID, valid interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notify", reflect.TypeOf((*MockServiceRevisionWorker)(nil).Notify), serviceID, valid) } +// MockServiceContractCache is a mock of ServiceContractCache interface. +type MockServiceContractCache struct { + ctrl *gomock.Controller + recorder *MockServiceContractCacheMockRecorder +} + +// MockServiceContractCacheMockRecorder is the mock recorder for MockServiceContractCache. +type MockServiceContractCacheMockRecorder struct { + mock *MockServiceContractCache +} + +// NewMockServiceContractCache creates a new mock instance. +func NewMockServiceContractCache(ctrl *gomock.Controller) *MockServiceContractCache { + mock := &MockServiceContractCache{ctrl: ctrl} + mock.recorder = &MockServiceContractCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockServiceContractCache) EXPECT() *MockServiceContractCacheMockRecorder { + return m.recorder +} + +// Clear mocks base method. +func (m *MockServiceContractCache) Clear() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Clear") + ret0, _ := ret[0].(error) + return ret0 +} + +// Clear indicates an expected call of Clear. +func (mr *MockServiceContractCacheMockRecorder) Clear() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockServiceContractCache)(nil).Clear)) +} + +// Close mocks base method. +func (m *MockServiceContractCache) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockServiceContractCacheMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockServiceContractCache)(nil).Close)) +} + +// Initialize mocks base method. +func (m *MockServiceContractCache) Initialize(c map[string]interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Initialize", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Initialize indicates an expected call of Initialize. +func (mr *MockServiceContractCacheMockRecorder) Initialize(c interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockServiceContractCache)(nil).Initialize), c) +} + +// ListVersions mocks base method. +func (m *MockServiceContractCache) ListVersions(service, namespace string) []*model.EnrichServiceContract { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVersions", service, namespace) + ret0, _ := ret[0].([]*model.EnrichServiceContract) + return ret0 +} + +// ListVersions indicates an expected call of ListVersions. +func (mr *MockServiceContractCacheMockRecorder) ListVersions(service, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVersions", reflect.TypeOf((*MockServiceContractCache)(nil).ListVersions), service, namespace) +} + +// Name mocks base method. +func (m *MockServiceContractCache) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockServiceContractCacheMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockServiceContractCache)(nil).Name)) +} + +// Query mocks base method. +func (m *MockServiceContractCache) Query(filter map[string]string, offset, limit uint32) ([]*model.EnrichServiceContract, uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Query", filter, offset, limit) + ret0, _ := ret[0].([]*model.EnrichServiceContract) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Query indicates an expected call of Query. +func (mr *MockServiceContractCacheMockRecorder) Query(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Query", reflect.TypeOf((*MockServiceContractCache)(nil).Query), filter, offset, limit) +} + +// Update mocks base method. +func (m *MockServiceContractCache) Update() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update") + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockServiceContractCacheMockRecorder) Update() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockServiceContractCache)(nil).Update)) +} + // MockInstanceCache is a mock of InstanceCache interface. type MockInstanceCache struct { ctrl *gomock.Controller @@ -698,6 +1078,20 @@ func (mr *MockInstanceCacheMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockInstanceCache)(nil).Close)) } +// DiscoverServiceInstances mocks base method. +func (m *MockInstanceCache) DiscoverServiceInstances(serviceID string, onlyHealthy bool) []*model.Instance { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DiscoverServiceInstances", serviceID, onlyHealthy) + ret0, _ := ret[0].([]*model.Instance) + return ret0 +} + +// DiscoverServiceInstances indicates an expected call of DiscoverServiceInstances. +func (mr *MockInstanceCacheMockRecorder) DiscoverServiceInstances(serviceID, onlyHealthy interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiscoverServiceInstances", reflect.TypeOf((*MockInstanceCache)(nil).DiscoverServiceInstances), serviceID, onlyHealthy) +} + // GetInstance mocks base method. func (m *MockInstanceCache) GetInstance(instanceID string) *model.Instance { m.ctrl.T.Helper() @@ -726,6 +1120,20 @@ func (mr *MockInstanceCacheMockRecorder) GetInstanceLabels(serviceID interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceLabels", reflect.TypeOf((*MockInstanceCache)(nil).GetInstanceLabels), serviceID) } +// GetInstances mocks base method. +func (m *MockInstanceCache) GetInstances(serviceID string) *model.ServiceInstances { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstances", serviceID) + ret0, _ := ret[0].(*model.ServiceInstances) + return ret0 +} + +// GetInstances indicates an expected call of GetInstances. +func (mr *MockInstanceCacheMockRecorder) GetInstances(serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstances", reflect.TypeOf((*MockInstanceCache)(nil).GetInstances), serviceID) +} + // GetInstancesByServiceID mocks base method. func (m *MockInstanceCache) GetInstancesByServiceID(serviceID string) []*model.Instance { m.ctrl.T.Helper() @@ -1791,6 +2199,20 @@ func (mr *MockConfigFileCacheMockRecorder) GetActiveRelease(namespace, group, fi return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveRelease", reflect.TypeOf((*MockConfigFileCache)(nil).GetActiveRelease), namespace, group, fileName) } +// GetGrayRelease mocks base method. +func (m *MockConfigFileCache) GetGrayRelease(namespace, group, fileName string) *model.ConfigFileRelease { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGrayRelease", namespace, group, fileName) + ret0, _ := ret[0].(*model.ConfigFileRelease) + return ret0 +} + +// GetGrayRelease indicates an expected call of GetGrayRelease. +func (mr *MockConfigFileCacheMockRecorder) GetGrayRelease(namespace, group, fileName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGrayRelease", reflect.TypeOf((*MockConfigFileCache)(nil).GetGrayRelease), namespace, group, fileName) +} + // GetGroupActiveReleases mocks base method. func (m *MockConfigFileCache) GetGroupActiveReleases(namespace, group string) ([]*model.ConfigFileRelease, string) { m.ctrl.T.Helper() @@ -2366,3 +2788,124 @@ func (mr *MockClientCacheMockRecorder) Update() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClientCache)(nil).Update)) } + +// MockGrayCache is a mock of GrayCache interface. +type MockGrayCache struct { + ctrl *gomock.Controller + recorder *MockGrayCacheMockRecorder +} + +// MockGrayCacheMockRecorder is the mock recorder for MockGrayCache. +type MockGrayCacheMockRecorder struct { + mock *MockGrayCache +} + +// NewMockGrayCache creates a new mock instance. +func NewMockGrayCache(ctrl *gomock.Controller) *MockGrayCache { + mock := &MockGrayCache{ctrl: ctrl} + mock.recorder = &MockGrayCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGrayCache) EXPECT() *MockGrayCacheMockRecorder { + return m.recorder +} + +// Clear mocks base method. +func (m *MockGrayCache) Clear() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Clear") + ret0, _ := ret[0].(error) + return ret0 +} + +// Clear indicates an expected call of Clear. +func (mr *MockGrayCacheMockRecorder) Clear() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockGrayCache)(nil).Clear)) +} + +// Close mocks base method. +func (m *MockGrayCache) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockGrayCacheMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockGrayCache)(nil).Close)) +} + +// GetGrayRule mocks base method. +func (m *MockGrayCache) GetGrayRule(name string) []*model0.ClientLabel { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGrayRule", name) + ret0, _ := ret[0].([]*model0.ClientLabel) + return ret0 +} + +// GetGrayRule indicates an expected call of GetGrayRule. +func (mr *MockGrayCacheMockRecorder) GetGrayRule(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGrayRule", reflect.TypeOf((*MockGrayCache)(nil).GetGrayRule), name) +} + +// HitGrayRule mocks base method. +func (m *MockGrayCache) HitGrayRule(name string, labels map[string]string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HitGrayRule", name, labels) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HitGrayRule indicates an expected call of HitGrayRule. +func (mr *MockGrayCacheMockRecorder) HitGrayRule(name, labels interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HitGrayRule", reflect.TypeOf((*MockGrayCache)(nil).HitGrayRule), name, labels) +} + +// Initialize mocks base method. +func (m *MockGrayCache) Initialize(c map[string]interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Initialize", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Initialize indicates an expected call of Initialize. +func (mr *MockGrayCacheMockRecorder) Initialize(c interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockGrayCache)(nil).Initialize), c) +} + +// Name mocks base method. +func (m *MockGrayCache) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockGrayCacheMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockGrayCache)(nil).Name)) +} + +// Update mocks base method. +func (m *MockGrayCache) Update() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update") + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockGrayCacheMockRecorder) Update() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockGrayCache)(nil).Update)) +} diff --git a/cache/service/instance.go b/cache/service/instance.go index 42a8e1fa3..232e70166 100644 --- a/cache/service/instance.go +++ b/cache/service/instance.go @@ -49,7 +49,7 @@ type instanceCache struct { // instanceid -> instance ids *utils.SyncMap[string, *model.Instance] // service id -> [instanceid ->instance] - services *utils.SyncMap[string, *ServiceInstances] + services *utils.SyncMap[string, *model.ServiceInstances] // service id -> [instanceCount] instanceCounts *utils.SyncMap[string, *model.InstanceCount] instancePorts *instancePorts @@ -74,7 +74,7 @@ func NewInstanceCache(storage store.Store, cacheMgr types.CacheManager) types.In func (ic *instanceCache) Initialize(opt map[string]interface{}) error { ic.svcCache = ic.BaseCache.CacheMgr.GetCacher(types.CacheService).(*serviceCache) ic.ids = utils.NewSyncMap[string, *model.Instance]() - ic.services = utils.NewSyncMap[string, *ServiceInstances]() + ic.services = utils.NewSyncMap[string, *model.ServiceInstances]() ic.instanceCounts = utils.NewSyncMap[string, *model.InstanceCount]() ic.instancePorts = newInstancePorts() if opt == nil { @@ -202,7 +202,7 @@ func (ic *instanceCache) handleUpdate(start time.Time, tx store.Tx) ([]*eventhub func (ic *instanceCache) Clear() error { ic.BaseCache.Clear() ic.ids = utils.NewSyncMap[string, *model.Instance]() - ic.services = utils.NewSyncMap[string, *ServiceInstances]() + ic.services = utils.NewSyncMap[string, *model.ServiceInstances]() ic.instanceCounts = utils.NewSyncMap[string, *model.InstanceCount]() ic.instancePorts.reset() ic.instanceCount = 0 @@ -246,7 +246,7 @@ func (ic *instanceCache) setInstances(ins map[string]*model.Instance) ([]*eventh for _, item := range ins { if _, ok := ic.services.Load(item.ServiceID); !ok { - ic.services.Store(item.ServiceID, newServiceInstances(0)) + ic.services.Store(item.ServiceID, model.NewServiceInstances(0)) } serviceInstances, _ := ic.services.Load(item.ServiceID) svc := ic.BaseCache.CacheMgr.GetCacher(types.CacheService).(types.ServiceCache).GetServiceByID(item.ServiceID) @@ -254,7 +254,7 @@ func (ic *instanceCache) setInstances(ins map[string]*model.Instance) ([]*eventh // 填充实例的服务名称数据信息 item.Proto.Namespace = utils.NewStringValue(svc.Namespace) item.Proto.Service = utils.NewStringValue(svc.Name) - serviceInstances.updateProtectThreshold(svc.ProtectThreshold()) + serviceInstances.UpdateProtectThreshold(svc.ProtectThreshold()) } progress++ @@ -376,7 +376,7 @@ func (ic *instanceCache) postProcessUpdatedServices(affect map[string]bool) { func (ic *instanceCache) runHealthyProtect(affect map[string]bool) { for serviceID := range affect { if serviceInstances, ok := ic.services.Load(serviceID); ok { - serviceInstances.runHealthyProtect() + serviceInstances.RunHealthyProtect() } } } @@ -433,6 +433,19 @@ func (ic *instanceCache) GetInstance(instanceID string) *model.Instance { return value } +// GetInstances 根据服务名获取实例,先查找服务名对应的服务ID,再找实例列表 +func (ic *instanceCache) GetInstances(serviceID string) *model.ServiceInstances { + if serviceID == "" { + return nil + } + + value, ok := ic.services.Load(serviceID) + if !ok { + return nil + } + return value +} + // GetInstancesByServiceID 根据ServiceID获取实例数据 func (ic *instanceCache) GetInstancesByServiceID(serviceID string) []*model.Instance { if serviceID == "" { @@ -444,7 +457,7 @@ func (ic *instanceCache) GetInstancesByServiceID(serviceID string) []*model.Inst return nil } - out := make([]*model.Instance, 0, value.totalCount()) + out := make([]*model.Instance, 0, value.TotalCount()) value.Range(func(k string, v *model.Instance) { out = append(out, v) }) @@ -617,143 +630,3 @@ func (b *instancePorts) listPort(serviceID string) []*model.ServicePort { const ( MetadataInstanceLastHeartbeatTime = "internal-lastheartbeat" ) - -func newServiceInstances(protectThreshold float32) *ServiceInstances { - return &ServiceInstances{ - instances: make(map[string]*model.Instance, 128), - healthyInstances: make(map[string]*model.Instance, 128), - unhealthyInstances: make(map[string]*model.Instance, 128), - protectInstances: make(map[string]*model.Instance, 128), - } -} - -type ServiceInstances struct { - lock sync.RWMutex - instances map[string]*model.Instance - healthyInstances map[string]*model.Instance - unhealthyInstances map[string]*model.Instance - protectInstances map[string]*model.Instance - protectThreshold float32 -} - -func (si *ServiceInstances) totalCount() int { - si.lock.RLock() - defer si.lock.RUnlock() - - return len(si.instances) -} - -func (si *ServiceInstances) updateProtectThreshold(protectThreshold float32) { - si.lock.Lock() - defer si.lock.Unlock() - - si.protectThreshold = protectThreshold -} - -func (si *ServiceInstances) UpsertInstance(ins *model.Instance) { - si.lock.Lock() - defer si.lock.Unlock() - - si.instances[ins.ID()] = ins - if ins.Healthy() { - si.healthyInstances[ins.ID()] = ins - } else { - si.unhealthyInstances[ins.ID()] = ins - } -} - -func (si *ServiceInstances) RemoveInstance(ins *model.Instance) { - si.lock.Lock() - defer si.lock.Unlock() - - delete(si.instances, ins.ID()) - delete(si.healthyInstances, ins.ID()) - delete(si.unhealthyInstances, ins.ID()) - delete(si.protectInstances, ins.ID()) -} - -func (si *ServiceInstances) Range(iterator func(id string, ins *model.Instance)) { - si.lock.RLock() - defer si.lock.RUnlock() - - for k, v := range si.instances { - iterator(k, v) - } -} - -func (si *ServiceInstances) GetInstances(onlyHealthy bool) []*model.Instance { - si.lock.RLock() - defer si.lock.RUnlock() - - ret := make([]*model.Instance, 0, len(si.healthyInstances)+len(si.protectInstances)) - if !onlyHealthy { - for k, v := range si.instances { - protectIns, ok := si.protectInstances[k] - if ok { - ret = append(ret, protectIns) - } else { - ret = append(ret, v) - } - } - } else { - for _, v := range si.healthyInstances { - ret = append(ret, v) - } - for _, v := range si.protectInstances { - ret = append(ret, v) - } - } - return ret -} - -func (si *ServiceInstances) runHealthyProtect() { - si.lock.Lock() - defer si.lock.Unlock() - - lastBeat := int64(-1) - - curProportion := float32(len(si.healthyInstances)) / float32(len(si.instances)) - if curProportion > si.protectThreshold { - // 不会触发, 并且清空当前保护状态的实例 - si.protectInstances = make(map[string]*model.Instance, 128) - return - } - instanceLastBeatTimes := map[string]int64{} - instances := si.unhealthyInstances - for i := range instances { - ins := instances[i] - metadata := ins.Metadata() - if len(metadata) == 0 { - continue - } - val, ok := metadata[MetadataInstanceLastHeartbeatTime] - if !ok { - continue - } - beatTime, _ := strconv.ParseInt(val, 10, 64) - if beatTime >= lastBeat { - lastBeat = beatTime - } - instanceLastBeatTimes[ins.ID()] = beatTime - } - if lastBeat == -1 { - return - } - for i := range instances { - ins := instances[i] - beatTime, ok := instanceLastBeatTimes[ins.ID()] - if !ok { - continue - } - needProtect := needZeroProtect(lastBeat, beatTime, int64(ins.HealthCheck().GetHeartbeat().GetTtl().GetValue())) - if !needProtect { - continue - } - si.protectInstances[ins.ID()] = ins - } -} - -// needZeroProtect . -func needZeroProtect(lastBeat, beatTime, ttl int64) bool { - return lastBeat-3*ttl > beatTime -} diff --git a/cache/service/router_rule_query.go b/cache/service/router_rule_query.go index 3b9427c56..09c78dc11 100644 --- a/cache/service/router_rule_query.go +++ b/cache/service/router_rule_query.go @@ -123,8 +123,10 @@ func (rc *routingConfigCache) QueryRoutingConfigsV2(args *types.RoutingArgs) (ui if err := rc.forceUpdate(); err != nil { return 0, nil, err } + hasSvcQuery := len(args.Service) != 0 || len(args.Namespace) != 0 hasSourceQuery := len(args.SourceService) != 0 || len(args.SourceNamespace) != 0 hasDestQuery := len(args.DestinationService) != 0 || len(args.DestinationNamespace) != 0 + needBoth := hasSourceQuery && hasDestQuery res := make([]*model.ExtendRouterConfig, 0, 8) @@ -134,16 +136,27 @@ func (rc *routingConfigCache) QueryRoutingConfigsV2(args *types.RoutingArgs) (ui } if routeRule.GetRoutingPolicy() == apitraffic.RoutingPolicy_RulePolicy { - if args.Namespace != "" && args.Service != "" { - if !queryRoutingRuleV2ByService(routeRule, args.Namespace, args.Service, - args.Namespace, args.Service, false) { - return + if args.Namespace != "" { + if args.SourceNamespace == "" { + args.SourceNamespace = args.Namespace + } + if args.DestinationNamespace == "" { + args.DestinationNamespace = args.Namespace } } - - if hasSourceQuery || hasDestQuery { - if !queryRoutingRuleV2ByService(routeRule, args.SourceNamespace, args.SourceService, args.DestinationNamespace, - args.DestinationService, hasSourceQuery && hasDestQuery) { + if args.Service != "" { + if args.SourceService == "" { + args.SourceService = args.Service + } + if args.DestinationService == "" { + args.DestinationService = args.Service + } + } + if hasSvcQuery || hasSourceQuery || hasDestQuery { + if !queryRoutingRuleV2ByService(routeRule, + args.SourceNamespace, args.SourceService, + args.DestinationNamespace, args.DestinationService, + needBoth) { return } } diff --git a/cache/test_export.go b/cache/test_export.go index 2a8306acb..caf4ba831 100644 --- a/cache/test_export.go +++ b/cache/test_export.go @@ -31,10 +31,14 @@ var ( if err != nil { return nil, err } + return mgr, err + } + + TestRun = func(ctx context.Context, mgr *CacheManager) error { if err := Run(mgr, ctx); err != nil { - return nil, err + return err } - return mgr, err + return nil } ) diff --git a/common/api/v1/codeinfo.go b/common/api/v1/codeinfo.go index 34d8bb5bc..213e2f436 100644 --- a/common/api/v1/codeinfo.go +++ b/common/api/v1/codeinfo.go @@ -166,6 +166,7 @@ const ( InvalidWatchConfigFileFormat = uint32(apimodel.Code_InvalidWatchConfigFileFormat) NotFoundResourceConfigFile = uint32(apimodel.Code_NotFoundResourceConfigFile) InvalidConfigFileTemplateName = uint32(apimodel.Code_InvalidConfigFileTemplateName) + InvalidMatchRule = uint32(apimodel.Code_InvalidMatchRule) // 鉴权相关错误码 InvalidUserOwners = uint32(apimodel.Code_InvalidUserOwners) @@ -329,6 +330,7 @@ var code2info = map[uint32]string{ InvalidWatchConfigFileFormat: "invalid watch config file format", NotFoundResourceConfigFile: "config file not existed", InvalidConfigFileTemplateName: "invalid config file template name", + InvalidMatchRule: "invalid gray config beta labels", // 鉴权错误 NotFoundUser: "not found user", diff --git a/common/api/v1/naming_response.go b/common/api/v1/naming_response.go index ee46f938c..9070359a8 100644 --- a/common/api/v1/naming_response.go +++ b/common/api/v1/naming_response.go @@ -20,6 +20,7 @@ package v1 import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/wrappers" + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apifault "github.com/polarismesh/specification/source/go/api/v1/fault_tolerance" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" @@ -36,6 +37,12 @@ type ResponseMessage interface { GetInfo() *wrappers.StringValue } +type ResponseMessageV2 interface { + proto.Message + GetCode() uint32 + GetInfo() string +} + /** * @brief 获取返回码前三位 * @note 返回码前三位和HTTP返回码定义一致 @@ -44,6 +51,14 @@ func CalcCode(rm ResponseMessage) int { return int(rm.GetCode().GetValue() / 1000) } +/** + * @brief 获取返回码前三位 + * @note 返回码前三位和HTTP返回码定义一致 + */ +func CalcCodeV2(rm ResponseMessageV2) int { + return int(rm.GetCode() / 1000) +} + /** * @brief BatchWriteResponse添加Response */ @@ -320,6 +335,14 @@ func NewDiscoverFaultDetectorResponse(code apimodel.Code, service *apiservice.Se } } +// 创建一个空白的 ConfigDiscoverResponse +func NewConfigDiscoverResponse(code apimodel.Code) *apiconfig.ConfigDiscoverResponse { + return &apiconfig.ConfigDiscoverResponse{ + Code: uint32(code), + Info: code2info[uint32(code)], + } +} + // 格式化responses // batch操作 // 如果所有子错误码一致,那么使用子错误码 diff --git a/common/batchjob/batch.go b/common/batchjob/batch.go deleted file mode 100644 index cb2661813..000000000 --- a/common/batchjob/batch.go +++ /dev/null @@ -1,281 +0,0 @@ -/** - * 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 batchjob - -import ( - "context" - "errors" - "sync" - "sync/atomic" - "time" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/polarismesh/polaris/common/log" - "github.com/polarismesh/polaris/common/metrics" -) - -var ( - ErrorBatchControllerStopped = errors.New("batch controller is stopped") - ErrorSubmitTaskTimeout = errors.New("submit task into batch controller timeout") -) - -const ( - shutdownNow = 1 - shutdownGraceful = 2 -) - -// BatchController 通用的批任务处理框架 -type BatchController struct { - lock sync.RWMutex - stop int32 - label string - conf CtrlConfig - handler func(tasks []Future) - tasksChan chan Future - idleSignal chan int - workers []chan []Future - cancel context.CancelFunc - allWorkersStop chan struct{} - unfinishJobs *prometheus.GaugeVec -} - -// NewBatchController 创建一个批任务处理 -func NewBatchController(ctx context.Context, conf CtrlConfig) *BatchController { - ctx, cancel := context.WithCancel(ctx) - bc := &BatchController{ - label: conf.Label, - conf: conf, - cancel: cancel, - tasksChan: make(chan Future, conf.QueueSize), - workers: make([]chan []Future, 0, conf.Concurrency), - idleSignal: make(chan int, conf.Concurrency), - allWorkersStop: make(chan struct{}), - } - bc.handler = func(tasks []Future) { - defer func() { - if err := recover(); err != nil { - log.Errorf("[Batch] %s trigger consumer panic : %+v", conf.Label, err) - } - }() - conf.Handler(tasks) - metrics.ReportFinishBatchJob(bc.label, int64(len(tasks))) - } - bc.runWorkers(ctx) - bc.mainLoop(ctx) - return bc -} - -// Submit 提交执行任务参数 -func (bc *BatchController) Submit(task Param) Future { - bc.lock.RLock() - defer bc.lock.RUnlock() - - if bc.isStop() { - return &errorFuture{task: task, err: ErrorBatchControllerStopped} - } - - ctx, cancel := context.WithCancel(context.Background()) - f := &future{ - task: task, - ctx: ctx, - cancel: cancel, - setsignal: make(chan struct{}, 1), - } - bc.tasksChan <- f - metrics.ReportAddBatchJob(bc.label, 1) - return f -} - -func (bc *BatchController) SubmitWithTimeout(task Param, timeout time.Duration) Future { - bc.lock.RLock() - defer bc.lock.RUnlock() - - if bc.isStop() { - return &errorFuture{task: task, err: ErrorBatchControllerStopped} - } - - ctx, cancel := context.WithCancel(context.Background()) - f := &future{ - task: task, - ctx: ctx, - cancel: cancel, - setsignal: make(chan struct{}, 1), - } - timer := time.NewTimer(timeout) - defer timer.Stop() - select { - case <-timer.C: - f.Cancel() - return &errorFuture{task: task, err: ErrorSubmitTaskTimeout} - case bc.tasksChan <- f: - metrics.ReportAddBatchJob(bc.label, 1) - return f - } -} - -func (bc *BatchController) runWorkers(ctx context.Context) { - wait := &sync.WaitGroup{} - wait.Add(int(bc.conf.Concurrency)) - - for i := uint32(0); i < bc.conf.Concurrency; i++ { - index := i - bc.workers = append(bc.workers, make(chan []Future)) - go func(index uint32) { - log.Debugf("[Batch] %s worker(%d) running in main loop", bc.label, index) - bc.workerLoop(ctx, int(index), wait) - }(index) - } - - go func() { - wait.Wait() - log.Infof("[Batch] %s close idle worker signal", bc.label) - close(bc.idleSignal) - close(bc.allWorkersStop) - }() -} - -func (bc *BatchController) workerLoop(ctx context.Context, index int, wait *sync.WaitGroup) { - stopFunc := func() { - defer wait.Done() - switch atomic.LoadInt32(&bc.stop) { - case shutdownGraceful: - replied := 0 - for futures := range bc.workers[index] { - replied += len(futures) - bc.handler(futures) - bc.idleSignal <- int(index) - } - log.Infof("[Batch] %s worker(%d) exit, handle future count: %d", bc.label, index, replied) - case shutdownNow: - stopped := 0 - for futures := range bc.workers[index] { - replyStoppedFutures(futures...) - stopped += len(futures) - } - log.Infof("[Batch] %s worker(%d) exit, reply stop msg to future count: %d", bc.label, index, stopped) - } - } - - bc.idleSignal <- index - for { - select { - case <-ctx.Done(): - stopFunc() - return - case futures := <-bc.workers[index]: - bc.handler(futures) - bc.idleSignal <- index - } - } -} - -func (bc *BatchController) mainLoop(ctx context.Context) { - go func() { - futures := make([]Future, 0, bc.conf.MaxBatchCount) - triggerConsume := func(data []Future) { - if len(data) == 0 { - return - } - idleIndex := <-bc.idleSignal - bc.workers[idleIndex] <- data - futures = make([]Future, 0, bc.conf.MaxBatchCount) - } - - stopFunc := func() { - close(bc.tasksChan) - log.Debugf("[Batch] %s begin close task chan", bc.label) - switch atomic.LoadInt32(&bc.stop) { - case shutdownGraceful: - triggerConsume(futures) - for future := range bc.tasksChan { - futures = append(futures, future) - if len(futures) == int(bc.conf.MaxBatchCount) { - triggerConsume(futures) - } - } - // 最后触发兜底 - triggerConsume(futures) - for i := range bc.workers { - close(bc.workers[i]) - } - case shutdownNow: - log.Debugf("[Batch] %s begin close worker loop", bc.label) - for i := range bc.workers { - close(bc.workers[i]) - } - stopped := len(futures) - replyStoppedFutures(futures...) - for future := range bc.tasksChan { - replyStoppedFutures(future) - stopped++ - } - log.Debugf("[Batch] %s do reply stop msg to future count: %d", bc.label, stopped) - } - <-bc.allWorkersStop - log.Debugf("[Batch] %s main loop exited", bc.label) - } - - log.Debugf("[Batch] %s running main loop", bc.label) - ticker := time.NewTicker(bc.conf.WaitTime) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - bc.lock.Lock() - defer bc.lock.Unlock() - stopFunc() - return - case <-ticker.C: - triggerConsume(futures) - case future := <-bc.tasksChan: - futures = append(futures, future) - if len(futures) == int(bc.conf.MaxBatchCount) { - triggerConsume(futures) - } - } - } - }() -} - -// Stop 关闭批任务执行器 -func (bc *BatchController) Stop() { - bc.lock.Lock() - defer bc.lock.Unlock() - log.Infof("[Batch] %s begin do stop", bc.label) - atomic.StoreInt32(&bc.stop, shutdownNow) - bc.cancel() -} - -// Stop 关闭批任务执行器 -func (bc *BatchController) GracefulStop() { - bc.lock.Lock() - defer bc.lock.Unlock() - atomic.StoreInt32(&bc.stop, shutdownGraceful) - bc.cancel() -} - -func (bc *BatchController) isStop() bool { - return atomic.LoadInt32(&bc.stop) == 1 || atomic.LoadInt32(&bc.stop) == shutdownGraceful -} - -func replyStoppedFutures(futures ...Future) { - for i := range futures { - _ = futures[i].Reply(nil, ErrorBatchControllerStopped) - } -} diff --git a/common/batchjob/batch_test.go b/common/batchjob/batch_test.go deleted file mode 100644 index e654390b6..000000000 --- a/common/batchjob/batch_test.go +++ /dev/null @@ -1,234 +0,0 @@ -/** - * 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 batchjob - -import ( - "context" - "errors" - "fmt" - "math/rand" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestNewBatchController(t *testing.T) { - total := 1000 - - totalTasks := int32(0) - testHandle := func(futures []Future) { - atomic.AddInt32(&totalTasks, int32(len(futures))) - time.Sleep(time.Duration(rand.Int63n(100)) * time.Millisecond) - for i := range futures { - futures[i].Reply(nil, nil) - } - } - - ctrl := NewBatchController(context.Background(), CtrlConfig{ - QueueSize: 32, - MaxBatchCount: 16, - WaitTime: 32 * time.Millisecond, - Concurrency: 8, - Handler: testHandle, - }) - - wg := &sync.WaitGroup{} - - for i := 0; i < total; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - future := ctrl.Submit(fmt.Sprintf("%d", i)) - _, _ = future.Done() - }(i) - } - - wg.Wait() - assert.Equal(t, total, int(atomic.LoadInt32(&totalTasks))) - ctrl.Stop() -} - -func TestNewBatchControllerTSubmitTimeout(t *testing.T) { - total := 1000 - - totalTasks := int32(0) - testHandle := func(futures []Future) { - time.Sleep(100 * time.Millisecond) - atomic.AddInt32(&totalTasks, int32(len(futures))) - for i := range futures { - futures[i].Reply(nil, nil) - } - } - - ctrl := NewBatchController(context.Background(), CtrlConfig{ - QueueSize: 1, - MaxBatchCount: uint32(total * 2), - WaitTime: 32 * time.Millisecond, - Concurrency: 8, - Handler: testHandle, - }) - - wg := &sync.WaitGroup{} - - for i := 0; i < total; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - future := ctrl.SubmitWithTimeout(fmt.Sprintf("%d", i), time.Millisecond) - _, err := future.Done() - if err != nil { - assert.True(t, errors.Is(err, ErrorSubmitTaskTimeout), err) - } - }(i) - } - - wg.Wait() - ctrl.Stop() -} - -func TestNewBatchControllerDoneTimeout(t *testing.T) { - total := 100 - - totalTasks := int32(0) - testHandle := func(futures []Future) { - time.Sleep(100 * time.Millisecond) - atomic.AddInt32(&totalTasks, int32(len(futures))) - } - - ctrl := NewBatchController(context.Background(), CtrlConfig{ - QueueSize: 1, - MaxBatchCount: uint32(total * 2), - WaitTime: 32 * time.Millisecond, - Concurrency: 8, - Handler: testHandle, - }) - - wg := &sync.WaitGroup{} - - for i := 0; i < total; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - future := ctrl.Submit(fmt.Sprintf("%d", i)) - _, err := future.DoneTimeout(time.Second) - assert.Error(t, err) - assert.True(t, errors.Is(err, context.DeadlineExceeded), err) - }(i) - } - - t.Log("BatchController already stop") - wg.Wait() - ctrl.Stop() -} - -func TestNewBatchControllerStop(t *testing.T) { - total := 1000 - - totalTasks := int32(0) - testHandle := func(futures []Future) { - atomic.AddInt32(&totalTasks, int32(len(futures))) - for i := range futures { - futures[i].Reply(atomic.LoadInt32(&totalTasks), nil) - } - } - - ctrl := NewBatchController(context.Background(), CtrlConfig{ - QueueSize: uint32(total * 2), - MaxBatchCount: 64, - WaitTime: 32 * time.Millisecond, - Concurrency: 8, - Handler: testHandle, - }) - - sbWg := &sync.WaitGroup{} - wg := &sync.WaitGroup{} - sbWg.Add(total) - wg.Add(total) - cancelTask := int32(0) - for i := 0; i < total; i++ { - go func(i int) { - defer func() { - wg.Done() - }() - future := ctrl.Submit(fmt.Sprintf("%d", i)) - sbWg.Done() - _, err := future.Done() - if err != nil { - if assert.ErrorIs(t, err, ErrorBatchControllerStopped) { - atomic.AddInt32(&cancelTask, 1) - } - } - }(i) - } - - ctrl.Stop() - t.Log("BatchController already stop") - sbWg.Wait() - t.Logf("cancel jobs : %d", atomic.LoadInt32(&cancelTask)) - wg.Wait() - t.Log("finish all batch job") -} - -func TestNewBatchControllerGracefulStop(t *testing.T) { - total := 1000 - - ctrl := NewBatchController(context.Background(), CtrlConfig{ - QueueSize: uint32(total * 2), - MaxBatchCount: 64, - WaitTime: 32 * time.Millisecond, - Concurrency: 8, - Handler: func(futures []Future) { - for i := range futures { - futures[i].Reply(nil, nil) - } - }, - }) - - sbWg := &sync.WaitGroup{} - wg := &sync.WaitGroup{} - sbWg.Add(total) - wg.Add(total) - submitTask := int32(0) - cancelTask := int32(0) - for i := 0; i < total; i++ { - go func(i int) { - defer func() { - wg.Done() - }() - future := ctrl.Submit(fmt.Sprintf("%d", i)) - atomic.AddInt32(&submitTask, 1) - sbWg.Done() - _, err := future.Done() - if err != nil { - if assert.ErrorIs(t, err, ErrorBatchControllerStopped) { - atomic.AddInt32(&cancelTask, 1) - } - } - }(i) - } - - ctrl.GracefulStop() - t.Log("BatchController already stop") - sbWg.Wait() - t.Logf("submit jobs : %d, cancel jobs : %d", atomic.LoadInt32(&submitTask), atomic.LoadInt32(&cancelTask)) - wg.Wait() - t.Log("finish all batch job") -} diff --git a/common/batchjob/future.go b/common/batchjob/future.go deleted file mode 100644 index 091ac276e..000000000 --- a/common/batchjob/future.go +++ /dev/null @@ -1,125 +0,0 @@ -/** - * 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 batchjob - -import ( - "context" - "errors" - "sync/atomic" - "time" -) - -type Param interface{} - -type Future interface { - Param() Param - Done() (interface{}, error) - DoneTimeout(timeout time.Duration) (interface{}, error) - Cancel() - Reply(result interface{}, err error) error -} - -type errorFuture struct { - task Param - err error -} - -func (f *errorFuture) Param() Param { - return f.task -} - -func (f *errorFuture) Done() (interface{}, error) { - return nil, f.err -} - -func (f *errorFuture) DoneTimeout(timeout time.Duration) (interface{}, error) { - return nil, f.err -} - -func (f *errorFuture) Cancel() { -} - -func (f *errorFuture) Reply(result interface{}, err error) error { - return nil -} - -type future struct { - task Param - setsignal chan struct{} - err error - result interface{} - replied int32 - ctx context.Context - cancel context.CancelFunc -} - -func (f *future) Param() Param { - return f.task -} - -func (f *future) Done() (interface{}, error) { - defer func() { - f.cancel() - }() - select { - case <-f.ctx.Done(): - return nil, f.ctx.Err() - case <-f.setsignal: - return f.result, f.err - } -} - -func (f *future) DoneTimeout(timeout time.Duration) (interface{}, error) { - timer := time.NewTimer(timeout) - defer func() { - timer.Stop() - f.cancel() - }() - select { - case <-timer.C: - return nil, context.DeadlineExceeded - case <-f.ctx.Done(): - return nil, f.ctx.Err() - case <-f.setsignal: - f.cancel() - return f.result, f.err - } -} - -func (f *future) Cancel() { - if atomic.CompareAndSwapInt32(&f.replied, 0, 1) { - close(f.setsignal) - } - f.cancel() -} - -var ( - ErrorReplyOnlyOnce = errors.New("reply only call once") - ErrorReplyCanceled = errors.New("reply canceled") -) - -func (f *future) Reply(result interface{}, err error) error { - if !atomic.CompareAndSwapInt32(&f.replied, 0, 1) { - return ErrorReplyOnlyOnce - } - f.result = result - f.err = err - f.setsignal <- struct{}{} - close(f.setsignal) - return nil -} diff --git a/common/metrics/types.go b/common/metrics/types.go index 9fa4b0318..484ceb6b1 100644 --- a/common/metrics/types.go +++ b/common/metrics/types.go @@ -18,6 +18,7 @@ package metrics import ( + "fmt" "strconv" "time" @@ -106,12 +107,41 @@ type DiscoveryMetric struct { Labels map[string]string } +func ResourceOfConfigFileList(group string) string { + return "CONFIG_FILE_LIST:" + group +} + +func ResourceOfConfigFile(group, name string) string { + return "CONFIG_FILE:" + group + "/" + name +} + +const ( + ActionGetConfigFile = "GET_CONFIG_FILE" + ActionListConfigFiles = "LIST_CONFIG_FILES" + ActionListConfigGroups = "LIST_CONFIG_GROUPS" + ActionPublishConfigFile = "PUBLISH_CONFIG_FILE" + ActionDiscoverInstance = "DISCOVER_INSTANCE" + ActionDiscoverServices = "DISCOVER_SERVICES" + ActionDiscoverRouterRule = "DISCOVER_ROUTER_RULE" + ActionDiscoverRateLimit = "DISCOVER_RATE_LIMIT" + ActionDiscoverCircuitBreaker = "DISCOVER_CIRCUIT_BREAKER" + ActionDiscoverFaultDetect = "DISCOVER_FAULT_DETECT" +) + type ClientDiscoverMetric struct { ClientIP string + Action string Namespace string Resource string + Revision string Timestamp int64 CostTime int64 + Success bool +} + +func (c ClientDiscoverMetric) String() string { + return fmt.Sprintf("%s|%s|%s|%s|%s|%s|%d|%+v", c.ClientIP, c.Action, c.Namespace, c.Resource, + c.Revision, time.Unix(c.Timestamp, 0).Format("2006-01-02 15:04:05"), c.CostTime, c.Success) } type ConfigMetricType string diff --git a/common/model/auth.go b/common/model/auth.go index c642a9ab3..6dc8007e6 100644 --- a/common/model/auth.go +++ b/common/model/auth.go @@ -22,6 +22,8 @@ import ( "fmt" "strconv" "time" + + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" ) var ( @@ -50,6 +52,18 @@ var ( ErrorTokenDisabled error = errors.New("token already disabled") ) +func ConvertToErrCode(err error) apimodel.Code { + if errors.Is(err, ErrorTokenNotExist) { + return apimodel.Code_TokenNotExisted + } + + if errors.Is(err, ErrorTokenDisabled) { + return apimodel.Code_TokenDisabled + } + + return apimodel.Code_NotAllowedAccess +} + const ( OperatorRoleKey string = "operator_role" OperatorPrincipalType string = "operator_principal" diff --git a/common/model/client.go b/common/model/client.go index fdc6668c0..dc0d6fd30 100644 --- a/common/model/client.go +++ b/common/model/client.go @@ -131,3 +131,14 @@ type PrometheusTarget struct { Targets []string `json:"targets"` Labels map[string]string `json:"labels"` } + +const ( + // ClientLabel_IP 客户端 IP + ClientLabel_IP = "CLIENT_IP" + // ClientLabel_ID 客户端 ID + ClientLabel_ID = "CLIENT_ID" + // ClientLabel_Version 客户端版本 + ClientLabel_Version = "CLIENT_VERSION" + // ClientLabel_Language 客户端语言 + ClientLabel_Language = "CLIENT_LANGUAGE" +) diff --git a/common/model/config_file.go b/common/model/config_file.go index 522a9f626..b82de08d4 100644 --- a/common/model/config_file.go +++ b/common/model/config_file.go @@ -22,19 +22,19 @@ import ( "time" "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" ) -type ReleaseType uint32 +type ReleaseType string const ( - _ ReleaseType = iota // ReleaseTypeFull 全量类型 - ReleaseTypeFull + ReleaseTypeFull = "" // ReleaseTypeGray 灰度类型 - ReleaseTypeGray + ReleaseTypeGray = "gray" ) /** ----------- DataObject ------------- */ @@ -54,6 +54,7 @@ type ConfigFileGroup struct { CreateBy string ModifyBy string Valid bool + Revision string } type ConfigFileKey struct { @@ -104,14 +105,14 @@ func (s *ConfigFile) KeyString() string { } func (s *ConfigFile) GetEncryptDataKey() string { - return s.Metadata[utils.ConfigFileTagKeyDataKey] + return s.Metadata[MetaKeyConfigFileDataKey] } func (s *ConfigFile) GetEncryptAlgo() string { if s.EncryptAlgo != "" { return s.EncryptAlgo } - return s.Metadata[utils.ConfigFileTagKeyEncryptAlgo] + return s.Metadata[MetaKeyConfigFileEncryptAlgo] } func (s *ConfigFile) IsEncrypted() bool { @@ -133,12 +134,12 @@ type ConfigFileRelease struct { } type ConfigFileReleaseKey struct { - Id uint64 - Name string - Namespace string - Group string - FileName string - Typ ReleaseType + Id uint64 + Name string + Namespace string + Group string + FileName string + ReleaseType ReleaseType } func (c ConfigFileReleaseKey) ToFileKey() *ConfigFileKey { @@ -153,15 +154,19 @@ func (c *ConfigFileReleaseKey) OwnerKey() string { return c.Namespace + "@" + c.Group } +func (c ConfigFileReleaseKey) FileKey() string { + return fmt.Sprintf("%v@%v@%v", c.Namespace, c.Group, c.FileName) +} + 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.ReleaseType) } func (c ConfigFileReleaseKey) ReleaseKey() string { return fmt.Sprintf("%v@%v@%v@%v", c.Namespace, c.Group, c.FileName, c.Name) } -// BuildKeyForClientConfigFileInfo 必须保证和 ConfigFileReleaseKey 是一样的生成规则 +// BuildKeyForClientConfigFileInfo 必须保证和 ConfigFileReleaseKey.FileKey 是一样的生成规则 func BuildKeyForClientConfigFileInfo(info *config_manage.ClientConfigFileInfo) string { key := info.GetNamespace().GetValue() + "@" + info.GetGroup().GetValue() + "@" + info.GetFileName().GetValue() @@ -184,14 +189,15 @@ type SimpleConfigFileRelease struct { ModifyTime time.Time ModifyBy string ReleaseDescription string + BetaLabels []*apimodel.ClientLabel } func (s *SimpleConfigFileRelease) GetEncryptDataKey() string { - return s.Metadata[utils.ConfigFileTagKeyDataKey] + return s.Metadata[MetaKeyConfigFileDataKey] } func (s *SimpleConfigFileRelease) GetEncryptAlgo() string { - return s.Metadata[utils.ConfigFileTagKeyEncryptAlgo] + return s.Metadata[MetaKeyConfigFileEncryptAlgo] } func (s *SimpleConfigFileRelease) IsEncrypted() bool { @@ -234,11 +240,11 @@ type ConfigFileReleaseHistory struct { } func (s ConfigFileReleaseHistory) GetEncryptDataKey() string { - return s.Metadata[utils.ConfigFileTagKeyDataKey] + return s.Metadata[MetaKeyConfigFileDataKey] } func (s ConfigFileReleaseHistory) GetEncryptAlgo() string { - return s.Metadata[utils.ConfigFileTagKeyEncryptAlgo] + return s.Metadata[MetaKeyConfigFileEncryptAlgo] } func (s ConfigFileReleaseHistory) IsEncrypted() bool { @@ -293,7 +299,7 @@ func ToConfigFileStore(file *config_manage.ConfigFile) *ConfigFile { metadata := ToTagMap(file.GetTags()) if file.GetEncryptAlgo().GetValue() != "" { - metadata[utils.ConfigFileTagKeyEncryptAlgo] = file.GetEncryptAlgo().GetValue() + metadata[MetaKeyConfigFileEncryptAlgo] = file.GetEncryptAlgo().GetValue() } return &ConfigFile{ @@ -359,7 +365,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)), + ReleaseType: utils.NewStringValue(string(release.ReleaseType)), } } diff --git a/common/model/gray.go b/common/model/gray.go index ae5bb3d66..ed5359be4 100644 --- a/common/model/gray.go +++ b/common/model/gray.go @@ -14,7 +14,7 @@ * 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 ( @@ -39,6 +39,7 @@ type GrayResource struct { ModifyTime time.Time CreateBy string ModifyBy string + Valid bool } // GetGrayConfigRealseKey 获取灰度资源key diff --git a/common/model/http.go b/common/model/http.go index 78d5d8642..836beae8a 100644 --- a/common/model/http.go +++ b/common/model/http.go @@ -25,6 +25,7 @@ type DebugHandlerGroup struct { } type DebugHandler struct { + Desc string Path string Handler http.HandlerFunc } diff --git a/common/model/instance.go b/common/model/instance.go new file mode 100644 index 000000000..0406d324d --- /dev/null +++ b/common/model/instance.go @@ -0,0 +1,170 @@ +/** + * 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 ( + "strconv" + "sync" +) + +type ServiceInstances struct { + lock sync.RWMutex + instances map[string]*Instance + healthyInstances map[string]*Instance + unhealthyInstances map[string]*Instance + protectInstances map[string]*Instance + protectThreshold float32 +} + +func NewServiceInstances(protectThreshold float32) *ServiceInstances { + return &ServiceInstances{ + instances: make(map[string]*Instance, 128), + healthyInstances: make(map[string]*Instance, 128), + unhealthyInstances: make(map[string]*Instance, 128), + protectInstances: make(map[string]*Instance, 128), + } +} + +func (si *ServiceInstances) TotalCount() int { + si.lock.RLock() + defer si.lock.RUnlock() + + return len(si.instances) +} + +func (si *ServiceInstances) UpdateProtectThreshold(protectThreshold float32) { + si.lock.Lock() + defer si.lock.Unlock() + + si.protectThreshold = protectThreshold +} + +func (si *ServiceInstances) UpsertInstance(ins *Instance) { + si.lock.Lock() + defer si.lock.Unlock() + + si.instances[ins.ID()] = ins + if ins.Healthy() { + si.healthyInstances[ins.ID()] = ins + } else { + si.unhealthyInstances[ins.ID()] = ins + } +} + +func (si *ServiceInstances) RemoveInstance(ins *Instance) { + si.lock.Lock() + defer si.lock.Unlock() + + delete(si.instances, ins.ID()) + delete(si.healthyInstances, ins.ID()) + delete(si.unhealthyInstances, ins.ID()) + delete(si.protectInstances, ins.ID()) +} + +func (si *ServiceInstances) Range(iterator func(id string, ins *Instance)) { + si.lock.RLock() + defer si.lock.RUnlock() + + for k, v := range si.instances { + iterator(k, v) + } +} + +func (si *ServiceInstances) GetInstances(onlyHealthy bool) []*Instance { + si.lock.RLock() + defer si.lock.RUnlock() + + ret := make([]*Instance, 0, len(si.healthyInstances)+len(si.protectInstances)) + if !onlyHealthy { + for k, v := range si.instances { + protectIns, ok := si.protectInstances[k] + if ok { + ret = append(ret, protectIns) + } else { + ret = append(ret, v) + } + } + } else { + for _, v := range si.healthyInstances { + ret = append(ret, v) + } + for _, v := range si.protectInstances { + ret = append(ret, v) + } + } + return ret +} + +func (si *ServiceInstances) ReachHealthyProtect() bool { + si.lock.RLock() + defer si.lock.RUnlock() + + return len(si.protectInstances) > 0 +} + +func (si *ServiceInstances) RunHealthyProtect() { + si.lock.Lock() + defer si.lock.Unlock() + + lastBeat := int64(-1) + + curProportion := float32(len(si.healthyInstances)) / float32(len(si.instances)) + if curProportion > si.protectThreshold { + // 不会触发, 并且清空当前保护状态的实例 + si.protectInstances = make(map[string]*Instance, 128) + return + } + instanceLastBeatTimes := map[string]int64{} + instances := si.unhealthyInstances + for i := range instances { + ins := instances[i] + metadata := ins.Metadata() + if len(metadata) == 0 { + continue + } + val, ok := metadata[MetadataInstanceLastHeartbeatTime] + if !ok { + continue + } + beatTime, _ := strconv.ParseInt(val, 10, 64) + if beatTime >= lastBeat { + lastBeat = beatTime + } + instanceLastBeatTimes[ins.ID()] = beatTime + } + if lastBeat == -1 { + return + } + for i := range instances { + ins := instances[i] + beatTime, ok := instanceLastBeatTimes[ins.ID()] + if !ok { + continue + } + needProtect := needZeroProtect(lastBeat, beatTime, int64(ins.HealthCheck().GetHeartbeat().GetTtl().GetValue())) + if !needProtect { + continue + } + si.protectInstances[ins.ID()] = ins + } +} + +// needZeroProtect . +func needZeroProtect(lastBeat, beatTime, ttl int64) bool { + return lastBeat-3*ttl > beatTime +} diff --git a/common/model/metadata.go b/common/model/metadata.go index 2f4cd7221..98577473a 100644 --- a/common/model/metadata.go +++ b/common/model/metadata.go @@ -24,3 +24,19 @@ const ( // MetaKeyBuildRevision build revision for server MetaKeyBuildRevision = "build-revision" ) + +const ( + // MetaKeyConfigFileUseEncrypted 配置加密开关标识,value 为 boolean + MetaKeyConfigFileUseEncrypted = "internal-encrypted" + // MetaKeyConfigFileDataKey 加密密钥 tag key + MetaKeyConfigFileDataKey = "internal-datakey" + // MetaKeyConfigFileEncryptAlgo 加密算法 tag key + MetaKeyConfigFileEncryptAlgo = "internal-encryptalgo" + // MetaKeyConfigFileSyncToKubernetes 配置同步到 kubernetes + MetaKeyConfigFileSyncToKubernetes = "internal-sync-to-kubernetes" + // ---- 以下参数仅适配 polaris-controller 生态 ---- + // MetaKeyConfigFileSyncSourceKey 配置同步来源 + MetaKeyConfigFileSyncSourceKey = "internal-sync-source" + // MetaKeyConfigFileSyncSourceClusterKey 配置同步来源所在集群 + MetaKeyConfigFileSyncSourceClusterKey = "internal-sync-sourcecluster" +) diff --git a/common/utils/collection.go b/common/utils/collection.go index 9604a04da..597cccf07 100644 --- a/common/utils/collection.go +++ b/common/utils/collection.go @@ -19,7 +19,6 @@ package utils import ( - "encoding/json" "sync" ) @@ -128,8 +127,7 @@ func (set *SyncSet[K]) Contains(val K) bool { func (set *SyncSet[K]) String() string { ret := set.ToSlice() - data, _ := json.Marshal(ret) - return string(data) + return MustJson(ret) } func NewSegmentMap[K comparable, V any](soltNum int, hashFunc func(k K) int) *SegmentMap[K, V] { diff --git a/common/utils/common.go b/common/utils/common.go index 3c2452738..75b86ccaf 100644 --- a/common/utils/common.go +++ b/common/utils/common.go @@ -329,6 +329,18 @@ func ParseClientAddress(ctx context.Context) string { return rid } +// ParseClientIP . +func ParseClientIP(ctx context.Context) string { + if ctx == nil { + return "" + } + rid, _ := ctx.Value(ContextClientAddress).(string) + if strings.Contains(rid, ":") { + return strings.Split(rid, ":")[0] + } + return rid +} + // ParseAuthToken 从ctx中获取token func ParseAuthToken(ctx context.Context) string { if ctx == nil { @@ -593,6 +605,9 @@ func CheckContractInterfaceTetrad(contractId string, source apiservice.Interface if contractId == "" { return "", api.NewResponseWithMsg(apimodel.Code_BadRequest, "invalid service_contract id") } + if req.GetId() != "" { + return req.GetId(), nil + } if req.GetMethod() == "" { return "", api.NewResponseWithMsg(apimodel.Code_BadRequest, "invalid service_contract interface method") } diff --git a/common/utils/config_file.go b/common/utils/config_file.go index f660d28bd..fa1c1e401 100644 --- a/common/utils/config_file.go +++ b/common/utils/config_file.go @@ -23,7 +23,7 @@ const ( // ReleaseTypeNormal 发布类型,全量发布 ReleaseTypeNormal = "normal" // ReleaseTypeGray 灰度发布 - ReleaseTypeGray = "gray" + ReleaseTypeGray = "betaing" // ReleaseTypeCancelGray 取消灰度发布 ReleaseTypeCancelGray = "cancel-gray" // ReleaseTypeDelete 发布类型,删除配置发布 @@ -60,12 +60,6 @@ const ( ConfigFileImportConflictSkip = "skip" // ConfigFileImportConflictOverwrite 导入配置文件发生冲突覆盖原配置文件 ConfigFileImportConflictOverwrite = "overwrite" - // ConfigFileTagKeyUseEncrypted 配置加密开关标识,value 为 boolean - ConfigFileTagKeyUseEncrypted = "internal-encrypted" - // ConfigFileTagKeyDataKey 加密密钥 tag key - ConfigFileTagKeyDataKey = "internal-datakey" - // ConfigFileTagKeyEncryptAlgo 加密算法 tag key - ConfigFileTagKeyEncryptAlgo = "internal-encryptalgo" ) // GenFileId 生成文件 Id diff --git a/common/utils/const.go b/common/utils/const.go index ee5f2c7fe..91f7eebb7 100644 --- a/common/utils/const.go +++ b/common/utils/const.go @@ -74,12 +74,3 @@ const ( // ContextOperator operator info ContextOperator = StringContext("operator") ) - -const ( - // EmptyErrString empty error string - EmptyErrString = "empty" - // NilErrString null pointer error string - NilErrString = "nil" - // MatchAll rule match all service or namespace value - MatchAll = "*" -) diff --git a/common/utils/match.go b/common/utils/match.go index b16ca2b49..1ffb7ad22 100644 --- a/common/utils/match.go +++ b/common/utils/match.go @@ -18,33 +18,84 @@ package utils import ( + "strconv" "strings" + regexp "github.com/dlclark/regexp2" 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 - } - } +const ( + // EmptyErrString empty error string + EmptyErrString = "empty" + // NilErrString null pointer error string + NilErrString = "nil" + // MatchAll rule match all service or namespace value + MatchAll = "*" +) + +func IsMatchAll(v string) bool { + return v == "" || v == MatchAll +} + +func MatchString(srcMetaValue string, matchValule *apimodel.MatchString, regexToPattern func(string) *regexp.Regexp) bool { + rawMetaValue := matchValule.GetValue().GetValue() + if IsMatchAll(rawMetaValue) { + return true + } + + switch matchValule.Type { + case apimodel.MatchString_REGEX: + matchExp := regexToPattern(rawMetaValue) + if matchExp == nil { + return false + } + match, err := matchExp.MatchString(srcMetaValue) + if err != nil { + return false + } + return match + case apimodel.MatchString_NOT_EQUALS: + return srcMetaValue != rawMetaValue + case apimodel.MatchString_EXACT: + return srcMetaValue == rawMetaValue + case apimodel.MatchString_IN: + find := false + tokens := strings.Split(rawMetaValue, ",") + for _, token := range tokens { + if token == srcMetaValue { + find = true + break } + } + return find + case apimodel.MatchString_NOT_IN: + tokens := strings.Split(rawMetaValue, ",") + for _, token := range tokens { + if token == srcMetaValue { + return false + } + } + return true + case apimodel.MatchString_RANGE: + // range 模式只支持数字 + tokens := strings.Split(rawMetaValue, "~") + if len(tokens) != 2 { + return false + } + left, err := strconv.ParseInt(tokens[0], 10, 64) + if err != nil { + return false + } + right, err := strconv.ParseInt(tokens[1], 10, 64) + if err != nil { + return false + } + srcVal, err := strconv.ParseInt(srcMetaValue, 10, 64) + if err != nil { return false } + return srcVal >= left && srcVal <= right } - return false + return true } diff --git a/common/utils/match_test.go b/common/utils/match_test.go index 4af6f86c9..f54738b77 100644 --- a/common/utils/match_test.go +++ b/common/utils/match_test.go @@ -20,39 +20,322 @@ package utils import ( "testing" - "github.com/golang/protobuf/ptypes/wrappers" + regexp "github.com/dlclark/regexp2" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" - "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" ) -func TestMatch(t *testing.T) { - tag := &apimodel.Tag{ - Key: &wrappers.StringValue{Value: "ip"}, - Value: &wrappers.StringValue{Value: "127.0.0.1"}, +func TestMatchString(t *testing.T) { + type args struct { + srcMetaValue string + matchValule *apimodel.MatchString + regexToPattern func(string) *regexp.Regexp } - 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"}, + tests := []struct { + name string + args args + want bool + }{ + // 测试等值匹配 + { + name: "等式匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_EXACT, + Value: &wrapperspb.StringValue{ + Value: "123", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, }, - } - - 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"}, + { + name: "等式匹配", + args: args{ + srcMetaValue: "1234", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_EXACT, + Value: &wrapperspb.StringValue{ + Value: "123", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + // 不等式匹配 + { + name: "不等式匹配", + args: args{ + srcMetaValue: "1234", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_NOT_EQUALS, + Value: &wrapperspb.StringValue{ + Value: "123", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, + }, + { + name: "不等式匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_NOT_EQUALS, + Value: &wrapperspb.StringValue{ + Value: "123", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + // 包含匹配 + { + name: "包含匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_IN, + Value: &wrapperspb.StringValue{ + Value: "123,456,123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, + }, + { + name: "包含匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_IN, + Value: &wrapperspb.StringValue{ + Value: "456,123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + { + name: "包含匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_IN, + Value: &wrapperspb.StringValue{ + Value: "456,asdad", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + // 不包含匹配 + { + name: "不包含匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_NOT_IN, + Value: &wrapperspb.StringValue{ + Value: "123,456,123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + { + name: "不包含匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_NOT_IN, + Value: &wrapperspb.StringValue{ + Value: "456,asdad", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, + }, + { + name: "不包含匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_NOT_IN, + Value: &wrapperspb.StringValue{ + Value: "456,123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, + }, + // 范围匹配 + { + name: "范围匹配", + args: args{ + srcMetaValue: "123", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "123~123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, + }, + { + name: "范围匹配", + args: args{ + srcMetaValue: "1234", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "123~123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: true, + }, + { + name: "范围匹配", + args: args{ + srcMetaValue: "12", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "123~123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + { + name: "范围匹配", + args: args{ + srcMetaValue: "12345643", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "123~123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + { + name: "范围匹配", + args: args{ + srcMetaValue: "12345643", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "a~123456", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + { + name: "范围匹配", + args: args{ + srcMetaValue: "12345643", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "1231~3a", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, + }, + { + name: "范围匹配", + args: args{ + srcMetaValue: "wx33", + matchValule: &apimodel.MatchString{ + Type: apimodel.MatchString_RANGE, + Value: &wrapperspb.StringValue{ + Value: "1231~123123", + }, + ValueType: apimodel.MatchString_TEXT, + }, + regexToPattern: func(s string) *regexp.Regexp { + return regexp.MustCompile(s, regexp.RE2) + }, + }, + want: false, }, } - - ok = Match(&matchKv, tags) - assert.Equal(t, ok, true) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := MatchString(tt.args.srcMetaValue, tt.args.matchValule, tt.args.regexToPattern); got != tt.want { + t.Errorf("MatchString() = %v, want %v", got, tt.want) + } + }) + } } diff --git a/config/api.go b/config/api.go index 46b187b46..a20ff72c3 100644 --- a/config/api.go +++ b/config/api.go @@ -21,6 +21,8 @@ import ( "context" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + + "github.com/polarismesh/polaris/common/model" ) type ( @@ -87,12 +89,12 @@ type ConfigFileReleaseOperate interface { GetConfigFileReleaseHistories(ctx context.Context, filter map[string]string) *apiconfig.ConfigBatchQueryResponse // UpsertAndReleaseConfigFile 创建/更新配置文件并发布 UpsertAndReleaseConfigFile(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse + // StopGrayConfigFileReleases 停止所有的灰度发布配置 + StopGrayConfigFileReleases(ctx context.Context, reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse } // ConfigFileClientOperate 给客户端提供服务接口,不同的上层协议抽象的公共服务逻辑 type ConfigFileClientOperate interface { - // GetConfigFileForClient 获取配置文件 - GetConfigFileForClient(ctx context.Context, req *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigClientResponse // CreateConfigFileFromClient 调用config_file的方法创建配置文件 CreateConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse // UpdateConfigFileFromClient 调用config_file的方法更新配置文件 @@ -103,11 +105,17 @@ type ConfigFileClientOperate interface { PublishConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigClientResponse // UpsertAndReleaseConfigFile 创建/更新配置文件并发布 UpsertAndReleaseConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse + // CasUpsertAndReleaseConfigFileFromClient 创建/更新配置文件并发布 + CasUpsertAndReleaseConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse // LongPullWatchFile 客户端监听配置文件 LongPullWatchFile(ctx context.Context, req *apiconfig.ClientWatchConfigFileRequest) (WatchCallback, error) // GetConfigFileNamesWithCache 获取某个配置分组下的配置文件 GetConfigFileNamesWithCache(ctx context.Context, req *apiconfig.ConfigFileGroupRequest) *apiconfig.ConfigClientListResponse + // GetConfigFileWithCache 获取配置文件 + GetConfigFileWithCache(ctx context.Context, req *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigClientResponse + // GetConfigGroupsWithCache 获取某个命名空间下的配置分组列表 + GetConfigGroupsWithCache(ctx context.Context, req *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigDiscoverResponse } // ConfigFileTemplateOperate config file template operate @@ -128,3 +136,16 @@ type ConfigCenterServer interface { ConfigFileClientOperate ConfigFileTemplateOperate } + +// ResourceHook The listener is placed before and after the resource operation, only normal flow +type ResourceHook interface { + // Before + Before(ctx context.Context, resourceType model.Resource) + // After + After(ctx context.Context, resourceType model.Resource, res *ResourceEvent) error +} + +// ResourceEvent 资源事件 +type ResourceEvent struct { + ConfigGroup *apiconfig.ConfigFileGroup +} diff --git a/config/client.go b/config/client.go index 9a68f82c7..0a0556fde 100644 --- a/config/client.go +++ b/config/client.go @@ -25,6 +25,7 @@ import ( 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" + "google.golang.org/protobuf/types/known/wrapperspb" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" @@ -37,14 +38,12 @@ type ( CompareFunction func(clientInfo *apiconfig.ClientConfigFileInfo, file *model.ConfigFileRelease) bool ) -// GetConfigFileForClient 从缓存中获取配置文件,如果客户端的版本号大于服务端,则服务端重新加载缓存 -func (s *Server) GetConfigFileForClient(ctx context.Context, +// GetConfigFileWithCache 从缓存中获取配置文件,如果客户端的版本号大于服务端,则服务端重新加载缓存 +func (s *Server) GetConfigFileWithCache(ctx context.Context, client *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigClientResponse { namespace := client.GetNamespace().GetValue() group := client.GetGroup().GetValue() fileName := client.GetFileName().GetValue() - clientVersion := client.GetVersion().GetValue() - configFileTags := client.GetTags() if namespace == "" || group == "" || fileName == "" { return api.NewConfigClientResponseWithInfo( @@ -52,37 +51,20 @@ func (s *Server) GetConfigFileForClient(ctx context.Context, } // 从缓存中获取灰度文件 var release *model.ConfigFileRelease - var match bool - if len(configFileTags) > 0 { - release = s.fileCache.GetActiveRelease(namespace, group, fileName, model.ReleaseTypeGray) - if release != nil { + var match = false + if len(client.GetTags()) > 0 { + if release = s.fileCache.GetGrayRelease(namespace, group, fileName); 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 - } - } + match = s.grayCache.HitGrayRule(key, model.ToTagMap(client.GetTags())) } } if !match { - release = s.fileCache.GetActiveRelease(namespace, group, fileName, model.ReleaseTypeFull) - if release == nil { + if release = s.fileCache.GetActiveRelease(namespace, group, fileName); release == nil { return api.NewConfigClientResponse(apimodel.Code_NotFoundResource, nil) } } // 客户端版本号大于服务端版本号,服务端不返回变更 todo: 结合灰度和全量版本 判断 - if clientVersion > release.Version { + if client.GetVersion().GetValue() > release.Version { return api.NewConfigClientResponse(apimodel.Code_DataNoChange, nil) } configFile, err := toClientInfo(client, release) @@ -93,44 +75,12 @@ func (s *Server) GetConfigFileForClient(ctx context.Context, return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, configFile) } -// UpsertAndReleaseConfigFile 创建/更新配置文件并发布 -func (s *Server) UpsertAndReleaseConfigFileFromClient(ctx context.Context, - req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { - return s.UpsertAndReleaseConfigFile(ctx, req) -} - -// DeleteConfigFileFromClient 调用config_file的方法更新配置文件 -func (s *Server) DeleteConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { - return s.DeleteConfigFile(ctx, req) -} - -// CreateConfigFileFromClient 调用config_file接口获取配置文件 -func (s *Server) CreateConfigFileFromClient(ctx context.Context, - client *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { - configResponse := s.CreateConfigFile(ctx, client) - return api.NewConfigClientResponseFromConfigResponse(configResponse) -} - -// UpdateConfigFileFromClient 调用config_file接口更新配置文件 -func (s *Server) UpdateConfigFileFromClient(ctx context.Context, - client *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { - configResponse := s.UpdateConfigFile(ctx, client) - return api.NewConfigClientResponseFromConfigResponse(configResponse) -} - -// PublishConfigFileFromClient 调用config_file_release接口发布配置文件 -func (s *Server) PublishConfigFileFromClient(ctx context.Context, - client *apiconfig.ConfigFileRelease) *apiconfig.ConfigClientResponse { - configResponse := s.PublishConfigFile(ctx, client) - return api.NewConfigClientResponseFromConfigResponse(configResponse) -} - // LongPullWatchFile . func (s *Server) LongPullWatchFile(ctx context.Context, req *apiconfig.ClientWatchConfigFileRequest) (WatchCallback, error) { watchFiles := req.GetWatchFiles() - tmpWatchCtx := BuildTimeoutWatchCtx(0)("") + tmpWatchCtx := BuildTimeoutWatchCtx(0)("", s.watchCenter.MatchBetaReleaseFile) for _, file := range watchFiles { tmpWatchCtx.AppendInterest(file) } @@ -155,12 +105,13 @@ func (s *Server) LongPullWatchFile(ctx context.Context, } func BuildTimeoutWatchCtx(watchTimeOut time.Duration) WatchContextFactory { - return func(clientId string) WatchContext { + return func(clientId string, matcher BetaReleaseMatcher) WatchContext { watchCtx := &LongPollWatchContext{ clientId: clientId, finishTime: time.Now().Add(watchTimeOut), - finishChan: make(chan *apiconfig.ConfigClientResponse), + finishChan: make(chan *apiconfig.ConfigClientResponse, 1), watchConfigFiles: map[string]*apiconfig.ClientConfigFileInfo{}, + betaMatcher: matcher, } return watchCtx } @@ -195,6 +146,7 @@ func (s *Server) GetConfigFileNamesWithCache(ctx context.Context, Name: utils.NewStringValue(releases[i].Name), Version: utils.NewUInt64Value(releases[i].Version), ReleaseTime: utils.NewStringValue(commontime.Time2String(releases[i].ModifyTime)), + Tags: model.FromTagMap(releases[i].Metadata), }) } @@ -208,6 +160,38 @@ func (s *Server) GetConfigFileNamesWithCache(ctx context.Context, } } +func (s *Server) GetConfigGroupsWithCache(ctx context.Context, req *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigDiscoverResponse { + namespace := req.GetNamespace().GetValue() + out := api.NewConfigDiscoverResponse(apimodel.Code_ExecuteSuccess) + if namespace == "" { + out.Code = uint32(apimodel.Code_BadRequest) + out.Info = "invalid namespace" + return out + } + + groups, revision := s.groupCache.ListGroups(namespace) + if revision == req.GetMd5().GetValue() { + out = api.NewConfigDiscoverResponse(apimodel.Code_DataNoChange) + out.Type = apiconfig.ConfigDiscoverResponse_CONFIG_FILE_GROUPS + return out + } + + ret := make([]*apiconfig.ConfigFileGroup, 0, len(groups)) + for i := range groups { + item := groups[i] + ret = append(ret, &apiconfig.ConfigFileGroup{ + Namespace: wrapperspb.String(item.Namespace), + Name: wrapperspb.String(item.Name), + }) + } + + out.Type = apiconfig.ConfigDiscoverResponse_CONFIG_FILE_GROUPS + out.ConfigFile = &apiconfig.ClientConfigFileInfo{Namespace: wrapperspb.String(namespace)} + out.Revision = revision + out.ConfigFileGroups = ret + return out +} + func CompareByVersion(clientInfo *apiconfig.ClientConfigFileInfo, file *model.ConfigFileRelease) bool { return clientInfo.GetVersion().GetValue() < file.Version } @@ -231,7 +215,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.ReleaseTypeFull) + release := s.fileCache.GetActiveRelease(namespace, group, fileName) if release != nil && compartor(configFile, release) { ret := &apiconfig.ClientConfigFileInfo{ Namespace: utils.NewStringValue(namespace), @@ -259,7 +243,7 @@ func toClientInfo(client *apiconfig.ClientConfigFileInfo, for k, v := range release.Metadata { ret[k] = v } - delete(ret, utils.ConfigFileTagKeyDataKey) + delete(ret, model.MetaKeyConfigFileDataKey) return ret }() @@ -293,10 +277,48 @@ func toClientInfo(client *apiconfig.ClientConfigFileInfo, } configFile.Tags = append(configFile.Tags, &apiconfig.ConfigFileTag{ - Key: utils.NewStringValue(utils.ConfigFileTagKeyDataKey), + Key: utils.NewStringValue(model.MetaKeyConfigFileDataKey), Value: utils.NewStringValue(dataKey), }, ) } return configFile, nil } + +// UpsertAndReleaseConfigFile 创建/更新配置文件并发布 +func (s *Server) UpsertAndReleaseConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { + return s.UpsertAndReleaseConfigFile(ctx, req) +} + +// DeleteConfigFileFromClient 调用config_file的方法更新配置文件 +func (s *Server) DeleteConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + return s.DeleteConfigFile(ctx, req) +} + +// CreateConfigFileFromClient 调用config_file接口获取配置文件 +func (s *Server) CreateConfigFileFromClient(ctx context.Context, + client *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { + configResponse := s.CreateConfigFile(ctx, client) + return api.NewConfigClientResponseFromConfigResponse(configResponse) +} + +// UpdateConfigFileFromClient 调用config_file接口更新配置文件 +func (s *Server) UpdateConfigFileFromClient(ctx context.Context, + client *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { + configResponse := s.UpdateConfigFile(ctx, client) + return api.NewConfigClientResponseFromConfigResponse(configResponse) +} + +// PublishConfigFileFromClient 调用config_file_release接口发布配置文件 +func (s *Server) PublishConfigFileFromClient(ctx context.Context, + client *apiconfig.ConfigFileRelease) *apiconfig.ConfigClientResponse { + configResponse := s.PublishConfigFile(ctx, client) + return api.NewConfigClientResponseFromConfigResponse(configResponse) +} + +// CasUpsertAndReleaseConfigFileFromClient 创建/更新配置文件并发布 +func (s *Server) CasUpsertAndReleaseConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { + return s.CasUpsertAndReleaseConfigFile(ctx, req) +} diff --git a/config/client_test.go b/config/client_test.go index 7b0a3134d..aa64ab8ec 100644 --- a/config/client_test.go +++ b/config/client_test.go @@ -56,8 +56,8 @@ func TestClientSetupAndFileNotExisted(t *testing.T) { Version: &wrapperspb.UInt64Value{Value: 0}, } - rsp := testSuit.ConfigServer().GetConfigFileForClient(testSuit.DefaultCtx, fileInfo) - assert.Equal(t, uint32(api.NotFoundResource), rsp.Code.GetValue(), "GetConfigFileForClient must notfound") + rsp := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, fileInfo) + assert.Equal(t, uint32(api.NotFoundResource), rsp.Code.GetValue(), "GetConfigFileWithCache must notfound") originSvr := testSuit.OriginConfigServer() rsp2, _ := originSvr.TestCheckClientConfigFile(testSuit.DefaultCtx, assembleDefaultClientConfigFile(0), config.TestCompareByVersion) @@ -88,7 +88,7 @@ func TestClientSetupAndFileExisted(t *testing.T) { assert.Equal(t, api.ExecuteSuccess, rsp.Code.GetValue(), rsp.GetInfo().GetValue()) rsp2 := testSuit.ConfigServer().PublishConfigFile(testSuit.DefaultCtx, assembleConfigFileRelease(configFile)) - assert.Equal(t, api.ExecuteSuccess, rsp2.Code.GetValue(), rsp.GetInfo().GetValue()) + assert.Equal(t, api.ExecuteSuccess, rsp2.Code.GetValue(), rsp2.GetInfo().GetValue()) fileInfo := &apiconfig.ClientConfigFileInfo{ Namespace: &wrapperspb.StringValue{Value: testNamespace}, @@ -101,8 +101,8 @@ func TestClientSetupAndFileExisted(t *testing.T) { _ = testSuit.DiscoverServer().Cache().ConfigFile().Update() // 拉取配置接口 - rsp3 := testSuit.ConfigServer().GetConfigFileForClient(testSuit.DefaultCtx, fileInfo) - assert.Equalf(t, api.ExecuteSuccess, rsp3.Code.GetValue(), "GetConfigFileForClient must success, acutal code : %d", rsp3.Code.GetValue()) + rsp3 := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, fileInfo) + assert.Equalf(t, api.ExecuteSuccess, rsp3.Code.GetValue(), "GetConfigFileWithCache must success, acutal code : %d", rsp3.Code.GetValue()) assert.NotNil(t, rsp3.ConfigFile) assert.Equal(t, uint64(1), rsp3.ConfigFile.Version.GetValue()) assert.Equal(t, configFile.Content.GetValue(), rsp3.ConfigFile.Content.GetValue()) @@ -349,7 +349,7 @@ func TestClientVersionBehindServer(t *testing.T) { _ = testSuit.DiscoverServer().Cache().ConfigFile().Update() // 拉取配置接口 - rsp4 := testSuit.ConfigServer().GetConfigFileForClient(testSuit.DefaultCtx, fileInfo) + rsp4 := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, fileInfo) assert.Equal(t, api.ExecuteSuccess, rsp4.Code.GetValue()) assert.NotNil(t, rsp4.ConfigFile) assert.Equal(t, uint64(5), rsp4.ConfigFile.Version.GetValue()) @@ -402,11 +402,11 @@ func TestWatchConfigFileAtFirstPublish(t *testing.T) { rsp := testSuit.ConfigServer().CreateConfigFile(testSuit.DefaultCtx, configFile) t.Log("create config file success") - assert.Equal(t, api.ExecuteSuccess, rsp.Code.GetValue()) + assert.Equal(t, api.ExecuteSuccess, rsp.Code.GetValue(), rsp.GetInfo().GetValue()) rsp2 := testSuit.ConfigServer().PublishConfigFile(testSuit.DefaultCtx, assembleConfigFileRelease(configFile)) t.Log("publish config file success") - assert.Equal(t, api.ExecuteSuccess, rsp2.Code.GetValue()) + assert.Equal(t, api.ExecuteSuccess, rsp2.Code.GetValue(), rsp2.GetInfo().GetValue()) saveData, err := testSuit.Storage.GetConfigFileActiveRelease(&model.ConfigFileKey{ Name: configFile.GetName().GetValue(), @@ -414,6 +414,7 @@ func TestWatchConfigFileAtFirstPublish(t *testing.T) { Group: configFile.GetGroup().GetValue(), }) assert.NoError(t, err) + assert.Equal(t, uint64(1), saveData.Version) assert.Equal(t, configFile.GetContent().GetValue(), saveData.Content) notifyRsp, err := (watchCtx.(*config.LongPollWatchContext)).GetNotifieResultWithTime(10 * time.Second) @@ -421,7 +422,7 @@ func TestWatchConfigFileAtFirstPublish(t *testing.T) { t.Fatal(err) } t.Logf("clientId=[%s] receive config publish msg", clientId) - receivedVersion := notifyRsp.ConfigFile.Version.GetValue() + receivedVersion := notifyRsp.GetConfigFile().GetVersion().GetValue() assert.Equal(t, uint64(1), receivedVersion) }) @@ -580,6 +581,6 @@ func TestDeleteConfigFile(t *testing.T) { } // 重新拉取配置,获取不到配置文件 - rsp4 := testSuit.ConfigServer().GetConfigFileForClient(testSuit.DefaultCtx, fileInfo) + rsp4 := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, fileInfo) assert.Equal(t, uint32(api.NotFoundResource), rsp4.Code.GetValue()) } diff --git a/config/common.go b/config/common.go index 3213689a8..2acf7c8a9 100644 --- a/config/common.go +++ b/config/common.go @@ -25,6 +25,55 @@ import ( "github.com/polarismesh/polaris/common/model" ) +var ( + availableSearch = map[string]map[string]string{ + "config_file": { + "namespace": "namespace", + "group": "group", + "name": "name", + "offset": "offset", + "limit": "limit", + "order_type": "order_type", + "order_field": "order_field", + }, + "config_file_release": { + "namespace": "namespace", + "group": "group", + "file_name": "file_name", + "fileName": "file_name", + "name": "release_name", + "release_name": "release_name", + "offset": "offset", + "limit": "limit", + "order_type": "order_type", + "order_field": "order_field", + "only_active": "only_active", + }, + "config_file_group": { + "namespace": "namespace", + "group": "name", + "name": "name", + "business": "business", + "department": "department", + "offset": "offset", + "limit": "limit", + "order_type": "order_type", + "order_field": "order_field", + }, + "config_file_release_history": { + "namespace": "namespace", + "group": "group", + "name": "file_name", + "offset": "offset", + "limit": "limit", + "endId": "endId", + "end_id": "endId", + "order_type": "order_type", + "order_field": "order_field", + }, + } +) + func (s *Server) checkNamespaceExisted(namespaceName string) bool { if val := s.caches.Namespace().GetNamespace(namespaceName); val != nil { return true diff --git a/config/config_chain.go b/config/config_chain.go index 88cbf78d6..7478169b8 100644 --- a/config/config_chain.go +++ b/config/config_chain.go @@ -20,6 +20,7 @@ package config import ( "context" "encoding/base64" + "fmt" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" @@ -98,7 +99,7 @@ func (chain *CryptoConfigFileChain) AfterGetFile(ctx context.Context, utils.ZapNamespace(file.Namespace), utils.ZapGroup(file.Group), utils.ZapFileName(file.Name), zap.Error(err)) } - delete(file.Metadata, utils.ConfigFileTagKeyDataKey) + delete(file.Metadata, model.MetaKeyConfigFileDataKey) return file, nil } @@ -200,9 +201,9 @@ func (chain *CryptoConfigFileChain) decryptConfigFileContent(dataKey, algorithm, // cleanEncryptConfigFileInfo 清理配置加密文件的内容信息 func (chain *CryptoConfigFileChain) cleanEncryptConfigFileInfo(ctx context.Context, configFile *model.ConfigFile) { - delete(configFile.Metadata, utils.ConfigFileTagKeyDataKey) - delete(configFile.Metadata, utils.ConfigFileTagKeyEncryptAlgo) - delete(configFile.Metadata, utils.ConfigFileTagKeyUseEncrypted) + delete(configFile.Metadata, model.MetaKeyConfigFileDataKey) + delete(configFile.Metadata, model.MetaKeyConfigFileEncryptAlgo) + delete(configFile.Metadata, model.MetaKeyConfigFileUseEncrypted) } // encryptConfigFile 加密配置文件 @@ -239,9 +240,9 @@ func (chain *CryptoConfigFileChain) encryptConfigFile(ctx context.Context, confi if len(configFile.Metadata) == 0 { configFile.Metadata = map[string]string{} } - configFile.Metadata[utils.ConfigFileTagKeyDataKey] = base64.StdEncoding.EncodeToString(dateKeyBytes) - configFile.Metadata[utils.ConfigFileTagKeyEncryptAlgo] = algorithm - configFile.Metadata[utils.ConfigFileTagKeyUseEncrypted] = "true" + configFile.Metadata[model.MetaKeyConfigFileDataKey] = base64.StdEncoding.EncodeToString(dateKeyBytes) + configFile.Metadata[model.MetaKeyConfigFileEncryptAlgo] = algorithm + configFile.Metadata[model.MetaKeyConfigFileUseEncrypted] = "true" return nil } @@ -272,7 +273,7 @@ func (chain *ReleaseConfigFileChain) AfterGetFile(ctx context.Context, group := file.Group name := file.Name // 首先检测灰度版本 - if grayFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name, model.ReleaseTypeGray); grayFile != nil { + if grayFile := chain.svr.fileCache.GetGrayRelease(namespace, group, name); grayFile != nil { if grayFile.Content == file.OriginContent { file.Status = utils.ReleaseTypeGray file.ReleaseBy = grayFile.ModifyBy @@ -280,7 +281,7 @@ func (chain *ReleaseConfigFileChain) AfterGetFile(ctx context.Context, } else { file.Status = utils.ReleaseStatusToRelease } - } else if fullFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name, model.ReleaseTypeFull); fullFile != nil { + } else if fullFile := chain.svr.fileCache.GetActiveRelease(namespace, group, name); fullFile != nil { // 如果最后一次发布的内容和当前文件内容一致,则展示最后一次发布状态。否则说明文件有修改,待发布 if fullFile.Content == file.OriginContent { file.Status = utils.ReleaseTypeNormal @@ -304,8 +305,16 @@ func (chain *ReleaseConfigFileChain) BeforeUpdateFile(ctx context.Context, // AfterGetFileRelease func (chain *ReleaseConfigFileChain) AfterGetFileRelease(ctx context.Context, - release *model.ConfigFileRelease) (*model.ConfigFileRelease, error) { - return release, nil + ret *model.ConfigFileRelease) (*model.ConfigFileRelease, error) { + + if ret.ReleaseType == model.ReleaseTypeGray { + rule := chain.svr.caches.Gray().GetGrayRule(model.GetGrayConfigRealseKey(ret.SimpleConfigFileRelease)) + if rule == nil { + return nil, fmt.Errorf("gray rule not found") + } + ret.BetaLabels = rule + } + return ret, nil } // AfterGetFileHistory diff --git a/config/config_file.go b/config/config_file.go index c4a5c1716..9b542c050 100644 --- a/config/config_file.go +++ b/config/config_file.go @@ -184,8 +184,8 @@ func (s *Server) updateConfigFileAttribute(saveData, updateData *model.ConfigFil if len(saveData.Metadata) == 0 { saveData.Metadata = map[string]string{} } - saveData.Metadata[utils.ConfigFileTagKeyDataKey] = oldMetadata[utils.ConfigFileTagKeyDataKey] - saveData.Metadata[utils.ConfigFileTagKeyEncryptAlgo] = oldMetadata[utils.ConfigFileTagKeyEncryptAlgo] + saveData.Metadata[model.MetaKeyConfigFileDataKey] = oldMetadata[model.MetaKeyConfigFileDataKey] + saveData.Metadata[model.MetaKeyConfigFileEncryptAlgo] = oldMetadata[model.MetaKeyConfigFileEncryptAlgo] } return saveData, needUpdate @@ -340,6 +340,7 @@ func (s *Server) SearchConfigFile(ctx context.Context, filter map[string]string) return out } + _ = s.caches.ConfigFile().Update() ret := make([]*apiconfig.ConfigFile, 0, len(files)) for _, file := range files { file, err := s.chains.AfterGetFile(ctx, file) diff --git a/config/config_file_release.go b/config/config_file_release.go index 1175a9ff1..77cbf1daa 100644 --- a/config/config_file_release.go +++ b/config/config_file_release.go @@ -18,9 +18,8 @@ package config import ( - "bytes" "context" - "errors" + "encoding/json" "fmt" "strings" "sync/atomic" @@ -42,7 +41,6 @@ import ( // PublishConfigFile 发布配置文件 func (s *Server) PublishConfigFile(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { - if err := CheckFileName(req.GetFileName()); err != nil { return api.NewConfigResponse(apimodel.Code_InvalidConfigFileName) } @@ -55,11 +53,7 @@ func (s *Server) PublishConfigFile(ctx context.Context, req *apiconfig.ConfigFil if !s.checkNamespaceExisted(req.GetNamespace().GetValue()) { return api.NewConfigResponse(apimodel.Code_NotFoundNamespace) } - - 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 { + if req.GetReleaseType().GetValue() == model.ReleaseTypeGray && len(req.GetBetaLabels()) == 0 { return api.NewConfigResponse(apimodel.Code_InvalidMatchRule) } @@ -75,21 +69,17 @@ func (s *Server) PublishConfigFile(ctx context.Context, req *apiconfig.ConfigFil data, resp := s.handlePublishConfigFile(ctx, tx, req) if resp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { _ = tx.Rollback() - if data != nil { - s.recordReleaseFail(ctx, utils.ReleaseTypeNormal, data, errors.New(resp.GetInfo().GetValue())) - } return resp } if err := tx.Commit(); err != nil { - s.recordReleaseFail(ctx, utils.ReleaseTypeNormal, data, err) 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.ReleaseTypeFull) { - s.recordReleaseSuccess(ctx, utils.ReleaseTypeNormal, data) - } else { + if req.GetReleaseType().GetValue() == model.ReleaseTypeGray { s.recordReleaseSuccess(ctx, utils.ReleaseTypeGray, data) + } else { + s.recordReleaseSuccess(ctx, utils.ReleaseTypeNormal, data) } resp.ConfigFileRelease = req @@ -107,13 +97,35 @@ func (s *Server) handlePublishConfigFile(ctx context.Context, tx store.Tx, group := req.GetGroup().GetValue() fileName := req.GetFileName().GetValue() + fileRelease := &model.ConfigFileRelease{ + SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ + ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ + Name: req.GetName().GetValue(), + Namespace: namespace, + Group: group, + FileName: fileName, + ReleaseType: model.ReleaseType(req.GetReleaseType().GetValue()), + }, + }, + } + + // 确认是否存在正在灰度发布中的配置文件 + betaRelease, err := s.storage.GetConfigFileBetaReleaseTx(tx, fileRelease.ToFileKey()) + if err != nil { + log.Error("[Config][File] get beta config file release in get target.", utils.RequestID(ctx), zap.Error(err)) + return nil, api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + if betaRelease != nil { + log.Error("[Config][File] still exist beta config file release.", utils.RequestID(ctx), zap.Error(err)) + return nil, api.NewConfigResponse(apimodel.Code_DataConflict) + } + // 获取待发布的 configFile 信息 toPublishFile, err := s.storage.GetConfigFileTx(tx, namespace, group, fileName) if err != nil { log.Error("[Config][Release] publish config file when get file.", utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(group), utils.ZapFileName(fileName), zap.Error(err)) - s.recordReleaseFail(ctx, utils.ReleaseTypeNormal, model.ToConfigFileReleaseStore(req), err) return nil, api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } if toPublishFile == nil { @@ -124,25 +136,15 @@ func (s *Server) handlePublishConfigFile(ctx context.Context, tx store.Tx, req.Name = utils.NewStringValue(fmt.Sprintf("%s-%d-%d", fileName, time.Now().Unix(), s.nextSequence())) } - fileRelease := &model.ConfigFileRelease{ - SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ - ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ - Name: req.GetName().GetValue(), - Namespace: namespace, - Group: group, - FileName: fileName, - Typ: model.ReleaseType(req.GetType().GetValue()), - }, - Format: toPublishFile.Format, - Metadata: toPublishFile.Metadata, - Comment: req.GetComment().GetValue(), - Md5: CalMd5(toPublishFile.Content), - CreateBy: utils.ParseUserName(ctx), - ModifyBy: utils.ParseUserName(ctx), - ReleaseDescription: req.GetReleaseDescription().GetValue(), - }, - Content: toPublishFile.Content, - } + fileRelease.Format = toPublishFile.Format + fileRelease.Metadata = toPublishFile.Metadata + fileRelease.Comment = req.GetComment().GetValue() + fileRelease.Md5 = CalMd5(toPublishFile.Content) + fileRelease.CreateBy = utils.ParseUserName(ctx) + fileRelease.ModifyBy = utils.ParseUserName(ctx) + fileRelease.ReleaseDescription = req.GetReleaseDescription().GetValue() + fileRelease.Content = toPublishFile.Content + saveRelease, err := s.storage.GetConfigFileReleaseTx(tx, fileRelease.ConfigFileReleaseKey) if err != nil { log.Error("[Config][Release] publish config file when get release.", @@ -159,29 +161,30 @@ func (s *Server) handlePublishConfigFile(ctx context.Context, tx store.Tx, return fileRelease, api.NewConfigFileResponse(commonstore.StoreCode2APICode(err), nil) } } else { - if err := s.storage.CreateConfigFileReleaseTx(tx, fileRelease); err != nil { + if err = s.storage.CreateConfigFileReleaseTx(tx, fileRelease); err != nil { log.Error("[Config][Release] publish config file when create release.", utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(group), utils.ZapFileName(fileName), zap.Error(err)) return fileRelease, api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } } - if req.GetType().GetValue() == uint32(model.ReleaseTypeGray) { - grayRule := req.GetGrayRule() - var buffer bytes.Buffer + if req.GetReleaseType().GetValue() == model.ReleaseTypeGray { + clientLabels := req.GetBetaLabels() + raw := make([]json.RawMessage, 0, len(clientLabels)) marshaler := jsonpb.Marshaler{} - err := marshaler.Marshal(&buffer, grayRule) - if err != nil { + for i := range clientLabels { + data, err := marshaler.MarshalToString(clientLabels[i]) 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) + return fileRelease, api.NewConfigResponseWithInfo(apimodel.Code_InvalidMatchRule, err.Error()) } + raw = append(raw, json.RawMessage(data)) } grayResource := &model.GrayResource{ Name: model.GetGrayConfigRealseKey(fileRelease.SimpleConfigFileRelease), - MatchRule: buffer.String(), + MatchRule: string(utils.MustJson(raw)), CreateBy: utils.ParseUserName(ctx), ModifyBy: utils.ParseUserName(ctx), } @@ -237,6 +240,7 @@ func (s *Server) GetConfigFileRelease(ctx context.Context, req *apiconfig.Config return api.NewConfigResponse(apimodel.Code_ExecuteSuccess) } + _ = s.caches.Gray().Update() ret, err = s.chains.AfterGetFileRelease(ctx, ret) if err != nil { log.Error("[Config][Release] get config file release run chain.", utils.RequestID(ctx), @@ -246,14 +250,6 @@ func (s *Server) GetConfigFileRelease(ctx context.Context, req *apiconfig.Config } 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) } @@ -286,29 +282,17 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, release := &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ - Name: req.GetName().GetValue(), - Namespace: req.GetNamespace().GetValue(), - Group: req.GetGroup().GetValue(), - FileName: req.GetFileName().GetValue(), - Typ: model.ReleaseType(req.GetType().GetValue()), + Name: req.GetName().GetValue(), + Namespace: req.GetNamespace().GetValue(), + Group: req.GetGroup().GetValue(), + FileName: req.GetFileName().GetValue(), + ReleaseType: model.ReleaseType(req.GetReleaseType().GetValue()), }, }, } var ( - errRef error - needRecord = true recordData *model.ConfigFileRelease ) - defer func() { - if !needRecord { - return - } - if errRef != nil { - s.recordReleaseFail(ctx, utils.ReleaseTypeDelete, recordData, errRef) - } else { - s.recordReleaseSuccess(ctx, utils.ReleaseTypeDelete, recordData) - } - }() tx, err := s.storage.StartTx() if err != nil { @@ -320,7 +304,6 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, _ = tx.Rollback() }() if _, err := s.storage.LockConfigFile(tx, release.ToFileKey()); err != nil { - errRef = err log.Error("[Config][File] delete config file release when lock.", utils.RequestID(ctx), zap.Error(err)) return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) @@ -328,18 +311,15 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, saveData, err := s.storage.GetConfigFileReleaseTx(tx, release.ConfigFileReleaseKey) if err != nil { - errRef = err return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } recordData = saveData if saveData == nil { - needRecord = false return api.NewConfigResponse(apimodel.Code_ExecuteSuccess) } // 如果存在处于 active 状态的配置,重新在激活一下,触发版本的更新变动 if saveData.Active { if err := s.storage.ActiveConfigFileReleaseTx(tx, saveData); err != nil { - errRef = err log.Error("[Config][File] delete config file release when re-active.", utils.RequestID(ctx), zap.Error(err)) return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) @@ -351,7 +331,6 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, utils.RequestID(ctx), utils.ZapNamespace(req.GetNamespace().GetValue()), utils.ZapGroup(req.GetGroup().GetValue()), utils.ZapFileName(req.GetFileName().GetValue()), zap.Error(err)) - errRef = err return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } @@ -360,10 +339,9 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, utils.RequestID(ctx), utils.ZapNamespace(req.GetNamespace().GetValue()), utils.ZapGroup(req.GetGroup().GetValue()), utils.ZapFileName(req.GetFileName().GetValue()), zap.Error(err)) - errRef = err return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } - + s.recordReleaseSuccess(ctx, utils.ReleaseTypeDelete, recordData) s.RecordHistory(ctx, configFileReleaseRecordEntry(ctx, req, release, model.ODelete)) return api.NewConfigResponse(apimodel.Code_ExecuteSuccess) } @@ -429,7 +407,7 @@ func (s *Server) GetConfigFileReleases(ctx context.Context, FileName: searchFilters["file_name"], ReleaseName: searchFilters["release_name"], OnlyActive: strings.Compare(searchFilters["only_active"], "true") == 0, - IncludeGray: false, + IncludeGray: true, } return s.handleDescribeConfigFileReleases(ctx, args) } @@ -444,7 +422,7 @@ func (s *Server) handleDescribeConfigFileReleases(ctx context.Context, ret := make([]*apiconfig.ConfigFileRelease, 0, len(simpleReleases)) for i := range simpleReleases { item := simpleReleases[i] - ret = append(ret, &apiconfig.ConfigFileRelease{ + viewData := &apiconfig.ConfigFileRelease{ Id: utils.NewUInt64Value(item.Id), Name: utils.NewStringValue(item.Name), Namespace: utils.NewStringValue(item.Namespace), @@ -459,8 +437,13 @@ 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)), - }) + ReleaseType: utils.NewStringValue(string(item.ReleaseType)), + } + // 查询配置灰度规则标签 + if item.ReleaseType == model.ReleaseTypeGray { + viewData.BetaLabels = s.caches.Gray().GetGrayRule(model.GetGrayConfigRealseKey(item)) + } + ret = append(ret, viewData) } resp := api.NewConfigBatchQueryResponse(apimodel.Code_ExecuteSuccess) @@ -497,11 +480,11 @@ func (s *Server) RollbackConfigFileRelease(ctx context.Context, data := &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ - Name: req.GetName().GetValue(), - Namespace: req.GetNamespace().GetValue(), - Group: req.GetGroup().GetValue(), - FileName: req.GetFileName().GetValue(), - Typ: model.ReleaseTypeFull, + Name: req.GetName().GetValue(), + Namespace: req.GetNamespace().GetValue(), + Group: req.GetGroup().GetValue(), + FileName: req.GetFileName().GetValue(), + ReleaseType: model.ReleaseTypeFull, }, }, } @@ -522,14 +505,12 @@ func (s *Server) RollbackConfigFileRelease(ctx context.Context, } if ret != nil { _ = tx.Rollback() - s.recordReleaseFail(ctx, utils.ReleaseTypeRollback, data, errors.New(ret.GetInfo().GetValue())) return ret } if err := tx.Commit(); err != nil { log.Error("[Config][File] rollback config file releasw when commit tx.", utils.RequestID(ctx), zap.Error(err)) - s.recordReleaseFail(ctx, utils.ReleaseTypeRollback, data, err) return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } @@ -561,6 +542,94 @@ func (s *Server) handleRollbackConfigFileRelease(ctx context.Context, tx store.T return targetRelease, nil } +// CasUpsertAndReleaseConfigFile 根据版本比对决定是否允许进行配置修改发布 +func (s *Server) CasUpsertAndReleaseConfigFile(ctx context.Context, + req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { + + if err := utils.CheckResourceName(req.GetNamespace()); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_BadRequest, "invalid config namespace") + } + if err := utils.CheckResourceName(req.GetGroup()); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_BadRequest, "invalid config group") + } + if err := CheckFileName(req.GetFileName()); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_BadRequest, "invalid config file_name") + } + + upsertFileReq := &apiconfig.ConfigFile{ + Name: req.GetFileName(), + Namespace: req.GetNamespace(), + Group: req.GetGroup(), + Content: req.GetContent(), + Format: req.GetFormat(), + Comment: req.GetComment(), + Tags: req.GetTags(), + CreateBy: utils.NewStringValue(utils.ParseUserName(ctx)), + ModifyBy: utils.NewStringValue(utils.ParseUserName(ctx)), + ReleaseTime: utils.NewStringValue(req.GetReleaseDescription().GetValue()), + } + if rsp := s.prepareCreateConfigFile(ctx, upsertFileReq); rsp.Code.Value != api.ExecuteSuccess { + return rsp + } + + tx, err := s.storage.StartTx() + if err != nil { + log.Error("[Config][File] upsert config file when begin tx.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + + defer func() { + _ = tx.Rollback() + }() + saveFile, err := s.storage.LockConfigFile(tx, &model.ConfigFileKey{ + Namespace: req.GetNamespace().GetValue(), + Group: req.GetGroup().GetValue(), + Name: req.GetFileName().GetValue(), + }) + if err != nil { + log.Error("[Config][File] lock config file when begin tx.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + + var upsertResp *apiconfig.ConfigResponse + if saveFile == nil { + if req.GetMd5().GetValue() != "" { + return api.NewConfigResponse(apimodel.Code_DataConflict) + } + upsertResp = s.handleCreateConfigFile(ctx, tx, upsertFileReq) + } else { + if req.GetMd5().GetValue() != CalMd5(saveFile.Content) { + return api.NewConfigResponse(apimodel.Code_DataConflict) + } + // 补充针对 Version、MD5 的比对逻辑,如果不满足,快速结束 + upsertResp = s.handleUpdateConfigFile(ctx, tx, upsertFileReq) + } + if upsertResp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { + return upsertResp + } + + data, releaseResp := s.handlePublishConfigFile(ctx, tx, &apiconfig.ConfigFileRelease{ + Name: req.GetReleaseName(), + Namespace: req.GetNamespace(), + Group: req.GetGroup(), + FileName: req.GetFileName(), + CreateBy: utils.NewStringValue(utils.ParseUserName(ctx)), + ModifyBy: utils.NewStringValue(utils.ParseUserName(ctx)), + ReleaseDescription: req.GetReleaseDescription(), + }) + if releaseResp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { + _ = tx.Rollback() + return releaseResp + } + + if err := tx.Commit(); err != nil { + log.Error("[Config][File] upsert config file when commit tx.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + s.recordReleaseHistory(ctx, data, utils.ReleaseTypeNormal, utils.ReleaseStatusSuccess, "") + return releaseResp +} + func (s *Server) UpsertAndReleaseConfigFile(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { @@ -618,21 +687,98 @@ func (s *Server) UpsertAndReleaseConfigFile(ctx context.Context, }) if releaseResp.GetCode().GetValue() != uint32(apimodel.Code_ExecuteSuccess) { _ = tx.Rollback() - if data != nil { - s.recordReleaseFail(ctx, utils.ReleaseTypeNormal, data, errors.New(releaseResp.GetInfo().GetValue())) - } return releaseResp } if err := tx.Commit(); err != nil { log.Error("[Config][File] upsert config file when commit tx.", utils.RequestID(ctx), zap.Error(err)) - s.recordReleaseFail(ctx, utils.ReleaseTypeNormal, data, err) return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) } s.recordReleaseHistory(ctx, data, utils.ReleaseTypeNormal, utils.ReleaseStatusSuccess, "") return releaseResp } +func (s *Server) StopGrayConfigFileReleases(ctx context.Context, reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse { + responses := api.NewConfigBatchWriteResponse(apimodel.Code_ExecuteSuccess) + chs := make([]chan *apiconfig.ConfigResponse, 0, len(reqs)) + for i, instance := range reqs { + chs = append(chs, make(chan *apiconfig.ConfigResponse)) + go func(index int, ins *apiconfig.ConfigFileRelease) { + chs[index] <- s.StopGrayConfigFileRelease(ctx, ins) + }(i, instance) + } + + for _, ch := range chs { + resp := <-ch + api.ConfigCollect(responses, resp) + } + return responses +} + +func (s *Server) StopGrayConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + if err := utils.CheckResourceName(req.GetNamespace()); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_BadRequest, "invalid config namespace") + } + if err := utils.CheckResourceName(req.GetGroup()); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_BadRequest, "invalid config group") + } + if err := CheckFileName(req.GetFileName()); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_BadRequest, "invalid config file_name") + } + tx, err := s.storage.StartTx() + if err != nil { + log.Error("[Config][File] stop beta config file when begin tx.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + + defer func() { + _ = tx.Rollback() + }() + + fileKey := &model.ConfigFileKey{ + Namespace: req.GetNamespace().GetValue(), + Group: req.GetGroup().GetValue(), + Name: req.GetFileName().GetValue(), + } + + if _, err := s.storage.LockConfigFile(tx, fileKey); err != nil { + log.Error("[Config][File] stop beta config file release in lock file.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + betaRelease, err := s.storage.GetConfigFileBetaReleaseTx(tx, fileKey) + if err != nil { + log.Error("[Config][File] stop beta config file release in get target.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + if betaRelease == nil { + return api.NewConfigResponse(apimodel.Code_ExecuteSuccess) + } + if err := s.storage.CleanGrayResource(tx, &model.GrayResource{ + Name: model.GetGrayConfigRealseKey(&model.SimpleConfigFileRelease{ + ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ + Namespace: req.GetNamespace().GetValue(), + Group: req.GetGroup().GetValue(), + Name: req.GetFileName().GetValue(), + ReleaseType: model.ReleaseTypeGray, + }, + }), + }); err != nil { + log.Error("[Config][File] stop beta config file release when clean beta rule.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + + if err = s.storage.DeleteConfigFileReleaseTx(tx, betaRelease.ConfigFileReleaseKey); err != nil { + log.Error("[Config][File] stop beta config file release.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + if err := tx.Commit(); err != nil { + log.Error("[Config][File] stop config file release when commit tx.", utils.RequestID(ctx), zap.Error(err)) + return api.NewConfigResponse(commonstore.StoreCode2APICode(err)) + } + s.recordReleaseHistory(ctx, betaRelease, utils.ReleaseTypeCancelGray, utils.ReleaseStatusSuccess, "") + return api.NewConfigResponse(apimodel.Code_ExecuteSuccess) +} + func (s *Server) cleanConfigFileReleases(ctx context.Context, tx store.Tx, file *model.ConfigFile) *apiconfig.ConfigResponse { @@ -656,10 +802,6 @@ func (s *Server) recordReleaseSuccess(ctx context.Context, rType string, release s.recordReleaseHistory(ctx, release, rType, utils.ReleaseStatusSuccess, "") } -func (s *Server) recordReleaseFail(ctx context.Context, rType string, release *model.ConfigFileRelease, err error) { - s.recordReleaseHistory(ctx, release, rType, utils.ReleaseStatusFail, err.Error()) -} - // configFileReleaseRecordEntry 生成服务的记录entry func configFileReleaseRecordEntry(ctx context.Context, req *apiconfig.ConfigFileRelease, md *model.ConfigFileRelease, operationType model.OperationType) *model.RecordEntry { diff --git a/config/config_file_release_test.go b/config/config_file_release_test.go index 3d7fb3e23..14d23a86a 100644 --- a/config/config_file_release_test.go +++ b/config/config_file_release_test.go @@ -23,7 +23,9 @@ import ( "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" + "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" ) @@ -149,7 +151,7 @@ func Test_PublishConfigFile(t *testing.T) { t.Run("client_get_configfile", func(t *testing.T) { // 客户端获取符合预期, 这里强制触发一次缓存数据同步 _ = testSuit.CacheMgr().TestUpdate() - clientResp := testSuit.ConfigServer().GetConfigFileForClient(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + clientResp := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ Namespace: utils.NewStringValue(mockNamespace), Group: utils.NewStringValue(mockGroup), FileName: utils.NewStringValue(mockFileName), @@ -248,7 +250,7 @@ func Test_RollbackConfigFileRelease(t *testing.T) { // 客户端获取符合预期, 这里强制触发一次缓存数据同步 _ = testSuit.CacheMgr().TestUpdate() - clientResp := testSuit.ConfigServer().GetConfigFileForClient(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + clientResp := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ Namespace: utils.NewStringValue(mockNamespace), Group: utils.NewStringValue(mockGroup), FileName: utils.NewStringValue(mockFileName), @@ -276,7 +278,188 @@ func Test_RollbackConfigFileRelease(t *testing.T) { }) } -// Test_DeleteConfigFileRelease 测试删除配置发布 -func Test_DeleteConfigFileRelease(t *testing.T) { +// Test_GrayConfigFileRelease 测试配置灰度发布 +func Test_GrayConfigFileRelease(t *testing.T) { + testSuit := newConfigCenterTestSuit(t) + + var ( + mockNamespace = "gray_mock_namespace" + mockGroup = "gray_mock_group" + mockFileName = "gray_mock_filename" + mockReleaseName = "gray_mock_release" + mockContent = "gray_mock_content" + mockBetaReleaseName = "gray_mock_beta_release" + mockNewContent = "gray_mock_content_v2" + mockClientIP = "1.1.1.1" + ) + + t.Run("first publish", func(t *testing.T) { + resp := testSuit.ConfigServer().UpsertAndReleaseConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFilePublishInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + ReleaseName: utils.NewStringValue(mockReleaseName), + Content: utils.NewStringValue(mockContent), + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + + resp = testSuit.ConfigServer().GetConfigFileRelease(testSuit.DefaultCtx, &config_manage.ConfigFileRelease{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + Name: utils.NewStringValue(mockReleaseName), + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + // 正常发布成功 + assert.Equal(t, mockContent, resp.GetConfigFileRelease().GetContent().GetValue()) + }) + + t.Run("gray_publish", func(t *testing.T) { + resp := testSuit.ConfigServer().UpdateConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFile{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + Name: utils.NewStringValue(mockFileName), + Content: utils.NewStringValue(mockNewContent), + }) + // 正常更新配置文件 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + + // 发布灰度配置 + resp = testSuit.ConfigServer().PublishConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFileRelease{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + Name: utils.NewStringValue(mockBetaReleaseName), + Content: utils.NewStringValue(mockNewContent), + ReleaseType: wrapperspb.String(model.ReleaseTypeGray), + BetaLabels: []*apimodel.ClientLabel{ + { + Key: model.ClientLabel_IP, + Value: &apimodel.MatchString{ + Type: apimodel.MatchString_EXACT, + Value: wrapperspb.String(mockClientIP), + ValueType: apimodel.MatchString_TEXT, + }, + }, + }, + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + resp = testSuit.ConfigServer().GetConfigFileRelease(testSuit.DefaultCtx, &config_manage.ConfigFileRelease{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + Name: utils.NewStringValue(mockBetaReleaseName), + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.String()) + // 正常发布成功 + assert.Equal(t, mockNewContent, resp.GetConfigFileRelease().GetContent().GetValue()) + + _ = testSuit.CacheMgr().TestUpdate() + + // 不带配置标签查询, 查不到处于灰度发布的配置 + clientRsp := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + }) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + assert.Equal(t, mockContent, clientRsp.GetConfigFile().GetContent().GetValue()) + + // 携带正确配置标签查询, 查到处于灰度发布的配置 + clientRsp = testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + Tags: []*config_manage.ConfigFileTag{ + { + Key: utils.NewStringValue(model.ClientLabel_IP), + Value: utils.NewStringValue(mockClientIP), + }, + }, + }) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + assert.Equal(t, mockNewContent, clientRsp.GetConfigFile().GetContent().GetValue()) + + // 携带不正确配置标签查询, 查到处于灰度发布的配置 + clientRsp = testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + Tags: []*config_manage.ConfigFileTag{ + { + Key: utils.NewStringValue(model.ClientLabel_IP), + Value: utils.NewStringValue(mockClientIP + "2"), + }, + }, + }) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + assert.Equal(t, mockContent, clientRsp.GetConfigFile().GetContent().GetValue()) + }) + + // 测试存在灰度发布配置时, 不得发布新的配置文件 + t.Run("normal_publish_when_exist_gray", func(t *testing.T) { + resp := testSuit.ConfigServer().UpsertAndReleaseConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFilePublishInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + ReleaseName: utils.NewStringValue(mockReleaseName), + Content: utils.NewStringValue(mockContent), + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_DataConflict), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + }) + + // 删除已发布的灰度配置,获取不到 + t.Run("delete_gray_release", func(t *testing.T) { + resp := testSuit.ConfigServer().StopGrayConfigFileReleases(testSuit.DefaultCtx, []*config_manage.ConfigFileRelease{ + &config_manage.ConfigFileRelease{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + }, + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + + _ = testSuit.CacheMgr().TestUpdate() + + // 不带配置标签查询, 查不到处于灰度发布的配置 + clientRsp := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + }) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + assert.Equal(t, mockContent, clientRsp.GetConfigFile().GetContent().GetValue()) + + // 携带正确配置标签查询, 查到处于灰度发布的配置 + clientRsp = testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + Tags: []*config_manage.ConfigFileTag{ + { + Key: utils.NewStringValue(model.ClientLabel_IP), + Value: utils.NewStringValue(mockClientIP), + }, + }, + }) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.GetInfo().GetValue()) + assert.Equal(t, mockContent, clientRsp.GetConfigFile().GetContent().GetValue()) + // 配置发布成功 + pubResp := testSuit.ConfigServer().UpsertAndReleaseConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFilePublishInfo{ + Namespace: utils.NewStringValue(mockNamespace), + Group: utils.NewStringValue(mockGroup), + FileName: utils.NewStringValue(mockFileName), + ReleaseName: utils.NewStringValue(mockReleaseName), + Content: utils.NewStringValue(mockContent), + }) + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), pubResp.GetCode().GetValue(), pubResp.GetInfo().GetValue()) + }) } diff --git a/config/config_file_test.go b/config/config_file_test.go index b2f3ed4ba..6fc4cab5b 100644 --- a/config/config_file_test.go +++ b/config/config_file_test.go @@ -23,10 +23,8 @@ import ( "encoding/hex" "errors" "fmt" - "reflect" "testing" - . "github.com/agiledragon/gomonkey/v2" "github.com/golang/mock/gomock" "github.com/golang/protobuf/ptypes/wrappers" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" @@ -600,13 +598,13 @@ func Test_encryptConfigFile(t *testing.T) { hasDataKeyTag := false hasAlgoTag := false for tagKey, tagVal := range tt.args.configFile.Metadata { - if tagKey == utils.ConfigFileTagKeyDataKey { + if tagKey == model.MetaKeyConfigFileDataKey { hasDataKeyTag = true if tt.args.dataKey != "" { assert.Equal(t, tt.args.dataKey, tagVal) } } - if tagKey == utils.ConfigFileTagKeyEncryptAlgo { + if tagKey == model.MetaKeyConfigFileEncryptAlgo { hasAlgoTag = true assert.Equal(t, tt.args.algorithm, tagVal) } @@ -650,8 +648,8 @@ func Test_decryptConfigFile(t *testing.T) { configFile: &model.ConfigFile{ Content: "YnLZ0SYuujFBHjYHAZVN5A==", Metadata: map[string]string{ - utils.ConfigFileTagKeyDataKey: base64.StdEncoding.EncodeToString(dataKey), - utils.ConfigFileTagKeyEncryptAlgo: "AES", + model.MetaKeyConfigFileDataKey: base64.StdEncoding.EncodeToString(dataKey), + model.MetaKeyConfigFileEncryptAlgo: "AES", }, CreateBy: "polaris", }, @@ -666,8 +664,8 @@ func Test_decryptConfigFile(t *testing.T) { configFile: &model.ConfigFile{ Content: "YnLZ0SYuujFBHjYHAZVN5A==", Metadata: map[string]string{ - utils.ConfigFileTagKeyDataKey: base64.StdEncoding.EncodeToString(dataKey), - utils.ConfigFileTagKeyEncryptAlgo: "AES", + model.MetaKeyConfigFileDataKey: base64.StdEncoding.EncodeToString(dataKey), + model.MetaKeyConfigFileEncryptAlgo: "AES", }, CreateBy: "polaris", }, @@ -683,7 +681,7 @@ func Test_decryptConfigFile(t *testing.T) { assert.Equal(t, tt.wantErr, err, tt.name) assert.Equal(t, tt.want, tt.args.configFile.Content, tt.name) for tagKey := range tt.args.configFile.Metadata { - if tagKey == utils.ConfigFileTagKeyDataKey { + if tagKey == model.MetaKeyConfigFileDataKey { t.Fatal("config tags has data key") } } @@ -767,12 +765,6 @@ func Test_GetConfigFileRichInfo(t *testing.T) { defer ctrl.Finish() t.Run("获取配置文件基本信息-解密配置文件-返回error", func(t *testing.T) { - crypto := &aes.AESCrypto{} - encryptFunc := ApplyMethod(reflect.TypeOf(crypto), "Decrypt", func(_ *aes.AESCrypto, plaintext string, key []byte) (string, error) { - return "", errors.New("mock encrypt error") - }) - defer encryptFunc.Reset() - configFile := assembleConfigFile() storage := storemock.NewMockStore(ctrl) @@ -787,7 +779,11 @@ func Test_GetConfigFileRichInfo(t *testing.T) { svr.TestMockStore(storage) svr.TestMockCryptoManager(&MockCryptoManager{ repos: map[string]plugin.Crypto{ - crypto.Name(): crypto, + (&aes.AESCrypto{}).Name(): &MockCrypto{ + mockDecrypt: func(cryptotext string, key []byte) (string, error) { + return "", errors.New("mock encrypt error") + }, + }, }, }) got := testSuit.ConfigServer().GetConfigFileRichInfo(testSuit.DefaultCtx, configFile) @@ -824,6 +820,7 @@ func (m *MockCryptoManager) GetCrypto(algo string) (plugin.Crypto, error) { } type MockCrypto struct { + mockDecrypt func(cryptotext string, key []byte) (string, error) } func (m *MockCrypto) Name() string { diff --git a/config/client_authibility.go b/config/interceptor/auth/client_authibility.go similarity index 67% rename from config/client_authibility.go rename to config/interceptor/auth/client_authibility.go index ebce18b05..3dc57ec65 100644 --- a/config/client_authibility.go +++ b/config/interceptor/auth/client_authibility.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -25,15 +25,16 @@ import ( api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/config" ) // UpsertAndReleaseConfigFileFromClient 创建/更新配置文件并发布 -func (s *serverAuthability) UpsertAndReleaseConfigFileFromClient(ctx context.Context, +func (s *ServerAuthability) UpsertAndReleaseConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { authCtx := s.collectConfigFilePublishAuthContext(ctx, []*apiconfig.ConfigFilePublishInfo{req}, model.Modify, "UpsertAndReleaseConfigFileFromClient") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - return api.NewConfigFileResponse(convertToErrCode(err), nil) + return api.NewConfigFileResponse(model.ConvertToErrCode(err), nil) } ctx = authCtx.GetRequestContext() @@ -43,7 +44,7 @@ func (s *serverAuthability) UpsertAndReleaseConfigFileFromClient(ctx context.Con } // CreateConfigFileFromClient 调用config_file的方法创建配置文件 -func (s *serverAuthability) CreateConfigFileFromClient(ctx context.Context, +func (s *ServerAuthability) CreateConfigFileFromClient(ctx context.Context, fileInfo *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { authCtx := s.collectClientConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{{ @@ -52,7 +53,7 @@ func (s *serverAuthability) CreateConfigFileFromClient(ctx context.Context, Group: fileInfo.Group}, }, model.Create, "CreateConfigFileFromClient") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - return api.NewConfigClientResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -62,12 +63,12 @@ func (s *serverAuthability) CreateConfigFileFromClient(ctx context.Context, } // UpdateConfigFileFromClient 调用config_file的方法更新配置文件 -func (s *serverAuthability) UpdateConfigFileFromClient(ctx context.Context, +func (s *ServerAuthability) UpdateConfigFileFromClient(ctx context.Context, fileInfo *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { authCtx := s.collectClientConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{fileInfo}, model.Modify, "UpdateConfigFileFromClient") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - return api.NewConfigClientResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -77,13 +78,13 @@ func (s *serverAuthability) UpdateConfigFileFromClient(ctx context.Context, } // DeleteConfigFileFromClient 删除配置文件,删除配置文件同时会通知客户端 Not_Found -func (s *serverAuthability) DeleteConfigFileFromClient(ctx context.Context, +func (s *ServerAuthability) DeleteConfigFileFromClient(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{req}, model.Delete, "DeleteConfigFileFromClient") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -93,7 +94,7 @@ func (s *serverAuthability) DeleteConfigFileFromClient(ctx context.Context, } // PublishConfigFileFromClient 调用config_file_release的方法发布配置文件 -func (s *serverAuthability) PublishConfigFileFromClient(ctx context.Context, +func (s *ServerAuthability) PublishConfigFileFromClient(ctx context.Context, fileInfo *apiconfig.ConfigFileRelease) *apiconfig.ConfigClientResponse { authCtx := s.collectClientConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{{ @@ -102,7 +103,7 @@ func (s *serverAuthability) PublishConfigFileFromClient(ctx context.Context, Group: fileInfo.Group}, }, model.Create, "PublishConfigFileFromClient") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - return api.NewConfigClientResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -111,8 +112,8 @@ func (s *serverAuthability) PublishConfigFileFromClient(ctx context.Context, return s.targetServer.PublishConfigFileFromClient(ctx, fileInfo) } -// GetConfigFileForClient 从缓存中获取配置文件,如果客户端的版本号大于服务端,则服务端重新加载缓存 -func (s *serverAuthability) GetConfigFileForClient(ctx context.Context, +// GetConfigFileWithCache 从缓存中获取配置文件,如果客户端的版本号大于服务端,则服务端重新加载缓存 +func (s *ServerAuthability) GetConfigFileWithCache(ctx context.Context, fileInfo *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigClientResponse { authCtx := s.collectClientConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{{ @@ -121,21 +122,21 @@ func (s *serverAuthability) GetConfigFileForClient(ctx context.Context, Group: fileInfo.Group}, }, model.Read, "GetConfigFileForClient") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - return api.NewConfigClientResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return s.targetServer.GetConfigFileForClient(ctx, fileInfo) + return s.targetServer.GetConfigFileWithCache(ctx, fileInfo) } // WatchConfigFiles 监听配置文件变化 -func (s *serverAuthability) LongPullWatchFile(ctx context.Context, - request *apiconfig.ClientWatchConfigFileRequest) (WatchCallback, error) { +func (s *ServerAuthability) LongPullWatchFile(ctx context.Context, + request *apiconfig.ClientWatchConfigFileRequest) (config.WatchCallback, error) { authCtx := s.collectClientWatchConfigFiles(ctx, request, model.Read, "LongPullWatchFile") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return func() *apiconfig.ConfigClientResponse { - return api.NewConfigClientResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) }, nil } @@ -146,7 +147,7 @@ func (s *serverAuthability) LongPullWatchFile(ctx context.Context, } // GetConfigFileNamesWithCache 获取某个配置分组下的配置文件 -func (s *serverAuthability) GetConfigFileNamesWithCache(ctx context.Context, +func (s *ServerAuthability) GetConfigFileNamesWithCache(ctx context.Context, req *apiconfig.ConfigFileGroupRequest) *apiconfig.ConfigClientListResponse { authCtx := s.collectClientConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{ @@ -156,7 +157,7 @@ func (s *serverAuthability) GetConfigFileNamesWithCache(ctx context.Context, }, }, model.Read, "GetConfigFileNamesWithCache") if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { - out := api.NewConfigClientListResponse(convertToErrCode(err)) + out := api.NewConfigClientListResponse(model.ConvertToErrCode(err)) return out } @@ -164,3 +165,37 @@ func (s *serverAuthability) GetConfigFileNamesWithCache(ctx context.Context, ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) return s.targetServer.GetConfigFileNamesWithCache(ctx, req) } + +func (s *ServerAuthability) GetConfigGroupsWithCache(ctx context.Context, + req *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigDiscoverResponse { + + authCtx := s.collectClientConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{ + { + Namespace: req.GetNamespace(), + }, + }, model.Read, "GetConfigGroupsWithCache") + if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + out := api.NewConfigDiscoverResponse(model.ConvertToErrCode(err)) + return out + } + + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.targetServer.GetConfigGroupsWithCache(ctx, req) +} + +// CasUpsertAndReleaseConfigFileFromClient 创建/更新配置文件并发布 +func (s *ServerAuthability) CasUpsertAndReleaseConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { + + authCtx := s.collectConfigFilePublishAuthContext(ctx, []*apiconfig.ConfigFilePublishInfo{req}, + model.Modify, "CasUpsertAndReleaseConfigFileFromClient") + if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + return api.NewConfigFileResponse(model.ConvertToErrCode(err), nil) + } + + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + + return s.targetServer.CasUpsertAndReleaseConfigFileFromClient(ctx, req) +} diff --git a/config/config_file_authibility.go b/config/interceptor/auth/config_file_authibility.go similarity index 80% rename from config/config_file_authibility.go rename to config/interceptor/auth/config_file_authibility.go index 020d6f9ac..321cd72ca 100644 --- a/config/config_file_authibility.go +++ b/config/interceptor/auth/config_file_authibility.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -28,12 +28,12 @@ import ( ) // CreateConfigFile 创建配置文件 -func (s *serverAuthability) CreateConfigFile(ctx context.Context, +func (s *ServerAuthability) CreateConfigFile(ctx context.Context, configFile *apiconfig.ConfigFile) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileAuthContext( ctx, []*apiconfig.ConfigFile{configFile}, model.Create, "CreateConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -43,13 +43,13 @@ func (s *serverAuthability) CreateConfigFile(ctx context.Context, } // GetConfigFileRichInfo 获取单个配置文件基础信息,包含发布状态等信息 -func (s *serverAuthability) GetConfigFileRichInfo(ctx context.Context, +func (s *ServerAuthability) GetConfigFileRichInfo(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileAuthContext( ctx, []*apiconfig.ConfigFile{req}, model.Read, "GetConfigFileRichInfo") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -57,12 +57,12 @@ func (s *serverAuthability) GetConfigFileRichInfo(ctx context.Context, } // SearchConfigFile 查询配置文件 -func (s *serverAuthability) SearchConfigFile(ctx context.Context, +func (s *ServerAuthability) SearchConfigFile(ctx context.Context, filter map[string]string) *apiconfig.ConfigBatchQueryResponse { authCtx := s.collectConfigFileAuthContext(ctx, nil, model.Read, "SearchConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigFileBatchQueryResponseWithMessage(convertToErrCode(err), err.Error()) + return api.NewConfigFileBatchQueryResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -71,12 +71,12 @@ func (s *serverAuthability) SearchConfigFile(ctx context.Context, } // UpdateConfigFile 更新配置文件 -func (s *serverAuthability) UpdateConfigFile( +func (s *ServerAuthability) UpdateConfigFile( ctx context.Context, configFile *apiconfig.ConfigFile) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileAuthContext( ctx, []*apiconfig.ConfigFile{configFile}, model.Modify, "UpdateConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -86,13 +86,13 @@ func (s *serverAuthability) UpdateConfigFile( } // DeleteConfigFile 删除配置文件,删除配置文件同时会通知客户端 Not_Found -func (s *serverAuthability) DeleteConfigFile(ctx context.Context, +func (s *ServerAuthability) DeleteConfigFile(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{req}, model.Delete, "DeleteConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -102,12 +102,12 @@ func (s *serverAuthability) DeleteConfigFile(ctx context.Context, } // BatchDeleteConfigFile 批量删除配置文件 -func (s *serverAuthability) BatchDeleteConfigFile(ctx context.Context, +func (s *ServerAuthability) BatchDeleteConfigFile(ctx context.Context, req []*apiconfig.ConfigFile) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileAuthContext(ctx, req, model.Delete, "BatchDeleteConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -116,7 +116,7 @@ func (s *serverAuthability) BatchDeleteConfigFile(ctx context.Context, return s.targetServer.BatchDeleteConfigFile(ctx, req) } -func (s *serverAuthability) ExportConfigFile(ctx context.Context, +func (s *ServerAuthability) ExportConfigFile(ctx context.Context, configFileExport *apiconfig.ConfigFileExportRequest) *apiconfig.ConfigExportResponse { var configFiles []*apiconfig.ConfigFile for _, group := range configFileExport.Groups { @@ -128,7 +128,7 @@ func (s *serverAuthability) ExportConfigFile(ctx context.Context, } authCtx := s.collectConfigFileAuthContext(ctx, configFiles, model.Read, "ExportConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigFileExportResponseWithMessage(convertToErrCode(err), err.Error()) + return api.NewConfigFileExportResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -136,11 +136,11 @@ func (s *serverAuthability) ExportConfigFile(ctx context.Context, return s.targetServer.ExportConfigFile(ctx, configFileExport) } -func (s *serverAuthability) ImportConfigFile(ctx context.Context, +func (s *ServerAuthability) ImportConfigFile(ctx context.Context, configFiles []*apiconfig.ConfigFile, conflictHandling string) *apiconfig.ConfigImportResponse { authCtx := s.collectConfigFileAuthContext(ctx, configFiles, model.Create, "ImportConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigFileImportResponseWithMessage(convertToErrCode(err), err.Error()) + return api.NewConfigFileImportResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -148,7 +148,7 @@ func (s *serverAuthability) ImportConfigFile(ctx context.Context, return s.targetServer.ImportConfigFile(ctx, configFiles, conflictHandling) } -func (s *serverAuthability) GetAllConfigEncryptAlgorithms( +func (s *ServerAuthability) GetAllConfigEncryptAlgorithms( ctx context.Context) *apiconfig.ConfigEncryptAlgorithmResponse { return s.targetServer.GetAllConfigEncryptAlgorithms(ctx) } diff --git a/config/config_file_group_authibility.go b/config/interceptor/auth/config_file_group_authibility.go similarity index 85% rename from config/config_file_group_authibility.go rename to config/interceptor/auth/config_file_group_authibility.go index a89d85801..49377293b 100644 --- a/config/config_file_group_authibility.go +++ b/config/interceptor/auth/config_file_group_authibility.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -30,14 +30,14 @@ import ( ) // CreateConfigFileGroup 创建配置文件组 -func (s *serverAuthability) CreateConfigFileGroup(ctx context.Context, +func (s *ServerAuthability) CreateConfigFileGroup(ctx context.Context, configFileGroup *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { authCtx := s.collectConfigGroupAuthContext(ctx, []*apiconfig.ConfigFileGroup{configFileGroup}, model.Create, "CreateConfigFileGroup") // 验证 token 信息 if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -47,13 +47,13 @@ func (s *serverAuthability) CreateConfigFileGroup(ctx context.Context, } // QueryConfigFileGroups 查询配置文件组 -func (s *serverAuthability) QueryConfigFileGroups(ctx context.Context, +func (s *ServerAuthability) QueryConfigFileGroups(ctx context.Context, filter map[string]string) *apiconfig.ConfigBatchQueryResponse { authCtx := s.collectConfigGroupAuthContext(ctx, nil, model.Read, "QueryConfigFileGroups") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigBatchQueryResponse(convertToErrCode(err)) + return api.NewConfigBatchQueryResponse(model.ConvertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -70,7 +70,7 @@ func (s *serverAuthability) QueryConfigFileGroups(ctx context.Context, editable := true // 如果鉴权能力没有开启,那就默认都可以进行编辑 if s.strategyMgn.GetAuthChecker().IsOpenConsoleAuth() { - editable = s.targetServer.caches.AuthStrategy().IsResourceEditable(principal, + editable = s.targetServer.CacheManager().AuthStrategy().IsResourceEditable(principal, apisecurity.ResourceType_ConfigGroups, fmt.Sprintf("%d", group.GetId().GetValue())) } group.Editable = utils.NewBoolValue(editable) @@ -81,13 +81,13 @@ func (s *serverAuthability) QueryConfigFileGroups(ctx context.Context, } // DeleteConfigFileGroup 删除配置文件组 -func (s *serverAuthability) DeleteConfigFileGroup( +func (s *ServerAuthability) DeleteConfigFileGroup( ctx context.Context, namespace, name string) *apiconfig.ConfigResponse { authCtx := s.collectConfigGroupAuthContext(ctx, []*apiconfig.ConfigFileGroup{{Name: utils.NewStringValue(name), Namespace: utils.NewStringValue(namespace)}}, model.Delete, "DeleteConfigFileGroup") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -97,13 +97,13 @@ func (s *serverAuthability) DeleteConfigFileGroup( } // UpdateConfigFileGroup 更新配置文件组 -func (s *serverAuthability) UpdateConfigFileGroup(ctx context.Context, +func (s *ServerAuthability) UpdateConfigFileGroup(ctx context.Context, configFileGroup *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { authCtx := s.collectConfigGroupAuthContext(ctx, []*apiconfig.ConfigFileGroup{configFileGroup}, model.Modify, "UpdateConfigFileGroup") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() diff --git a/config/config_file_release_authibility.go b/config/interceptor/auth/config_file_release_authibility.go similarity index 73% rename from config/config_file_release_authibility.go rename to config/interceptor/auth/config_file_release_authibility.go index 7a6101e45..3b5b64f70 100644 --- a/config/config_file_release_authibility.go +++ b/config/interceptor/auth/config_file_release_authibility.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -28,14 +28,14 @@ import ( ) // PublishConfigFile 发布配置文件 -func (s *serverAuthability) PublishConfigFile(ctx context.Context, +func (s *ServerAuthability) PublishConfigFile(ctx context.Context, configFileRelease *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{configFileRelease}, model.Modify, "PublishConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -45,14 +45,14 @@ func (s *serverAuthability) PublishConfigFile(ctx context.Context, } // GetConfigFileRelease 获取配置文件发布内容 -func (s *serverAuthability) GetConfigFileRelease(ctx context.Context, +func (s *ServerAuthability) GetConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{req}, model.Read, "GetConfigFileRelease") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -60,13 +60,13 @@ func (s *serverAuthability) GetConfigFileRelease(ctx context.Context, } // DeleteConfigFileReleases implements ConfigCenterServer. -func (s *serverAuthability) DeleteConfigFileReleases(ctx context.Context, +func (s *ServerAuthability) DeleteConfigFileReleases(ctx context.Context, reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse { authCtx := s.collectConfigFileReleaseAuthContext(ctx, reqs, model.Delete, "DeleteConfigFileReleases") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigBatchWriteResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigBatchWriteResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -74,13 +74,13 @@ func (s *serverAuthability) DeleteConfigFileReleases(ctx context.Context, } // GetConfigFileReleaseVersions implements ConfigCenterServer. -func (s *serverAuthability) GetConfigFileReleaseVersions(ctx context.Context, +func (s *ServerAuthability) GetConfigFileReleaseVersions(ctx context.Context, filters map[string]string) *apiconfig.ConfigBatchQueryResponse { authCtx := s.collectConfigFileReleaseAuthContext(ctx, nil, model.Read, "GetConfigFileReleaseVersions") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigBatchQueryResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigBatchQueryResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -88,13 +88,13 @@ func (s *serverAuthability) GetConfigFileReleaseVersions(ctx context.Context, } // GetConfigFileReleases implements ConfigCenterServer. -func (s *serverAuthability) GetConfigFileReleases(ctx context.Context, +func (s *ServerAuthability) GetConfigFileReleases(ctx context.Context, filters map[string]string) *apiconfig.ConfigBatchQueryResponse { authCtx := s.collectConfigFileReleaseAuthContext(ctx, nil, model.Read, "GetConfigFileReleases") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigBatchQueryResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigBatchQueryResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -102,13 +102,13 @@ func (s *serverAuthability) GetConfigFileReleases(ctx context.Context, } // RollbackConfigFileReleases implements ConfigCenterServer. -func (s *serverAuthability) RollbackConfigFileReleases(ctx context.Context, +func (s *ServerAuthability) RollbackConfigFileReleases(ctx context.Context, reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse { authCtx := s.collectConfigFileReleaseAuthContext(ctx, reqs, model.Modify, "RollbackConfigFileReleases") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigBatchWriteResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigBatchWriteResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) @@ -116,12 +116,12 @@ func (s *serverAuthability) RollbackConfigFileReleases(ctx context.Context, } // UpsertAndReleaseConfigFile . -func (s *serverAuthability) UpsertAndReleaseConfigFile(ctx context.Context, +func (s *ServerAuthability) UpsertAndReleaseConfigFile(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { authCtx := s.collectConfigFilePublishAuthContext(ctx, []*apiconfig.ConfigFilePublishInfo{req}, model.Modify, "UpsertAndReleaseConfigFile") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigFileResponse(convertToErrCode(err), nil) + return api.NewConfigFileResponse(model.ConvertToErrCode(err), nil) } ctx = authCtx.GetRequestContext() @@ -129,3 +129,18 @@ func (s *serverAuthability) UpsertAndReleaseConfigFile(ctx context.Context, return s.targetServer.UpsertAndReleaseConfigFile(ctx, req) } + +func (s *ServerAuthability) StopGrayConfigFileReleases(ctx context.Context, + reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse { + + authCtx := s.collectConfigFileReleaseAuthContext(ctx, reqs, + model.Modify, "StopGrayConfigFileReleases") + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigBatchWriteResponse(model.ConvertToErrCode(err)) + } + + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + + return s.targetServer.StopGrayConfigFileReleases(ctx, reqs) +} diff --git a/config/config_file_release_history_authibility.go b/config/interceptor/auth/config_file_release_history_authibility.go similarity index 89% rename from config/config_file_release_history_authibility.go rename to config/interceptor/auth/config_file_release_history_authibility.go index 18f2f6323..5bf7c024f 100644 --- a/config/config_file_release_history_authibility.go +++ b/config/interceptor/auth/config_file_release_history_authibility.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -28,13 +28,13 @@ import ( ) // GetConfigFileReleaseHistory 获取配置文件发布历史记录 -func (s *serverAuthability) GetConfigFileReleaseHistories(ctx context.Context, +func (s *ServerAuthability) GetConfigFileReleaseHistories(ctx context.Context, filter map[string]string) *apiconfig.ConfigBatchQueryResponse { authCtx := s.collectConfigFileReleaseHistoryAuthContext(ctx, nil, model.Read, "GetConfigFileReleaseHistories") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigBatchQueryResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigBatchQueryResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) diff --git a/config/config_file_template_authibility.go b/config/interceptor/auth/config_file_template_authibility.go similarity index 84% rename from config/config_file_template_authibility.go rename to config/interceptor/auth/config_file_template_authibility.go index 992bd0d02..84d49e4d1 100644 --- a/config/config_file_template_authibility.go +++ b/config/interceptor/auth/config_file_template_authibility.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -28,11 +28,11 @@ import ( ) // GetAllConfigFileTemplates get all config file templates -func (s *serverAuthability) GetAllConfigFileTemplates(ctx context.Context) *apiconfig.ConfigBatchQueryResponse { +func (s *ServerAuthability) GetAllConfigFileTemplates(ctx context.Context) *apiconfig.ConfigBatchQueryResponse { authCtx := s.collectConfigFileTemplateAuthContext(ctx, []*apiconfig.ConfigFileTemplate{}, model.Read, "GetAllConfigFileTemplates") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigFileBatchQueryResponseWithMessage(convertToErrCode(err), err.Error()) + return api.NewConfigFileBatchQueryResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -41,11 +41,11 @@ func (s *serverAuthability) GetAllConfigFileTemplates(ctx context.Context) *apic } // GetConfigFileTemplate get config file template -func (s *serverAuthability) GetConfigFileTemplate(ctx context.Context, name string) *apiconfig.ConfigResponse { +func (s *ServerAuthability) GetConfigFileTemplate(ctx context.Context, name string) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileTemplateAuthContext(ctx, []*apiconfig.ConfigFileTemplate{}, model.Read, "GetAllConfigFileTemplates") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -54,13 +54,13 @@ func (s *serverAuthability) GetConfigFileTemplate(ctx context.Context, name stri } // CreateConfigFileTemplate create config file template -func (s *serverAuthability) CreateConfigFileTemplate(ctx context.Context, +func (s *ServerAuthability) CreateConfigFileTemplate(ctx context.Context, template *apiconfig.ConfigFileTemplate) *apiconfig.ConfigResponse { authCtx := s.collectConfigFileTemplateAuthContext(ctx, []*apiconfig.ConfigFileTemplate{template}, model.Create, "CreateConfigFileTemplate") if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { - return api.NewConfigResponseWithInfo(convertToErrCode(err), err.Error()) + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() diff --git a/config/interceptor/auth/log.go b/config/interceptor/auth/log.go new file mode 100644 index 000000000..1e01c54dd --- /dev/null +++ b/config/interceptor/auth/log.go @@ -0,0 +1,25 @@ +/** + * 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 config_auth + +import commonLog "github.com/polarismesh/polaris/common/log" + +var ( + log = commonLog.GetScopeOrDefaultByName(commonLog.ConfigLoggerName) + authLog = commonLog.GetScopeOrDefaultByName(commonLog.AuthLoggerName) +) diff --git a/config/resource_listener.go b/config/interceptor/auth/resource_listener.go similarity index 75% rename from config/resource_listener.go rename to config/interceptor/auth/resource_listener.go index 85ab90463..2b89f99a7 100644 --- a/config/resource_listener.go +++ b/config/interceptor/auth/resource_listener.go @@ -15,39 +15,26 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" "strconv" - apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/config" ) -// ResourceHook The listener is placed before and after the resource operation, only normal flow -type ResourceHook interface { - // Before - Before(ctx context.Context, resourceType model.Resource) - // After - After(ctx context.Context, resourceType model.Resource, res *ResourceEvent) error -} - -// ResourceEvent 资源事件 -type ResourceEvent struct { - ConfigGroup *apiconfig.ConfigFileGroup -} - // Before this function is called before the resource operation -func (s *serverAuthability) Before(ctx context.Context, resourceType model.Resource) { +func (s *ServerAuthability) Before(ctx context.Context, resourceType model.Resource) { // do nothing } // After this function is called after the resource operation -func (s *serverAuthability) After(ctx context.Context, resourceType model.Resource, res *ResourceEvent) error { +func (s *ServerAuthability) After(ctx context.Context, resourceType model.Resource, res *config.ResourceEvent) error { switch resourceType { case model.RConfigGroup: return s.onConfigGroupResource(ctx, res) @@ -57,7 +44,7 @@ func (s *serverAuthability) After(ctx context.Context, resourceType model.Resour } // onConfigGroupResource -func (s *serverAuthability) onConfigGroupResource(ctx context.Context, res *ResourceEvent) error { +func (s *ServerAuthability) onConfigGroupResource(ctx context.Context, res *config.ResourceEvent) error { authCtx := ctx.Value(utils.ContextAuthContextKey).(*model.AcquireContext) authCtx.SetAttachment(model.ResourceAttachmentKey, map[apisecurity.ResourceType][]model.ResourceEntry{ diff --git a/config/server_authability.go b/config/interceptor/auth/server_authability.go similarity index 86% rename from config/server_authability.go rename to config/interceptor/auth/server_authability.go index c71ecc103..8629a5c97 100644 --- a/config/server_authability.go +++ b/config/interceptor/auth/server_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package config +package config_auth import ( "context" @@ -28,20 +28,21 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/config" ) -var _ ConfigCenterServer = (*serverAuthability)(nil) +var _ config.ConfigCenterServer = (*ServerAuthability)(nil) // Server 配置中心核心服务 -type serverAuthability struct { - targetServer *Server +type ServerAuthability struct { + targetServer *config.Server userMgn auth.UserServer strategyMgn auth.StrategyServer } -func newServerAuthAbility(targetServer *Server, - userMgn auth.UserServer, strategyMgn auth.StrategyServer) ConfigCenterServer { - proxy := &serverAuthability{ +func New(targetServer *config.Server, + userMgn auth.UserServer, strategyMgn auth.StrategyServer) config.ConfigCenterServer { + proxy := &ServerAuthability{ targetServer: targetServer, userMgn: userMgn, strategyMgn: strategyMgn, @@ -50,7 +51,7 @@ func newServerAuthAbility(targetServer *Server, return proxy } -func (s *serverAuthability) collectConfigFileAuthContext(ctx context.Context, req []*apiconfig.ConfigFile, +func (s *ServerAuthability) collectConfigFileAuthContext(ctx context.Context, req []*apiconfig.ConfigFile, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -61,7 +62,7 @@ func (s *serverAuthability) collectConfigFileAuthContext(ctx context.Context, re ) } -func (s *serverAuthability) collectClientConfigFileAuthContext(ctx context.Context, req []*apiconfig.ConfigFile, +func (s *ServerAuthability) collectClientConfigFileAuthContext(ctx context.Context, req []*apiconfig.ConfigFile, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -73,7 +74,7 @@ func (s *serverAuthability) collectClientConfigFileAuthContext(ctx context.Conte ) } -func (s *serverAuthability) collectClientWatchConfigFiles(ctx context.Context, +func (s *ServerAuthability) collectClientWatchConfigFiles(ctx context.Context, req *apiconfig.ClientWatchConfigFileRequest, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -85,7 +86,7 @@ func (s *serverAuthability) collectClientWatchConfigFiles(ctx context.Context, ) } -func (s *serverAuthability) collectConfigFileReleaseAuthContext(ctx context.Context, req []*apiconfig.ConfigFileRelease, +func (s *ServerAuthability) collectConfigFileReleaseAuthContext(ctx context.Context, req []*apiconfig.ConfigFileRelease, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -96,7 +97,7 @@ func (s *serverAuthability) collectConfigFileReleaseAuthContext(ctx context.Cont ) } -func (s *serverAuthability) collectConfigFilePublishAuthContext(ctx context.Context, req []*apiconfig.ConfigFilePublishInfo, +func (s *ServerAuthability) collectConfigFilePublishAuthContext(ctx context.Context, req []*apiconfig.ConfigFilePublishInfo, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -107,7 +108,7 @@ func (s *serverAuthability) collectConfigFilePublishAuthContext(ctx context.Cont ) } -func (s *serverAuthability) collectClientConfigFileReleaseAuthContext(ctx context.Context, +func (s *ServerAuthability) collectClientConfigFileReleaseAuthContext(ctx context.Context, req []*apiconfig.ConfigFileRelease, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -119,7 +120,7 @@ func (s *serverAuthability) collectClientConfigFileReleaseAuthContext(ctx contex ) } -func (s *serverAuthability) collectConfigFileReleaseHistoryAuthContext( +func (s *ServerAuthability) collectConfigFileReleaseHistoryAuthContext( ctx context.Context, req []*apiconfig.ConfigFileReleaseHistory, op model.ResourceOperation, methodName string) *model.AcquireContext { @@ -132,7 +133,7 @@ func (s *serverAuthability) collectConfigFileReleaseHistoryAuthContext( ) } -func (s *serverAuthability) collectConfigGroupAuthContext(ctx context.Context, req []*apiconfig.ConfigFileGroup, +func (s *ServerAuthability) collectConfigGroupAuthContext(ctx context.Context, req []*apiconfig.ConfigFileGroup, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -143,7 +144,7 @@ func (s *serverAuthability) collectConfigGroupAuthContext(ctx context.Context, r ) } -func (s *serverAuthability) collectConfigFileTemplateAuthContext(ctx context.Context, +func (s *ServerAuthability) collectConfigFileTemplateAuthContext(ctx context.Context, req []*apiconfig.ConfigFileTemplate, op model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -151,7 +152,7 @@ func (s *serverAuthability) collectConfigFileTemplateAuthContext(ctx context.Con ) } -func (s *serverAuthability) queryConfigGroupResource(ctx context.Context, +func (s *ServerAuthability) queryConfigGroupResource(ctx context.Context, req []*apiconfig.ConfigFileGroup) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { @@ -178,7 +179,7 @@ func (s *serverAuthability) queryConfigGroupResource(ctx context.Context, } // queryConfigFileResource config file资源的鉴权转换为config group的鉴权 -func (s *serverAuthability) queryConfigFileResource(ctx context.Context, +func (s *ServerAuthability) queryConfigFileResource(ctx context.Context, req []*apiconfig.ConfigFile) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { @@ -204,7 +205,7 @@ func (s *serverAuthability) queryConfigFileResource(ctx context.Context, return ret } -func (s *serverAuthability) queryConfigFileReleaseResource(ctx context.Context, +func (s *ServerAuthability) queryConfigFileReleaseResource(ctx context.Context, req []*apiconfig.ConfigFileRelease) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { @@ -230,7 +231,7 @@ func (s *serverAuthability) queryConfigFileReleaseResource(ctx context.Context, return ret } -func (s *serverAuthability) queryConfigFilePublishResource(ctx context.Context, +func (s *ServerAuthability) queryConfigFilePublishResource(ctx context.Context, req []*apiconfig.ConfigFilePublishInfo) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { @@ -254,7 +255,7 @@ func (s *serverAuthability) queryConfigFilePublishResource(ctx context.Context, return ret } -func (s *serverAuthability) queryConfigFileReleaseHistoryResource(ctx context.Context, +func (s *ServerAuthability) queryConfigFileReleaseHistoryResource(ctx context.Context, req []*apiconfig.ConfigFileReleaseHistory) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { @@ -280,12 +281,12 @@ func (s *serverAuthability) queryConfigFileReleaseHistoryResource(ctx context.Co return ret } -func (s *serverAuthability) queryConfigGroupRsEntryByNames(ctx context.Context, namespace string, +func (s *ServerAuthability) queryConfigGroupRsEntryByNames(ctx context.Context, namespace string, names []string) ([]model.ResourceEntry, error) { configFileGroups := make([]*model.ConfigFileGroup, 0, len(names)) for i := range names { - data := s.targetServer.groupCache.GetGroupByName(namespace, names[i]) + data := s.targetServer.GroupCache().GetGroupByName(namespace, names[i]) if data == nil { continue } @@ -305,7 +306,7 @@ func (s *serverAuthability) queryConfigGroupRsEntryByNames(ctx context.Context, return entries, nil } -func (s *serverAuthability) queryWatchConfigFilesResource(ctx context.Context, +func (s *ServerAuthability) queryWatchConfigFilesResource(ctx context.Context, req *apiconfig.ClientWatchConfigFileRequest) map[apisecurity.ResourceType][]model.ResourceEntry { files := req.GetWatchFiles() if len(files) == 0 { @@ -321,7 +322,7 @@ func (s *serverAuthability) queryWatchConfigFilesResource(ctx context.Context, continue } temp[key] = struct{}{} - data := s.targetServer.groupCache.GetGroupByName(namespace, groupName) + data := s.targetServer.GroupCache().GetGroupByName(namespace, groupName) if data == nil { continue } diff --git a/common/batchjob/config.go b/config/interceptor/register.go similarity index 50% rename from common/batchjob/config.go rename to config/interceptor/register.go index 913b94137..ea3ba0825 100644 --- a/common/batchjob/config.go +++ b/config/interceptor/register.go @@ -15,35 +15,28 @@ * specific language governing permissions and limitations under the License. */ -package batchjob +package config_chain import ( - "errors" - "time" + "github.com/polarismesh/polaris/auth" + "github.com/polarismesh/polaris/config" + config_auth "github.com/polarismesh/polaris/config/interceptor/auth" ) -// CtrlConfig CtrlConfig . -type CtrlConfig struct { - // Label 批任务执行器标签 - Label string `json:"label"` - // QueueSize 注册请求队列的长度 - QueueSize uint32 `json:"queueSize"` - // WaitTime 最长多久一次批量操作 - WaitTime time.Duration `json:"waitTime"` - // MaxBatchCount 每次操作最大的批量数 - MaxBatchCount uint32 `json:"maxBatchCount"` - // Concurrency 任务工作协程数量 - Concurrency uint32 `json:"concurrency"` - // Handler 任务处理函数 - Handler func(tasks []Future) -} +func init() { + err := config.RegisterServerProxy("auth", func(svr *config.Server, pre config.ConfigCenterServer) (config.ConfigCenterServer, error) { + userMgn, err := auth.GetUserServer() + if err != nil { + return nil, err + } + strategyMgn, err := auth.GetStrategyServer() + if err != nil { + return nil, err + } -func (c CtrlConfig) Verify() error { - if c.Handler == nil { - return errors.New("Handler is nil") - } - if c.Label == "" { - return errors.New("Label is empty") + return config_auth.New(svr, userMgn, strategyMgn), nil + }) + if err != nil { + panic(err) } - return nil } diff --git a/config/options.go b/config/options.go index 595e95742..2117ac52e 100644 --- a/config/options.go +++ b/config/options.go @@ -18,14 +18,15 @@ package config import ( + "os" "path/filepath" - "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/common/utils" ) var ( - configCacheEntries = []cache.ConfigEntry{ + configCacheEntries = []cachetypes.ConfigEntry{ { Name: "configFile", }, @@ -36,11 +37,11 @@ var ( ) var ( - testConfigCacheEntries = []cache.ConfigEntry{ + testConfigCacheEntries = []cachetypes.ConfigEntry{ { Name: "configFile", Option: map[string]interface{}{ - "cachePath": filepath.Join("/tmp/polaris/cache/", utils.NewUUID()), + "cachePath": filepath.Join(os.TempDir(), "/polaris/cache/", utils.NewUUID()), }, }, { diff --git a/config/server.go b/config/server.go index 89737b06c..30b38e884 100644 --- a/config/server.go +++ b/config/server.go @@ -20,6 +20,7 @@ package config import ( "context" "errors" + "fmt" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" @@ -43,61 +44,25 @@ const ( var ( server ConfigCenterServer originServer = &Server{} + // serverProxyFactories Service Server API 代理工厂 + serverProxyFactories = map[string]ServerProxyFactory{} ) -var ( - availableSearch = map[string]map[string]string{ - "config_file": { - "namespace": "namespace", - "group": "group", - "name": "name", - "offset": "offset", - "limit": "limit", - "order_type": "order_type", - "order_field": "order_field", - }, - "config_file_release": { - "namespace": "namespace", - "group": "group", - "file_name": "file_name", - "fileName": "file_name", - "name": "release_name", - "release_name": "release_name", - "offset": "offset", - "limit": "limit", - "order_type": "order_type", - "order_field": "order_field", - "only_active": "only_active", - }, - "config_file_group": { - "namespace": "namespace", - "group": "name", - "name": "name", - "business": "business", - "department": "department", - "offset": "offset", - "limit": "limit", - "order_type": "order_type", - "order_field": "order_field", - }, - "config_file_release_history": { - "namespace": "namespace", - "group": "group", - "name": "file_name", - "offset": "offset", - "limit": "limit", - "endId": "endId", - "end_id": "endId", - "order_type": "order_type", - "order_field": "order_field", - }, +type ServerProxyFactory func(svr *Server, pre ConfigCenterServer) (ConfigCenterServer, error) + +func RegisterServerProxy(name string, factor ServerProxyFactory) error { + if _, ok := serverProxyFactories[name]; ok { + return fmt.Errorf("duplicate ServerProxyFactory, name(%s)", name) } -) + serverProxyFactories[name] = factor + return nil +} // Config 配置中心模块启动参数 type Config struct { - Open bool `yaml:"open"` - ContentMaxLength int64 `yaml:"contentMaxLength"` + Open bool `yaml:"open"` + ContentMaxLength int64 `yaml:"contentMaxLength"` + Interceptors []string `yaml:"-"` } // Server 配置中心核心服务 @@ -135,13 +100,29 @@ func Initialize(ctx context.Context, config Config, s store.Store, cacheMgn *cac return nil } - cacheMgn.OpenResourceCache(configCacheEntries...) + if err := cacheMgn.OpenResourceCache(configCacheEntries...); err != nil { + return err + } err := originServer.initialize(ctx, config, s, namespaceOperator, cacheMgn) if err != nil { return err } - server = newServerAuthAbility(originServer, userMgn, strategyMgn) + // 需要返回包装代理的 DiscoverServer + order := config.Interceptors + for i := range order { + factory, exist := serverProxyFactories[order[i]] + if !exist { + return fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) + } + + proxySvr, err := factory(originServer, server) + if err != nil { + return err + } + server = proxySvr + } + originServer.initialized = true return nil } @@ -159,7 +140,7 @@ func (s *Server) initialize(ctx context.Context, config Config, ss store.Store, s.groupCache = cacheMgn.ConfigGroup() s.grayCache = cacheMgn.Gray() - s.watchCenter, err = NewWatchCenter(cacheMgn.ConfigFile()) + s.watchCenter, err = NewWatchCenter(cacheMgn) if err != nil { return err } @@ -207,11 +188,20 @@ func (s *Server) WatchCenter() *watchCenter { return s.watchCenter } +func (s *Server) CacheManager() cachetypes.CacheManager { + return s.caches +} + // Cache 获取配置中心缓存模块 -func (s *Server) Cache() cachetypes.ConfigFileCache { +func (s *Server) FileCache() cachetypes.ConfigFileCache { return s.fileCache } +// Cache 获取配置中心缓存模块 +func (s *Server) GroupCache() cachetypes.ConfigGroupCache { + return s.groupCache +} + // CryptoManager 获取加密管理 func (s *Server) CryptoManager() plugin.CryptoManager { return s.cryptoManager @@ -324,3 +314,9 @@ func (cc *ConfigChains) AfterGetFileHistory(ctx context.Context, } return history, nil } + +func GetChainOrder() []string { + return []string{ + "auth", + } +} diff --git a/config/test_export.go b/config/test_export.go index 3d5b96b9a..15aadeea5 100644 --- a/config/test_export.go +++ b/config/test_export.go @@ -19,8 +19,10 @@ package config import ( "context" + "fmt" apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + "go.uber.org/zap" "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/cache" @@ -36,11 +38,28 @@ func TestInitialize(ctx context.Context, config Config, s store.Store, cacheMgn strategyMgn auth.StrategyServer) (ConfigCenterServer, ConfigCenterServer, error) { mockServer := &Server{} - cacheMgn.OpenResourceCache(testConfigCacheEntries...) + log.Info("Config.TestInitialize", zap.Any("entries", testConfigCacheEntries)) + _ = cacheMgn.OpenResourceCache(testConfigCacheEntries...) if err := mockServer.initialize(ctx, config, s, namespaceOperator, cacheMgn); err != nil { return nil, nil, err } - return newServerAuthAbility(mockServer, userMgn, strategyMgn), mockServer, nil + + var proxySvr ConfigCenterServer + var err error + // 需要返回包装代理的 ConfigCenterServer + order := config.Interceptors + for i := range order { + factory, exist := serverProxyFactories[order[i]] + if !exist { + return nil, nil, fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) + } + + proxySvr, err = factory(mockServer, proxySvr) + if err != nil { + return nil, nil, err + } + } + return proxySvr, mockServer, nil } func (s *Server) TestCheckClientConfigFile(ctx context.Context, files []*apiconfig.ClientConfigFileInfo, diff --git a/config/watcher.go b/config/watcher.go index 5e70f393a..9a3dbcff7 100644 --- a/config/watcher.go +++ b/config/watcher.go @@ -46,13 +46,17 @@ var ( ) type ( + BetaReleaseMatcher func(clientLabels map[string]string, event *model.SimpleConfigFileRelease) bool + FileReleaseCallback func(clientId string, rsp *apiconfig.ConfigClientResponse) bool - WatchContextFactory func(clientId string) WatchContext + WatchContextFactory func(clientId string, matcher BetaReleaseMatcher) WatchContext WatchContext interface { // ClientID . ClientID() string + // ClientLabels . + ClientLabels() map[string]string // AppendInterest . AppendInterest(item *apiconfig.ClientConfigFileInfo) // RemoveInterest . @@ -74,10 +78,16 @@ type ( type LongPollWatchContext struct { clientId string + labels map[string]string once sync.Once finishTime time.Time finishChan chan *apiconfig.ConfigClientResponse watchConfigFiles map[string]*apiconfig.ClientConfigFileInfo + betaMatcher BetaReleaseMatcher +} + +func (c *LongPollWatchContext) ClientLabels() map[string]string { + return c.labels } // IsOnce @@ -101,7 +111,7 @@ func (c *LongPollWatchContext) GetNotifieResultWithTime(timeout time.Duration) ( } func (c *LongPollWatchContext) ShouldExpire(now time.Time) bool { - return true + return now.After(c.finishTime) } // ClientID . @@ -110,7 +120,11 @@ func (c *LongPollWatchContext) ClientID() string { } func (c *LongPollWatchContext) ShouldNotify(event *model.SimpleConfigFileRelease) bool { - key := event.ActiveKey() + if event.ReleaseType == model.ReleaseTypeGray && !c.betaMatcher(c.ClientLabels(), event) { + return false + } + + key := event.FileKey() watchFile, ok := c.watchConfigFiles[key] if !ok { return false @@ -160,17 +174,19 @@ type watchCenter struct { watchers *utils.SyncMap[string, *utils.SyncSet[string]] // fileCache fileCache cachetypes.ConfigFileCache + cacheMgr cachetypes.CacheManager cancel context.CancelFunc } // NewWatchCenter 创建一个客户端监听配置发布的处理中心 -func NewWatchCenter(fileCache cachetypes.ConfigFileCache) (*watchCenter, error) { +func NewWatchCenter(cacheMgr cachetypes.CacheManager) (*watchCenter, error) { ctx, cancel := context.WithCancel(context.Background()) wc := &watchCenter{ clients: utils.NewSyncMap[string, WatchContext](), watchers: utils.NewSyncMap[string, *utils.SyncSet[string]](), - fileCache: fileCache, + fileCache: cacheMgr.ConfigFile(), + cacheMgr: cacheMgr, cancel: cancel, } @@ -213,19 +229,29 @@ func (wc *watchCenter) checkQuickResponseClient(watchCtx WatchContext) *apiconfi return api.NewConfigClientResponseWithInfo(apimodel.Code_BadRequest, "namespace & group & fileName can not be empty") } + // 从缓存中获取灰度文件 + var release *model.ConfigFileRelease + var match = false + if len(watchCtx.ClientLabels()) > 0 { + if release = wc.fileCache.GetGrayRelease(namespace, group, fileName); release != nil { + key := model.GetGrayConfigRealseKey(release.SimpleConfigFileRelease) + match = wc.cacheMgr.Gray().HitGrayRule(key, watchCtx.ClientLabels()) + } + } + if !match { + release = wc.fileCache.GetActiveRelease(namespace, group, fileName) + } // 从缓存中获取最新的配置文件信息 - if release := wc.fileCache.GetActiveRelease(namespace, group, fileName, model.ReleaseTypeFull); release != nil { - if watchCtx.ShouldNotify(release.SimpleConfigFileRelease) { - ret := &apiconfig.ClientConfigFileInfo{ - Namespace: utils.NewStringValue(namespace), - Group: utils.NewStringValue(group), - FileName: utils.NewStringValue(fileName), - Version: utils.NewUInt64Value(release.Version), - Md5: utils.NewStringValue(release.Md5), - Name: utils.NewStringValue(release.Name), - } - return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, ret) + if release != nil && watchCtx.ShouldNotify(release.SimpleConfigFileRelease) { + ret := &apiconfig.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(namespace), + Group: utils.NewStringValue(group), + FileName: utils.NewStringValue(fileName), + Version: utils.NewUInt64Value(release.Version), + Md5: utils.NewStringValue(release.Md5), + Name: utils.NewStringValue(release.Name), } + return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, ret) } } return nil @@ -245,11 +271,11 @@ func (wc *watchCenter) DelWatchContext(clientId string) (WatchContext, bool) { func (wc *watchCenter) AddWatcher(clientId string, watchFiles []*apiconfig.ClientConfigFileInfo, factory WatchContextFactory) WatchContext { watchCtx, _ := wc.clients.ComputeIfAbsent(clientId, func(k string) WatchContext { - return factory(clientId) + return factory(clientId, wc.MatchBetaReleaseFile) }) for _, file := range watchFiles { - fileKey := utils.GenFileId(file.Namespace.GetValue(), file.Group.GetValue(), file.FileName.GetValue()) + fileKey := utils.GenFileId(file.GetNamespace().GetValue(), file.GetGroup().GetValue(), file.GetFileName().GetValue()) watchCtx.AppendInterest(file) clientIds, _ := wc.watchers.ComputeIfAbsent(fileKey, func(k string) *utils.SyncSet[string] { @@ -304,7 +330,8 @@ func (wc *watchCenter) notifyToWatchers(publishConfigFile *model.SimpleConfigFil return } - log.Info("[Config][Watcher] received config file publish message.", zap.String("file", watchFileId)) + log.Info("[Config][Watcher] received config file publish message.", zap.String("file", watchFileId), + zap.Int("clients", clientIds.Len())) changeNotifyRequest := publishConfigFile.ToSpecNotifyClientRequest() response := api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, changeNotifyRequest) @@ -329,6 +356,10 @@ func (wc *watchCenter) notifyToWatchers(publishConfigFile *model.SimpleConfigFil }) } +func (wc *watchCenter) MatchBetaReleaseFile(clientLabels map[string]string, event *model.SimpleConfigFileRelease) bool { + return wc.cacheMgr.Gray().HitGrayRule(model.GetGrayConfigRealseKey(event), clientLabels) +} + func (wc *watchCenter) Close() { wc.cancel() wc.subCtx.Cancel() @@ -348,10 +379,9 @@ func (wc *watchCenter) startHandleTimeoutRequestWorker(ctx context.Context) { tNow := time.Now() waitRemove := make([]WatchContext, 0, 32) wc.clients.Range(func(client string, watchCtx WatchContext) { - if !watchCtx.ShouldExpire(tNow) { - return + if watchCtx.ShouldExpire(tNow) { + waitRemove = append(waitRemove, watchCtx) } - waitRemove = append(waitRemove, watchCtx) }) for i := range waitRemove { diff --git a/go.mod b/go.mod index 2ae4d8941..973b5f1f9 100644 --- a/go.mod +++ b/go.mod @@ -82,15 +82,14 @@ require ( require ( github.com/DATA-DOG/go-sqlmock v1.5.0 - github.com/agiledragon/gomonkey/v2 v2.10.1 - github.com/polarismesh/specification v1.4.2-alpha + github.com/polarismesh/specification v1.4.2-alpha.6 ) require ( + github.com/dlclark/regexp2 v1.10.0 go.etcd.io/bbolt v1.3.7 google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect ) replace gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.2.2 - diff --git a/go.sum b/go.sum index 46ef66798..ef222bb38 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/agiledragon/gomonkey/v2 v2.10.1 h1:FPJJNykD1957cZlGhr9X0zjr291/lbazoZ/dmc4mS4c= -github.com/agiledragon/gomonkey/v2 v2.10.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -60,6 +58,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -91,6 +90,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/emicklei/go-restful/v3 v3.7.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -186,6 +187,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -305,8 +307,11 @@ github.com/nicksnyder/go-i18n/v2 v2.2.0 h1:MNXbyPvd141JJqlU6gJKrczThxJy+kdCNivxZ github.com/nicksnyder/go-i18n/v2 v2.2.0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -318,8 +323,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/polarismesh/specification v1.4.2-alpha.6 h1:EUhATwFjb4lGIrI/UEoaVdyLWpjZrOuDTQn4S12QE10= +github.com/polarismesh/specification v1.4.2-alpha.6/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= @@ -773,6 +778,7 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/import-format.sh b/import-format.sh index 4a4086b8c..a6335c62c 100644 --- a/import-format.sh +++ b/import-format.sh @@ -17,6 +17,8 @@ # 格式化 go.mod go mod tidy -compat=1.17 +docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.55.2 golangci-lint run -v + # 处理 go imports 的格式化 rm -rf style_tool rm -rf goimports-reviser @@ -25,7 +27,7 @@ mkdir -p style_tool cd style_tool -is_arm=$(/usr/bin/uname -m | grep "arm|aarch64" | wc -l) +is_arm=$(/usr/bin/uname -m | grep -E "arm|aarch64" | wc -l) goimports_target_file="goimports-reviser_3.3.1_linux_amd64.tar.gz" if [ "$(uname)" == "Darwin" ]; then diff --git a/namespace/default.go b/namespace/default.go index 4a8ad13f8..bebab4e8b 100644 --- a/namespace/default.go +++ b/namespace/default.go @@ -58,9 +58,11 @@ func Initialize(ctx context.Context, nsOpt *Config, storage store.Store, cacheMg } func initialize(_ context.Context, nsOpt *Config, storage store.Store, cacheMgn *cache.CacheManager) error { - cacheMgn.OpenResourceCache(cache.ConfigEntry{ + if err := cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ Name: cachetypes.NamespaceName, - }) + }); err != nil { + return err + } namespaceServer.caches = cacheMgn namespaceServer.storage = storage namespaceServer.cfg = *nsOpt diff --git a/namespace/test_export.go b/namespace/test_export.go index 8a23222e5..fbb98a16b 100644 --- a/namespace/test_export.go +++ b/namespace/test_export.go @@ -24,12 +24,17 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/plugin" "github.com/polarismesh/polaris/store" ) func TestInitialize(_ context.Context, nsOpt *Config, storage store.Store, cacheMgn *cache.CacheManager, userMgn auth.UserServer, strategyMgn auth.StrategyServer) (NamespaceOperateServer, error) { + _ = cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ + Name: cachetypes.NamespaceName, + }) + nsOpt.AutoCreate = true namespaceServer := &Server{} namespaceServer.caches = cacheMgn namespaceServer.storage = storage diff --git a/plugin.go b/plugin.go index 6d6e460c3..a1563408e 100644 --- a/plugin.go +++ b/plugin.go @@ -32,6 +32,7 @@ import ( _ "github.com/polarismesh/polaris/cache/config" _ "github.com/polarismesh/polaris/cache/namespace" _ "github.com/polarismesh/polaris/cache/service" + _ "github.com/polarismesh/polaris/config/interceptor" _ "github.com/polarismesh/polaris/plugin/cmdb/memory" _ "github.com/polarismesh/polaris/plugin/crypto/aes" _ "github.com/polarismesh/polaris/plugin/discoverevent/local" @@ -45,6 +46,7 @@ import ( _ "github.com/polarismesh/polaris/plugin/statis/logger" _ "github.com/polarismesh/polaris/plugin/statis/prometheus" _ "github.com/polarismesh/polaris/plugin/whitelist" + _ "github.com/polarismesh/polaris/service/interceptor" _ "github.com/polarismesh/polaris/store/boltdb" _ "github.com/polarismesh/polaris/store/mysql" ) diff --git a/plugin/cmdb/memory/memory.go b/plugin/cmdb/memory/memory.go index d4fb60500..d086d4ee1 100644 --- a/plugin/cmdb/memory/memory.go +++ b/plugin/cmdb/memory/memory.go @@ -61,6 +61,9 @@ func (m *Memory) Initialize(c *plugin.ConfigEntry) error { url, _ := c.Option["url"].(string) token, _ := c.Option["token"].(string) interval, _ := c.Option["interval"].(string) + if len(url) == 0 { + return nil + } tick, err := time.ParseDuration(interval) if err != nil { @@ -75,7 +78,6 @@ func (m *Memory) Initialize(c *plugin.ConfigEntry) error { m.cancel = cancel go m.doFetch(ctx, tick) - return nil } diff --git a/plugin/healthchecker.go b/plugin/healthchecker.go index 696c03a69..218c082b7 100644 --- a/plugin/healthchecker.go +++ b/plugin/healthchecker.go @@ -21,6 +21,8 @@ import ( "context" "os" "sync" + + "github.com/polarismesh/polaris/common/model" ) // ReportRequest report heartbeat request @@ -97,7 +99,7 @@ type HealthChecker interface { // Delete delete the id Delete(ctx context.Context, id string) error // DebugHandlers return debug handlers - DebugHandlers() []DebugHandler + DebugHandlers() []model.DebugHandler } // GetHealthChecker get the health checker by name diff --git a/plugin/healthchecker/leader/checker_leader.go b/plugin/healthchecker/leader/checker_leader.go index 97e474e42..4c62a4553 100644 --- a/plugin/healthchecker/leader/checker_leader.go +++ b/plugin/healthchecker/leader/checker_leader.go @@ -28,8 +28,8 @@ import ( "go.uber.org/zap" "google.golang.org/grpc/metadata" - "github.com/polarismesh/polaris/common/batchjob" "github.com/polarismesh/polaris/common/eventhub" + "github.com/polarismesh/polaris/common/model" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/plugin" @@ -429,91 +429,21 @@ func (c *LeaderHealthChecker) isLeader() bool { return atomic.LoadInt32(&c.leader) == 1 } -func (c *LeaderHealthChecker) DebugHandlers() []plugin.DebugHandler { - return []plugin.DebugHandler{ +func (c *LeaderHealthChecker) DebugHandlers() []model.DebugHandler { + return []model.DebugHandler{ { Path: "/debug/checker/leader/info", + Desc: "Query Leader Node Information", Handler: handleDescribeLeaderInfo(c), }, { Path: "/debug/checker/leader/cache", + Desc: "Query heart rate data information, only Leader node processing", Handler: handleDescribeBeatCache(c), }, } } -func (c *LeaderHealthChecker) handleSendGetRecords(futures []batchjob.Future) { - peers := make(map[string]*PeerReadTask) - for i := range futures { - taskInfo := futures[i].Param() - task := taskInfo.(*PeerTask) - peer := task.Peer - if peer.isClose() { - _ = futures[i].Reply(nil, ErrorPeerClosed) - continue - } - if _, ok := peers[peer.Host()]; !ok { - peers[peer.Host()] = &PeerReadTask{ - Peer: peer, - Keys: make([]string, 0, 16), - Futures: make(map[string][]batchjob.Future), - } - } - key := task.Key - peers[peer.Host()].Keys = append(peers[peer.Host()].Keys, key) - if _, ok := peers[peer.Host()].Futures[key]; !ok { - peers[peer.Host()].Futures[key] = make([]batchjob.Future, 0, 4) - } - peers[peer.Host()].Futures[key] = append(peers[peer.Host()].Futures[key], futures[i]) - } - - for i := range peers { - peer := peers[i].Peer - keys := peers[i].Keys - peerfutures := peers[i].Futures - resp := peer.Cache.Get(keys...) - for key := range resp { - fs := peerfutures[key] - for index := range fs { - _ = fs[index].Reply(map[string]*ReadBeatRecord{ - key: resp[key], - }, nil) - } - } - } - for i := range futures { - _ = futures[i].Reply(nil, ErrorRecordNotFound) - } -} - -func (c *LeaderHealthChecker) handleSendPutRecords(futures []batchjob.Future) { - peers := make(map[string]*PeerWriteTask) - for i := range futures { - taskInfo := futures[i].Param() - task := taskInfo.(*PeerTask) - peer := task.Peer - if peer.isClose() { - _ = futures[i].Reply(nil, ErrorPeerClosed) - continue - } - if _, ok := peers[peer.Host()]; !ok { - peers[peer.Host()] = &PeerWriteTask{ - Peer: peer, - Records: make([]WriteBeatRecord, 0, 16), - } - } - peers[peer.Host()].Records = append(peers[peer.Host()].Records, *task.Record) - } - - for i := range peers { - peer := peers[i].Peer - peer.Cache.Put(peers[i].Records...) - } - for i := range futures { - _ = futures[i].Reply(struct{}{}, nil) - } -} - func isSendFromPeer(ctx context.Context) bool { if md, ok := metadata.FromIncomingContext(ctx); ok { if _, exist := md[sendResource]; exist { @@ -522,9 +452,3 @@ func isSendFromPeer(ctx context.Context) bool { } return false } - -type PeerTask struct { - Peer *RemotePeer - Key string - Record *WriteBeatRecord -} diff --git a/plugin/healthchecker/leader/checker_leader_test.go b/plugin/healthchecker/leader/checker_leader_test.go index 8455e10cc..01e7a188a 100644 --- a/plugin/healthchecker/leader/checker_leader_test.go +++ b/plugin/healthchecker/leader/checker_leader_test.go @@ -25,7 +25,6 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - "github.com/polarismesh/polaris/common/batchjob" "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/plugin" @@ -34,6 +33,7 @@ import ( ) func TestLeaderHealthChecker_OnEvent(t *testing.T) { + t.SkipNow() ctrl := gomock.NewController(t) eventhub.InitEventHub() t.Cleanup(func() { @@ -47,12 +47,6 @@ func TestLeaderHealthChecker_OnEvent(t *testing.T) { s: mockStore, conf: &Config{ SoltNum: 0, - Batch: batchjob.CtrlConfig{ - QueueSize: 16, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 32, - Concurrency: 1, - }, }, } err := checker.Initialize(&plugin.ConfigEntry{ diff --git a/plugin/healthchecker/leader/config.go b/plugin/healthchecker/leader/config.go index 9311368ab..71766927c 100644 --- a/plugin/healthchecker/leader/config.go +++ b/plugin/healthchecker/leader/config.go @@ -18,29 +18,18 @@ package leader import ( - "time" - "github.com/mitchellh/mapstructure" - - "github.com/polarismesh/polaris/common/batchjob" ) type Config struct { - SoltNum int32 `json:"soltNum"` - StreamNum int32 `json:"streamNum"` - Batch batchjob.CtrlConfig `json:"batch,omitempty"` + SoltNum int32 `json:"soltNum"` + StreamNum int32 `json:"streamNum"` } func unmarshal(options map[string]interface{}) (*Config, error) { config := &Config{ SoltNum: DefaultSoltNum, StreamNum: int32(streamNum), - Batch: batchjob.CtrlConfig{ - QueueSize: 16384, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 64, - Concurrency: 512, - }, } decodeConfig := &mapstructure.DecoderConfig{ DecodeHook: mapstructure.StringToTimeDurationHookFunc(), diff --git a/plugin/healthchecker/leader/peer.go b/plugin/healthchecker/leader/peer.go index 563694bc6..1fa67c40a 100644 --- a/plugin/healthchecker/leader/peer.go +++ b/plugin/healthchecker/leader/peer.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "math/rand" + "net" "sync" "sync/atomic" "time" @@ -29,9 +30,9 @@ import ( apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" "go.uber.org/zap" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" - "github.com/polarismesh/polaris/common/batchjob" commonhash "github.com/polarismesh/polaris/common/hash" "github.com/polarismesh/polaris/common/utils" ) @@ -65,8 +66,10 @@ type Peer interface { Close() error // Host . Host() string - // Storage + // Storage . Storage() BeatRecordCache + // IsAlive . + IsAlive() bool } // LocalPeer Heartbeat data storage node @@ -88,6 +91,10 @@ func (p *LocalPeer) Serve(ctx context.Context, checker *LeaderHealthChecker, return nil } +func (p *LocalPeer) IsAlive() bool { + return true +} + // Get get records func (p *LocalPeer) Host() string { return utils.LocalHost @@ -164,7 +171,7 @@ func (p *RemotePeer) Serve(_ context.Context, checker *LeaderHealthChecker, for i := 0; i < streamNum; i++ { conn, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", listenIP, listenPort), grpc.WithBlock(), - grpc.WithInsecure(), + grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { _ = p.Close() @@ -191,6 +198,20 @@ func (p *RemotePeer) Host() string { return p.host } +func (p *RemotePeer) IsAlive() bool { + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%v", p.Host(), p.port), time.Second) + defer func() { + if conn != nil { + _ = conn.Close() + } + }() + + if err != nil { + return false + } + return true +} + // Get get records func (p *RemotePeer) Get(key string) (*ReadBeatRecord, error) { ret := p.Cache.Get(key) @@ -305,19 +326,6 @@ var ( ErrorPeerClosed = errors.New("peer alrady closed") ) -// PeerWriteTask peer write task -type PeerWriteTask struct { - Peer *RemotePeer - Records []WriteBeatRecord -} - -// PeerReadTask peer read task -type PeerReadTask struct { - Peer *RemotePeer - Keys []string - Futures map[string][]batchjob.Future -} - type beatSender struct { lock sync.RWMutex sender apiservice.PolarisHeartbeatGRPC_BatchHeartbeatClient diff --git a/plugin/healthchecker/leader/peer_test.go b/plugin/healthchecker/leader/peer_test.go index 38bd5b860..e727f3602 100644 --- a/plugin/healthchecker/leader/peer_test.go +++ b/plugin/healthchecker/leader/peer_test.go @@ -19,7 +19,6 @@ package leader import ( "context" - "errors" "fmt" "io" "net" @@ -32,7 +31,6 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/grpc" - "github.com/polarismesh/polaris/common/batchjob" "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/plugin" @@ -100,6 +98,7 @@ func (mp *MockPeerImpl) Host() string { } func TestLocalPeer(t *testing.T) { + t.SkipNow() localPeer := newLocalPeer() assert.NotNil(t, localPeer) ctrl := gomock.NewController(t) @@ -110,13 +109,7 @@ func TestLocalPeer(t *testing.T) { self: NewLocalPeerFunc(), s: mockStore, conf: &Config{ - SoltNum: 0, - Batch: batchjob.CtrlConfig{ - QueueSize: 16, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 32, - Concurrency: 1, - }, + SoltNum: 1, }, } err := checker.Initialize(&plugin.ConfigEntry{ @@ -131,15 +124,7 @@ func TestLocalPeer(t *testing.T) { }) localPeer.Initialize(Config{ - SoltNum: 0, - Batch: batchjob.CtrlConfig{ - Label: "MockLocalPeer", - QueueSize: 1024, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 32, - Concurrency: 1, - Handler: func([]batchjob.Future) { panic("not implemented") }, - }, + SoltNum: 1, }) err = localPeer.Serve(context.Background(), checker, "127.0.0.1", 21111) @@ -181,6 +166,7 @@ func TestLocalPeer(t *testing.T) { } func TestRemotePeer(t *testing.T) { + t.SkipNow() // close old event hub eventhub.InitEventHub() ctrl := gomock.NewController(t) @@ -190,14 +176,7 @@ func TestRemotePeer(t *testing.T) { self: NewLocalPeerFunc(), s: mockStore, conf: &Config{ - SoltNum: 0, - Batch: batchjob.CtrlConfig{ - Label: "MockRemotePeer", - QueueSize: 1024, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 32, - Concurrency: 1, - }, + SoltNum: 1, }, } @@ -277,13 +256,7 @@ func newMockPolarisGRPCSever(t *testing.T, port uint32) (*MockPolarisGRPCServer, self: NewLocalPeerFunc(), s: mockStore, conf: &Config{ - SoltNum: 0, - Batch: batchjob.CtrlConfig{ - QueueSize: 16, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 32, - Concurrency: 1, - }, + SoltNum: 1, }, } err = checker.Initialize(&plugin.ConfigEntry{ @@ -292,15 +265,7 @@ func newMockPolarisGRPCSever(t *testing.T, port uint32) (*MockPolarisGRPCServer, assert.NoError(t, err) lp := NewLocalPeerFunc().(*LocalPeer) lp.Initialize(Config{ - SoltNum: 0, - Batch: batchjob.CtrlConfig{ - Label: "MockLocalPeer", - QueueSize: 1024, - WaitTime: 32 * time.Millisecond, - MaxBatchCount: 32, - Concurrency: 1, - Handler: func([]batchjob.Future) { panic("not implemented") }, - }, + SoltNum: 1, }) err = lp.Serve(context.Background(), checker, "127.0.0.1", port) @@ -310,7 +275,6 @@ func newMockPolarisGRPCSever(t *testing.T, port uint32) (*MockPolarisGRPCServer, } server := grpc.NewServer() - service_manage.RegisterPolarisGRPCServer(server, svr) service_manage.RegisterPolarisHeartbeatGRPCServer(server, svr) t.Cleanup(func() { @@ -331,35 +295,6 @@ type MockPolarisGRPCServer struct { peer *LocalPeer } -// 客户端上报 -func (ms *MockPolarisGRPCServer) ReportClient(context.Context, - *service_manage.Client) (*service_manage.Response, error) { - return nil, errors.New("unsupport") -} - -// 被调方注册服务实例 -func (ms *MockPolarisGRPCServer) RegisterInstance(context.Context, - *service_manage.Instance) (*service_manage.Response, error) { - return nil, errors.New("unsupport") -} - -// 被调方反注册服务实例 -func (ms *MockPolarisGRPCServer) DeregisterInstance(context.Context, - *service_manage.Instance) (*service_manage.Response, error) { - return nil, errors.New("unsupport") -} - -// 统一发现接口 -func (ms *MockPolarisGRPCServer) Discover(_ service_manage.PolarisGRPC_DiscoverServer) error { - return errors.New("unsupport") -} - -// 被调方上报心跳 -func (ms *MockPolarisGRPCServer) Heartbeat(context.Context, - *service_manage.Instance) (*service_manage.Response, error) { - return nil, errors.New("unsupport") -} - // BatchHeartbeat 批量上报心跳 func (ms *MockPolarisGRPCServer) BatchHeartbeat(svr service_manage.PolarisHeartbeatGRPC_BatchHeartbeatServer) error { for { diff --git a/plugin/healthchecker/memory/checker_memory.go b/plugin/healthchecker/memory/checker_memory.go index 0a9bb8781..8182ebbe7 100644 --- a/plugin/healthchecker/memory/checker_memory.go +++ b/plugin/healthchecker/memory/checker_memory.go @@ -22,6 +22,7 @@ import ( "sync/atomic" commonLog "github.com/polarismesh/polaris/common/log" + "github.com/polarismesh/polaris/common/model" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/plugin" @@ -169,8 +170,8 @@ func (r *MemoryHealthChecker) SuspendTimeSec() int64 { return atomic.LoadInt64(&r.suspendTimeSec) } -func (r *MemoryHealthChecker) DebugHandlers() []plugin.DebugHandler { - return []plugin.DebugHandler{} +func (r *MemoryHealthChecker) DebugHandlers() []model.DebugHandler { + return []model.DebugHandler{} } func init() { diff --git a/plugin/healthchecker/redis/checker_redis.go b/plugin/healthchecker/redis/checker_redis.go index 4ecf87a03..7897c8925 100644 --- a/plugin/healthchecker/redis/checker_redis.go +++ b/plugin/healthchecker/redis/checker_redis.go @@ -27,6 +27,7 @@ import ( "time" commonlog "github.com/polarismesh/polaris/common/log" + "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/redispool" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" @@ -311,8 +312,8 @@ func (r *RedisHealthChecker) SuspendTimeSec() int64 { return atomic.LoadInt64(&r.suspendTimeSec) } -func (r *RedisHealthChecker) DebugHandlers() []plugin.DebugHandler { - return []plugin.DebugHandler{} +func (r *RedisHealthChecker) DebugHandlers() []model.DebugHandler { + return []model.DebugHandler{} } func init() { diff --git a/plugin/plugin.go b/plugin/plugin.go index 3f71d5fa4..dfe1f99f2 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -19,7 +19,6 @@ package plugin import ( "fmt" - "net/http" "sync" ) @@ -76,8 +75,3 @@ type PluginChanConfig struct { Option map[string]interface{} `yaml:"option"` Entries []ConfigEntry `yaml:"entries"` } - -type DebugHandler struct { - Path string - Handler http.HandlerFunc -} diff --git a/plugin/statis/base/apicall.go b/plugin/statis/base/apicall.go index 2bd912f09..7834a0bef 100644 --- a/plugin/statis/base/apicall.go +++ b/plugin/statis/base/apicall.go @@ -75,10 +75,14 @@ func NewComponentStatics(ctx context.Context, t metrics.CallMetricType, handler // add 添加接口调用数据 func (a *ComponentStatics) Add(ac *APICall) { startTime := time.Now() - a.acc <- ac - passDuration := time.Since(startTime) - if passDuration >= MaxAddDuration { - log.Warnf("[APICall]add api call cost %s, exceed max %s", passDuration, MaxAddDuration) + select { + case a.acc <- ac: + passDuration := time.Since(startTime) + if passDuration >= MaxAddDuration { + log.Warnf("[APICall]add api call cost %s, exceed max %s", passDuration, MaxAddDuration) + } + default: + // quick return. } } diff --git a/plugin/statis/logger/log.go b/plugin/statis/logger/log.go index f9da33e91..04dfc5f01 100644 --- a/plugin/statis/logger/log.go +++ b/plugin/statis/logger/log.go @@ -21,5 +21,5 @@ import commonlog "github.com/polarismesh/polaris/common/log" var ( log = commonlog.RegisterScope(PluginName, "", 0) - discoverlog = commonlog.RegisterScope("discoverstatis", "", 0) + discoverlog = commonlog.RegisterScope("discoverstat", "", 0) ) diff --git a/plugin/statis/logger/statis.go b/plugin/statis/logger/statis.go index 58dba969c..8c1a32037 100644 --- a/plugin/statis/logger/statis.go +++ b/plugin/statis/logger/statis.go @@ -20,10 +20,10 @@ package logger import ( "context" "fmt" + "math" + "strconv" "time" - "go.uber.org/zap" - commonlog "github.com/polarismesh/polaris/common/log" "github.com/polarismesh/polaris/common/metrics" commontime "github.com/polarismesh/polaris/common/time" @@ -97,8 +97,7 @@ func (s *StatisWorker) ReportConfigMetrics(metric ...metrics.ConfigMetrics) { // ReportDiscoverCall report discover service times func (s *StatisWorker) ReportDiscoverCall(metric metrics.ClientDiscoverMetric) { - discoverlog.Info("", zap.String("client-ip", metric.ClientIP), zap.String("namespace", metric.Namespace), - zap.String("resource", metric.Resource), zap.Int64("access-time", metric.Timestamp)) + discoverlog.Infof(metric.String()) } func (a *StatisWorker) metricsHandle(mt metrics.CallMetricType, start time.Time, @@ -114,14 +113,10 @@ func (a *StatisWorker) metricsHandle(mt metrics.CallMetricType, start time.Time, scope = log } - header := fmt.Sprintf("Statis %s:\n", startStr) - - header += fmt.Sprintf( - "%-48v|%12v|%17v|%12v|%12v|%12v|%12v|%12v|\n", "", "Protocol", "TrafficDirection", "Code", "Count", - "Min(ms)", "Max(ms)", "Avg(ms)") - var msg string + var prefixMax int for i := range statics { + prefixMax = int(math.Max(float64(prefixMax), float64(len(statics[i].API)))) msg += formatAPICallStatisItem(mt, statics[i]) } if len(msg) == 0 { @@ -129,6 +124,12 @@ func (a *StatisWorker) metricsHandle(mt metrics.CallMetricType, start time.Time, return } + header := fmt.Sprintf("Statis %s:\n", startStr) + + header += fmt.Sprintf( + "%-"+strconv.Itoa(prefixMax)+"v|%12v|%17v|%12v|%12v|%12v|%12v|%12v|\n", "", "Protocol", "TrafficDirection", "Code", "Count", + "Min(ms)", "Max(ms)", "Avg(ms)") + log.Info(header + msg) } diff --git a/release/build_docker.sh b/release/build_docker.sh index 1d0291b68..65388edcb 100755 --- a/release/build_docker.sh +++ b/release/build_docker.sh @@ -35,6 +35,7 @@ platforms="" for arch in ${arch_list[@]}; do export GOARCH=${arch} + export GOOS="linux" make build VERSION=${docker_tag} ARCH=${arch} if [ $? != 0 ]; then @@ -46,7 +47,8 @@ for arch in ${arch_list[@]}; do platforms+="linux/${arch}," done -platforms=${platforms::-1} +echo "${platforms}" +platforms=${platforms%?} extra_tags="" pre_release=`echo ${docker_tag}|egrep "(alpha|beta|rc|[T|t]est)"|wc -l` diff --git a/release/cluster/helm/templates/config-polaris-server.yaml b/release/cluster/helm/templates/config-polaris-server.yaml index 08b237f1f..f8402398c 100644 --- a/release/cluster/helm/templates/config-polaris-server.yaml +++ b/release/cluster/helm/templates/config-polaris-server.yaml @@ -130,7 +130,7 @@ data: - stdout errorOutputPaths: - stderr - discoverLocal: + discoverstat: rotateOutputPath: log/statis/polaris-discoverstat.log errorRotateOutputPath: log/statis/polaris-discoverstat-error.log rotationMaxSize: 100 @@ -164,6 +164,18 @@ data: - stdout errorOutputPaths: - stderr + nacos-apiserver: + rotateOutputPath: log/runtime/nacos-apiserver.log + errorRotateOutputPath: log/runtime/nacos-apiserver-error.log + rotationMaxSize: 100 + rotationMaxBackups: 30 + rotationMaxAge: 7 + outputLevel: info + compress: true + outputPaths: + - stdout + errorOutputPaths: + - stderr # 按顺序启动server startInOrder: open: true # 是否开启,默认是关闭 @@ -197,6 +209,16 @@ data: whiteList: 127.0.0.1 purgeCounterInterval: 10s purgeCounterExpired: 5s + - name: service-nacos + option: + listenIP: "0.0.0.0" + listenPort: {{ .Values.service.nacosPort }} + # 设置 nacos 默认命名空间对应 Polaris 命名空间信息 + defaultNamespace: default + connLimit: + openConnLimit: false + maxConnPerHost: 128 + maxConnLimit: 10240 - name: api-http # 协议名,全局唯一 option: listenIP: "0.0.0.0" @@ -343,7 +365,7 @@ data: store: {{- if eq .Values.global.mode "cluster" }} # 数据库存储插件 - {{- with .Values.polaris.storage.db.server }} + {{- with .Values.polaris.storage.db }} name: defaultStore option: master: @@ -374,11 +396,6 @@ data: discoverEvent: entries: - name: discoverEventLocal - discoverStatis: - name: discoverLocal - option: - interval: 60 # 统计间隔,单位为秒 - outputPath: ./discover-statis statis: entries: - name: local diff --git a/release/cluster/helm/values.yaml b/release/cluster/helm/values.yaml index 5eba698f3..4505b92c6 100644 --- a/release/cluster/helm/values.yaml +++ b/release/cluster/helm/values.yaml @@ -31,7 +31,6 @@ polaris: cpu: "500m" memory: "1000Mi" healthChecker: - type: heartbeatRedis type: heartbeatLeader replicaCount: 1 limiterReplicaCount: 1 @@ -40,11 +39,10 @@ polaris: clientOpen: false storage: db: - server: - address: localhost:3306 - name: polaris_server - user: root - password: polaris@123456 + address: localhost:3306 + name: polaris_server + user: root + password: polaris@123456 redis: address: localhost:6379 # ACL user from redis v6.0, remove it if ACL is not available @@ -67,6 +65,7 @@ service: type: LoadBalancer webPort: 8080 eurekaPort: 8761 + nacosPort: 8848 httpPort: 8090 serviceGrpcPort: 8091 xdsv3Port: 15010 diff --git a/release/cluster/kubernetes/02-polaris-server-config.yaml b/release/cluster/kubernetes/02-polaris-server-config.yaml index ee92dd366..7a2f88742 100644 --- a/release/cluster/kubernetes/02-polaris-server-config.yaml +++ b/release/cluster/kubernetes/02-polaris-server-config.yaml @@ -104,7 +104,7 @@ data: - stdout errorOutputPaths: - stderr - discoverLocal: + discoverstat: rotateOutputPath: log/statis/polaris-discoverstat.log errorRotateOutputPath: log/statis/polaris-discoverstat-error.log rotationMaxSize: 100 @@ -160,6 +160,18 @@ data: - stdout errorOutputPaths: - stderr + nacos-apiserver: + rotateOutputPath: log/runtime/nacos-apiserver.log + errorRotateOutputPath: log/runtime/nacos-apiserver-error.log + rotationMaxSize: 100 + rotationMaxBackups: 30 + rotationMaxAge: 7 + outputLevel: info + compress: true + outputPaths: + - stdout + errorOutputPaths: + - stderr # 按顺序启动server startInOrder: open: true # 是否开启,默认是关闭 @@ -247,6 +259,16 @@ data: openConnLimit: false maxConnPerHost: 128 maxConnLimit: 10240 + - name: service-nacos + option: + listenIP: "0.0.0.0" + listenPort: 8848 + # 设置 nacos 默认命名空间对应 Polaris 命名空间信息 + defaultNamespace: default + connLimit: + openConnLimit: false + maxConnPerHost: 128 + maxConnLimit: 10240 # - name: service-l5 # option: # listenIP: 0.0.0.0 @@ -317,29 +339,9 @@ data: maxBatchCount: 32 concurrency: 64 checkers: - - name: heartbeatMemory - # - name: heartbeatLeader - # option: - # soltNum: 128 - # streamNum: 128 # default value is runtime.GOMAXPROCS(0) - # batch: - # queueSize: 16384 - # waitTime: 32ms - # maxBatchCount: 64 - # concurrency: 512 - # - name: heartbeatRedis - # option: - # kvAddr: ##REDIS_ADDR## - # # ACL user from redis v6.0, remove it if ACL is not available - # kvUser: ##REDIS_USER# - # kvPasswd: ##REDIS_PWD## - # poolSize: 200 - # minIdleConns: 30 - # idleTimeout: 120s - # connectTimeout: 200ms - # msgTimeout: 200ms - # concurrency: 200 - # withTLS: false + - name: heartbeatLeader + option: + soltNum: 128 # Maintain configuration maintain: jobs: @@ -394,11 +396,6 @@ data: discoverEvent: entries: - name: discoverEventLocal - discoverStatis: - name: discoverLocal - option: - interval: 60 # 统计间隔,单位为秒 - outputPath: ./discover-statis statis: entries: - name: local diff --git a/release/conf/polaris-server.yaml b/release/conf/polaris-server.yaml index e3ef6234a..e72911d65 100644 --- a/release/conf/polaris-server.yaml +++ b/release/conf/polaris-server.yaml @@ -125,7 +125,7 @@ bootstrap: rotationMaxAge: 7 outputLevel: info compress: true - discoverLocal: + discoverstat: rotateOutputPath: log/statis/polaris-discoverstat.log errorRotateOutputPath: log/statis/polaris-discoverstat-error.log rotationMaxSize: 100 @@ -133,6 +133,7 @@ bootstrap: rotationMaxAge: 7 outputLevel: info compress: true + onlyContent: true local: rotateOutputPath: log/statis/polaris-statis.log errorRotateOutputPath: log/statis/polaris-statis-error.log @@ -184,7 +185,7 @@ bootstrap: polaris_service: ## level: self_address > network_inter > probe_address ## Obtain the IP of the VM or POD where Polaris is located by making a TCP connection with the probe_adreess address - probe_address: ##DB_ADDR## + # probe_address: ##DB_ADDR## ## Set the name of the gateway to get your own IP # network_inter: eth0 ## Show the setting node itself IP information @@ -406,17 +407,9 @@ healthcheck: # option: # # Heartbeat Record MAP number of shards # soltNum: 128 - # # The number of GRPC connections used to process heartbeat forward request processing between leader and follower, default value is runtime.GOMAXPROCS(0) + # # The number of GRPC connections used to process heartbeat forward request processing between leader and follower, + # # default value is runtime.GOMAXPROCS(0) # streamNum: 128 - # batch: - # # Heartbeat forwarding processing task cache queue - # queueSize: 16384 - # # The maximum waiting time for task batch - # waitTime: 32ms - # # The maximum number of heartbeat forwarding tasks of single -batch tasks - # maxBatchCount: 64 - # # Number of workers - # concurrency: 512 # Configuration center module start configuration config: # Whether to start the configuration module @@ -424,9 +417,10 @@ config: # Maximum number of number of file characters contentMaxLength: 20000 # Cache configuration - cache: - # 缓存增量同步数据时,相较于当前时刻需要往回倒退多少秒, 即在 T 时刻的增量同步,实际增量数据时间范围为 [T - abs(DiffTime), ∞) + # When the incremental synchronization data is cached, the actual incremental data time range is as follows: + # How many seconds need to be backtracked from the current time, that is, + # the incremental synchronization at time T [T - abs(DiffTime), ∞) diffTime: 5s # Maintain configuration maintain: @@ -494,11 +488,6 @@ plugin: discoverEvent: entries: - name: discoverEventLocal - discoverStatis: - name: discoverLocal - option: - # Statistical interval, the unit is second - interval: 60 statis: entries: - name: local diff --git a/release/standalone/docker-compose/README.md b/release/standalone/docker-compose/README.md index b4ff33ecc..4304cc784 100644 --- a/release/standalone/docker-compose/README.md +++ b/release/standalone/docker-compose/README.md @@ -5,7 +5,6 @@ - polaris-server 北极星核心服务 - polaris-console 北极星控制台 - mysql 替换默认的boltdb存储 -- redis 替换默认的基于内存的心跳检查 - polaris-prometheus - polaris-pushgateway - grafana @@ -16,7 +15,6 @@ ```shell docker volume create --name=vlm_data_mysql -docker volume create --name=vlm_data_redis ``` ### 启动服务 @@ -52,7 +50,6 @@ docker-compose down ```shell docker volume rm vlm_data_mysql -docker volume rm vlm_data_redis ``` ### 访问 diff --git a/release/standalone/docker-compose/docker-compose.yaml b/release/standalone/docker-compose/docker-compose.yaml index 4233e9177..30c2f77cd 100644 --- a/release/standalone/docker-compose/docker-compose.yaml +++ b/release/standalone/docker-compose/docker-compose.yaml @@ -22,8 +22,6 @@ networks: volumes: vlm_data_mysql: external: true - vlm_data_redis: - external: true services: polaris-server: @@ -49,9 +47,7 @@ services: - backend links: - mysql - - redis depends_on: - - redis - mysql polaris-console: container_name: polaris-console @@ -115,17 +111,6 @@ services: - 3306 networks: - backend - redis: - image: 'bitnami/redis:latest' - restart: always - environment: - - REDIS_PASSWORD=polaris - volumes: - - vlm_data_redis:/data - networks: - - backend - expose: - - 6379 prometheus: container_name: prometheus hostname: polaris-prometheus diff --git a/release/standalone/docker-compose/server/polaris-server.yaml b/release/standalone/docker-compose/server/polaris-server.yaml index fdcb3f781..191b80858 100644 --- a/release/standalone/docker-compose/server/polaris-server.yaml +++ b/release/standalone/docker-compose/server/polaris-server.yaml @@ -9,10 +9,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr auth: rotateOutputPath: log/runtime/polaris-auth.log errorRotateOutputPath: log/runtime/polaris-auth-error.log @@ -20,10 +16,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr store: rotateOutputPath: log/runtime/polaris-store.log errorRotateOutputPath: log/runtime/polaris-store-error.log @@ -31,10 +23,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr cache: rotateOutputPath: log/runtime/polaris-cache.log errorRotateOutputPath: log/runtime/polaris-cache-error.log @@ -42,10 +30,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr naming: rotateOutputPath: log/runtime/polaris-naming.log errorRotateOutputPath: log/runtime/polaris-naming-error.log @@ -53,10 +37,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr healthcheck: rotateOutputPath: log/runtime/polaris-healthcheck.log errorRotateOutputPath: log/runtime/polaris-healthcheck-error.log @@ -64,10 +44,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr xdsv3: rotateOutputPath: log/runtime/polaris-xdsv3.log errorRotateOutputPath: log/polaris-xdsv3-error.log @@ -75,10 +51,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr apiserver: rotateOutputPath: log/runtime/polaris-apiserver.log errorRotateOutputPath: log/runtime/polaris-apiserver-error.log @@ -86,10 +58,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr token-bucket: rotateOutputPath: log/runtime/polaris-ratelimit.log errorRotateOutputPath: log/runtime/polaris-ratelimit-error.log @@ -97,10 +65,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr default: rotateOutputPath: log/runtime/polaris-default.log errorRotateOutputPath: log/runtime/polaris-default-error.log @@ -108,10 +72,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr discoverEventLocal: rotateOutputPath: log/event/polaris-discoverevent.log errorRotateOutputPath: log/event/polaris-discoverevent-error.log @@ -119,10 +79,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr discoverLocal: rotateOutputPath: log/statis/polaris-discoverstat.log errorRotateOutputPath: log/statis/polaris-discoverstat-error.log @@ -130,10 +86,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr local: rotateOutputPath: log/statis/polaris-statis.log errorRotateOutputPath: log/statis/polaris-statis-error.log @@ -141,10 +93,6 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 outputLevel: info - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr HistoryLogger: rotateOutputPath: log/operation/polaris-history.log errorRotateOutputPath: log/operation/polaris-history-error.log @@ -154,10 +102,14 @@ bootstrap: rotationMaxDurationForHour: 24 outputLevel: info logCaller: false - # outputPaths: - # - stdout - # errorOutputPaths: - # - stderr + nacos-apiserver: + rotateOutputPath: log/runtime/nacos-apiserver.log + errorRotateOutputPath: log/runtime/nacos-apiserver-error.log + rotationMaxSize: 100 + rotationMaxBackups: 30 + rotationMaxAge: 7 + outputLevel: info + compress: true # 按顺序启动server startInOrder: open: true # 是否开启,默认是关闭 @@ -251,28 +203,42 @@ apiservers: openConnLimit: false maxConnPerHost: 128 maxConnLimit: 10240 + - name: service-nacos + option: + listenIP: "0.0.0.0" + listenPort: 8848 + # 设置 nacos 默认命名空间对应 Polaris 命名空间信息 + defaultNamespace: default + connLimit: + openConnLimit: false + maxConnPerHost: 128 + maxConnLimit: 10240 # - name: service-l5 # option: # listenIP: 0.0.0.0 # listenPort: 7779 # clusterName: cl5.discover -# 核心逻辑的配置 +# Core logic configuration auth: - # auth的option现已迁移至user及strategy - # 在auth.option配置依然有效,但是会收到警告,提醒您尽快迁移配置至user及strategy级别下 + # auth's option has migrated to auth.user and auth.strategy + # it's still available when filling auth.option, but you will receive warning log that auth.option has deprecated. user: name: defaultUser option: - # token 加密的 salt,鉴权解析 token 时需要依靠这个 salt 去解密 token 的信息 - # salt 的长度需要满足以下任意一个:len(salt) in [16, 24, 32] + # Token encrypted SALT, you need to rely on this SALT to decrypt the information of the Token when analyzing the Token + # The length of SALT needs to satisfy the following one:len(salt) in [16, 24, 32] salt: polarismesh@2021 strategy: name: defaultStrategy option: - # 控制台鉴权能力开关,默认开启 + # Console auth switch, default true consoleOpen: true - # 客户端鉴权能力开关, 默认关闭 + # Console Strict Model, default true + consoleStrict: true + # Customer auth switch, default false clientOpen: false + # Customer Strict Model, default close + clientStrict: false namespace: # 是否允许自动创建命名空间 autoCreate: true @@ -325,16 +291,7 @@ healthcheck: maxBatchCount: 32 concurrency: 64 checkers: - - name: heartbeatRedis - option: - kvAddr: redis:6379 - kvPasswd: polaris - poolSize: 200 - minIdleConns: 30 - idleTimeout: 120s - connectTimeout: 200ms - msgTimeout: 200ms - concurrency: 200 + - name: heartbeatLeader # 配置中心模块启动配置 config: # 是否启动配置模块 @@ -365,11 +322,6 @@ plugin: discoverEvent: entries: - name: discoverEventLocal - discoverStatis: - name: discoverLocal - option: - interval: 60 # 统计间隔,单位为秒 - outputPath: ./discover-statis statis: entries: - name: local diff --git a/release/tool/include b/release/tool/include index d90243408..58174c42a 100644 --- a/release/tool/include +++ b/release/tool/include @@ -49,7 +49,7 @@ function start() { set +e ulimit -n 409600 set -e - nohup $cmdline >>./log/stdout 2>&1 & + nohup $cmdline >>/dev/null 2>&1 & } function stop() { diff --git a/service/api.go b/service/api.go index c71bc0680..d6cc37ad1 100644 --- a/service/api.go +++ b/service/api.go @@ -18,6 +18,10 @@ package service import ( + "context" + + apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" + "github.com/polarismesh/polaris/cache" "github.com/polarismesh/polaris/common/model" ) @@ -38,7 +42,28 @@ type DiscoverServer interface { Cache() *cache.CacheManager // L5OperateServer L5 related operations L5OperateServer - // // GetServiceInstanceRevision Get the version of the service GetServiceInstanceRevision(serviceID string, instances []*model.Instance) (string, error) } + +// ResourceHook The listener is placed before and after the resource operation, only normal flow +type ResourceHook interface { + + // Before + // @param ctx + // @param resourceType + Before(ctx context.Context, resourceType model.Resource) + + // After + // @param ctx + // @param resourceType + // @param res + After(ctx context.Context, resourceType model.Resource, res *ResourceEvent) error +} + +// ResourceEvent 资源事件 +type ResourceEvent struct { + ReqService *apiservice.Service + Service *model.Service + IsRemove bool +} diff --git a/service/batch/future.go b/service/batch/future.go index bd0ddfa54..a690ad830 100644 --- a/service/batch/future.go +++ b/service/batch/future.go @@ -31,6 +31,7 @@ import ( // InstanceFuture 创建实例的异步结构体 type InstanceFuture struct { + isRegis bool // 任务开始时间 begin time.Time // 服务的id @@ -53,7 +54,9 @@ type InstanceFuture struct { // Reply future的应答 func (future *InstanceFuture) Reply(cur time.Time, code apimodel.Code, result error) { - reportRegisInstanceCost(future.begin, cur, code) + if future.isRegis { + reportRegisInstanceCost(future.begin, cur, code) + } if code == apimodel.Code_InstanceRegisTimeout { metrics.ReportDropInstanceRegisTask() } diff --git a/service/batch/instance.go b/service/batch/instance.go index 1f38ed565..97013432a 100644 --- a/service/batch/instance.go +++ b/service/batch/instance.go @@ -273,6 +273,7 @@ func (ctrl *InstanceCtrl) registerHandler(futures []*InstanceFuture) error { remains := make(map[string]*InstanceFuture, len(futures)) for i := range futures { entry := futures[i] + entry.isRegis = true if _, ok := remains[entry.request.GetId().GetValue()]; ok { entry.Reply(cur, apimodel.Code_SameInstanceRequest, ErrorSameRegIsInstanceRequest) diff --git a/service/client_check_test.go b/service/client_check_test.go index 604f1da98..eb851816a 100644 --- a/service/client_check_test.go +++ b/service/client_check_test.go @@ -38,7 +38,10 @@ func TestClientCheck(t *testing.T) { }); err != nil { t.Fatal(err) } - defer discoverSuit.Destroy() + t.Cleanup(func() { + discoverSuit.cleanReportClient() + discoverSuit.Destroy() + }) clientId1 := "111" clientId2 := "222" diff --git a/service/client_test.go b/service/client_test.go index 458f270f7..06b3fa84a 100644 --- a/service/client_test.go +++ b/service/client_test.go @@ -350,10 +350,12 @@ func TestServer_ReportClient(t *testing.T) { if err := discoverSuit.Initialize(); err != nil { t.Fatal(err) } - defer discoverSuit.Destroy() + t.Cleanup(func() { + discoverSuit.cleanReportClient() + discoverSuit.Destroy() + }) clients := mockReportClients(1) - defer discoverSuit.cleanReportClient() for i := range clients { resp := discoverSuit.DiscoverServer().ReportClient(discoverSuit.DefaultCtx, clients[i]) @@ -368,10 +370,18 @@ func TestServer_GetReportClient(t *testing.T) { if err := discoverSuit.Initialize(); err != nil { t.Fatal(err) } - defer discoverSuit.Destroy() + // 主动触发清理之前的 ReportClient 数据 + discoverSuit.cleanReportClient() + // 强制触发缓存更新 + _ = discoverSuit.DiscoverServer().Cache().TestUpdate() + t.Log("finish sleep to wait cache refresh") + + t.Cleanup(func() { + discoverSuit.cleanReportClient() + discoverSuit.Destroy() + }) clients := mockReportClients(5) - defer discoverSuit.cleanReportClient() wait := sync.WaitGroup{} wait.Add(5) @@ -389,8 +399,8 @@ func TestServer_GetReportClient(t *testing.T) { t.Log("finish sleep to wait cache refresh") resp := discoverSuit.DiscoverServer().GetPrometheusTargets(context.Background(), map[string]string{}) + t.Logf("get report clients result: %#v", resp) assert.Equal(t, apiv1.ExecuteSuccess, resp.Code) - assert.True(t, len(resp.Response) >= 0 && len(resp.Response) <= 5) }) } diff --git a/service/default.go b/service/default.go index 88760dd02..882d015f0 100644 --- a/service/default.go +++ b/service/default.go @@ -20,11 +20,11 @@ package service import ( "context" "errors" + "fmt" "sync" "golang.org/x/sync/singleflight" - "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/plugin" @@ -48,18 +48,31 @@ const ( DefaultTLL = 5 ) +type ServerProxyFactory func(svr *Server, pre DiscoverServer) (DiscoverServer, error) + var ( server DiscoverServer namingServer *Server = new(Server) once = sync.Once{} finishInit = false + // serverProxyFactories Service Server API 代理工厂 + serverProxyFactories = map[string]ServerProxyFactory{} ) +func RegisterServerProxy(name string, factor ServerProxyFactory) error { + if _, ok := serverProxyFactories[name]; ok { + return fmt.Errorf("duplicate ServerProxyFactory, name(%s)", name) + } + serverProxyFactories[name] = factor + return nil +} + // Config 核心逻辑层配置 type Config struct { - L5Open bool `yaml:"l5Open"` - AutoCreate bool `yaml:"autoCreate"` - Batch map[string]interface{} `yaml:"batch"` + L5Open bool `yaml:"l5Open"` + AutoCreate *bool `yaml:"autoCreate"` + Batch map[string]interface{} `yaml:"batch"` + Interceptors []string `yaml:"-"` } // Initialize 初始化 @@ -111,16 +124,20 @@ func initialize(ctx context.Context, namingOpt *Config, opts ...InitOption) erro // 插件初始化 pluginInitialize() - userMgn, err := auth.GetUserServer() - if err != nil { - return err + // 需要返回包装代理的 DiscoverServer + order := namingOpt.Interceptors + for i := range order { + factory, exist := serverProxyFactories[order[i]] + if !exist { + return fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) + } + + proxySvr, err := factory(namingServer, server) + if err != nil { + return err + } + server = proxySvr } - strategyMgn, err := auth.GetStrategyServer() - if err != nil { - return err - } - - server = newServerAuthAbility(namingServer, userMgn, strategyMgn) return nil } @@ -171,3 +188,9 @@ func pluginInitialize() { } namingServer.subCtxs = append(namingServer.subCtxs, subCtx) } + +func GetChainOrder() []string { + return []string{ + "auth", + } +} diff --git a/service/default_test.go b/service/default_test.go index 4131ed7b2..1c21dc3e4 100644 --- a/service/default_test.go +++ b/service/default_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/polarismesh/polaris/auth" + cachemock "github.com/polarismesh/polaris/cache/mock" "github.com/polarismesh/polaris/store/mock" ) @@ -37,13 +38,17 @@ func Test_Initialize(t *testing.T) { ctrl := gomock.NewController(t) s := mock.NewMockStore(ctrl) + cacheMgr := cachemock.NewMockCacheManager(ctrl) + cacheMgr.EXPECT().OpenResourceCache(gomock.Any()).Return(nil).AnyTimes() _, _, err := auth.TestInitialize(context.Background(), &auth.Config{ Option: map[string]interface{}{}, - }, s, nil) + }, s, cacheMgr) assert.NoError(t, err) - err = Initialize(context.Background(), &Config{}) + err = Initialize(context.Background(), &Config{ + Interceptors: GetChainOrder(), + }) assert.NoError(t, err) svr, err := GetOriginServer() diff --git a/service/circuitbreaker_config_authability.go b/service/interceptor/auth/circuitbreaker_config_authability.go similarity index 80% rename from service/circuitbreaker_config_authability.go rename to service/interceptor/auth/circuitbreaker_config_authability.go index 278767dc1..dd98af254 100644 --- a/service/circuitbreaker_config_authability.go +++ b/service/interceptor/auth/circuitbreaker_config_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -25,73 +25,73 @@ import ( ) // CreateCircuitBreakers creates circuit breakers -func (svr *serverAuthAbility) CreateCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) CreateCircuitBreakers(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { return svr.targetServer.CreateCircuitBreakers(ctx, reqs) } // CreateCircuitBreakerVersions creates circuit breaker versions -func (svr *serverAuthAbility) CreateCircuitBreakerVersions(ctx context.Context, +func (svr *ServerAuthAbility) CreateCircuitBreakerVersions(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { return svr.targetServer.CreateCircuitBreakerVersions(ctx, reqs) } // DeleteCircuitBreakers delete circuit breakers -func (svr *serverAuthAbility) DeleteCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) DeleteCircuitBreakers(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { return svr.targetServer.DeleteCircuitBreakers(ctx, reqs) } // UpdateCircuitBreakers update circuit breakers -func (svr *serverAuthAbility) UpdateCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) UpdateCircuitBreakers(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { return svr.targetServer.UpdateCircuitBreakers(ctx, reqs) } // ReleaseCircuitBreakers release circuit breakers -func (svr *serverAuthAbility) ReleaseCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) ReleaseCircuitBreakers(ctx context.Context, reqs []*apiservice.ConfigRelease) *apiservice.BatchWriteResponse { return svr.targetServer.ReleaseCircuitBreakers(ctx, reqs) } // UnBindCircuitBreakers unbind circuit breakers -func (svr *serverAuthAbility) UnBindCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) UnBindCircuitBreakers(ctx context.Context, reqs []*apiservice.ConfigRelease) *apiservice.BatchWriteResponse { return svr.targetServer.UnBindCircuitBreakers(ctx, reqs) } // GetCircuitBreaker get circuit breaker -func (svr *serverAuthAbility) GetCircuitBreaker(ctx context.Context, +func (svr *ServerAuthAbility) GetCircuitBreaker(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { return svr.targetServer.GetCircuitBreaker(ctx, query) } // GetCircuitBreakerVersions get circuit breaker versions -func (svr *serverAuthAbility) GetCircuitBreakerVersions(ctx context.Context, +func (svr *ServerAuthAbility) GetCircuitBreakerVersions(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { return svr.targetServer.GetCircuitBreakerVersions(ctx, query) } // GetMasterCircuitBreakers get master circuit breakers -func (svr *serverAuthAbility) GetMasterCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) GetMasterCircuitBreakers(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { return svr.targetServer.GetMasterCircuitBreakers(ctx, query) } // GetReleaseCircuitBreakers get release circuit breakers -func (svr *serverAuthAbility) GetReleaseCircuitBreakers(ctx context.Context, +func (svr *ServerAuthAbility) GetReleaseCircuitBreakers(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { return svr.targetServer.GetReleaseCircuitBreakers(ctx, query) } // GetCircuitBreakerByService get circuit breaker by service -func (svr *serverAuthAbility) GetCircuitBreakerByService(ctx context.Context, +func (svr *ServerAuthAbility) GetCircuitBreakerByService(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { return svr.targetServer.GetCircuitBreakerByService(ctx, query) } // GetCircuitBreakerToken get circuit breaker token -func (svr *serverAuthAbility) GetCircuitBreakerToken( +func (svr *ServerAuthAbility) GetCircuitBreakerToken( ctx context.Context, req *apifault.CircuitBreaker) *apiservice.Response { return svr.targetServer.GetCircuitBreakerToken(ctx, req) } diff --git a/service/circuitbreaker_rule_authability.go b/service/interceptor/auth/circuitbreaker_rule_authability.go similarity index 92% rename from service/circuitbreaker_rule_authability.go rename to service/interceptor/auth/circuitbreaker_rule_authability.go index 012a02f66..79a1b98f2 100644 --- a/service/circuitbreaker_rule_authability.go +++ b/service/interceptor/auth/circuitbreaker_rule_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -28,7 +28,7 @@ import ( "github.com/polarismesh/polaris/common/utils" ) -func (svr *serverAuthAbility) CreateCircuitBreakerRules( +func (svr *ServerAuthAbility) CreateCircuitBreakerRules( ctx context.Context, request []*apifault.CircuitBreakerRule) *apiservice.BatchWriteResponse { // TODO not support CircuitBreaker resource auth, so we set op is read authCtx := svr.collectCircuitBreakerRuleV2AuthContext(ctx, request, model.Read, "CreateCircuitBreakerRules") @@ -43,7 +43,7 @@ func (svr *serverAuthAbility) CreateCircuitBreakerRules( return svr.targetServer.CreateCircuitBreakerRules(ctx, request) } -func (svr *serverAuthAbility) DeleteCircuitBreakerRules( +func (svr *ServerAuthAbility) DeleteCircuitBreakerRules( ctx context.Context, request []*apifault.CircuitBreakerRule) *apiservice.BatchWriteResponse { authCtx := svr.collectCircuitBreakerRuleV2AuthContext(ctx, request, model.Read, "DeleteCircuitBreakerRules") _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) @@ -56,7 +56,7 @@ func (svr *serverAuthAbility) DeleteCircuitBreakerRules( return svr.targetServer.DeleteCircuitBreakerRules(ctx, request) } -func (svr *serverAuthAbility) EnableCircuitBreakerRules( +func (svr *ServerAuthAbility) EnableCircuitBreakerRules( ctx context.Context, request []*apifault.CircuitBreakerRule) *apiservice.BatchWriteResponse { authCtx := svr.collectCircuitBreakerRuleV2AuthContext(ctx, request, model.Read, "EnableCircuitBreakerRules") _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) @@ -69,7 +69,7 @@ func (svr *serverAuthAbility) EnableCircuitBreakerRules( return svr.targetServer.EnableCircuitBreakerRules(ctx, request) } -func (svr *serverAuthAbility) UpdateCircuitBreakerRules( +func (svr *ServerAuthAbility) UpdateCircuitBreakerRules( ctx context.Context, request []*apifault.CircuitBreakerRule) *apiservice.BatchWriteResponse { authCtx := svr.collectCircuitBreakerRuleV2AuthContext(ctx, request, model.Read, "UpdateCircuitBreakerRules") _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) @@ -82,7 +82,7 @@ func (svr *serverAuthAbility) UpdateCircuitBreakerRules( return svr.targetServer.UpdateCircuitBreakerRules(ctx, request) } -func (svr *serverAuthAbility) GetCircuitBreakerRules( +func (svr *ServerAuthAbility) GetCircuitBreakerRules( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectCircuitBreakerRuleV2AuthContext(ctx, nil, model.Read, "GetCircuitBreakerRules") _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) diff --git a/service/client_v1_authability.go b/service/interceptor/auth/client_v1_authability.go similarity index 91% rename from service/client_v1_authability.go rename to service/interceptor/auth/client_v1_authability.go index 28c41ef0d..a4f0119a2 100644 --- a/service/client_v1_authability.go +++ b/service/interceptor/auth/client_v1_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -28,7 +28,7 @@ import ( ) // RegisterInstance create one instance -func (svr *serverAuthAbility) RegisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { +func (svr *ServerAuthAbility) RegisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { authCtx := svr.collectClientInstanceAuthContext( ctx, []*apiservice.Instance{req}, model.Create, "RegisterInstance") @@ -45,7 +45,7 @@ func (svr *serverAuthAbility) RegisterInstance(ctx context.Context, req *apiserv } // DeregisterInstance delete onr instance -func (svr *serverAuthAbility) DeregisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { +func (svr *ServerAuthAbility) DeregisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { authCtx := svr.collectClientInstanceAuthContext( ctx, []*apiservice.Instance{req}, model.Create, "DeregisterInstance") @@ -62,19 +62,19 @@ func (svr *serverAuthAbility) DeregisterInstance(ctx context.Context, req *apise } // ReportClient is the interface for reporting client authability -func (svr *serverAuthAbility) ReportClient(ctx context.Context, req *apiservice.Client) *apiservice.Response { +func (svr *ServerAuthAbility) ReportClient(ctx context.Context, req *apiservice.Client) *apiservice.Response { return svr.targetServer.ReportClient(ctx, req) } // GetPrometheusTargets Used for client acquisition service information -func (svr *serverAuthAbility) GetPrometheusTargets(ctx context.Context, +func (svr *ServerAuthAbility) GetPrometheusTargets(ctx context.Context, query map[string]string) *model.PrometheusDiscoveryResponse { return svr.targetServer.GetPrometheusTargets(ctx, query) } // GetServiceWithCache is the interface for getting service with cache -func (svr *serverAuthAbility) GetServiceWithCache( +func (svr *ServerAuthAbility) GetServiceWithCache( ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( @@ -92,7 +92,7 @@ func (svr *serverAuthAbility) GetServiceWithCache( } // ServiceInstancesCache is the interface for getting service instances cache -func (svr *serverAuthAbility) ServiceInstancesCache( +func (svr *ServerAuthAbility) ServiceInstancesCache( ctx context.Context, filter *apiservice.DiscoverFilter, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( @@ -110,7 +110,7 @@ func (svr *serverAuthAbility) ServiceInstancesCache( } // GetRoutingConfigWithCache is the interface for getting routing config with cache -func (svr *serverAuthAbility) GetRoutingConfigWithCache( +func (svr *ServerAuthAbility) GetRoutingConfigWithCache( ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( @@ -128,7 +128,7 @@ func (svr *serverAuthAbility) GetRoutingConfigWithCache( } // GetRateLimitWithCache is the interface for getting rate limit with cache -func (svr *serverAuthAbility) GetRateLimitWithCache( +func (svr *ServerAuthAbility) GetRateLimitWithCache( ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( @@ -146,7 +146,7 @@ func (svr *serverAuthAbility) GetRateLimitWithCache( } // GetCircuitBreakerWithCache is the interface for getting a circuit breaker with cache -func (svr *serverAuthAbility) GetCircuitBreakerWithCache( +func (svr *ServerAuthAbility) GetCircuitBreakerWithCache( ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( @@ -163,7 +163,7 @@ func (svr *serverAuthAbility) GetCircuitBreakerWithCache( return svr.targetServer.GetCircuitBreakerWithCache(ctx, req) } -func (svr *serverAuthAbility) GetFaultDetectWithCache( +func (svr *ServerAuthAbility) GetFaultDetectWithCache( ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( @@ -181,7 +181,7 @@ func (svr *serverAuthAbility) GetFaultDetectWithCache( } // UpdateInstance update single instance -func (svr *serverAuthAbility) UpdateInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { +func (svr *ServerAuthAbility) UpdateInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { authCtx := svr.collectClientInstanceAuthContext( ctx, []*apiservice.Instance{req}, model.Modify, "UpdateInstance") diff --git a/service/faultdetect_config_authability.go b/service/interceptor/auth/faultdetect_config_authability.go similarity index 92% rename from service/faultdetect_config_authability.go rename to service/interceptor/auth/faultdetect_config_authability.go index 1862b9144..dac4979aa 100644 --- a/service/faultdetect_config_authability.go +++ b/service/interceptor/auth/faultdetect_config_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -28,7 +28,7 @@ import ( "github.com/polarismesh/polaris/common/utils" ) -func (svr *serverAuthAbility) CreateFaultDetectRules( +func (svr *ServerAuthAbility) CreateFaultDetectRules( ctx context.Context, request []*apifault.FaultDetectRule) *apiservice.BatchWriteResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, request, model.Read, "CreateFaultDetectRules") @@ -40,7 +40,7 @@ func (svr *serverAuthAbility) CreateFaultDetectRules( return svr.targetServer.CreateFaultDetectRules(ctx, request) } -func (svr *serverAuthAbility) DeleteFaultDetectRules( +func (svr *ServerAuthAbility) DeleteFaultDetectRules( ctx context.Context, request []*apifault.FaultDetectRule) *apiservice.BatchWriteResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, request, model.Read, "DeleteFaultDetectRules") @@ -52,7 +52,7 @@ func (svr *serverAuthAbility) DeleteFaultDetectRules( return svr.targetServer.DeleteFaultDetectRules(ctx, request) } -func (svr *serverAuthAbility) UpdateFaultDetectRules( +func (svr *ServerAuthAbility) UpdateFaultDetectRules( ctx context.Context, request []*apifault.FaultDetectRule) *apiservice.BatchWriteResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, request, model.Read, "UpdateFaultDetectRules") @@ -64,7 +64,7 @@ func (svr *serverAuthAbility) UpdateFaultDetectRules( return svr.targetServer.UpdateFaultDetectRules(ctx, request) } -func (svr *serverAuthAbility) GetFaultDetectRules( +func (svr *ServerAuthAbility) GetFaultDetectRules( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, nil, model.Read, "GetFaultDetectRules") if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { diff --git a/service/instance_authability.go b/service/interceptor/auth/instance_authability.go similarity index 91% rename from service/instance_authability.go rename to service/interceptor/auth/instance_authability.go index 2cc360acb..e208bbd0c 100644 --- a/service/instance_authability.go +++ b/service/interceptor/auth/instance_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -30,7 +30,7 @@ import ( ) // CreateInstances create instances -func (svr *serverAuthAbility) CreateInstances(ctx context.Context, +func (svr *ServerAuthAbility) CreateInstances(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Create, "CreateInstances") @@ -49,7 +49,7 @@ func (svr *serverAuthAbility) CreateInstances(ctx context.Context, } // DeleteInstances delete instances -func (svr *serverAuthAbility) DeleteInstances(ctx context.Context, +func (svr *ServerAuthAbility) DeleteInstances(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Delete, "DeleteInstances") @@ -68,7 +68,7 @@ func (svr *serverAuthAbility) DeleteInstances(ctx context.Context, } // DeleteInstancesByHost 目前只允许 super account 进行数据删除 -func (svr *serverAuthAbility) DeleteInstancesByHost(ctx context.Context, +func (svr *ServerAuthAbility) DeleteInstancesByHost(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Delete, "DeleteInstancesByHost") @@ -87,7 +87,7 @@ func (svr *serverAuthAbility) DeleteInstancesByHost(ctx context.Context, } // UpdateInstances update instances -func (svr *serverAuthAbility) UpdateInstances(ctx context.Context, +func (svr *ServerAuthAbility) UpdateInstances(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Modify, "UpdateInstances") @@ -103,7 +103,7 @@ func (svr *serverAuthAbility) UpdateInstances(ctx context.Context, } // UpdateInstancesIsolate update instances -func (svr *serverAuthAbility) UpdateInstancesIsolate(ctx context.Context, +func (svr *ServerAuthAbility) UpdateInstancesIsolate(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Modify, "UpdateInstancesIsolate") @@ -119,7 +119,7 @@ func (svr *serverAuthAbility) UpdateInstancesIsolate(ctx context.Context, } // GetInstances get instances -func (svr *serverAuthAbility) GetInstances(ctx context.Context, +func (svr *ServerAuthAbility) GetInstances(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectInstanceAuthContext(ctx, nil, model.Read, "GetInstances") _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) @@ -134,7 +134,7 @@ func (svr *serverAuthAbility) GetInstances(ctx context.Context, } // GetInstancesCount get instances to count -func (svr *serverAuthAbility) GetInstancesCount(ctx context.Context) *apiservice.BatchQueryResponse { +func (svr *ServerAuthAbility) GetInstancesCount(ctx context.Context) *apiservice.BatchQueryResponse { authCtx := svr.collectInstanceAuthContext(ctx, nil, model.Read, "GetInstancesCount") _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { @@ -146,7 +146,7 @@ func (svr *serverAuthAbility) GetInstancesCount(ctx context.Context) *apiservice return svr.targetServer.GetInstancesCount(ctx) } -func (svr *serverAuthAbility) GetInstanceLabels(ctx context.Context, +func (svr *ServerAuthAbility) GetInstanceLabels(ctx context.Context, query map[string]string) *apiservice.Response { authCtx := svr.collectInstanceAuthContext(ctx, nil, model.Read, "GetInstanceLabels") diff --git a/service/l5_service_authability.go b/service/interceptor/auth/l5_service_authability.go similarity index 89% rename from service/l5_service_authability.go rename to service/interceptor/auth/l5_service_authability.go index 0e2644d1c..7f1b4ea81 100644 --- a/service/l5_service_authability.go +++ b/service/interceptor/auth/l5_service_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -28,12 +28,12 @@ import ( // Stat::instance()->inc_sync_req_cnt(); // 保存client的IP,该函数只是存储到本地的缓存中 // Stat::instance()->add_agent(sbac.agent_ip()); -func (svr *serverAuthAbility) SyncByAgentCmd(ctx context.Context, sbac *l5.Cl5SyncByAgentCmd) ( +func (svr *ServerAuthAbility) SyncByAgentCmd(ctx context.Context, sbac *l5.Cl5SyncByAgentCmd) ( *l5.Cl5SyncByAgentAckCmd, error) { return svr.targetServer.SyncByAgentCmd(ctx, sbac) } // RegisterByNameCmd 根据名字获取sid信息 -func (svr *serverAuthAbility) RegisterByNameCmd(rbnc *l5.Cl5RegisterByNameCmd) (*l5.Cl5RegisterByNameAckCmd, error) { +func (svr *ServerAuthAbility) RegisterByNameCmd(rbnc *l5.Cl5RegisterByNameCmd) (*l5.Cl5RegisterByNameAckCmd, error) { return svr.targetServer.RegisterByNameCmd(rbnc) } diff --git a/service/interceptor/auth/log.go b/service/interceptor/auth/log.go new file mode 100644 index 000000000..2a1030e9d --- /dev/null +++ b/service/interceptor/auth/log.go @@ -0,0 +1,27 @@ +/** + * 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 service_auth + +import ( + commonlog "github.com/polarismesh/polaris/common/log" +) + +var ( + log = commonlog.GetScopeOrDefaultByName(commonlog.NamingLoggerName) + authLog = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) +) diff --git a/service/ratelimit_config_authability.go b/service/interceptor/auth/ratelimit_config_authability.go similarity index 93% rename from service/ratelimit_config_authability.go rename to service/interceptor/auth/ratelimit_config_authability.go index 35cf83f94..a0f2be54c 100644 --- a/service/ratelimit_config_authability.go +++ b/service/interceptor/auth/ratelimit_config_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -30,7 +30,7 @@ import ( ) // CreateRateLimits creates rate limits for a namespace. -func (svr *serverAuthAbility) CreateRateLimits( +func (svr *ServerAuthAbility) CreateRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, reqs, model.Create, "CreateRateLimits") @@ -46,7 +46,7 @@ func (svr *serverAuthAbility) CreateRateLimits( } // DeleteRateLimits deletes rate limits for a namespace. -func (svr *serverAuthAbility) DeleteRateLimits( +func (svr *ServerAuthAbility) DeleteRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, reqs, model.Delete, "DeleteRateLimits") @@ -62,7 +62,7 @@ func (svr *serverAuthAbility) DeleteRateLimits( } // UpdateRateLimits updates rate limits for a namespace. -func (svr *serverAuthAbility) UpdateRateLimits( +func (svr *ServerAuthAbility) UpdateRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, reqs, model.Modify, "UpdateRateLimits") @@ -78,7 +78,7 @@ func (svr *serverAuthAbility) UpdateRateLimits( } // EnableRateLimits 启用限流规则 -func (svr *serverAuthAbility) EnableRateLimits( +func (svr *ServerAuthAbility) EnableRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, nil, model.Read, "EnableRateLimits") @@ -94,7 +94,7 @@ func (svr *serverAuthAbility) EnableRateLimits( } // GetRateLimits gets rate limits for a namespace. -func (svr *serverAuthAbility) GetRateLimits( +func (svr *ServerAuthAbility) GetRateLimits( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectRateLimitAuthContext(ctx, nil, model.Read, "GetRateLimits") diff --git a/service/resource_listen.go b/service/interceptor/auth/resource_listen.go similarity index 72% rename from service/resource_listen.go rename to service/interceptor/auth/resource_listen.go index 044a57219..ed88283a7 100644 --- a/service/resource_listen.go +++ b/service/interceptor/auth/resource_listen.go @@ -15,47 +15,25 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" - apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/service" ) -// ResourceHook The listener is placed before and after the resource operation, only normal flow -type ResourceHook interface { - - // Before - // @param ctx - // @param resourceType - Before(ctx context.Context, resourceType model.Resource) - - // After - // @param ctx - // @param resourceType - // @param res - After(ctx context.Context, resourceType model.Resource, res *ResourceEvent) error -} - -// ResourceEvent 资源事件 -type ResourceEvent struct { - ReqService *apiservice.Service - Service *model.Service - IsRemove bool -} - // Before this function is called before the resource operation -func (svr *serverAuthAbility) Before(ctx context.Context, resourceType model.Resource) { +func (svr *ServerAuthAbility) Before(ctx context.Context, resourceType model.Resource) { // do nothing } // After this function is called after the resource operation -func (svr *serverAuthAbility) After(ctx context.Context, resourceType model.Resource, res *ResourceEvent) error { +func (svr *ServerAuthAbility) After(ctx context.Context, resourceType model.Resource, res *service.ResourceEvent) error { switch resourceType { case model.RService: return svr.onServiceResource(ctx, res) @@ -65,7 +43,7 @@ func (svr *serverAuthAbility) After(ctx context.Context, resourceType model.Reso } // onServiceResource 服务资源的处理,只处理服务,namespace 只由 namespace 相关的进行处理, -func (svr *serverAuthAbility) onServiceResource(ctx context.Context, res *ResourceEvent) error { +func (svr *ServerAuthAbility) onServiceResource(ctx context.Context, res *service.ResourceEvent) error { authCtx := ctx.Value(utils.ContextAuthContextKey).(*model.AcquireContext) ownerId := utils.ParseOwnerID(ctx) diff --git a/service/routing_config_v1_authability.go b/service/interceptor/auth/routing_config_v1_authability.go similarity index 93% rename from service/routing_config_v1_authability.go rename to service/interceptor/auth/routing_config_v1_authability.go index b187ee007..b20cabe6e 100644 --- a/service/routing_config_v1_authability.go +++ b/service/interceptor/auth/routing_config_v1_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -30,7 +30,7 @@ import ( ) // CreateRoutingConfigs creates routing configs -func (svr *serverAuthAbility) CreateRoutingConfigs( +func (svr *ServerAuthAbility) CreateRoutingConfigs( ctx context.Context, reqs []*apitraffic.Routing) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, reqs, model.Create, "CreateRoutingConfigs") @@ -46,7 +46,7 @@ func (svr *serverAuthAbility) CreateRoutingConfigs( } // DeleteRoutingConfigs deletes routing configs -func (svr *serverAuthAbility) DeleteRoutingConfigs( +func (svr *ServerAuthAbility) DeleteRoutingConfigs( ctx context.Context, reqs []*apitraffic.Routing) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, reqs, model.Delete, "DeleteRoutingConfigs") @@ -62,7 +62,7 @@ func (svr *serverAuthAbility) DeleteRoutingConfigs( } // UpdateRoutingConfigs updates routing configs -func (svr *serverAuthAbility) UpdateRoutingConfigs( +func (svr *ServerAuthAbility) UpdateRoutingConfigs( ctx context.Context, reqs []*apitraffic.Routing) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, reqs, model.Modify, "UpdateRoutingConfigs") @@ -78,7 +78,7 @@ func (svr *serverAuthAbility) UpdateRoutingConfigs( } // GetRoutingConfigs gets routing configs -func (svr *serverAuthAbility) GetRoutingConfigs( +func (svr *ServerAuthAbility) GetRoutingConfigs( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, nil, model.Read, "GetRoutingConfigs") diff --git a/service/routing_config_v2_authability.go b/service/interceptor/auth/routing_config_v2_authability.go similarity index 91% rename from service/routing_config_v2_authability.go rename to service/interceptor/auth/routing_config_v2_authability.go index d19693381..94e703ab2 100644 --- a/service/routing_config_v2_authability.go +++ b/service/interceptor/auth/routing_config_v2_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -29,7 +29,7 @@ import ( ) // CreateRoutingConfigsV2 批量创建路由配置 -func (svr *serverAuthAbility) CreateRoutingConfigsV2(ctx context.Context, +func (svr *ServerAuthAbility) CreateRoutingConfigsV2(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { // TODO not support RouteRuleV2 resource auth, so we set op is read @@ -43,7 +43,7 @@ func (svr *serverAuthAbility) CreateRoutingConfigsV2(ctx context.Context, } // DeleteRoutingConfigsV2 批量删除路由配置 -func (svr *serverAuthAbility) DeleteRoutingConfigsV2(ctx context.Context, +func (svr *ServerAuthAbility) DeleteRoutingConfigsV2(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "DeleteRoutingConfigsV2") @@ -56,7 +56,7 @@ func (svr *serverAuthAbility) DeleteRoutingConfigsV2(ctx context.Context, } // UpdateRoutingConfigsV2 批量更新路由配置 -func (svr *serverAuthAbility) UpdateRoutingConfigsV2(ctx context.Context, +func (svr *ServerAuthAbility) UpdateRoutingConfigsV2(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "UpdateRoutingConfigsV2") @@ -69,7 +69,7 @@ func (svr *serverAuthAbility) UpdateRoutingConfigsV2(ctx context.Context, } // EnableRoutings batch enable routing rules -func (svr *serverAuthAbility) EnableRoutings(ctx context.Context, +func (svr *ServerAuthAbility) EnableRoutings(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "EnableRoutings") @@ -82,7 +82,7 @@ func (svr *serverAuthAbility) EnableRoutings(ctx context.Context, } // QueryRoutingConfigsV2 提供给OSS的查询路由配置的接口 -func (svr *serverAuthAbility) QueryRoutingConfigsV2(ctx context.Context, +func (svr *ServerAuthAbility) QueryRoutingConfigsV2(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { return svr.targetServer.QueryRoutingConfigsV2(ctx, query) diff --git a/service/server_authability.go b/service/interceptor/auth/server_authability.go similarity index 89% rename from service/server_authability.go rename to service/interceptor/auth/server_authability.go index 0bc925dc3..60b9a0c17 100644 --- a/service/server_authability.go +++ b/service/interceptor/auth/server_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -32,20 +32,21 @@ import ( "github.com/polarismesh/polaris/cache" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/service" ) -// serverAuthAbility 带有鉴权能力的 discoverServer +// ServerAuthAbility 带有鉴权能力的 discoverServer // // 该层会对请求参数做一些调整,根据具体的请求发起人,设置为数据对应的 owner,不可为为别人进行创建资源 -type serverAuthAbility struct { - targetServer *Server +type ServerAuthAbility struct { + targetServer *service.Server userMgn auth.UserServer strategyMgn auth.StrategyServer } -func newServerAuthAbility(targetServer *Server, - userMgn auth.UserServer, strategyMgn auth.StrategyServer) DiscoverServer { - proxy := &serverAuthAbility{ +func NewServerAuthAbility(targetServer *service.Server, + userMgn auth.UserServer, strategyMgn auth.StrategyServer) service.DiscoverServer { + proxy := &ServerAuthAbility{ targetServer: targetServer, userMgn: userMgn, strategyMgn: strategyMgn, @@ -57,24 +58,24 @@ func newServerAuthAbility(targetServer *Server, } // Cache Get cache management -func (svr *serverAuthAbility) Cache() *cache.CacheManager { +func (svr *ServerAuthAbility) Cache() *cache.CacheManager { return svr.targetServer.Cache() } // GetServiceInstanceRevision 获取服务实例的版本号 -func (svr *serverAuthAbility) GetServiceInstanceRevision(serviceID string, +func (svr *ServerAuthAbility) GetServiceInstanceRevision(serviceID string, instances []*model.Instance) (string, error) { return svr.targetServer.GetServiceInstanceRevision(serviceID, instances) } // collectServiceAuthContext 对于服务的处理,收集所有的与鉴权的相关信息 // -// @receiver svr serverAuthAbility +// @receiver svr ServerAuthAbility // @param ctx 请求上下文 ctx // @param req 实际请求对象 // @param resourceOp 该接口的数据操作类型 // @return *model.AcquireContext 返回鉴权上下文 -func (svr *serverAuthAbility) collectServiceAuthContext(ctx context.Context, req []*apiservice.Service, +func (svr *ServerAuthAbility) collectServiceAuthContext(ctx context.Context, req []*apiservice.Service, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -87,12 +88,12 @@ func (svr *serverAuthAbility) collectServiceAuthContext(ctx context.Context, req // collectServiceAliasAuthContext 对于服务别名的处理,收集所有的与鉴权的相关信息 // -// @receiver svr serverAuthAbility +// @receiver svr ServerAuthAbility // @param ctx 请求上下文 ctx // @param req 实际请求对象 // @param resourceOp 该接口的数据操作类型 // @return *model.AcquireContext 返回鉴权上下文 -func (svr *serverAuthAbility) collectServiceAliasAuthContext(ctx context.Context, req []*apiservice.ServiceAlias, +func (svr *ServerAuthAbility) collectServiceAliasAuthContext(ctx context.Context, req []*apiservice.ServiceAlias, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -105,12 +106,12 @@ func (svr *serverAuthAbility) collectServiceAliasAuthContext(ctx context.Context // collectInstanceAuthContext 对于服务实例的处理,收集所有的与鉴权的相关信息 // -// @receiver svr serverAuthAbility +// @receiver svr ServerAuthAbility // @param ctx 请求上下文 ctx // @param req 实际请求对象 // @param resourceOp 该接口的数据操作类型 // @return *model.AcquireContext 返回鉴权上下文 -func (svr *serverAuthAbility) collectInstanceAuthContext(ctx context.Context, req []*apiservice.Instance, +func (svr *ServerAuthAbility) collectInstanceAuthContext(ctx context.Context, req []*apiservice.Instance, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -122,7 +123,7 @@ func (svr *serverAuthAbility) collectInstanceAuthContext(ctx context.Context, re } // collectClientInstanceAuthContext 对于服务实例的处理,收集所有的与鉴权的相关信息 -func (svr *serverAuthAbility) collectClientInstanceAuthContext(ctx context.Context, req []*apiservice.Instance, +func (svr *ServerAuthAbility) collectClientInstanceAuthContext(ctx context.Context, req []*apiservice.Instance, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -136,12 +137,12 @@ func (svr *serverAuthAbility) collectClientInstanceAuthContext(ctx context.Conte // collectCircuitBreakerAuthContext 对于服务熔断的处理,收集所有的与鉴权的相关信息 // -// @receiver svr serverAuthAbility +// @receiver svr ServerAuthAbility // @param ctx 请求上下文 ctx // @param req 实际请求对象 // @param resourceOp 该接口的数据操作类型 // @return *model.AcquireContext 返回鉴权上下文 -func (svr *serverAuthAbility) collectCircuitBreakerAuthContext(ctx context.Context, req []*apifault.CircuitBreaker, +func (svr *ServerAuthAbility) collectCircuitBreakerAuthContext(ctx context.Context, req []*apifault.CircuitBreaker, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -159,7 +160,7 @@ func (svr *serverAuthAbility) collectCircuitBreakerAuthContext(ctx context.Conte // @param req // @param resourceOp // @return *model.AcquireContext -func (svr *serverAuthAbility) collectCircuitBreakerReleaseAuthContext(ctx context.Context, +func (svr *ServerAuthAbility) collectCircuitBreakerReleaseAuthContext(ctx context.Context, req []*apiservice.ConfigRelease, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -172,12 +173,12 @@ func (svr *serverAuthAbility) collectCircuitBreakerReleaseAuthContext(ctx contex // collectRouteRuleAuthContext 对于服务路由规则的处理,收集所有的与鉴权的相关信息 // -// @receiver svr serverAuthAbility +// @receiver svr ServerAuthAbility // @param ctx 请求上下文 ctx // @param req 实际请求对象 // @param resourceOp 该接口的数据操作类型 // @return *model.AcquireContext 返回鉴权上下文 -func (svr *serverAuthAbility) collectRouteRuleAuthContext(ctx context.Context, req []*apitraffic.Routing, +func (svr *ServerAuthAbility) collectRouteRuleAuthContext(ctx context.Context, req []*apitraffic.Routing, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -190,12 +191,12 @@ func (svr *serverAuthAbility) collectRouteRuleAuthContext(ctx context.Context, r // collectRateLimitAuthContext 对于服务限流规则的处理,收集所有的与鉴权的相关信息 // -// @receiver svr serverAuthAbility +// @receiver svr ServerAuthAbility // @param ctx 请求上下文 ctx // @param req 实际请求对象 // @param resourceOp 该接口的数据操作类型 // @return *model.AcquireContext 返回鉴权上下文 -func (svr *serverAuthAbility) collectRateLimitAuthContext(ctx context.Context, req []*apitraffic.Rule, +func (svr *ServerAuthAbility) collectRateLimitAuthContext(ctx context.Context, req []*apitraffic.Rule, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -207,7 +208,7 @@ func (svr *serverAuthAbility) collectRateLimitAuthContext(ctx context.Context, r } // collectRouteRuleV2AuthContext 收集路由v2规则 -func (svr *serverAuthAbility) collectRouteRuleV2AuthContext(ctx context.Context, req []*apitraffic.RouteRule, +func (svr *ServerAuthAbility) collectRouteRuleV2AuthContext(ctx context.Context, req []*apitraffic.RouteRule, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( model.WithRequestContext(ctx), @@ -219,7 +220,7 @@ func (svr *serverAuthAbility) collectRouteRuleV2AuthContext(ctx context.Context, } // collectRouteRuleV2AuthContext 收集熔断v2规则 -func (svr *serverAuthAbility) collectCircuitBreakerRuleV2AuthContext(ctx context.Context, +func (svr *ServerAuthAbility) collectCircuitBreakerRuleV2AuthContext(ctx context.Context, req []*apifault.CircuitBreakerRule, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( @@ -232,7 +233,7 @@ func (svr *serverAuthAbility) collectCircuitBreakerRuleV2AuthContext(ctx context } // collectRouteRuleV2AuthContext 收集主动探测规则 -func (svr *serverAuthAbility) collectFaultDetectAuthContext(ctx context.Context, +func (svr *ServerAuthAbility) collectFaultDetectAuthContext(ctx context.Context, req []*apifault.FaultDetectRule, resourceOp model.ResourceOperation, methodName string) *model.AcquireContext { return model.NewAcquireContext( @@ -245,7 +246,7 @@ func (svr *serverAuthAbility) collectFaultDetectAuthContext(ctx context.Context, } // queryServiceResource 根据所给的 service 信息,收集对应的 ResourceEntry 列表 -func (svr *serverAuthAbility) queryServiceResource( +func (svr *ServerAuthAbility) queryServiceResource( req []*apiservice.Service) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -272,7 +273,7 @@ func (svr *serverAuthAbility) queryServiceResource( } // queryServiceAliasResource 根据所给的 servicealias 信息,收集对应的 ResourceEntry 列表 -func (svr *serverAuthAbility) queryServiceAliasResource( +func (svr *ServerAuthAbility) queryServiceAliasResource( req []*apiservice.ServiceAlias) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -301,7 +302,7 @@ func (svr *serverAuthAbility) queryServiceAliasResource( // queryInstanceResource 根据所给的 instances 信息,收集对应的 ResourceEntry 列表 // 由于实例是注册到服务下的,因此只需要判断,是否有对应服务的权限即可 -func (svr *serverAuthAbility) queryInstanceResource( +func (svr *ServerAuthAbility) queryInstanceResource( req []*apiservice.Instance) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -342,7 +343,7 @@ func (svr *serverAuthAbility) queryInstanceResource( } // queryCircuitBreakerResource 根据所给的 CircuitBreaker 信息,收集对应的 ResourceEntry 列表 -func (svr *serverAuthAbility) queryCircuitBreakerResource( +func (svr *ServerAuthAbility) queryCircuitBreakerResource( req []*apifault.CircuitBreaker) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -367,7 +368,7 @@ func (svr *serverAuthAbility) queryCircuitBreakerResource( } // queryCircuitBreakerReleaseResource 根据所给的 CircuitBreakerRelease 信息,收集对应的 ResourceEntry 列表 -func (svr *serverAuthAbility) queryCircuitBreakerReleaseResource( +func (svr *ServerAuthAbility) queryCircuitBreakerReleaseResource( req []*apiservice.ConfigRelease) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -393,7 +394,7 @@ func (svr *serverAuthAbility) queryCircuitBreakerReleaseResource( } // queryRouteRuleResource 根据所给的 RouteRule 信息,收集对应的 ResourceEntry 列表 -func (svr *serverAuthAbility) queryRouteRuleResource( +func (svr *ServerAuthAbility) queryRouteRuleResource( req []*apitraffic.Routing) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -419,7 +420,7 @@ func (svr *serverAuthAbility) queryRouteRuleResource( } // queryRateLimitConfigResource 根据所给的 RateLimit 信息,收集对应的 ResourceEntry 列表 -func (svr *serverAuthAbility) queryRateLimitConfigResource( +func (svr *ServerAuthAbility) queryRateLimitConfigResource( req []*apitraffic.Rule) map[apisecurity.ResourceType][]model.ResourceEntry { if len(req) == 0 { return make(map[apisecurity.ResourceType][]model.ResourceEntry) @@ -445,7 +446,7 @@ func (svr *serverAuthAbility) queryRateLimitConfigResource( } // convertToDiscoverResourceEntryMaps 通用方法,进行转换为期望的、服务相关的 ResourceEntry -func (svr *serverAuthAbility) convertToDiscoverResourceEntryMaps(nsSet *utils.Set[string], +func (svr *ServerAuthAbility) convertToDiscoverResourceEntryMaps(nsSet *utils.Set[string], svcSet *utils.Map[string, *model.Service]) map[apisecurity.ResourceType][]model.ResourceEntry { var ( param = nsSet.ToSlice() diff --git a/service/service_alias_authability.go b/service/interceptor/auth/service_alias_authability.go similarity index 94% rename from service/service_alias_authability.go rename to service/interceptor/auth/service_alias_authability.go index b77d82f8e..644d9bbbf 100644 --- a/service/service_alias_authability.go +++ b/service/interceptor/auth/service_alias_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -29,7 +29,7 @@ import ( ) // CreateServiceAlias creates a service alias -func (svr *serverAuthAbility) CreateServiceAlias( +func (svr *ServerAuthAbility) CreateServiceAlias( ctx context.Context, req *apiservice.ServiceAlias) *apiservice.Response { authCtx := svr.collectServiceAliasAuthContext( ctx, []*apiservice.ServiceAlias{req}, model.Create, "CreateServiceAlias") @@ -51,7 +51,7 @@ func (svr *serverAuthAbility) CreateServiceAlias( } // DeleteServiceAliases deletes service aliases -func (svr *serverAuthAbility) DeleteServiceAliases(ctx context.Context, +func (svr *ServerAuthAbility) DeleteServiceAliases(ctx context.Context, reqs []*apiservice.ServiceAlias) *apiservice.BatchWriteResponse { authCtx := svr.collectServiceAliasAuthContext(ctx, reqs, model.Delete, "DeleteServiceAliases") @@ -66,7 +66,7 @@ func (svr *serverAuthAbility) DeleteServiceAliases(ctx context.Context, } // UpdateServiceAlias updates service alias -func (svr *serverAuthAbility) UpdateServiceAlias( +func (svr *ServerAuthAbility) UpdateServiceAlias( ctx context.Context, req *apiservice.ServiceAlias) *apiservice.Response { authCtx := svr.collectServiceAliasAuthContext( ctx, []*apiservice.ServiceAlias{req}, model.Modify, "UpdateServiceAlias") @@ -82,7 +82,7 @@ func (svr *serverAuthAbility) UpdateServiceAlias( } // GetServiceAliases gets service aliases -func (svr *serverAuthAbility) GetServiceAliases(ctx context.Context, +func (svr *ServerAuthAbility) GetServiceAliases(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAliasAuthContext(ctx, nil, model.Read, "GetServiceAliases") diff --git a/service/service_authability.go b/service/interceptor/auth/service_authability.go similarity index 93% rename from service/service_authability.go rename to service/interceptor/auth/service_authability.go index 4d21f82f7..52c212646 100644 --- a/service/service_authability.go +++ b/service/interceptor/auth/service_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -29,7 +29,7 @@ import ( ) // CreateServices 批量创建服务 -func (svr *serverAuthAbility) CreateServices( +func (svr *ServerAuthAbility) CreateServices( ctx context.Context, reqs []*apiservice.Service) *apiservice.BatchWriteResponse { authCtx := svr.collectServiceAuthContext(ctx, reqs, model.Create, "CreateServices") @@ -55,7 +55,7 @@ func (svr *serverAuthAbility) CreateServices( } // DeleteServices 批量删除服务 -func (svr *serverAuthAbility) DeleteServices( +func (svr *ServerAuthAbility) DeleteServices( ctx context.Context, reqs []*apiservice.Service) *apiservice.BatchWriteResponse { authCtx := svr.collectServiceAuthContext(ctx, reqs, model.Delete, "DeleteServices") @@ -74,7 +74,7 @@ func (svr *serverAuthAbility) DeleteServices( } // UpdateServices 对于服务修改来说,只针对服务本身,而不需要检查命名空间 -func (svr *serverAuthAbility) UpdateServices( +func (svr *ServerAuthAbility) UpdateServices( ctx context.Context, reqs []*apiservice.Service) *apiservice.BatchWriteResponse { authCtx := svr.collectServiceAuthContext(ctx, reqs, model.Modify, "UpdateServices") @@ -93,7 +93,7 @@ func (svr *serverAuthAbility) UpdateServices( } // UpdateServiceToken 更新服务的 token -func (svr *serverAuthAbility) UpdateServiceToken( +func (svr *ServerAuthAbility) UpdateServiceToken( ctx context.Context, req *apiservice.Service) *apiservice.Response { authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Modify, "UpdateServiceToken") @@ -111,7 +111,7 @@ func (svr *serverAuthAbility) UpdateServiceToken( return svr.targetServer.UpdateServiceToken(ctx, req) } -func (svr *serverAuthAbility) GetAllServices(ctx context.Context, +func (svr *ServerAuthAbility) GetAllServices(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetAllServices") @@ -126,7 +126,7 @@ func (svr *serverAuthAbility) GetAllServices(ctx context.Context, } // GetServices 批量获取服务 -func (svr *serverAuthAbility) GetServices( +func (svr *ServerAuthAbility) GetServices( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServices") @@ -158,7 +158,7 @@ func (svr *serverAuthAbility) GetServices( } // GetServicesCount 批量获取服务数量 -func (svr *serverAuthAbility) GetServicesCount(ctx context.Context) *apiservice.BatchQueryResponse { +func (svr *ServerAuthAbility) GetServicesCount(ctx context.Context) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServicesCount") if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { @@ -171,7 +171,7 @@ func (svr *serverAuthAbility) GetServicesCount(ctx context.Context) *apiservice. } // GetServiceToken 获取服务的 token -func (svr *serverAuthAbility) GetServiceToken(ctx context.Context, req *apiservice.Service) *apiservice.Response { +func (svr *ServerAuthAbility) GetServiceToken(ctx context.Context, req *apiservice.Service) *apiservice.Response { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceToken") if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { @@ -184,7 +184,7 @@ func (svr *serverAuthAbility) GetServiceToken(ctx context.Context, req *apiservi } // GetServiceOwner 获取服务的 owner -func (svr *serverAuthAbility) GetServiceOwner( +func (svr *ServerAuthAbility) GetServiceOwner( ctx context.Context, req []*apiservice.Service) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceOwner") diff --git a/service/service_contract_authability.go b/service/interceptor/auth/service_contract_authability.go similarity index 92% rename from service/service_contract_authability.go rename to service/interceptor/auth/service_contract_authability.go index 9bcb7a867..fbc58ab01 100644 --- a/service/service_contract_authability.go +++ b/service/interceptor/auth/service_contract_authability.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package service +package service_auth import ( "context" @@ -28,7 +28,7 @@ import ( ) // CreateServiceContracts . -func (svr *serverAuthAbility) CreateServiceContracts(ctx context.Context, +func (svr *ServerAuthAbility) CreateServiceContracts(ctx context.Context, req []*apiservice.ServiceContract) *apiservice.BatchWriteResponse { services := make([]*apiservice.Service, 0, len(req)) for i := range req { @@ -49,7 +49,7 @@ func (svr *serverAuthAbility) CreateServiceContracts(ctx context.Context, } // GetServiceContracts . -func (svr *serverAuthAbility) GetServiceContracts(ctx context.Context, +func (svr *ServerAuthAbility) GetServiceContracts(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceContract") if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { @@ -62,7 +62,7 @@ func (svr *serverAuthAbility) GetServiceContracts(ctx context.Context, } // GetServiceContractVersions . -func (svr *serverAuthAbility) GetServiceContractVersions(ctx context.Context, +func (svr *ServerAuthAbility) GetServiceContractVersions(ctx context.Context, filter map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceContractVersions") @@ -76,7 +76,7 @@ func (svr *serverAuthAbility) GetServiceContractVersions(ctx context.Context, } // DeleteServiceContracts . -func (svr *serverAuthAbility) DeleteServiceContracts(ctx context.Context, +func (svr *ServerAuthAbility) DeleteServiceContracts(ctx context.Context, req []*apiservice.ServiceContract) *apiservice.BatchWriteResponse { services := make([]*apiservice.Service, 0, len(req)) for i := range req { @@ -97,7 +97,7 @@ func (svr *serverAuthAbility) DeleteServiceContracts(ctx context.Context, } // CreateServiceContractInterfaces . -func (svr *serverAuthAbility) CreateServiceContractInterfaces(ctx context.Context, contract *apiservice.ServiceContract, +func (svr *ServerAuthAbility) CreateServiceContractInterfaces(ctx context.Context, contract *apiservice.ServiceContract, source apiservice.InterfaceDescriptor_Source) *apiservice.Response { authCtx := svr.collectServiceAuthContext(ctx, []*apiservice.Service{ { @@ -115,7 +115,7 @@ func (svr *serverAuthAbility) CreateServiceContractInterfaces(ctx context.Contex } // AppendServiceContractInterfaces . -func (svr *serverAuthAbility) AppendServiceContractInterfaces(ctx context.Context, +func (svr *ServerAuthAbility) AppendServiceContractInterfaces(ctx context.Context, contract *apiservice.ServiceContract, source apiservice.InterfaceDescriptor_Source) *apiservice.Response { authCtx := svr.collectServiceAuthContext(ctx, []*apiservice.Service{ { @@ -133,7 +133,7 @@ func (svr *serverAuthAbility) AppendServiceContractInterfaces(ctx context.Contex } // DeleteServiceContractInterfaces . -func (svr *serverAuthAbility) DeleteServiceContractInterfaces(ctx context.Context, +func (svr *ServerAuthAbility) DeleteServiceContractInterfaces(ctx context.Context, contract *apiservice.ServiceContract) *apiservice.Response { authCtx := svr.collectServiceAuthContext(ctx, []*apiservice.Service{ { diff --git a/service/interceptor/register.go b/service/interceptor/register.go new file mode 100644 index 000000000..860b59ad6 --- /dev/null +++ b/service/interceptor/register.go @@ -0,0 +1,42 @@ +/** + * 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 service_chain + +import ( + "github.com/polarismesh/polaris/auth" + "github.com/polarismesh/polaris/service" + service_auth "github.com/polarismesh/polaris/service/interceptor/auth" +) + +func init() { + err := service.RegisterServerProxy("auth", func(svr *service.Server, pre service.DiscoverServer) (service.DiscoverServer, error) { + userMgn, err := auth.GetUserServer() + if err != nil { + return nil, err + } + strategyMgn, err := auth.GetStrategyServer() + if err != nil { + return nil, err + } + + return service_auth.NewServerAuthAbility(svr, userMgn, strategyMgn), nil + }) + if err != nil { + panic(err) + } +} diff --git a/service/l5_service_test.go b/service/l5_service_test.go index e0d869607..306a670c0 100644 --- a/service/l5_service_test.go +++ b/service/l5_service_test.go @@ -35,7 +35,7 @@ func TestComputeNamespace(t *testing.T) { { name: "string", args: args{ - modID: 192002625, + modID: 192000065, cmdID: 65000, }, want: []string{ProductionNamespace}, diff --git a/service/options.go b/service/options.go index 143b2e76a..d3b0f0fed 100644 --- a/service/options.go +++ b/service/options.go @@ -33,30 +33,48 @@ import ( - name: client # Load Client-SDK instance data */ +func GetRegisterCaches() []cachetypes.ConfigEntry { + ret := []cachetypes.ConfigEntry{} + // ret = append(ret, l5CacheEntry) + ret = append(ret, namingCacheEntries...) + return ret +} + +func GetAllCaches() []cachetypes.ConfigEntry { + ret := []cachetypes.ConfigEntry{} + ret = append(ret, l5CacheEntry) + ret = append(ret, namingCacheEntries...) + ret = append(ret, governanceCacheEntries...) + return ret +} + var ( - l5CacheEntry = cache.ConfigEntry{ + l5CacheEntry = cachetypes.ConfigEntry{ Name: cachetypes.L5Name, } - namingCacheEntries = []cache.ConfigEntry{ + namingCacheEntries = []cachetypes.ConfigEntry{ { Name: cachetypes.ServiceName, Option: map[string]interface{}{ "disableBusiness": false, - "needMeta": true, + "needMeta": true, }, }, { Name: cachetypes.InstanceName, Option: map[string]interface{}{ "disableBusiness": false, - "needMeta": true, + "needMeta": true, }, }, { Name: cachetypes.ServiceContractName, }, + { + Name: cachetypes.ClientName, + }, } - governanceCacheEntries = []cache.ConfigEntry{ + governanceCacheEntries = []cachetypes.ConfigEntry{ { Name: cachetypes.RoutingConfigName, }, @@ -95,10 +113,10 @@ func WithStorage(storage store.Store) InitOption { func WithCacheManager(cacheOpt *cache.Config, c *cache.CacheManager) InitOption { return func(s *Server) { log.Infof("[Naming][Server] cache is open, can access the client api function") - c.OpenResourceCache(namingCacheEntries...) - c.OpenResourceCache(governanceCacheEntries...) + _ = c.OpenResourceCache(namingCacheEntries...) + _ = c.OpenResourceCache(governanceCacheEntries...) if s.isSupportL5() { - c.OpenResourceCache(l5CacheEntry) + _ = c.OpenResourceCache(l5CacheEntry) } s.caches = c } diff --git a/service/ratelimit_config_test.go b/service/ratelimit_config_test.go index 9301bd9ef..a6d69549c 100644 --- a/service/ratelimit_config_test.go +++ b/service/ratelimit_config_test.go @@ -426,6 +426,7 @@ func TestUpdateRateLimit(t *testing.T) { } if len(resp.GetRateLimits()) == 0 { errs <- errors.New("ratelimit rule count is zero") + return } checkRateLimit(t, rateLimitResp, resp.GetRateLimits()[0]) }(i) diff --git a/service/server.go b/service/server.go index 5f72a1554..cba356dab 100644 --- a/service/server.go +++ b/service/server.go @@ -68,7 +68,10 @@ func (s *Server) isSupportL5() bool { } func (s *Server) allowAutoCreate() bool { - return s.config.AutoCreate + if s.config.AutoCreate == nil { + return true + } + return *s.config.AutoCreate } // HealthServer 健康检查Server diff --git a/service/service_contract.go b/service/service_contract.go index 56fee3c31..e15650483 100644 --- a/service/service_contract.go +++ b/service/service_contract.go @@ -267,7 +267,7 @@ func (s *Server) GetServiceContractVersions(ctx context.Context, filter map[stri Ctime: commontime.Time2String(item.CreateTime), Mtime: commontime.Time2String(item.ModifyTime), }); err != nil { - log.Errorf("[Service][Contract] list all versions fail", utils.RequestID(ctx), zap.String("namespace", namespace), + log.Error("[Service][Contract] list all versions fail", utils.RequestID(ctx), zap.String("namespace", namespace), zap.String("service", serviceName), zap.Error(err)) return api.NewBatchQueryResponse(apimodel.Code_ExecuteException) } diff --git a/service/service_test.go b/service/service_test.go index 8de50d4e5..57716eb20 100644 --- a/service/service_test.go +++ b/service/service_test.go @@ -36,6 +36,7 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" @@ -1373,7 +1374,7 @@ func TestConcurrencyCreateSameService(t *testing.T) { &model.Namespace{ Name: "mock_ns", }, - }, nil) + }, nil).AnyTimes() mockStore.EXPECT().GetUnixSecond(gomock.Any()).Return(time.Now().Unix(), nil).AnyTimes() cacheMgr, err = cache.TestCacheInitialize(ctx, &cache.Config{}, mockStore) assert.NoError(t, err) @@ -1386,7 +1387,7 @@ func TestConcurrencyCreateSameService(t *testing.T) { }, mockStore, cacheMgr, userMgn, strategyMgn) assert.NoError(t, err) - cacheMgr.OpenResourceCache([]cache.ConfigEntry{ + cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ { Name: "namespace", }, diff --git a/service/test_export.go b/service/test_export.go index 42ef4202c..63666f29a 100644 --- a/service/test_export.go +++ b/service/test_export.go @@ -19,6 +19,7 @@ package service import ( "context" + "fmt" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" @@ -26,6 +27,7 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/namespace" "github.com/polarismesh/polaris/service/batch" @@ -55,19 +57,20 @@ func TestNewServer(mockStore store.Store, nsSvr namespace.NamespaceOperateServer } // TestInitialize 初始化 -func TestInitialize(ctx context.Context, namingOpt *Config, cacheOpt *cache.Config, bc *batch.Controller, - cacheMgr *cache.CacheManager, storage store.Store, namespaceSvr namespace.NamespaceOperateServer, +func TestInitialize(ctx context.Context, namingOpt *Config, cacheOpt *cache.Config, + cacheEntries []cachetypes.ConfigEntry, bc *batch.Controller, cacheMgr *cache.CacheManager, + storage store.Store, namespaceSvr namespace.NamespaceOperateServer, healthSvr *healthcheck.Server, userMgn auth.UserServer, strategyMgn auth.StrategyServer) (DiscoverServer, DiscoverServer, error) { - cacheMgr.OpenResourceCache([]cache.ConfigEntry{ - { - Name: "service", - }, { - Name: "instance", - }, { - Name: "serviceContract", - }, - }...) + entrites := []cachetypes.ConfigEntry{} + if len(cacheEntries) != 0 { + entrites = cacheEntries + } else { + entrites = append(entrites, l5CacheEntry) + entrites = append(entrites, namingCacheEntries...) + entrites = append(entrites, governanceCacheEntries...) + } + _ = cacheMgr.OpenResourceCache(entrites...) namingServer.healthServer = healthSvr namingServer.storage = storage // 注入命名空间管理模块 @@ -83,7 +86,24 @@ func TestInitialize(ctx context.Context, namingOpt *Config, cacheOpt *cache.Conf namingServer.createServiceSingle = &singleflight.Group{} // 插件初始化 pluginInitialize() - return newServerAuthAbility(namingServer, userMgn, strategyMgn), namingServer, nil + + var proxySvr DiscoverServer + var err error + // 需要返回包装代理的 DiscoverServer + order := namingOpt.Interceptors + for i := range order { + factory, exist := serverProxyFactories[order[i]] + if !exist { + return nil, nil, fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) + } + + proxySvr, err = factory(namingServer, proxySvr) + if err != nil { + return nil, nil, err + } + } + + return proxySvr, namingServer, nil } // TestSerialCreateInstance . diff --git a/store/api.go b/store/api.go index f9726c43b..a5034c3d0 100644 --- a/store/api.go +++ b/store/api.go @@ -70,7 +70,11 @@ type NamespaceStore interface { // GrayStore Gray storage interface type GrayStore interface { + // CleanGrayResource . + CleanGrayResource(tx Tx, data *model.GrayResource) error + // CreateGrayResourceTx . CreateGrayResourceTx(tx Tx, data *model.GrayResource) error + // GetMoreGrayResouces . GetMoreGrayResouces(firstUpdate bool, mtime time.Time) ([]*model.GrayResource, error) } diff --git a/store/boltdb/circuitbreaker_rule.go b/store/boltdb/circuitbreaker_rule.go index 240c0a860..11a707dc0 100644 --- a/store/boltdb/circuitbreaker_rule.go +++ b/store/boltdb/circuitbreaker_rule.go @@ -125,6 +125,11 @@ func (c *circuitBreakerStore) UpdateCircuitBreakerRule(cbRule *model.CircuitBrea CbFieldDstMethod: cbRule.DstMethod, CbFieldRule: cbRule.Rule, } + if cbRule.Enable { + properties[CommonFieldEnableTime] = time.Now() + } else { + properties[CommonFieldEnableTime] = time.Unix(0, 0) + } if err := dbOp.UpdateValue(tblCircuitBreakerRule, cbRule.ID, properties); err != nil { log.Errorf("[Store][CircuitBreaker] update rule(%s) exec err: %s", cbRule.ID, err.Error()) return store.Error(err) @@ -253,12 +258,17 @@ func (c *circuitBreakerStore) GetCircuitBreakerRules( if ok && !validVal.(bool) { return false } - if hasSvc && hasSvcNs { - srcSvcValue := m[CbFieldSrcService] + if hasSvcNs { srcNsValue := m[CbFieldSrcNamespace] - dstSvcValue := m[CbFieldDstService] dstNsValue := m[CbFieldDstNamespace] - if !((srcSvcValue == svc && srcNsValue == svcNs) || (dstSvcValue == svc && dstNsValue == svcNs)) { + if !((srcNsValue == "*" || srcNsValue == svcNs) || (dstNsValue == "*" || dstNsValue == svcNs)) { + return false + } + } + if hasSvc { + srcSvcValue := m[CbFieldSrcService] + dstSvcValue := m[CbFieldDstService] + if !((srcSvcValue == svc || srcSvcValue == "*") || (dstSvcValue == svc || dstSvcValue == "*")) { return false } } @@ -351,9 +361,8 @@ func sublistCircuitBreakerRules(cbRules []*model.CircuitBreakerRule, offset, lim return true } else if cbRules[i].ModifyTime.Before(cbRules[j].ModifyTime) { return false - } else { - return strings.Compare(cbRules[i].ID, cbRules[j].ID) < 0 } + return strings.Compare(cbRules[i].ID, cbRules[j].ID) < 0 }) return cbRules[beginIndex:endIndex] diff --git a/store/boltdb/config_file_release.go b/store/boltdb/config_file_release.go index 8f8284d5e..49632f508 100644 --- a/store/boltdb/config_file_release.go +++ b/store/boltdb/config_file_release.go @@ -156,7 +156,7 @@ func (cfr *configFileReleaseStore) GetConfigFileActiveReleaseTx(tx store.Tx, dbTx := tx.GetDelegateTx().(*bolt.Tx) fields := []string{FileReleaseFieldActive, FileReleaseFieldNamespace, FileReleaseFieldGroup, - FileReleaseFieldFileName, FileReleaseFieldValid} + FileReleaseFieldFileName, FileReleaseFieldValid, FileReleaseFieldType} values := make(map[string]interface{}, 1) err := loadValuesByFilter(dbTx, tblConfigFileRelease, fields, &ConfigFileRelease{}, func(m map[string]interface{}) bool { @@ -169,6 +169,46 @@ func (cfr *configFileReleaseStore) GetConfigFileActiveReleaseTx(tx store.Tx, if !active { return false } + if relType, _ := m[FileReleaseFieldType].(string); relType != model.ReleaseTypeFull { + return false + } + saveNs, _ := m[FileReleaseFieldNamespace].(string) + saveGroup, _ := m[FileReleaseFieldGroup].(string) + saveFileName, _ := m[FileReleaseFieldFileName].(string) + + expect := saveNs == file.Namespace && saveGroup == file.Group && saveFileName == file.Name + return expect + }, values) + if err != nil { + return nil, err + } + for _, v := range values { + return cfr.toModelData(v.(*ConfigFileRelease)), nil + } + return nil, nil +} + +func (cfr *configFileReleaseStore) GetConfigFileBetaReleaseTx(tx store.Tx, + file *model.ConfigFileKey) (*model.ConfigFileRelease, error) { + dbTx := tx.GetDelegateTx().(*bolt.Tx) + + fields := []string{FileReleaseFieldActive, FileReleaseFieldNamespace, FileReleaseFieldGroup, + FileReleaseFieldFileName, FileReleaseFieldValid, FileReleaseFieldType} + values := make(map[string]interface{}, 1) + err := loadValuesByFilter(dbTx, tblConfigFileRelease, fields, &ConfigFileRelease{}, + func(m map[string]interface{}) bool { + valid, _ := m[FileReleaseFieldValid].(bool) + // 已经删除的不管 + if !valid { + return false + } + active, _ := m[FileReleaseFieldActive].(bool) + if !active { + return false + } + if relType, _ := m[FileReleaseFieldType].(string); relType != model.ReleaseTypeGray { + return false + } saveNs, _ := m[FileReleaseFieldNamespace].(string) saveGroup, _ := m[FileReleaseFieldGroup].(string) saveFileName, _ := m[FileReleaseFieldFileName].(string) @@ -267,7 +307,7 @@ func (cfr *configFileReleaseStore) GetMoreReleaseFile(firstUpdate bool, modifyTime time.Time) ([]*model.ConfigFileRelease, error) { if firstUpdate { - modifyTime = time.Time{} + modifyTime = time.Unix(0, 0) } fields := []string{FileReleaseFieldModifyTime} @@ -298,7 +338,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) + properties[FileReleaseFieldType] = string(release.ReleaseType) return updateValue(dbTx, tblConfigFileRelease, release.ReleaseKey(), properties) } @@ -306,7 +346,7 @@ func (cfr *configFileReleaseStore) inactiveConfigFileRelease(tx *bolt.Tx, release *model.ConfigFileRelease) (uint64, error) { fields := []string{FileReleaseFieldNamespace, FileReleaseFieldGroup, FileReleaseFieldFileName, - FileReleaseFieldVersion, FileReleaseFieldFlag, FileReleaseFieldActive} + FileReleaseFieldVersion, FileReleaseFieldFlag, FileReleaseFieldActive, FileReleaseFieldType} values := map[string]interface{}{} var maxVersion uint64 @@ -325,6 +365,10 @@ func (cfr *configFileReleaseStore) inactiveConfigFileRelease(tx *bolt.Tx, saveNs, _ := m[FileReleaseFieldNamespace].(string) saveGroup, _ := m[FileReleaseFieldGroup].(string) saveFileName, _ := m[FileReleaseFieldFileName].(string) + releaseType, _ := m[FileReleaseFieldType].(string) + if releaseType != string(release.ReleaseType) { + return false + } expect := saveNs == release.Namespace && saveGroup == release.Group && saveFileName == release.FileName @@ -389,19 +433,19 @@ type ConfigFileRelease struct { ModifyTime time.Time ModifyBy string Content string - Typ uint32 + Typ string } func (cfr *configFileReleaseStore) toModelData(data *ConfigFileRelease) *model.ConfigFileRelease { return &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ - Id: data.Id, - Name: data.Name, - Namespace: data.Namespace, - Group: data.Group, - FileName: data.FileName, - Typ: model.ReleaseType(data.Typ), + Id: data.Id, + Name: data.Name, + Namespace: data.Namespace, + Group: data.Group, + FileName: data.FileName, + ReleaseType: model.ReleaseType(data.Typ), }, Comment: data.Comment, Md5: data.Md5, @@ -440,6 +484,6 @@ func (cfr *configFileReleaseStore) toStoreData(data *model.ConfigFileRelease) *C ModifyTime: data.ModifyTime, ModifyBy: data.ModifyBy, Content: data.Content, - Typ: uint32(data.Typ), + Typ: string(data.ConfigFileReleaseKey.ReleaseType), } } diff --git a/store/boltdb/faultdetector.go b/store/boltdb/faultdetector.go index 6df6c21dd..e43e32718 100644 --- a/store/boltdb/faultdetector.go +++ b/store/boltdb/faultdetector.go @@ -284,9 +284,8 @@ func sublistFaultDetectRules(cbRules []*model.FaultDetectRule, offset, limit uin return true } else if cbRules[i].ModifyTime.Before(cbRules[j].ModifyTime) { return false - } else { - return strings.Compare(cbRules[i].ID, cbRules[j].ID) < 0 } + return strings.Compare(cbRules[i].ID, cbRules[j].ID) < 0 }) return cbRules[beginIndex:endIndex] diff --git a/store/boltdb/gray_resource.go b/store/boltdb/gray_resource.go index 191023217..782cd4531 100644 --- a/store/boltdb/gray_resource.go +++ b/store/boltdb/gray_resource.go @@ -30,8 +30,7 @@ import ( var _ store.GrayStore = (*grayStore)(nil) const ( - tblGrayResource string = "GrayResource" - + tblGrayResource string = "GrayResource" GrayResourceFieldModifyTime string = "ModifyTime" ) @@ -60,6 +59,22 @@ func (cfr *grayStore) CreateGrayResourceTx(proxyTx store.Tx, grayResource *model return nil } +func (cfr *grayStore) CleanGrayResource(proxyTx store.Tx, data *model.GrayResource) error { + tx := proxyTx.GetDelegateTx().(*bolt.Tx) + + properties := map[string]interface{}{ + GrayResourceFieldModifyTime: time.Now(), + CommonFieldValid: false, + } + + err := updateValue(tx, tblGrayResource, data.Name, properties) + if err != nil { + log.Error("[GrayResource] update 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) { diff --git a/store/boltdb/instance.go b/store/boltdb/instance.go index 4b3e2c183..0b61e055f 100644 --- a/store/boltdb/instance.go +++ b/store/boltdb/instance.go @@ -848,9 +848,8 @@ func getRealInstancesList(originServices map[string]interface{}, offset, limit u return true } else if instances[i].ModifyTime.Before(instances[j].ModifyTime) { return false - } else { - return strings.Compare(instances[i].ID(), instances[j].ID()) < 0 } + return strings.Compare(instances[i].ID(), instances[j].ID()) < 0 }) return instances[beginIndex:endIndex] diff --git a/store/boltdb/ratelimit.go b/store/boltdb/ratelimit.go index 35cb9b535..6ded1e67c 100644 --- a/store/boltdb/ratelimit.go +++ b/store/boltdb/ratelimit.go @@ -358,9 +358,8 @@ func getRealRateConfList(routeConf []*model.ExtendRateLimit, offset, limit uint3 return true } else if routeConf[i].RateLimit.ModifyTime.Before(routeConf[j].RateLimit.ModifyTime) { return false - } else { - return strings.Compare(routeConf[i].RateLimit.ID, routeConf[j].RateLimit.ID) < 0 } + return strings.Compare(routeConf[i].RateLimit.ID, routeConf[j].RateLimit.ID) < 0 }) return routeConf[beginIndex:endIndex] diff --git a/store/boltdb/routing.go b/store/boltdb/routing.go index e954e8bea..1f66c2838 100644 --- a/store/boltdb/routing.go +++ b/store/boltdb/routing.go @@ -373,9 +373,8 @@ func getRealRouteConfList(routeConf []*model.ExtendRoutingConfig, offset, limit return true } else if routeConf[i].Config.ModifyTime.Before(routeConf[j].Config.ModifyTime) { return false - } else { - return strings.Compare(routeConf[i].Config.ID, routeConf[j].Config.ID) < 0 } + return strings.Compare(routeConf[i].Config.ID, routeConf[j].Config.ID) < 0 }) return routeConf[beginIndex:endIndex] diff --git a/store/boltdb/service.go b/store/boltdb/service.go index 202125d9a..06a72583f 100644 --- a/store/boltdb/service.go +++ b/store/boltdb/service.go @@ -953,10 +953,9 @@ func getRealServicesList(originServices map[string]*model.Service, offset, limit return true } else if services[i].ModifyTime.Before(services[j].ModifyTime) { return false - } else { - // compare id if modifyTime is the same - return services[i].ID < services[j].ID } + // compare id if modifyTime is the same + return services[i].ID < services[j].ID }) return services[beginIndex:endIndex] @@ -988,10 +987,9 @@ func doPageAliasServices(originServices []*model.ServiceAlias, offset, limit uin return true } else if services[i].ModifyTime.Before(services[j].ModifyTime) { return false - } else { - // compare id if modifyTime is the same - return services[i].ID < services[j].ID } + // compare id if modifyTime is the same + return services[i].ID < services[j].ID }) return services[beginIndex:endIndex] diff --git a/store/config_file_api.go b/store/config_file_api.go index 949de0e1d..ef5d47b94 100644 --- a/store/config_file_api.go +++ b/store/config_file_api.go @@ -92,6 +92,8 @@ type ConfigFileReleaseStore interface { GetMoreReleaseFile(firstUpdate bool, modifyTime time.Time) ([]*model.ConfigFileRelease, error) // CountConfigReleases 获取一个配置文件组下的文件数量 CountConfigReleases(namespace, group string, onlyActive bool) (uint64, error) + // GetConfigFileBetaReleaseTx 获取灰度发布的配置文件信息 + GetConfigFileBetaReleaseTx(tx Tx, file *model.ConfigFileKey) (*model.ConfigFileRelease, error) } // ConfigFileReleaseHistoryStore 配置文件发布历史存储接口 diff --git a/store/mock/api_mock.go b/store/mock/api_mock.go index 2c620d0f1..2e4f07c02 100644 --- a/store/mock/api_mock.go +++ b/store/mock/api_mock.go @@ -347,6 +347,20 @@ func (mr *MockStoreMockRecorder) CleanConfigFileReleasesTx(tx, namespace, group, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanConfigFileReleasesTx", reflect.TypeOf((*MockStore)(nil).CleanConfigFileReleasesTx), tx, namespace, group, fileName) } +// CleanGrayResource mocks base method. +func (m *MockStore) CleanGrayResource(tx store.Tx, data *model.GrayResource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CleanGrayResource", tx, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// CleanGrayResource indicates an expected call of CleanGrayResource. +func (mr *MockStoreMockRecorder) CleanGrayResource(tx, data interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanGrayResource", reflect.TypeOf((*MockStore)(nil).CleanGrayResource), tx, data) +} + // CleanInstance mocks base method. func (m *MockStore) CleanInstance(instanceID string) error { m.ctrl.T.Helper() @@ -521,6 +535,20 @@ func (mr *MockStoreMockRecorder) CreateFaultDetectRule(conf interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFaultDetectRule", reflect.TypeOf((*MockStore)(nil).CreateFaultDetectRule), conf) } +// CreateGrayResourceTx mocks base method. +func (m *MockStore) CreateGrayResourceTx(tx store.Tx, data *model.GrayResource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGrayResourceTx", tx, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGrayResourceTx indicates an expected call of CreateGrayResourceTx. +func (mr *MockStoreMockRecorder) CreateGrayResourceTx(tx, data interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGrayResourceTx", reflect.TypeOf((*MockStore)(nil).CreateGrayResourceTx), tx, data) +} + // CreateRateLimit mocks base method. func (m *MockStore) CreateRateLimit(limiting *model.RateLimit) error { m.ctrl.T.Helper() @@ -991,6 +1019,21 @@ func (mr *MockStoreMockRecorder) GetConfigFileActiveReleaseTx(tx, file interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigFileActiveReleaseTx", reflect.TypeOf((*MockStore)(nil).GetConfigFileActiveReleaseTx), tx, file) } +// GetConfigFileBetaReleaseTx mocks base method. +func (m *MockStore) GetConfigFileBetaReleaseTx(tx store.Tx, file *model.ConfigFileKey) (*model.ConfigFileRelease, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConfigFileBetaReleaseTx", tx, file) + ret0, _ := ret[0].(*model.ConfigFileRelease) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConfigFileBetaReleaseTx indicates an expected call of GetConfigFileBetaReleaseTx. +func (mr *MockStoreMockRecorder) GetConfigFileBetaReleaseTx(tx, file interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigFileBetaReleaseTx", reflect.TypeOf((*MockStore)(nil).GetConfigFileBetaReleaseTx), tx, file) +} + // GetConfigFileGroup mocks base method. func (m *MockStore) GetConfigFileGroup(namespace, name string) (*model.ConfigFileGroup, error) { m.ctrl.T.Helper() @@ -1325,6 +1368,21 @@ func (mr *MockStoreMockRecorder) GetMoreConfigGroup(firstUpdate, mtime interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreConfigGroup", reflect.TypeOf((*MockStore)(nil).GetMoreConfigGroup), firstUpdate, mtime) } +// GetMoreGrayResouces mocks base method. +func (m *MockStore) GetMoreGrayResouces(firstUpdate bool, mtime time.Time) ([]*model.GrayResource, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreGrayResouces", firstUpdate, mtime) + ret0, _ := ret[0].([]*model.GrayResource) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreGrayResouces indicates an expected call of GetMoreGrayResouces. +func (mr *MockStoreMockRecorder) GetMoreGrayResouces(firstUpdate, mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreGrayResouces", reflect.TypeOf((*MockStore)(nil).GetMoreGrayResouces), firstUpdate, mtime) +} + // GetMoreInstances mocks base method. func (m *MockStore) GetMoreInstances(tx store.Tx, mtime time.Time, firstUpdate, needMeta bool, serviceID []string) (map[string]*model.Instance, error) { m.ctrl.T.Helper() @@ -2648,6 +2706,72 @@ func (mr *MockNamespaceStoreMockRecorder) UpdateNamespaceToken(name, token inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNamespaceToken", reflect.TypeOf((*MockNamespaceStore)(nil).UpdateNamespaceToken), name, token) } +// MockGrayStore is a mock of GrayStore interface. +type MockGrayStore struct { + ctrl *gomock.Controller + recorder *MockGrayStoreMockRecorder +} + +// MockGrayStoreMockRecorder is the mock recorder for MockGrayStore. +type MockGrayStoreMockRecorder struct { + mock *MockGrayStore +} + +// NewMockGrayStore creates a new mock instance. +func NewMockGrayStore(ctrl *gomock.Controller) *MockGrayStore { + mock := &MockGrayStore{ctrl: ctrl} + mock.recorder = &MockGrayStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGrayStore) EXPECT() *MockGrayStoreMockRecorder { + return m.recorder +} + +// CleanGrayResource mocks base method. +func (m *MockGrayStore) CleanGrayResource(tx store.Tx, data *model.GrayResource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CleanGrayResource", tx, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// CleanGrayResource indicates an expected call of CleanGrayResource. +func (mr *MockGrayStoreMockRecorder) CleanGrayResource(tx, data interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanGrayResource", reflect.TypeOf((*MockGrayStore)(nil).CleanGrayResource), tx, data) +} + +// CreateGrayResourceTx mocks base method. +func (m *MockGrayStore) CreateGrayResourceTx(tx store.Tx, data *model.GrayResource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGrayResourceTx", tx, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGrayResourceTx indicates an expected call of CreateGrayResourceTx. +func (mr *MockGrayStoreMockRecorder) CreateGrayResourceTx(tx, data interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGrayResourceTx", reflect.TypeOf((*MockGrayStore)(nil).CreateGrayResourceTx), tx, data) +} + +// GetMoreGrayResouces mocks base method. +func (m *MockGrayStore) GetMoreGrayResouces(firstUpdate bool, mtime time.Time) ([]*model.GrayResource, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreGrayResouces", firstUpdate, mtime) + ret0, _ := ret[0].([]*model.GrayResource) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreGrayResouces indicates an expected call of GetMoreGrayResouces. +func (mr *MockGrayStoreMockRecorder) GetMoreGrayResouces(firstUpdate, mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreGrayResouces", reflect.TypeOf((*MockGrayStore)(nil).GetMoreGrayResouces), firstUpdate, mtime) +} + // MockTransaction is a mock of Transaction interface. type MockTransaction struct { ctrl *gomock.Controller @@ -2874,32 +2998,3 @@ 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/circuitbreaker_config_v2.go b/store/mysql/circuitbreaker_config_v2.go index 49e5dc39c..4ba9cbab9 100644 --- a/store/mysql/circuitbreaker_config_v2.go +++ b/store/mysql/circuitbreaker_config_v2.go @@ -332,12 +332,12 @@ func genCircuitBreakerRuleSQL(query map[string]string) (string, []interface{}) { } } if len(svcQueryValue) > 0 { - str += " and (dst_service = ? or dst_service = '*')" - args = append(args, svcQueryValue) + str += " and (dst_service = ? or dst_service = '*' or src_service = ? or src_service = '*')" + args = append(args, svcQueryValue, svcQueryValue) } if len(svcNamespaceQueryValue) > 0 { - str += " and (dst_namespace = ? or dst_namespace = '*')" - args = append(args, svcNamespaceQueryValue) + str += " and (dst_namespace = ? or dst_namespace = '*' or dst_namespace = ? or dst_namespace = '*')" + args = append(args, svcNamespaceQueryValue, svcNamespaceQueryValue) } return str, args } diff --git a/store/mysql/config_file_release.go b/store/mysql/config_file_release.go index 2bbf76656..dd3659023 100644 --- a/store/mysql/config_file_release.go +++ b/store/mysql/config_file_release.go @@ -62,13 +62,13 @@ func (cfr *configFileReleaseStore) CreateConfigFileReleaseTx(tx store.Tx, data * } s := "INSERT INTO config_file_release(name, namespace, `group`, file_name, content , comment, md5, " + - " version, create_time, create_by , modify_time, modify_by, active, tags, description, type) " + + " version, create_time, create_by , modify_time, modify_by, active, tags, description, release_type) " + " VALUES (?, ?, ?, ?, ? , ?, ?, ?, sysdate(), ? , sysdate(), ?, 1, ?, ?, ?)" args = []interface{}{ data.Name, data.Namespace, data.Group, data.FileName, data.Content, data.Comment, data.Md5, maxVersion + 1, - data.CreateBy, data.ModifyBy, utils.MustJson(data.Metadata), data.ReleaseDescription, data.Typ, + data.CreateBy, data.ModifyBy, utils.MustJson(data.Metadata), data.ReleaseDescription, data.ReleaseType, } if _, err = dbTx.Exec(s, args...); err != nil { return store.Error(err) @@ -178,7 +178,7 @@ 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 release_type = ? AND flag = 0 " var ( rows *sql.Rows err error @@ -201,6 +201,38 @@ func (cfr *configFileReleaseStore) GetConfigFileActiveReleaseTx(tx store.Tx, return nil, nil } +// GetConfigFileBetaReleaseTx . +func (cfr *configFileReleaseStore) GetConfigFileBetaReleaseTx(tx store.Tx, + file *model.ConfigFileKey) (*model.ConfigFileRelease, error) { + if tx == nil { + return nil, ErrTxIsNil + } + + dbTx := tx.GetDelegateTx().(*BaseTx) + querySql := cfr.baseQuerySql() + "WHERE namespace = ? AND `group` = ? AND " + + " file_name = ? AND active = 1 AND release_type = ? AND flag = 0 " + var ( + rows *sql.Rows + err error + ) + + rows, err = dbTx.Query(querySql, file.Namespace, file.Group, file.Name, model.ReleaseTypeGray) + if err != nil { + return nil, err + } + fileRelease, err := cfr.transferRows(rows) + if err != nil { + return nil, err + } + if len(fileRelease) > 1 { + return nil, errors.New("multi active file release found") + } + if len(fileRelease) > 0 { + return fileRelease[0], nil + } + return nil, nil +} + // ActiveConfigFileReleaseTx func (cfr *configFileReleaseStore) ActiveConfigFileReleaseTx(tx store.Tx, release *model.ConfigFileRelease) error { if tx == nil { @@ -212,10 +244,10 @@ func (cfr *configFileReleaseStore) ActiveConfigFileReleaseTx(tx store.Tx, releas if err != nil { return err } - args := []interface{}{maxVersion + 1, release.Typ, release.Namespace, release.Group, + args := []interface{}{maxVersion + 1, release.ReleaseType, release.Namespace, release.Group, release.FileName, release.Name} // update 指定的 release 记录,设置其 active、version 以及 mtime - updateSql := "UPDATE config_file_release SET active = 1, version = ?, modify_time = sysdate(), type=? " + + updateSql := "UPDATE config_file_release SET active = 1, version = ?, modify_time = sysdate(), release_type = ? " + " WHERE namespace = ? AND `group` = ? AND file_name = ? AND name = ?" if _, err := dbTx.Exec(updateSql, args...); err != nil { return store.Error(err) @@ -229,13 +261,21 @@ func (cfr *configFileReleaseStore) inactiveConfigFileRelease(tx *BaseTx, return 0, ErrTxIsNil } - args := []interface{}{release.Namespace, release.Group, release.FileName, release.Typ} + args := []interface{}{release.Namespace, release.Group, release.FileName, release.ReleaseType} // 先取消所有 active == true 的记录 if _, err := tx.Exec("UPDATE config_file_release SET active = 0, modify_time = sysdate() "+ - " WHERE namespace = ? AND `group` = ? AND file_name = ? AND active = 1 AND type=?", args...); err != nil { + " WHERE namespace = ? AND `group` = ? AND file_name = ? AND active = 1 AND release_type = ?", args...); err != nil { return 0, err } + return cfr.selectMaxVersion(tx, release) +} + +func (cfr *configFileReleaseStore) selectMaxVersion(tx *BaseTx, release *model.ConfigFileRelease) (uint64, error) { + if tx == nil { + return 0, ErrTxIsNil + } + args := []interface{}{release.Namespace, release.Group, release.FileName, release.ReleaseType} // 生成最新的 version 版本信息 row := tx.QueryRow("SELECT IFNULL(MAX(`version`), 0) FROM config_file_release WHERE namespace = ? AND "+ " `group` = ? AND file_name = ?", args[:3]...) @@ -285,7 +325,7 @@ func (cfr *configFileReleaseStore) CountConfigReleases(namespace, group string, func (cfr *configFileReleaseStore) baseQuerySql() string { return "SELECT id, name, namespace, `group`, file_name, content, IFNULL(comment, ''), " + " md5, version, UNIX_TIMESTAMP(create_time), IFNULL(create_by, ''), UNIX_TIMESTAMP(modify_time), " + - " IFNULL(modify_by, ''), flag, IFNULL(tags, ''), active, IFNULL(description, ''), type FROM config_file_release " + " IFNULL(modify_by, ''), flag, IFNULL(tags, ''), active, IFNULL(description, ''), IFNULL(release_type, '') FROM config_file_release " } func (cfr *configFileReleaseStore) transferRows(rows *sql.Rows) ([]*model.ConfigFileRelease, error) { @@ -308,7 +348,7 @@ func (cfr *configFileReleaseStore) transferRows(rows *sql.Rows) ([]*model.Config &fileRelease.FileName, &fileRelease.Content, &fileRelease.Comment, &fileRelease.Md5, &fileRelease.Version, &ctime, &fileRelease.CreateBy, &mtime, &fileRelease.ModifyBy, &fileRelease.Flag, &tags, &active, &fileRelease.ReleaseDescription, - &fileRelease.Typ) + &fileRelease.ReleaseType) if err != nil { return nil, err } diff --git a/store/mysql/gray_resouce.go b/store/mysql/gray_resouce.go index 2edc20488..eccdcc82d 100644 --- a/store/mysql/gray_resouce.go +++ b/store/mysql/gray_resouce.go @@ -51,6 +51,20 @@ func (g *grayStore) CreateGrayResourceTx(tx store.Tx, data *model.GrayResource) return nil } +func (g *grayStore) CleanGrayResource(tx store.Tx, data *model.GrayResource) error { + if tx == nil { + return ErrTxIsNil + } + dbTx := tx.GetDelegateTx().(*BaseTx) + s := "UPDATE gray_resource SET flag = 1, modify_time = sysdate() WHERE name = ?" + + args := []interface{}{data.Name} + 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) { @@ -60,7 +74,10 @@ func (g *grayStore) GetMoreGrayResouces(firstUpdate bool, } 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(?)" + " UNIX_TIMESTAMP(modify_time), IFNULL(modify_by, ''), flag FROM gray_resource WHERE modify_time > FROM_UNIXTIME(?)" + if firstUpdate { + s += " AND flag = 0 " + } rows, err := g.slave.Query(s, timeToTimestamp(modifyTime)) if err != nil { return nil, err @@ -80,12 +97,13 @@ func (g *grayStore) fetchGrayResourceRows(rows *sql.Rows) ([]*model.GrayResource grayResources := make([]*model.GrayResource, 0, 32) for rows.Next() { - var ctime, mtime int64 + var ctime, mtime, valid int64 grayResource := &model.GrayResource{} if err := rows.Scan(&grayResource.Name, &grayResource.MatchRule, &ctime, - &grayResource.CreateBy, &mtime, &grayResource.ModifyBy); err != nil { + &grayResource.CreateBy, &mtime, &grayResource.ModifyBy, &valid); err != nil { return nil, err } + grayResource.Valid = valid == 0 grayResource.CreateTime = time.Unix(ctime, 0) grayResource.ModifyTime = time.Unix(mtime, 0) grayResources = append(grayResources, grayResource) diff --git a/store/mysql/ratelimit_config.go b/store/mysql/ratelimit_config.go index c11523ed0..14fb06cf4 100644 --- a/store/mysql/ratelimit_config.go +++ b/store/mysql/ratelimit_config.go @@ -305,6 +305,7 @@ func fetchRateLimitCacheRows(rows *sql.Rows) ([]*model.RateLimit, error) { } rateLimit.CreateTime = time.Unix(ctime, 0) rateLimit.ModifyTime = time.Unix(mtime, 0) + rateLimit.EnableTime = time.Unix(etime, 0) rateLimit.Valid = true if flag == 1 { rateLimit.Valid = false diff --git a/store/mysql/scripts/delta/v1_17_3-v1_18_0.sql b/store/mysql/scripts/delta/v1_17_3-v1_18_0.sql index b0c73ab95..590fe7c2d 100644 --- a/store/mysql/scripts/delta/v1_17_3-v1_18_0.sql +++ b/store/mysql/scripts/delta/v1_17_3-v1_18_0.sql @@ -29,6 +29,9 @@ ADD COLUMN `service_export_to` TEXT COMMENT 'namespace metadata'; ALTER TABLE namespace ADD COLUMN `metadata` TEXT COMMENT 'namespace metadata'; +ALTER TABLE config_file_release +ADD COLUMN `release_type` VARCHAR(25) NOT NULL DEFAULT '' COMMENT '文件类型:"":全量 gray:灰度'; + /* 服务契约表 */ CREATE TABLE service_contract ( `id` VARCHAR(128) NOT NULL COMMENT '服务契约主键', @@ -69,4 +72,17 @@ CREATE TABLE service_contract_detail ( PRIMARY KEY (`id`), -- 服务契约id + method + path + source 需保证唯一 KEY (`contract_id`, `path`, `method`) - ) ENGINE = InnoDB; \ No newline at end of file + ) ENGINE = InnoDB; + +/* 灰度资源 */ +CREATE TABLE `gray_resource` +( + `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 '最后更新人', + `flag` TINYINT(4) DEFAULT 0 COMMENT '逻辑删除标志位, 0 位有效, 1 为逻辑删除', + PRIMARY KEY (`name`) +) ENGINE = InnoDB COMMENT = '灰度资源表'; diff --git a/store/mysql/scripts/polaris_server.sql b/store/mysql/scripts/polaris_server.sql index 34480baad..d11f3c301 100644 --- a/store/mysql/scripts/polaris_server.sql +++ b/store/mysql/scripts/polaris_server.sql @@ -478,31 +478,32 @@ CREATE TABLE `config_file_group` -- CREATE TABLE `config_file_release` ( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `name` VARCHAR(128) DEFAULT NULL COMMENT '发布标题', - `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', - `group` VARCHAR(128) NOT NULL COMMENT '所属的文件组', - `file_name` VARCHAR(128) NOT NULL COMMENT '配置文件名', - `format` VARCHAR(16) DEFAULT 'text' COMMENT '文件格式,枚举值', - `content` LONGTEXT NOT NULL COMMENT '文件内容', - `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', - `md5` VARCHAR(128) NOT NULL COMMENT 'content的md5值', - `version` BIGINT(11) NOT NULL COMMENT '版本号,每次发布自增1', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否被删除', - `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `create_by` VARCHAR(32) DEFAULT NULL COMMENT '创建人', - `modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', - `modify_by` VARCHAR(32) DEFAULT NULL COMMENT '最后更新人', - `tags` TEXT COMMENT '文件标签', - `active` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否处于使用中', - `description` VARCHAR(512) DEFAULT NULL COMMENT '发布描述', - `type` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '文件类型:1:全量 2:灰度', + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(128) DEFAULT NULL COMMENT '发布标题', + `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', + `group` VARCHAR(128) NOT NULL COMMENT '所属的文件组', + `file_name` VARCHAR(128) NOT NULL COMMENT '配置文件名', + `format` VARCHAR(16) DEFAULT 'text' COMMENT '文件格式,枚举值', + `content` LONGTEXT NOT NULL COMMENT '文件内容', + `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', + `md5` VARCHAR(128) NOT NULL COMMENT 'content的md5值', + `version` BIGINT(11) NOT NULL COMMENT '版本号,每次发布自增1', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否被删除', + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` VARCHAR(32) DEFAULT NULL COMMENT '创建人', + `modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `modify_by` VARCHAR(32) DEFAULT NULL COMMENT '最后更新人', + `tags` TEXT COMMENT '文件标签', + `active` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否处于使用中', + `description` VARCHAR(512) DEFAULT NULL COMMENT '发布描述', + `release_type` VARCHAR(25) NOT NULL DEFAULT '' COMMENT '文件类型:"":全量 gray:灰度', PRIMARY KEY (`id`), UNIQUE KEY `uk_file` (`namespace`, `group`, `file_name`, `name`), KEY `idx_modify_time` (`modify_time`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '配置文件发布表'; + -- -------------------------------------------------------- -- -- Table structure `config_file_release_history` @@ -926,5 +927,6 @@ CREATE TABLE `gray_resource` `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 '最后更新人', + `flag` TINYINT(4) DEFAULT 0 COMMENT '逻辑删除标志位, 0 位有效, 1 为逻辑删除', PRIMARY KEY (`name`) ) ENGINE = InnoDB COMMENT = '灰度资源表'; diff --git a/store/mysql/service_contract.go b/store/mysql/service_contract.go index 6680afc41..3d26c2e70 100644 --- a/store/mysql/service_contract.go +++ b/store/mysql/service_contract.go @@ -228,7 +228,7 @@ func (s *serviceContractStore) DeleteServiceContractInterfaces(contract *model.E // GetMoreServiceContracts 查询服务契约数据 func (s *serviceContractStore) GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) { - querySql := "SELECT id, name, namespace, service, protocol, version, revision, flag,content, " + + querySql := "SELECT id, name, namespace, service, protocol, version, revision, flag, content, " + " UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM service_contract WHERE mtime >= ? " if firstUpdate { mtime = time.Unix(0, 1) @@ -281,7 +281,7 @@ func (s *serviceContractStore) GetMoreServiceContracts(firstUpdate bool, mtime t contractDetailMap := map[string][]*model.InterfaceDescriptor{} if len(idList) > 0 { queryDetailSql := "SELECT id, contract_id, method, path, content, revision," + - "flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime), source " + + "flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime), IFNULL(source, 1) " + " FROM service_contract_detail WHERE contract_id IN (" + strings.Join(idList, ",") + ")" detailRows, err := tx.Query(queryDetailSql) if err != nil { diff --git a/test/data/service_test.yaml b/test/data/service_test.yaml index 37030fa61..fa755a720 100644 --- a/test/data/service_test.yaml +++ b/test/data/service_test.yaml @@ -23,7 +23,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -34,7 +34,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -56,7 +56,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -67,7 +67,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -78,7 +78,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -89,7 +89,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -122,7 +122,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -139,7 +139,7 @@ bootstrap: # - stdout # errorOutputPaths: # - stderr - discoverLocal: + discoverstat: rotateOutputPath: log/statis/polaris-discoverstat.log errorRotateOutputPath: log/statis/polaris-discoverstat-error.log rotationMaxSize: 100 diff --git a/test/data/service_test_sqldb.yaml b/test/data/service_test_sqldb.yaml index cb1d6016b..a813f7793 100644 --- a/test/data/service_test_sqldb.yaml +++ b/test/data/service_test_sqldb.yaml @@ -23,7 +23,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -34,7 +34,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -45,7 +45,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error # outputPaths: # - stdout # errorOutputPaths: @@ -56,7 +56,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -67,7 +67,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -78,7 +78,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -89,7 +89,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -100,7 +100,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error # outputPaths: # - stdout # errorOutputPaths: @@ -111,7 +111,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error # outputPaths: # - stdout # errorOutputPaths: @@ -122,7 +122,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: info # outputPaths: # - stdout # errorOutputPaths: @@ -133,19 +133,19 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error onlyContent: true # outputPaths: # - stdout # errorOutputPaths: # - stderr - discoverLocal: + discoverstat: rotateOutputPath: log/statis/polaris-discoverstat.log errorRotateOutputPath: log/statis/polaris-discoverstat-error.log rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error # outputPaths: # - stdout # errorOutputPaths: @@ -156,7 +156,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error # outputPaths: # - stdout # errorOutputPaths: @@ -168,7 +168,7 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 rotationMaxDurationForHour: 24 - outputLevel: debug + outputLevel: error onlyContent: true # outputPaths: # - stdout @@ -180,7 +180,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: debug + outputLevel: error # outputPaths: # - stdout # errorOutputPaths: diff --git a/test/outlier/backend/main.go b/test/outlier/backend/main.go index a3b1fb8a5..df39c4e66 100644 --- a/test/outlier/backend/main.go +++ b/test/outlier/backend/main.go @@ -41,18 +41,18 @@ func main() { }) http.HandleFunc("/fail", func(w http.ResponseWriter, r *http.Request) { status = 1 - w.Write([]byte("ok")) + _, _ = w.Write([]byte("ok")) }) http.HandleFunc("/success", func(w http.ResponseWriter, r *http.Request) { status = 0 - w.Write([]byte("ok")) + _, _ = w.Write([]byte("ok")) }) http.HandleFunc("/healthCheck", func(w http.ResponseWriter, r *http.Request) { fmt.Printf("%v /healthCheck request\n", time.Now().Format("2006-01-02 15:04:05")) if status == 1 { time.Sleep(5 * time.Second) } else { - w.Write([]byte("ok")) + _, _ = w.Write([]byte("ok")) } }) diff --git a/test/outlier/frontend/main.go b/test/outlier/frontend/main.go index 66818b7ba..48d801417 100644 --- a/test/outlier/frontend/main.go +++ b/test/outlier/frontend/main.go @@ -38,7 +38,7 @@ func main() { if interval == 0 { interval = 500 } - w.Write(data) + _, _ = w.Write(data) }) go func() { for { diff --git a/test/suit/test_suit.go b/test/suit/test_suit.go index 19bb892b3..b8d30e90f 100644 --- a/test/suit/test_suit.go +++ b/test/suit/test_suit.go @@ -37,6 +37,7 @@ import ( "github.com/polarismesh/polaris/auth" _ "github.com/polarismesh/polaris/auth/defaultauth" "github.com/polarismesh/polaris/cache" + cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/eventhub" "github.com/polarismesh/polaris/common/log" @@ -44,6 +45,7 @@ import ( "github.com/polarismesh/polaris/common/metrics" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/config" + _ "github.com/polarismesh/polaris/config/interceptor" ns "github.com/polarismesh/polaris/namespace" "github.com/polarismesh/polaris/plugin" _ "github.com/polarismesh/polaris/plugin/cmdb/memory" @@ -61,6 +63,7 @@ import ( "github.com/polarismesh/polaris/service" "github.com/polarismesh/polaris/service/batch" "github.com/polarismesh/polaris/service/healthcheck" + _ "github.com/polarismesh/polaris/service/interceptor" "github.com/polarismesh/polaris/store" "github.com/polarismesh/polaris/store/boltdb" _ "github.com/polarismesh/polaris/store/boltdb" @@ -107,16 +110,19 @@ type Bootstrap struct { } type TestConfig struct { - Bootstrap Bootstrap `yaml:"bootstrap"` - Cache cache.Config `yaml:"cache"` - Namespace ns.Config `yaml:"namespace"` - Naming service.Config `yaml:"naming"` - Config config.Config `yaml:"config"` - HealthChecks healthcheck.Config `yaml:"healthcheck"` - Store store.Config `yaml:"store"` - Auth auth.Config `yaml:"auth"` - Plugin plugin.Config `yaml:"plugin"` - ReplaceStore store.Store + Bootstrap Bootstrap `yaml:"bootstrap"` + Cache cache.Config `yaml:"cache"` + Namespace ns.Config `yaml:"namespace"` + Naming service.Config `yaml:"naming"` + DisableConfig bool + Config config.Config `yaml:"config"` + HealthChecks healthcheck.Config `yaml:"healthcheck"` + Store store.Config `yaml:"store"` + DisableAuth bool + Auth auth.Config `yaml:"auth"` + Plugin plugin.Config `yaml:"plugin"` + ReplaceStore store.Store + ServiceCacheEntries []cachetypes.ConfigEntry } var InjectTestDataClean func() TestDataClean @@ -225,6 +231,8 @@ func (d *DiscoverTestSuit) loadConfig() error { fmt.Printf("[ERROR] %v\n", err) return err } + d.cfg.Naming.Interceptors = service.GetChainOrder() + d.cfg.Config.Interceptors = config.GetChainOrder() return err } @@ -310,17 +318,22 @@ func (d *DiscoverTestSuit) initialize(opts ...options) error { panic(err) } d.cacheMgr = cacheMgn + _ = d.cacheMgr.OpenResourceCache(cachetypes.ConfigEntry{ + Name: cachetypes.GrayName, + }) - // 初始化鉴权层 - userMgn, strategyMgn, err := auth.TestInitialize(ctx, &d.cfg.Auth, d.Storage, cacheMgn) - if err != nil { - panic(err) + if !d.cfg.DisableAuth { + // 初始化鉴权层 + userMgn, strategyMgn, err := auth.TestInitialize(ctx, &d.cfg.Auth, d.Storage, cacheMgn) + if err != nil { + panic(err) + } + d.userMgn = userMgn + d.strategyMgn = strategyMgn } - d.userMgn = userMgn - d.strategyMgn = strategyMgn // 初始化命名空间模块 - namespaceSvr, err := ns.TestInitialize(ctx, &d.cfg.Namespace, d.Storage, cacheMgn, userMgn, strategyMgn) + namespaceSvr, err := ns.TestInitialize(ctx, &d.cfg.Namespace, d.Storage, cacheMgn, d.userMgn, d.strategyMgn) if err != nil { panic(err) } @@ -364,24 +377,28 @@ func (d *DiscoverTestSuit) initialize(opts ...options) error { healthCheckServer.SetServiceCache(cacheMgn.Service()) healthCheckServer.SetInstanceCache(cacheMgn.Instance()) - val, originVal, err := service.TestInitialize(ctx, &d.cfg.Naming, &d.cfg.Cache, bc, cacheMgn, d.Storage, namespaceSvr, - healthCheckServer, userMgn, strategyMgn) + val, originVal, err := service.TestInitialize(ctx, &d.cfg.Naming, &d.cfg.Cache, d.cfg.ServiceCacheEntries, + bc, cacheMgn, d.Storage, namespaceSvr, healthCheckServer, d.userMgn, d.strategyMgn) if err != nil { panic(err) } d.server = val d.originSvr = originVal - confVal, confOriginVal, err := config.TestInitialize(ctx, d.cfg.Config, d.Storage, cacheMgn, namespaceSvr, userMgn, strategyMgn) - if err != nil { - panic(err) + if !d.cfg.DisableConfig { + confVal, confOriginVal, err := config.TestInitialize(ctx, d.cfg.Config, d.Storage, cacheMgn, namespaceSvr, d.userMgn, d.strategyMgn) + if err != nil { + panic(err) + } + d.configServer = confVal + d.configOriginSvr = confOriginVal } - d.configServer = confVal - d.configOriginSvr = confOriginVal // 多等待一会 - d.updateCacheInterval = d.server.Cache().GetUpdateCacheInterval() + time.Millisecond*500 - + d.updateCacheInterval = d.cacheMgr.GetUpdateCacheInterval() + time.Millisecond*500 + if err := cache.TestRun(ctx, d.cacheMgr); err != nil { + return err + } time.Sleep(5 * time.Second) return nil } diff --git a/version b/version index f72019128..13e94ce5c 100644 --- a/version +++ b/version @@ -1 +1 @@ -v1.18.0 +v1.18.0 \ No newline at end of file