From 6ffbeeb26fe2874e53c25d2345528ff5a91bf299 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Sun, 25 Sep 2022 16:35:01 +0800 Subject: [PATCH 01/23] docs:add error code desc --- release/conf/i18n/en.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/release/conf/i18n/en.toml b/release/conf/i18n/en.toml index 5236d4dcc..b0fa36774 100644 --- a/release/conf/i18n/en.toml +++ b/release/conf/i18n/en.toml @@ -156,11 +156,19 @@ 401002 = "auth token empty" #EmptyAutToken 401003 = "token already disabled" #TokenDisabled 401004 = "token not existed" #TokenNotExisted +<<<<<<< HEAD 403001 = "token verify exception" #AuthTokenVerifyException 403002 = "operation role exception" #OperationRoleException 404001 = "not found the host cmdb" #CMDBNotFindHost 409000 = "data is conflict, please try again" #DataConflict 429001 = "instance has too many requests" #InstanceTooManyRequests +======= +403001 = "server limit the ip access" #IPRateLimit +403002 = "server limit the api access" #APIRateLimit +404001 = "not found the host cmdb" #CMDBNotFindHost +409000 = "data is conflict, please try again" #DataConflict +429001 = "your instance has too many requests" #InstanceTooManyRequests +>>>>>>> c07080f0... docs:add error code desc 500000 = "execute exception" #ExecuteException 500001 = "store layer exception" #StoreLayerException 500002 = "cmdb plugin exception" #CMDBPluginException @@ -169,3 +177,8 @@ 500006 = "parse circuit breaker failed" #ParseCircuitBreakerException 500007 = "heartbeat execute exception" #HeartbeatException 500008 = "instance async regist timeout" #InstanceRegisTimeout +<<<<<<< HEAD +======= +500100 = "token verify exception" #AuthTokenVerifyException +500101 = "operation role exception" #OperationRoleException +>>>>>>> c07080f0... docs:add error code desc From 67406c5a40e285a507c0a3e102c1d8e7ea80f49a Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Wed, 19 Oct 2022 16:00:33 +0800 Subject: [PATCH 02/23] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4license-checker?= =?UTF-8?q?=E7=9A=84=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/golangci-lint.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 394cb4df3..4fa69ac1e 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,10 +15,6 @@ name: golangci-lint on: - push: - branches: - - main - - release* pull_request: branches: - main From 6b2b913133321398047db3e029967154b6759f9c Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Wed, 19 Oct 2022 17:08:41 +0800 Subject: [PATCH 03/23] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4license-checker?= =?UTF-8?q?=E7=9A=84=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/golangci-lint.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 4fa69ac1e..394cb4df3 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,6 +15,10 @@ name: golangci-lint on: + push: + branches: + - main + - release* pull_request: branches: - main From c7825aa53d9dc5d934866f0e80ddd71afcdb46fa Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Tue, 27 Jun 2023 11:58:15 +0800 Subject: [PATCH 04/23] =?UTF-8?q?hotfix:=E4=BF=AE=E5=A4=8D=E9=89=B4?= =?UTF-8?q?=E6=9D=83interceptor=E9=81=97=E6=BC=8F=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release/conf/i18n/en.toml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/release/conf/i18n/en.toml b/release/conf/i18n/en.toml index b0fa36774..5236d4dcc 100644 --- a/release/conf/i18n/en.toml +++ b/release/conf/i18n/en.toml @@ -156,19 +156,11 @@ 401002 = "auth token empty" #EmptyAutToken 401003 = "token already disabled" #TokenDisabled 401004 = "token not existed" #TokenNotExisted -<<<<<<< HEAD 403001 = "token verify exception" #AuthTokenVerifyException 403002 = "operation role exception" #OperationRoleException 404001 = "not found the host cmdb" #CMDBNotFindHost 409000 = "data is conflict, please try again" #DataConflict 429001 = "instance has too many requests" #InstanceTooManyRequests -======= -403001 = "server limit the ip access" #IPRateLimit -403002 = "server limit the api access" #APIRateLimit -404001 = "not found the host cmdb" #CMDBNotFindHost -409000 = "data is conflict, please try again" #DataConflict -429001 = "your instance has too many requests" #InstanceTooManyRequests ->>>>>>> c07080f0... docs:add error code desc 500000 = "execute exception" #ExecuteException 500001 = "store layer exception" #StoreLayerException 500002 = "cmdb plugin exception" #CMDBPluginException @@ -177,8 +169,3 @@ 500006 = "parse circuit breaker failed" #ParseCircuitBreakerException 500007 = "heartbeat execute exception" #HeartbeatException 500008 = "instance async regist timeout" #InstanceRegisTimeout -<<<<<<< HEAD -======= -500100 = "token verify exception" #AuthTokenVerifyException -500101 = "operation role exception" #OperationRoleException ->>>>>>> c07080f0... docs:add error code desc From f4d04f9d7026be7d985f99382ed2bc36ba3e95fb Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Thu, 28 Mar 2024 22:47:11 +0800 Subject: [PATCH 05/23] refactor:store lane rule --- common/model/naming.go | 8 + config/config_chain.go | 2 + plugin/healthchecker/leader/peer.go | 199 ++++++++++++------ plugin/healthchecker/leader/peer_mock_test.go | 8 +- plugin/healthchecker/leader/peer_test.go | 4 +- 5 files changed, 149 insertions(+), 72 deletions(-) diff --git a/common/model/naming.go b/common/model/naming.go index f4a234c7d..bcda9c854 100644 --- a/common/model/naming.go +++ b/common/model/naming.go @@ -166,6 +166,14 @@ type ServiceAlias struct { ExportTo map[string]struct{} } +func (s *ServiceAlias) ListExportTo() []*wrappers.StringValue { + ret := make([]*wrappers.StringValue, 0, len(s.ExportTo)) + for i := range s.ExportTo { + ret = append(ret, &wrappers.StringValue{Value: i}) + } + return ret +} + // WeightType 服务下实例的权重类型 type WeightType uint32 diff --git a/config/config_chain.go b/config/config_chain.go index ff1f5c8d5..92854f80e 100644 --- a/config/config_chain.go +++ b/config/config_chain.go @@ -145,6 +145,7 @@ func (chain *CryptoConfigFileChain) AfterGetFileRelease(ctx context.Context, utils.ZapNamespace(release.Namespace), utils.ZapGroup(release.Group), utils.ZapFileName(release.Name), zap.Error(err)) } + delete(release.Metadata, model.MetaKeyConfigFileDataKey) return release, nil } @@ -167,6 +168,7 @@ func (chain *CryptoConfigFileChain) AfterGetFileHistory(ctx context.Context, utils.ZapNamespace(history.Namespace), utils.ZapGroup(history.Group), utils.ZapFileName(history.Name), zap.Error(err)) } + delete(history.Metadata, model.MetaKeyConfigFileDataKey) return history, err } diff --git a/plugin/healthchecker/leader/peer.go b/plugin/healthchecker/leader/peer.go index eba5d4eee..443b6523f 100644 --- a/plugin/healthchecker/leader/peer.go +++ b/plugin/healthchecker/leader/peer.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "io" "math/rand" "sync" "sync/atomic" @@ -38,7 +37,8 @@ import ( ) var ( - ErrorLeaderNotAlive = errors.New("leader not alive") + ErrorLeaderNotAlive = errors.New("leader not alive") + ErrorConnectionNotAvailable = errors.New("connection not available") ) type ( @@ -50,8 +50,8 @@ type ( var ( NewLocalPeerFunc = newLocalPeer NewRemotePeerFunc = newRemotePeer - ConnectPeer = doConnect + ConnectPeer = doConnect CreateBeatClientFunc = createBeatClient ) @@ -126,13 +126,15 @@ type RemotePeer struct { host string // Port peer listen port to provider grpc service port uint32 + // + cmutex *sync.RWMutex // Conn grpc connection - conns []*grpc.ClientConn + conns map[int]*grpc.ClientConn // Puters 批量心跳发送, 由于一个 stream 对于 server 是一个 goroutine,为了加快 follower 发往 leader 的效率 // 这里采用多个 Putter Client 创建多个 Stream - puters []*beatSender + puters map[int]*beatSender // Cache data storage - cache BeatRecordCache + Cache BeatRecordCache // cancel . cancel context.CancelFunc // conf . @@ -147,25 +149,17 @@ func (p *RemotePeer) Initialize(conf Config) { p.conf = conf } -func (p *RemotePeer) Serve(ctx context.Context, checker *LeaderHealthChecker, +func (p *RemotePeer) Serve(_ context.Context, checker *LeaderHealthChecker, listenIP string, listenPort uint32) error { - ctx, cancel := context.WithCancel(ctx) + ctx, cancel := context.WithCancel(context.Background()) p.cancel = cancel p.host = listenIP p.port = listenPort - - connectPeer := ConnectPeer - val := ctx.Value(ConnectFuncContextKey{}) - if val != nil { - // 正常情况下只是为了测试场景使用 - connectPeer = val.(ConnectPeerFunc) - } - if err := connectPeer(p); err != nil { + p.cmutex = &sync.RWMutex{} + if err := ConnectPeer(p); err != nil { return err } - p.cache = newRemoteBeatRecordCache(p.GetFunc, p.PutFunc, p.DelFunc, p.Ping) - // 启动前先设置 Leader 为 alive 状态 - atomic.StoreInt32(&p.leaderAlive, 1) + p.Cache = newRemoteBeatRecordCache(p.GetFunc, p.PutFunc, p.DelFunc, p.Ping) go p.checkLeaderAlive(ctx) return nil } @@ -179,8 +173,11 @@ func (p *RemotePeer) IsAlive() bool { } func (p *RemotePeer) Ping() error { - client := p.choseOneClient() - _, err := client.BatchGetHeartbeat(context.Background(), &apiservice.GetHeartbeatsRequest{}, + client, err := p.choseOneClient() + if err != nil { + return err + } + _, err = client.BatchGetHeartbeat(context.Background(), &apiservice.GetHeartbeatsRequest{}, grpc.Header(&metadata.MD{ sendResource: []string{utils.LocalHost}, })) @@ -200,7 +197,10 @@ func (p *RemotePeer) GetFunc(req *apiservice.GetHeartbeatsRequest) (*apiservice. }) observer.Observe(float64(time.Since(start).Milliseconds())) }() - client := p.choseOneClient() + client, err := p.choseOneClient() + if err != nil { + return nil, err + } resp, err := client.BatchGetHeartbeat(context.Background(), req, grpc.Header(&metadata.MD{ sendResource: []string{utils.LocalHost}, })) @@ -226,10 +226,14 @@ func (p *RemotePeer) PutFunc(req *apiservice.HeartbeatsRequest) error { }) observer.Observe(float64(time.Since(start).Milliseconds())) }() - if err := p.choseOneSender().Send(req); err != nil { + client, err := p.choseOneSender() + if err != nil { + return err + } + if err := client.Send(req); err != nil { code = "-1" - plog.Error("[HealthCheck][Leader] send put record request", zap.String("host", p.Host()), - zap.Uint32("port", p.port), zap.Error(err)) + plog.Error("[HealthCheck][Leader] send put record request", zap.String("info", req.String()), + zap.String("host", p.Host()), zap.Uint32("port", p.port), zap.Error(err)) return err } return nil @@ -248,7 +252,10 @@ func (p *RemotePeer) DelFunc(req *apiservice.DelHeartbeatsRequest) error { }) observer.Observe(float64(time.Since(start).Milliseconds())) }() - client := p.choseOneClient() + client, err := p.choseOneClient() + if err != nil { + return err + } if _, err := client.BatchDelHeartbeat(context.Background(), req, grpc.Header(&metadata.MD{ sendResource: []string{utils.LocalHost}, })); err != nil { @@ -261,7 +268,7 @@ func (p *RemotePeer) DelFunc(req *apiservice.DelHeartbeatsRequest) error { } func (p *RemotePeer) Storage() BeatRecordCache { - return p.cache + return p.Cache } // Close close peer life @@ -273,14 +280,28 @@ func (p *RemotePeer) Close() error { return nil } -func (p *RemotePeer) choseOneClient() apiservice.PolarisHeartbeatGRPCClient { +func (p *RemotePeer) choseOneClient() (apiservice.PolarisHeartbeatGRPCClient, error) { + p.cmutex.RLock() + defer p.cmutex.RUnlock() + + if len(p.conns) == 0 { + return nil, ErrorConnectionNotAvailable + } + index := rand.Intn(len(p.conns)) return CreateBeatClientFunc(p.conns[index]) } -func (p *RemotePeer) choseOneSender() *beatSender { +func (p *RemotePeer) choseOneSender() (*beatSender, error) { + p.cmutex.RLock() + defer p.cmutex.RUnlock() + + if len(p.puters) == 0 { + return nil, ErrorConnectionNotAvailable + } + index := rand.Intn(len(p.puters)) - return p.puters[index] + return p.puters[index], nil } func (p *RemotePeer) checkLeaderAlive(ctx context.Context) { @@ -310,6 +331,9 @@ func (p *RemotePeer) checkLeaderAlive(ctx context.Context) { } func (p *RemotePeer) doClose() { + p.cmutex.Lock() + defer p.cmutex.Unlock() + if p.cancel != nil { p.cancel() } @@ -325,13 +349,63 @@ func (p *RemotePeer) doClose() { } } -func createBeatClient(conn *grpc.ClientConn) apiservice.PolarisHeartbeatGRPCClient { - return apiservice.NewPolarisHeartbeatGRPCClient(conn) +func createBeatClient(conn *grpc.ClientConn) (apiservice.PolarisHeartbeatGRPCClient, error) { + return apiservice.NewPolarisHeartbeatGRPCClient(conn), nil +} + +func (p *RemotePeer) reconnect(i int) { + for { + if ok := p.doReconnect(i); ok { + plog.Info("[HealthCheck][Leader] reconnect all success", zap.String("host", p.Host()), + zap.Uint32("port", p.port)) + return + } + time.Sleep(time.Second) + } +} + +func (p *RemotePeer) doReconnect(i int) bool { + p.cmutex.Lock() + defer p.cmutex.Unlock() + + // 先关闭老的连接 + if oldConn := p.conns[i]; oldConn != nil { + _ = oldConn.Close() + } + if oldSender := p.puters[i]; oldSender != nil { + _ = oldSender.close() + } + + // 先删除有问题的 connection + delete(p.conns, i) + delete(p.puters, i) + + conn, err := grpc.DialContext(context.Background(), fmt.Sprintf("%s:%d", p.Host(), p.port), + grpc.WithBlock(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithTimeout(5*time.Second), + ) + if err != nil { + plog.Error("[HealthCheck][Leader] reconnect connection", zap.String("host", p.Host()), + zap.Uint32("port", p.port), zap.Error(err)) + return false + } + + sender, err := newBeatSender(i, conn, p) + if err != nil { + plog.Error("[HealthCheck][Leader] reconnect grpc-client", zap.String("host", p.Host()), + zap.Uint32("port", p.port), zap.Error(err)) + return false + } + + p.conns[i] = conn + p.puters[i] = sender + return true } func doConnect(p *RemotePeer) error { - p.conns = make([]*grpc.ClientConn, 0, streamNum) - p.puters = make([]*beatSender, 0, streamNum) + p.conns = make(map[int]*grpc.ClientConn, streamNum) + p.puters = make(map[int]*beatSender, streamNum) for i := 0; i < streamNum; i++ { conn, err := grpc.DialContext(context.Background(), fmt.Sprintf("%s:%d", p.Host(), p.port), grpc.WithBlock(), @@ -342,40 +416,40 @@ func doConnect(p *RemotePeer) error { p.doClose() return err } - p.conns = append(p.conns, conn) + p.conns[i] = conn } for i := 0; i < streamNum; i++ { - client := apiservice.NewPolarisHeartbeatGRPCClient(p.conns[i]) - puter, err := client.BatchHeartbeat(context.Background(), grpc.Header(&metadata.MD{ - sendResource: []string{utils.LocalHost}, - })) + sender, err := newBeatSender(i, p.conns[i], p) if err != nil { p.doClose() return err } - sender := &beatSender{ - peer: p, - lock: &sync.RWMutex{}, - sender: puter, - } - p.puters = append(p.puters, sender) + p.puters[i] = sender } return nil } -func newBeatSender(p *RemotePeer, client apiservice.PolarisHeartbeatGRPC_BatchHeartbeatClient) *beatSender { - ctx, cancel := context.WithCancel(context.Background()) +func newBeatSender(index int, conn *grpc.ClientConn, p *RemotePeer) (*beatSender, error) { + client := apiservice.NewPolarisHeartbeatGRPCClient(conn) + puter, err := client.BatchHeartbeat(context.Background(), grpc.Header(&metadata.MD{ + sendResource: []string{utils.LocalHost}, + })) + if err != nil { + return nil, err + } + sender := &beatSender{ + index: index, peer: p, + sender: puter, lock: &sync.RWMutex{}, - sender: client, - cancel: cancel, } - go sender.doRecv(ctx) - return sender + go sender.Recv() + return sender, nil } type beatSender struct { + index int peer *RemotePeer lock *sync.RWMutex sender apiservice.PolarisHeartbeatGRPC_BatchHeartbeatClient @@ -388,27 +462,20 @@ func (s *beatSender) Send(req *apiservice.HeartbeatsRequest) error { return s.sender.Send(req) } -func (s *beatSender) doRecv(ctx context.Context) { +// Recv . +func (s *beatSender) Recv() { for { - select { - case <-ctx.Done(): - plog.Info("[HealthCheck][Leader] cancel receive put record result", zap.String("host", s.peer.Host()), - zap.Uint32("port", s.peer.port)) + if _, err := s.sender.Recv(); err != nil { + plog.Error("[HealthCheck][Leader] receive put record result", zap.String("host", s.peer.Host()), + zap.Uint32("port", s.peer.port), zap.Error(err)) + go s.peer.reconnect(s.index) return - default: - if _, err := s.sender.Recv(); err != nil { - if err != io.EOF { - plog.Error("[HealthCheck][Leader] receive put record result", zap.String("host", s.peer.Host()), - zap.Uint32("port", s.peer.port), zap.Error(err)) - } - } } } } func (s *beatSender) close() error { - if s.cancel != nil { - s.cancel() - } + s.lock.Lock() + defer s.lock.Unlock() return s.sender.CloseSend() } diff --git a/plugin/healthchecker/leader/peer_mock_test.go b/plugin/healthchecker/leader/peer_mock_test.go index e4daa8ed5..074d68a8b 100644 --- a/plugin/healthchecker/leader/peer_mock_test.go +++ b/plugin/healthchecker/leader/peer_mock_test.go @@ -261,11 +261,11 @@ func (ms *MockPolarisGRPCServer) mockRemotePeerConnect(p *RemotePeer) error { if err != nil { return err } - p.conns = []*grpc.ClientConn{ - &grpc.ClientConn{}, + p.conns = map[int]*grpc.ClientConn{ + 0: &grpc.ClientConn{}, } - p.puters = []*beatSender{ - { + p.puters = map[int]*beatSender{ + 0: { lock: &sync.RWMutex{}, sender: sender, peer: p, diff --git a/plugin/healthchecker/leader/peer_test.go b/plugin/healthchecker/leader/peer_test.go index 3b325d53c..a2b7197b0 100644 --- a/plugin/healthchecker/leader/peer_test.go +++ b/plugin/healthchecker/leader/peer_test.go @@ -126,10 +126,10 @@ func TestRemotePeer(t *testing.T) { cancel() }) - CreateBeatClientFunc = func(conn *grpc.ClientConn) service_manage.PolarisHeartbeatGRPCClient { + CreateBeatClientFunc = func(conn *grpc.ClientConn) (service_manage.PolarisHeartbeatGRPCClient, error) { return &MockPolarisHeartbeatClient{ peer: mockSvr.peer, - } + }, nil } ctx = context.WithValue(ctx, ConnectFuncContextKey{}, ConnectPeerFunc(mockSvr.mockRemotePeerConnect)) From f8ab936ccfb5f24daa7f164d29d0b79c8363de04 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Tue, 16 Apr 2024 12:46:29 +0800 Subject: [PATCH 06/23] refactor:fix config notify watch client may cause client hangup --- apiserver/eurekaserver/write_test.go | 1 - apiserver/nacosserver/model/config.go | 25 +- apiserver/nacosserver/v1/access.go | 8 +- apiserver/nacosserver/v1/config/access.go | 43 +- .../nacosserver/v1/config/config_file.go | 188 +++ apiserver/nacosserver/v1/http/utils.go | 50 + cache/api/types.go | 51 +- cache/cache.go | 5 + cache/default.go | 2 + cache/mock/cache_mock.go | 171 +- cache/service/lane.go | 356 ++++ cache/service/service_contract.go | 270 +-- common/api/v1/config_response.go | 7 + common/api/v1/naming_response.go | 10 + common/model/contract.go | 26 +- common/model/lane.go | 202 +++ common/utils/common.go | 12 + config/api.go | 6 +- config/client.go | 14 +- config/config_file.go | 56 +- config/config_file_group.go | 47 +- config/config_file_release.go | 115 +- config/config_file_release_history.go | 16 +- config/config_file_template.go | 16 - .../auth/config_file_release_authibility.go | 29 + config/interceptor/auth/server_authability.go | 2 +- config/interceptor/paramcheck/client_check.go | 219 +++ .../paramcheck/config_file_check.go | 113 ++ .../paramcheck/config_file_group_check.go | 99 ++ .../paramcheck/config_file_release_check.go | 183 +++ .../config_file_release_history_check.go | 52 + .../paramcheck/config_file_template_check.go | 61 + config/interceptor/paramcheck/log.go | 25 + config/interceptor/paramcheck/server.go | 176 ++ config/interceptor/register.go | 20 +- config/server.go | 13 +- config/test_export.go | 2 +- config/watcher.go | 17 +- go.mod | 2 +- go.sum | 4 +- release/conf/polaris-server.yaml | 32 +- service/api_v1.go | 2 + service/client_v1.go | 225 +-- .../auth/circuitbreaker_config_authability.go | 24 +- .../auth/circuitbreaker_rule_authability.go | 10 +- .../interceptor/auth/client_v1_authability.go | 42 +- .../auth/faultdetect_config_authability.go | 8 +- .../interceptor/auth/instance_authability.go | 16 +- .../auth/l5_service_authability.go | 4 +- .../auth/ratelimit_config_authability.go | 10 +- .../auth/routing_config_v1_authability.go | 8 +- .../auth/routing_config_v2_authability.go | 10 +- .../interceptor/auth/server_authability.go | 24 +- .../auth/service_alias_authability.go | 8 +- .../interceptor/auth/service_authability.go | 18 +- .../auth/service_contract_authability.go | 14 +- service/interceptor/paramcheck/check.go | 341 ++++ service/interceptor/paramcheck/client.go | 198 +++ service/interceptor/paramcheck/server.go | 49 + service/interceptor/register.go | 10 +- service/service_contract.go | 25 +- store/boltdb/default.go | 2 + store/boltdb/lane.go | 371 +++++ store/boltdb/service_contract.go | 247 ++- store/discover_api.go | 33 +- store/mock/api_mock.go | 196 ++- store/mysql/common.go | 8 + store/mysql/default.go | 2 + store/mysql/lane.go | 542 +++++++ store/mysql/scripts/delta/v1_18_0-v1_18_1.sql | 71 + store/mysql/scripts/polaris_server.sql | 1441 +++++++++-------- store/mysql/service_contract.go | 394 +++-- 72 files changed, 5381 insertions(+), 1718 deletions(-) create mode 100644 cache/service/lane.go create mode 100644 common/model/lane.go create mode 100644 config/interceptor/paramcheck/client_check.go create mode 100644 config/interceptor/paramcheck/config_file_check.go create mode 100644 config/interceptor/paramcheck/config_file_group_check.go create mode 100644 config/interceptor/paramcheck/config_file_release_check.go create mode 100644 config/interceptor/paramcheck/config_file_release_history_check.go create mode 100644 config/interceptor/paramcheck/config_file_template_check.go create mode 100644 config/interceptor/paramcheck/log.go create mode 100644 config/interceptor/paramcheck/server.go create mode 100644 service/interceptor/paramcheck/check.go create mode 100644 service/interceptor/paramcheck/client.go create mode 100644 service/interceptor/paramcheck/server.go create mode 100644 store/boltdb/lane.go create mode 100644 store/mysql/lane.go create mode 100644 store/mysql/scripts/delta/v1_18_0-v1_18_1.sql diff --git a/apiserver/eurekaserver/write_test.go b/apiserver/eurekaserver/write_test.go index 67e1a1127..b7f983ab5 100644 --- a/apiserver/eurekaserver/write_test.go +++ b/apiserver/eurekaserver/write_test.go @@ -127,7 +127,6 @@ func TestEurekaServer_renew(t *testing.T) { 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() diff --git a/apiserver/nacosserver/model/config.go b/apiserver/nacosserver/model/config.go index c0b1b2aa2..5ee24902e 100644 --- a/apiserver/nacosserver/model/config.go +++ b/apiserver/nacosserver/model/config.go @@ -28,6 +28,8 @@ import ( "github.com/emicklei/go-restful/v3" "github.com/polarismesh/specification/source/go/api/v1/config_manage" "google.golang.org/protobuf/types/known/wrapperspb" + + commonmodel "github.com/polarismesh/polaris/common/model" ) type ConfigFileBase struct { @@ -78,18 +80,17 @@ func (i *ConfigFile) ToSpecConfigFile() *config_manage.ConfigFilePublishInfo { ReleaseDescription: wrapperspb.String(i.Description), } - // TODO 暂时不支持 Nacos 的配置加解密 - // isCipher := strings.HasPrefix(i.DataId, "cipher-") && i.DataId != "cipher-" - // if isCipher { - // specFile.Encrypted = wrapperspb.Bool(true) - // specFile.EncryptAlgo = wrapperspb.String(strings.Split(i.DataId, "-")[1]) - // if i.EncryptedDataKey != "" { - // specFile.Tags = append(specFile.Tags, &config_manage.ConfigFileTag{ - // Key: wrapperspb.String(utils.ConfigFileTagKeyDataKey), - // Value: wrapperspb.String(i.EncryptedDataKey), - // }) - // } - // } + isCipher := strings.HasPrefix(i.DataId, "cipher-") && i.DataId != "cipher-" + if isCipher { + specFile.Encrypted = wrapperspb.Bool(true) + specFile.EncryptAlgo = wrapperspb.String(strings.Split(i.DataId, "-")[1]) + if i.EncryptedDataKey != "" { + specFile.Tags = append(specFile.Tags, &config_manage.ConfigFileTag{ + Key: wrapperspb.String(commonmodel.MetaKeyConfigFileDataKey), + Value: wrapperspb.String(i.EncryptedDataKey), + }) + } + } return specFile } diff --git a/apiserver/nacosserver/v1/access.go b/apiserver/nacosserver/v1/access.go index 7c8ebf439..52364a3bc 100644 --- a/apiserver/nacosserver/v1/access.go +++ b/apiserver/nacosserver/v1/access.go @@ -25,15 +25,11 @@ import ( func (n *NacosV1Server) GetAuthServer() (*restful.WebService, error) { ws := new(restful.WebService) - ws.Route(ws.POST("/v1/auth/login").To(n.Login)) - ws.Route(ws.POST("/v1/auth/users/login").To(n.Login)) + ws.Route(ws.POST("/nacos/v1/auth/login").To(n.Login)) + ws.Route(ws.POST("/nacos/v1/auth/users/login").To(n.Login)) return ws, nil } -func (n *NacosV1Server) addSystemAccess(ws *restful.WebService) { - ws.Route(ws.GET("/operator/metrics").To(n.ServerHealthStatus)) -} - func (n *NacosV1Server) Login(req *restful.Request, rsp *restful.Response) { handler := nacoshttp.Handler{ Request: req, diff --git a/apiserver/nacosserver/v1/config/access.go b/apiserver/nacosserver/v1/config/access.go index 266f6e7a7..ed484fdda 100644 --- a/apiserver/nacosserver/v1/config/access.go +++ b/apiserver/nacosserver/v1/config/access.go @@ -18,6 +18,7 @@ package config import ( + "archive/zip" "net/http" "github.com/emicklei/go-restful/v3" @@ -35,12 +36,21 @@ func (n *ConfigServer) GetClientServer() (*restful.WebService, error) { } func (n *ConfigServer) addConfigFileAccess(ws *restful.WebService) { - ws.Route(ws.POST("/").To(n.PublishConfig)) + ws.Route(ws.POST("/").To(n.Dispatch)) ws.Route(ws.GET("/").To(n.GetConfig)) ws.Route(ws.DELETE("/").To(n.DeleteConfig)) ws.Route(ws.POST("/listener").To(n.WatchConfigs)) } +func (n *ConfigServer) Dispatch(req *restful.Request, rsp *restful.Response) { + switch req.Request.URL.RawQuery { + case "": + n.PublishConfig(req, rsp) + case "import=true": + n.ConfigImport(req, rsp) + } +} + func (n *ConfigServer) PublishConfig(req *restful.Request, rsp *restful.Response) { handler := nacoshttp.Handler{ Request: req, @@ -123,6 +133,37 @@ func (n *ConfigServer) WatchConfigs(req *restful.Request, rsp *restful.Response) n.handleWatch(handler.ParseHeaderContext(), listenCtx, rsp) } +func (n *ConfigServer) ConfigImport(req *restful.Request, rsp *restful.Response) { + handler := nacoshttp.Handler{ + Request: req, + Response: rsp, + } + + var metaDataItem *ZipItem + var items = make([]*ZipItem, 0, 32) + + handler.ProcessZip(func(f *zip.File, data []byte) { + if (f.Name == ConfigExportMetadata || f.Name == ConfigExpotrMetadataV2) && metaDataItem == nil { + metaDataItem = &ZipItem{ + Name: f.Name, + Data: data, + } + return + } + items = append(items, &ZipItem{ + Name: f.Name, + Data: data, + }) + }) + + policy := req.QueryParameter("policy") + + n.handleConfigImport(handler.ParseHeaderContext(), policy, &UnZipResult{ + Meta: metaDataItem, + Items: items, + }, rsp) +} + func parseConfigFileBase(req *restful.Request) (*model.ConfigFileBase, error) { namespace := nacoshttp.Optional(req, model.ParamTenant, model.DefaultNacosConfigNamespace) dataId, err := nacoshttp.Required(req, "dataId") diff --git a/apiserver/nacosserver/v1/config/config_file.go b/apiserver/nacosserver/v1/config/config_file.go index c263d51b0..911e987be 100644 --- a/apiserver/nacosserver/v1/config/config_file.go +++ b/apiserver/nacosserver/v1/config/config_file.go @@ -28,8 +28,10 @@ import ( "github.com/polarismesh/specification/source/go/api/v1/config_manage" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "go.uber.org/zap" + "gopkg.in/yaml.v2" "github.com/polarismesh/polaris/apiserver/nacosserver/model" + nacoshttp "github.com/polarismesh/polaris/apiserver/nacosserver/v1/http" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/metrics" commonmodel "github.com/polarismesh/polaris/common/model" @@ -241,6 +243,192 @@ func (n *ConfigServer) BuildTimeoutWatchCtx(ctx context.Context, watchTimeOut ti } } +const ( + ConfigExportMetadata = ".meta.yml" + ConfigExpotrMetadataV2 = ".metadata.yml" +) + +func (n *ConfigServer) handleConfigImport(ctx context.Context, policy string, result *UnZipResult, rsp *restful.Response) { + var files []*model.ConfigFile + var err error + + if result.Meta != nil && result.Meta.Name == ConfigExpotrMetadataV2 { + files, err = n.parseImportV2(result) + } + + if err != nil { + nacoshttp.WrirteNacosResponse(map[string]interface{}{ + "errMsg": "解析数据失败", + "code": 100004, + }, rsp) + return + } + + if len(files) == 0 { + nacoshttp.WrirteNacosResponse(map[string]interface{}{ + "errMsg": "导入的文件数据为空", + "code": 100005, + }, rsp) + return + } + + for _, file := range files { + // 看下之前有没有存在已发布的配置文件 + existVal := n.cacheSvr.ConfigFile().GetActiveRelease(file.Namespace, file.Group, file.DataId) + switch policy { + case "ABORT": + if existVal != nil { + return + } + case "SKIP": + if existVal != nil { + continue + } + fallthrough + default: + if _, err := n.handlePublishConfig(ctx, file); err != nil { + nacoshttp.WrirteNacosErrorResponse(err, rsp) + return + } + } + } + nacoshttp.WrirteNacosResponse(map[string]interface{}{ + "errMsg": "导入成功", + "code": 200, + }, rsp) +} + +func (n *ConfigServer) parseImportV1(result *UnZipResult) ([]*model.ConfigFile, error) { + metaData := map[string]string{} + if result.Meta != nil { + metaDataStr := strings.ReplaceAll(string(result.Meta.Data), "[\r\n]+", "|") + metaDataArr := strings.Split(metaDataStr, "\\|") + for _, meta := range metaDataArr { + metaArr := strings.Split(meta, "=") + if len(metaArr) == 2 { + metaData[metaArr[0]] = metaArr[1] + } + } + } + + files := make([]*model.ConfigFile, 0, len(result.Items)) + for _, item := range result.Items { + name := item.Name + groupDataId := strings.Split(name, "/") + if len(groupDataId) != 2 { + return nil, nil + } + group := groupDataId[0] + dataId := groupDataId[1] + if strings.Contains(dataId, ":") { + dataId = dataId[0:strings.LastIndex(dataId, ".")] + "~" + dataId[strings.LastIndex(dataId, ".")+1:] + } + metaDataId := group + "." + dataId + ".app" + appName := "" + if val, ok := metaData[metaDataId]; ok { + appName = val + } + + files = append(files, &model.ConfigFile{ + ConfigFileBase: model.ConfigFileBase{ + Namespace: result.Namespace, + Group: group, + DataId: dataId, + }, + Content: string(item.Data), + AppName: appName, + }) + } + return files, nil +} + +func (n *ConfigServer) parseImportV2(result *UnZipResult) ([]*model.ConfigFile, error) { + meta := &ConfigMetadata{} + if err := yaml.Unmarshal(result.Meta.Data, meta); err != nil { + return nil, err + } + if err := meta.Valid(); err != nil { + return nil, err + } + + metaKeys := meta.CalcKeys() + for _, item := range result.Items { + name := item.Name + groupDataId := strings.Split(name, "/") + if len(groupDataId) != 2 { + return nil, nil + } + group := groupDataId[0] + if _, ok := metaKeys[group]; !ok { + return nil, nil + } + dataId := groupDataId[1] + if _, ok := metaKeys[group][dataId]; !ok { + return nil, nil + } + + metaKeys[group][dataId] = string(item.Data) + } + + files := make([]*model.ConfigFile, 0, len(meta.Metadata)) + for _, item := range meta.Metadata { + if _, ok := metaKeys[item.Group]; !ok { + return nil, nil + } + if _, ok := metaKeys[item.Group][item.DataId]; !ok { + return nil, nil + } + + files = append(files, &model.ConfigFile{ + ConfigFileBase: model.ConfigFileBase{ + Namespace: result.Namespace, + Group: item.Group, + DataId: item.DataId, + }, + Content: metaKeys[item.Group][item.DataId], + Type: item.Type, + Description: item.Desc, + AppName: item.AppName, + }) + } + return files, nil +} + +type ConfigMetadata struct { + Metadata []ConfigExportItem `yaml:"metadata"` +} + +func (c *ConfigMetadata) CalcKeys() map[string]map[string]string { + return nil +} + +// Valid . +func (c *ConfigMetadata) Valid() error { + return &model.NacosError{ + ErrCode: 100002, + ErrMsg: "导入的元数据非法", + } +} + +type ConfigExportItem struct { + Group string `yaml:"group"` + DataId string `yaml:"dataId"` + Desc string `yaml:"desc"` + Type string `yaml:"type"` + AppName string `yaml:"appName"` +} + +type UnZipResult struct { + Namespace string + Meta *ZipItem + Items []*ZipItem +} + +type ZipItem struct { + Name string + Data []byte +} + func md5OldResult(items []*model.ConfigListenItem) string { sb := strings.Builder{} for i := range items { diff --git a/apiserver/nacosserver/v1/http/utils.go b/apiserver/nacosserver/v1/http/utils.go index bf4a0731f..8eb5bbce7 100644 --- a/apiserver/nacosserver/v1/http/utils.go +++ b/apiserver/nacosserver/v1/http/utils.go @@ -18,6 +18,8 @@ package http import ( + "archive/zip" + "bytes" "context" "encoding/json" "fmt" @@ -98,6 +100,54 @@ func (h *Handler) ParseHeaderContext() context.Context { return ctx } +func (h *Handler) ProcessZip(consumer func(f *zip.File, data []byte)) error { + req := h.Request + rsp := h.Response + + req.Request.Body = http.MaxBytesReader(rsp, req.Request.Body, utils.MaxRequestBodySize) + + file, _, err := req.Request.FormFile(utils.ConfigFileFormKey) + if err != nil { + return err + } + defer file.Close() + + var buf bytes.Buffer + if _, err := io.Copy(&buf, file); err != nil { + return err + } + + data := buf.Bytes() + zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + return err + } + + extractFileContent := func(f *zip.File) ([]byte, error) { + rc, err := f.Open() + if err != nil { + return nil, err + } + defer rc.Close() + var buf bytes.Buffer + if _, err := io.Copy(&buf, rc); err != nil { + return nil, err + } + return buf.Bytes(), nil + } + + // 提取元数据文件 + for _, file := range zr.File { + content, err := extractFileContent(file) + if err != nil { + return err + } + consumer(file, content) + } + + return nil +} + // ParseQueryParams 解析并获取HTTP的query params func ParseQueryParams(req *restful.Request) map[string]string { queryParams := make(map[string]string) diff --git a/cache/api/types.go b/cache/api/types.go index a6c3ef9a2..301344adb 100644 --- a/cache/api/types.go +++ b/cache/api/types.go @@ -18,6 +18,7 @@ package api import ( + "context" "runtime" "sync" "time" @@ -47,6 +48,8 @@ const ( L5Name = "l5" // RoutingConfigName router config name RoutingConfigName = "routingConfig" + // LaneRuleName lane rule config name + LaneRuleName = "laneRule" // RateLimitConfigName rate limit config name RateLimitConfigName = "rateLimitConfig" // CircuitBreakerName circuit breaker config name @@ -89,6 +92,7 @@ const ( CacheConfigGroup CacheServiceContract CacheGray + CacheLaneRule CacheLast ) @@ -244,12 +248,8 @@ type ( // ServiceContractCache . ServiceContractCache interface { Cache - // Query . - Query(filter map[string]string, offset, limit uint32) ([]*model.EnrichServiceContract, uint32, error) - // ListVersions . - ListVersions(service, namespace string) []*model.EnrichServiceContract // Get . - Get(req *model.ServiceContract) *model.EnrichServiceContract + Get(ctx context.Context, req *model.ServiceContract) *model.EnrichServiceContract } ) @@ -295,6 +295,14 @@ type ( } ) +type ( + LaneCache interface { + Cache + // GetLaneRules 根据serviceID获取泳道规则 + GetLaneRules(serviceKey *model.Service) ([]*model.LaneGroupProto, string) + } +) + type ( // RoutingArgs Routing rules query parameters RoutingArgs struct { @@ -713,3 +721,36 @@ type ( HitGrayRule(name string, labels map[string]string) bool } ) + +func NewExpireEntry[T any](t T, maxAlive time.Duration) *ExpireEntry[T] { + return &ExpireEntry[T]{ + data: t, + maxAlive: maxAlive, + } +} + +func EmptyExpireEntry[T any](t T, maxAlive time.Duration) *ExpireEntry[T] { + return &ExpireEntry[T]{ + empty: true, + maxAlive: maxAlive, + } +} + +type ExpireEntry[T any] struct { + empty bool + data T + lastAccess time.Time + maxAlive time.Duration +} + +func (e *ExpireEntry[T]) Get() T { + if e.empty { + return e.data + } + e.lastAccess = time.Now() + return e.data +} + +func (e *ExpireEntry[T]) IsExpire() bool { + return time.Since(e.lastAccess) > e.maxAlive +} diff --git a/cache/cache.go b/cache/cache.go index 4d01d7ea6..c91e859d8 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -204,6 +204,11 @@ func (nc *CacheManager) ServiceContract() types.ServiceContractCache { return nc.caches[types.CacheServiceContract].(types.ServiceContractCache) } +// LaneRule 获取泳道规则缓存信息 +func (nc *CacheManager) LaneRule() types.LaneCache { + return nc.caches[types.CacheLaneRule].(types.LaneCache) +} + // User Get user information cache information func (nc *CacheManager) User() types.UserCache { return nc.caches[types.CacheUser].(types.UserCache) diff --git a/cache/default.go b/cache/default.go index 484678b13..4b1144986 100644 --- a/cache/default.go +++ b/cache/default.go @@ -49,6 +49,7 @@ func init() { RegisterCache(types.ClientName, types.CacheClient) RegisterCache(types.ServiceContractName, types.CacheServiceContract) RegisterCache(types.GrayName, types.CacheGray) + RegisterCache(types.LaneRuleName, types.CacheLaneRule) } var ( @@ -98,6 +99,7 @@ func newCacheManager(ctx context.Context, cacheOpt *Config, storage store.Store) mgr.RegisterCacher(types.CacheFaultDetector, cachesvc.NewFaultDetectCache(storage, mgr)) mgr.RegisterCacher(types.CacheCL5, cachesvc.NewL5Cache(storage, mgr)) mgr.RegisterCacher(types.CacheServiceContract, cachesvc.NewServiceContractCache(storage, mgr)) + mgr.RegisterCacher(types.CacheLaneRule, cachesvc.NewLaneCache(storage, mgr)) // 配置分组 & 配置发布缓存 mgr.RegisterCacher(types.CacheConfigFile, cacheconfig.NewConfigFileCache(storage, mgr)) mgr.RegisterCacher(types.CacheConfigGroup, cacheconfig.NewConfigGroupCache(storage, mgr)) diff --git a/cache/mock/cache_mock.go b/cache/mock/cache_mock.go index 0775b9686..81baae2b1 100644 --- a/cache/mock/cache_mock.go +++ b/cache/mock/cache_mock.go @@ -5,6 +5,7 @@ package mock import ( + context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -956,17 +957,17 @@ func (mr *MockServiceContractCacheMockRecorder) Close() *gomock.Call { } // Get mocks base method. -func (m *MockServiceContractCache) Get(req *model.ServiceContract) *model.EnrichServiceContract { +func (m *MockServiceContractCache) Get(ctx context.Context, req *model.ServiceContract) *model.EnrichServiceContract { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", req) + ret := m.ctrl.Call(m, "Get", ctx, req) ret0, _ := ret[0].(*model.EnrichServiceContract) return ret0 } // Get indicates an expected call of Get. -func (mr *MockServiceContractCacheMockRecorder) Get(req interface{}) *gomock.Call { +func (mr *MockServiceContractCacheMockRecorder) Get(ctx, req interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockServiceContractCache)(nil).Get), req) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockServiceContractCache)(nil).Get), ctx, req) } // Initialize mocks base method. @@ -983,20 +984,6 @@ func (mr *MockServiceContractCacheMockRecorder) Initialize(c interface{}) *gomoc 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() @@ -1011,22 +998,6 @@ func (mr *MockServiceContractCacheMockRecorder) Name() *gomock.Call { 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() @@ -1397,6 +1368,114 @@ func (mr *MockFaultDetectCacheMockRecorder) Update() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockFaultDetectCache)(nil).Update)) } +// MockLaneCache is a mock of LaneCache interface. +type MockLaneCache struct { + ctrl *gomock.Controller + recorder *MockLaneCacheMockRecorder +} + +// MockLaneCacheMockRecorder is the mock recorder for MockLaneCache. +type MockLaneCacheMockRecorder struct { + mock *MockLaneCache +} + +// NewMockLaneCache creates a new mock instance. +func NewMockLaneCache(ctrl *gomock.Controller) *MockLaneCache { + mock := &MockLaneCache{ctrl: ctrl} + mock.recorder = &MockLaneCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLaneCache) EXPECT() *MockLaneCacheMockRecorder { + return m.recorder +} + +// Clear mocks base method. +func (m *MockLaneCache) 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 *MockLaneCacheMockRecorder) Clear() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockLaneCache)(nil).Clear)) +} + +// Close mocks base method. +func (m *MockLaneCache) 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 *MockLaneCacheMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockLaneCache)(nil).Close)) +} + +// GetLaneRules mocks base method. +func (m *MockLaneCache) GetLaneRules(serviceKey *model.Service) ([]*model.LaneGroupProto, string) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLaneRules", serviceKey) + ret0, _ := ret[0].([]*model.LaneGroupProto) + ret1, _ := ret[1].(string) + return ret0, ret1 +} + +// GetLaneRules indicates an expected call of GetLaneRules. +func (mr *MockLaneCacheMockRecorder) GetLaneRules(serviceKey interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLaneRules", reflect.TypeOf((*MockLaneCache)(nil).GetLaneRules), serviceKey) +} + +// Initialize mocks base method. +func (m *MockLaneCache) 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 *MockLaneCacheMockRecorder) Initialize(c interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockLaneCache)(nil).Initialize), c) +} + +// Name mocks base method. +func (m *MockLaneCache) 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 *MockLaneCacheMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockLaneCache)(nil).Name)) +} + +// Update mocks base method. +func (m *MockLaneCache) 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 *MockLaneCacheMockRecorder) Update() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockLaneCache)(nil).Update)) +} + // MockRoutingConfigCache is a mock of RoutingConfigCache interface. type MockRoutingConfigCache struct { ctrl *gomock.Controller @@ -2214,32 +2293,32 @@ func (mr *MockConfigFileCacheMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConfigFileCache)(nil).Close)) } -// GetActiveRelease mocks base method. -func (m *MockConfigFileCache) GetActiveRelease(namespace, group, fileName string) *model.ConfigFileRelease { +// GetActiveGrayRelease mocks base method. +func (m *MockConfigFileCache) GetActiveGrayRelease(namespace, group, fileName string) *model.ConfigFileRelease { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetActiveRelease", namespace, group, fileName) + ret := m.ctrl.Call(m, "GetActiveGrayRelease", namespace, group, fileName) ret0, _ := ret[0].(*model.ConfigFileRelease) return ret0 } -// GetActiveRelease indicates an expected call of GetActiveRelease. -func (mr *MockConfigFileCacheMockRecorder) GetActiveRelease(namespace, group, fileName interface{}) *gomock.Call { +// GetActiveGrayRelease indicates an expected call of GetActiveGrayRelease. +func (mr *MockConfigFileCacheMockRecorder) GetActiveGrayRelease(namespace, group, fileName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveRelease", reflect.TypeOf((*MockConfigFileCache)(nil).GetActiveRelease), namespace, group, fileName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveGrayRelease", reflect.TypeOf((*MockConfigFileCache)(nil).GetActiveGrayRelease), namespace, group, fileName) } -// GetActiveGrayRelease mocks base method. -func (m *MockConfigFileCache) GetActiveGrayRelease(namespace, group, fileName string) *model.ConfigFileRelease { +// GetActiveRelease mocks base method. +func (m *MockConfigFileCache) GetActiveRelease(namespace, group, fileName string) *model.ConfigFileRelease { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetActiveGrayRelease", namespace, group, fileName) + ret := m.ctrl.Call(m, "GetActiveRelease", namespace, group, fileName) ret0, _ := ret[0].(*model.ConfigFileRelease) return ret0 } -// GetActiveGrayRelease indicates an expected call of GetActiveGrayRelease. -func (mr *MockConfigFileCacheMockRecorder) GetActiveGrayRelease(namespace, group, fileName interface{}) *gomock.Call { +// GetActiveRelease indicates an expected call of GetActiveRelease. +func (mr *MockConfigFileCacheMockRecorder) GetActiveRelease(namespace, group, fileName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveGrayRelease", reflect.TypeOf((*MockConfigFileCache)(nil).GetActiveGrayRelease), namespace, group, fileName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveRelease", reflect.TypeOf((*MockConfigFileCache)(nil).GetActiveRelease), namespace, group, fileName) } // GetGroupActiveReleases mocks base method. diff --git a/cache/service/lane.go b/cache/service/lane.go new file mode 100644 index 000000000..96cb76a32 --- /dev/null +++ b/cache/service/lane.go @@ -0,0 +1,356 @@ +/** + * 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 + +import ( + "time" + + "github.com/golang/protobuf/proto" + apitraffic "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" + "go.uber.org/zap" + "golang.org/x/sync/singleflight" + protoV2 "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + types "github.com/polarismesh/polaris/cache/api" + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/store" +) + +func NewLaneCache(storage store.Store, cacheMgr types.CacheManager) types.LaneCache { + return &LaneCache{ + BaseCache: types.NewBaseCache(storage, cacheMgr), + } +} + +type LaneCache struct { + *types.BaseCache + // single . + single singleflight.Group + // groups name -> *model.LaneGroupProto + groups *utils.SyncMap[string, *model.LaneGroupProto] + // serviceRules namespace -> service -> []*model.LaneRuleProto + serviceRules *utils.SyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.LaneGroupProto]]] + // revisions namespace -> service -> revision + revisions *utils.SyncMap[string, *utils.SyncMap[string, string]] +} + +// Initialize . +func (lc *LaneCache) Initialize(c map[string]interface{}) error { + lc.serviceRules = utils.NewSyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.LaneGroupProto]]]() + lc.revisions = utils.NewSyncMap[string, *utils.SyncMap[string, string]]() + lc.groups = utils.NewSyncMap[string, *model.LaneGroupProto]() + lc.single = singleflight.Group{} + return nil +} + +// Update . +func (lc *LaneCache) Update() error { + // 多个线程竞争,只有一个线程进行更新 + err, _ := lc.singleUpdate() + return err +} + +func (lc *LaneCache) singleUpdate() (error, bool) { + // 多个线程竞争,只有一个线程进行更新 + _, err, shared := lc.single.Do(lc.Name(), func() (interface{}, error) { + return nil, lc.DoCacheUpdate(lc.Name(), lc.realUpdate) + }) + return err, shared +} + +// Update . +func (lc *LaneCache) realUpdate() (map[string]time.Time, int64, error) { + start := time.Now() + + // 获取泳道规则信息 + rules, err := lc.Store().GetMoreLaneGroups(lc.LastFetchTime(), lc.IsFirstUpdate()) + if err != nil { + log.Errorf("[Cache] lane cache update err: %s", err.Error()) + return nil, -1, err + } + + mtime, addCnt, updateCnt, delCnt := lc.setLaneRules(rules) + log.Info("[Cache][Lane] get more lane rules", + zap.Int("pull-from-store", len(rules)), zap.Int("add", addCnt), zap.Int("update", updateCnt), + zap.Int("delete", delCnt), zap.Time("last", lc.LastMtime()), zap.Duration("used", time.Since(start))) + return map[string]time.Time{ + lc.Name(): mtime, + }, int64(len(rules)), err +} + +func (lc *LaneCache) setLaneRules(items map[string]*model.LaneGroup) (time.Time, int, int, int) { + lastMtime := lc.LastMtime().Unix() + add := 0 + update := 0 + del := 0 + affectSvcs := map[string]map[string]struct{}{} + + for i := range items { + item := items[i] + saveVal, err := item.ToProto() + if err != nil { + log.Error("[Cache][Lane] unmarshal rule text to LaneRule spec", zap.Error(err)) + continue + } + if item.ModifyTime.Unix() > lastMtime { + lastMtime = item.ModifyTime.Unix() + } + + oldVal, exist := lc.groups.Load(item.ID) + if !item.Valid { + del++ + _, _ = lc.groups.Delete(item.ID) + if exist { + lc.processLaneRuleDelete(oldVal, affectSvcs) + } + continue + } + if exist { + update++ + } else { + add++ + } + lc.groups.Store(item.ID, saveVal) + lc.processLaneRuleUpsert(oldVal, saveVal, affectSvcs) + } + lc.postUpdateRevisions(affectSvcs) + + return time.Unix(lastMtime, 0), add, update, del +} + +func (lc *LaneCache) processLaneRuleUpsert(old, item *model.LaneGroupProto, affectSvcs map[string]map[string]struct{}) { + waitDelServices := map[string]map[string]struct{}{} + addService := func(ns, svc string) { + if _, ok := waitDelServices[ns]; !ok { + waitDelServices[ns] = map[string]struct{}{} + } + waitDelServices[ns][svc] = struct{}{} + } + removeServiceIfExist := func(ns, svc string) { + if _, ok := waitDelServices[ns]; !ok { + waitDelServices[ns] = map[string]struct{}{} + } + if _, ok := waitDelServices[ns][svc]; ok { + delete(waitDelServices[ns], svc) + } + } + + handle := func(rule *model.LaneGroupProto, serviceOp func(ns, svc string), ruleOp func(string, string, *model.LaneGroupProto)) { + if rule == nil { + return + } + + for i := range rule.Proto.Destinations { + dest := rule.Proto.Destinations[i] + serviceOp(dest.Namespace, dest.Service) + ruleOp(dest.Namespace, dest.Service, rule) + } + + for i := range rule.Proto.Entries { + entry := rule.Proto.Entries[i] + switch model.TrafficEntryType(entry.Type) { + case model.TrafficEntry_MicroService: + selector := &apitraffic.ServiceSelector{} + if err := anyToSelector(entry.Selector, selector); err != nil { + continue + } + serviceOp(selector.Namespace, selector.Service) + ruleOp(selector.Namespace, selector.Service, rule) + case model.TrafficEntry_SpringCloudGateway: + selector := &apitraffic.ServiceGatewaySelector{} + if err := anyToSelector(entry.Selector, selector); err != nil { + continue + } + serviceOp(selector.Namespace, selector.Service) + ruleOp(selector.Namespace, selector.Service, rule) + default: + // do nothing + } + } + } + + handle(item, addService, func(ns, svc string, group *model.LaneGroupProto) { + if _, ok := affectSvcs[ns]; !ok { + affectSvcs[ns] = map[string]struct{}{} + } + affectSvcs[ns][svc] = struct{}{} + lc.upsertServiceRule(ns, svc, group) + }) + handle(old, removeServiceIfExist, func(ns, svc string, group *model.LaneGroupProto) { + if _, ok := affectSvcs[ns]; !ok { + affectSvcs[ns] = map[string]struct{}{} + } + affectSvcs[ns][svc] = struct{}{} + }) + + for ns := range waitDelServices { + for svc := range waitDelServices[ns] { + lc.cleanServiceRule(ns, svc, old) + } + } +} + +func (lc *LaneCache) processLaneRuleDelete(item *model.LaneGroupProto, affectSvcs map[string]map[string]struct{}) { + message := item.Proto + // 先清理 destinations + for i := range message.Destinations { + dest := message.Destinations[i] + if _, ok := affectSvcs[dest.Namespace]; !ok { + affectSvcs[dest.Namespace] = map[string]struct{}{} + } + affectSvcs[dest.Namespace][dest.Service] = struct{}{} + lc.cleanServiceRule(dest.Namespace, dest.Service, item) + } + + for i := range message.Entries { + entry := message.Entries[i] + var ns string + var svc string + switch model.TrafficEntryType(entry.Type) { + case model.TrafficEntry_MicroService: + selector := &apitraffic.ServiceSelector{} + if err := anyToSelector(entry.Selector, selector); err != nil { + continue + } + ns = selector.Namespace + svc = selector.Service + lc.cleanServiceRule(selector.Namespace, selector.Service, item) + case model.TrafficEntry_SpringCloudGateway: + selector := &apitraffic.ServiceGatewaySelector{} + if err := anyToSelector(entry.Selector, selector); err != nil { + continue + } + ns = selector.Namespace + svc = selector.Service + lc.cleanServiceRule(selector.Namespace, selector.Service, item) + } + if _, ok := affectSvcs[ns]; !ok { + affectSvcs[ns] = map[string]struct{}{} + } + affectSvcs[ns][svc] = struct{}{} + } +} + +func (lc *LaneCache) upsertServiceRule(namespace, service string, item *model.LaneGroupProto) { + namespaceContainer, _ := lc.serviceRules.ComputeIfAbsent(namespace, + func(k string) *utils.SyncMap[string, *utils.SyncMap[string, *model.LaneGroupProto]] { + return utils.NewSyncMap[string, *utils.SyncMap[string, *model.LaneGroupProto]]() + }) + serviceContainer, _ := namespaceContainer.ComputeIfAbsent(service, + func(k string) *utils.SyncMap[string, *model.LaneGroupProto] { + return utils.NewSyncMap[string, *model.LaneGroupProto]() + }) + serviceContainer.Store(item.ID, item) +} + +func (lc *LaneCache) cleanServiceRule(namespace, service string, item *model.LaneGroupProto) { + namespaceContainer, ok := lc.serviceRules.Load(namespace) + if !ok { + return + } + serviceContainer, ok := namespaceContainer.Load(service) + if !ok { + return + } + if item == nil { + return + } + + serviceContainer.Delete(item.ID) + + if serviceContainer.Len() == 0 { + namespaceContainer.Delete(service) + } +} + +func (lc *LaneCache) postUpdateRevisions(affectSvcs map[string]map[string]struct{}) { + for ns, svsList := range affectSvcs { + nsContainer, ok := lc.serviceRules.Load(ns) + if !ok { + continue + } + lc.revisions.ComputeIfAbsent(ns, func(k string) *utils.SyncMap[string, string] { + return utils.NewSyncMap[string, string]() + }) + nsRevisions, _ := lc.revisions.Load(ns) + for svc := range svsList { + revisions := make([]string, 0, 32) + svcContainer, ok := nsContainer.Load(svc) + if !ok { + continue + } + svcContainer.Range(func(key string, val *model.LaneGroupProto) { + revisions = append(revisions, val.Revision) + }) + revision, err := types.CompositeComputeRevision(revisions) + if err != nil { + continue + } + nsRevisions.Store(svc, revision) + } + } +} + +func (lc *LaneCache) GetLaneRules(serviceKey *model.Service) ([]*model.LaneGroupProto, string) { + namespaceContainer, ok := lc.serviceRules.Load(serviceKey.Namespace) + if !ok { + return []*model.LaneGroupProto{}, "" + } + serviceContainer, ok := namespaceContainer.Load(serviceKey.Name) + if !ok { + return []*model.LaneGroupProto{}, "" + } + ret := make([]*model.LaneGroupProto, 0, 32) + serviceContainer.Range(func(ruleId string, val *model.LaneGroupProto) { + ret = append(ret, val) + }) + + nsRevision, ok := lc.revisions.Load(serviceKey.Namespace) + if !ok { + return ret, "" + } + revision, _ := nsRevision.Load(serviceKey.Name) + return ret, revision +} + +func (lc *LaneCache) LastMtime() time.Time { + return lc.BaseCache.LastMtime(lc.Name()) +} + +// Clear . +func (lc *LaneCache) Clear() error { + lc.revisions = utils.NewSyncMap[string, *utils.SyncMap[string, string]]() + lc.groups = utils.NewSyncMap[string, *model.LaneGroupProto]() + lc.serviceRules = utils.NewSyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.LaneGroupProto]]]() + return nil +} + +// Name . +func (lc *LaneCache) Name() string { + return types.LaneRuleName +} + +func anyToSelector(data *anypb.Any, msg proto.Message) error { + if err := anypb.UnmarshalTo(data, proto.MessageV2(msg), + protoV2.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}); err != nil { + return err + } + return nil +} diff --git a/cache/service/service_contract.go b/cache/service/service_contract.go index b3aab090c..7739524a2 100644 --- a/cache/service/service_contract.go +++ b/cache/service/service_contract.go @@ -18,8 +18,8 @@ package service import ( - "sort" - "strings" + "context" + "sync/atomic" "time" "go.uber.org/zap" @@ -32,261 +32,89 @@ import ( ) const ( - ServiceContractName = "serviceContract" + maxAliveDuration = time.Minute + faultAliveDuration = 5 * time.Second ) func NewServiceContractCache(storage store.Store, cacheMgr types.CacheManager) types.ServiceContractCache { return &serviceContractCache{ - BaseCache: types.NewBaseCache(storage, cacheMgr), + lastRunTime: time.Now(), + lastStoreErrTime: 0, + BaseCache: types.NewBaseCache(storage, cacheMgr), } } type serviceContractCache struct { *types.BaseCache - lastMtimeLogged int64 + lastRunTime time.Time + lastStoreErrTime int64 + lastMtimeLogged int64 // data namespace/service/name/protocol/version -> *model.EnrichServiceContract - data *utils.SyncMap[string, *model.EnrichServiceContract] - // contracts 服务契约缓存,namespace -> service -> []*model.EnrichServiceContract - contracts *utils.SyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.EnrichServiceContract]]] + data *utils.SyncMap[string, *types.ExpireEntry[*model.EnrichServiceContract]] singleGroup *singleflight.Group } -// Initialize +// Initialize . func (sc *serviceContractCache) Initialize(c map[string]interface{}) error { + sc.lastRunTime = time.Now() sc.singleGroup = &singleflight.Group{} - sc.data = utils.NewSyncMap[string, *model.EnrichServiceContract]() - sc.contracts = utils.NewSyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.EnrichServiceContract]]]() + sc.data = utils.NewSyncMap[string, *types.ExpireEntry[*model.EnrichServiceContract]]() return nil } -// Update +// Update . func (sc *serviceContractCache) Update() error { - err, _ := sc.singleUpdate() - return err -} - -func (sc *serviceContractCache) singleUpdate() (error, bool) { - // 多个线程竞争,只有一个线程进行更新 - _, err, shared := sc.singleGroup.Do(sc.Name(), func() (interface{}, error) { - defer func() { - sc.lastMtimeLogged = types.LogLastMtime(sc.lastMtimeLogged, sc.LastMtime(sc.Name()).Unix(), "ServiceContract") - }() - return nil, sc.DoCacheUpdate(sc.Name(), sc.realUpdate) - }) - return err, shared -} - -func (sc *serviceContractCache) realUpdate() (map[string]time.Time, int64, error) { - start := time.Now() - values, err := sc.Store().GetMoreServiceContracts(sc.IsFirstUpdate(), sc.LastFetchTime()) - if err != nil { - log.Errorf("[Cache][ServiceContract] update service_contract err: %s", err.Error()) - return nil, 0, err + if time.Since(sc.lastRunTime) < time.Minute { + return nil } - - lastMtimes, update, del := sc.setContracts(values) - costTime := time.Since(start) - log.Info( - "[Cache][ServiceContract] get more service_contract", zap.Int("upsert", update), zap.Int("delete", del), - zap.Time("last", sc.LastMtime(sc.Name())), zap.Duration("used", costTime)) - return lastMtimes, int64(len(values)), err -} - -func (sc *serviceContractCache) setContracts(values []*model.EnrichServiceContract) (map[string]time.Time, int, int) { - var ( - upsert, del int - lastMtime time.Time - ) - for i := range values { - item := values[i] - namespace := item.Namespace - service := item.Service - - if _, ok := sc.contracts.Load(namespace); !ok { - sc.contracts.Store(namespace, utils.NewSyncMap[string, *utils.SyncMap[string, *model.EnrichServiceContract]]()) - } - namespaceVal, _ := sc.contracts.Load(namespace) - - if _, ok := namespaceVal.Load(service); !ok { - namespaceVal.Store(service, utils.NewSyncMap[string, *model.EnrichServiceContract]()) + sc.lastRunTime = time.Now() + + lastStoreErrTime := atomic.LoadInt64(&sc.lastStoreErrTime) + // 如果存储层在近 1min 中内发生错误,则不会清理 expire 的数据 + showSkip := time.Now().Unix()-lastStoreErrTime < 60 + log.Info("[ServiceContract] cache expire entry clean start") + waitDel := make([]string, 0, 4) + sc.data.ReadRange(func(key string, val *types.ExpireEntry[*model.EnrichServiceContract]) { + if showSkip { + return } - - serviceVal, _ := namespaceVal.Load(service) - if !item.Valid { - del++ - sc.data.Delete(item.GetCacheKey()) - serviceVal.Delete(item.ID) - continue + if val.IsExpire() { + waitDel = append(waitDel, key) } - - upsert++ - sc.data.Store(item.GetCacheKey(), item) - serviceVal.Store(item.ID, item) + }) + for i := range waitDel { + sc.data.Delete(waitDel[i]) + log.Info("[ServiceContract] cache expire entry", zap.String("key", waitDel[i])) } - return map[string]time.Time{ - sc.Name(): lastMtime, - }, upsert, del + return nil } -// Clear +// Clear . func (sc *serviceContractCache) Clear() error { - sc.data = utils.NewSyncMap[string, *model.EnrichServiceContract]() - sc.contracts = utils.NewSyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.EnrichServiceContract]]]() + sc.data = utils.NewSyncMap[string, *types.ExpireEntry[*model.EnrichServiceContract]]() return nil } -// Name +// Name . func (sc *serviceContractCache) Name() string { - return ServiceContractName + return types.ServiceContractName } -// forceQueryUpdate 为了确保读取的数据是最新的,这里需要做一个强制 update 的动作进行数据读取处理 -func (sc *serviceContractCache) forceQueryUpdate() error { - err, shared := sc.singleUpdate() - // shared == true,表示当前已经有正在 update 执行的任务,这个任务不一定能够读取到最新的数据 - // 为了避免读取到脏数据,在发起一次 singleUpdate - if shared { - naminglog.Debug("[Server][ServiceContract][Query] force query update from store") - err, _ = sc.singleUpdate() - } - return err -} - -func (sc *serviceContractCache) Get(req *model.ServiceContract) *model.EnrichServiceContract { - ret, _ := sc.data.Load(req.GetCacheKey()) - return ret -} - -// Query . -func (sc *serviceContractCache) Query(filter map[string]string, offset, limit uint32) ([]*model.EnrichServiceContract, uint32, error) { - if err := sc.forceQueryUpdate(); err != nil { - return nil, 0, err - } - - values := make([]*model.EnrichServiceContract, 0, 64) - - searchNamespace := filter["namespace"] - searchService := filter["service"] - searchName := filter["name"] - searchProtocol := filter["protocol"] - searchVersion := filter["version"] - searchInterfaceName := filter["interface_name"] - searchInterfacePath := filter["interface_path"] - - sc.contracts.ReadRange(func(namespace string, services *utils.SyncMap[string, *utils.SyncMap[string, *model.EnrichServiceContract]]) { - if searchNamespace != "" { - if !utils.IsWildMatch(namespace, searchNamespace) { - return - } +func (sc *serviceContractCache) Get(ctx context.Context, req *model.ServiceContract) *model.EnrichServiceContract { + ret, _ := sc.data.ComputeIfAbsent(req.GetCacheKey(), func(k string) *types.ExpireEntry[*model.EnrichServiceContract] { + id, err := utils.CalculateContractID(req.Namespace, req.Service, req.Type, req.Protocol, req.Version) + if err != nil { + return types.EmptyExpireEntry(&model.EnrichServiceContract{}, faultAliveDuration) } - - services.ReadRange(func(service string, contracts *utils.SyncMap[string, *model.EnrichServiceContract]) { - if searchService != "" { - if !utils.IsWildMatch(service, searchService) { - return - } - } - contracts.ReadRange(func(_ string, val *model.EnrichServiceContract) { - if searchName != "" { - names := strings.Split(searchName, ",") - for i := range names { - if !utils.IsWildMatch(val.Name, names[i]) { - return - } - } - } - if searchProtocol != "" { - if !utils.IsWildMatch(val.Protocol, searchProtocol) { - return - } - } - if searchVersion != "" { - if !utils.IsWildMatch(val.Version, searchVersion) { - return - } - } - // 支持针对接口信息反向查询服务契约列表 - if searchInterfaceName != "" || searchInterfacePath != "" { - tmpContract := &model.EnrichServiceContract{ - ServiceContract: val.ServiceContract, - Interfaces: []*model.InterfaceDescriptor{}, - } - for i := range val.Interfaces { - descriptor := val.Interfaces[i] - if !utils.IsWildMatch(descriptor.Name, searchInterfaceName) { - continue - } - if !utils.IsWildMatch(descriptor.Path, searchInterfacePath) { - continue - } - tmpContract.Interfaces = append(tmpContract.Interfaces, descriptor) - } - values = append(values, tmpContract) - } else { - values = append(values, val) - } - return - }) - }) - }) - - sort.Slice(values, func(i, j int) bool { - return values[j].ModifyTime.Before(values[i].ModifyTime) - }) - retVal, total := sc.toPage(values, offset, limit) - return retVal, total, nil -} - -// ListVersions . -func (sc *serviceContractCache) ListVersions(searchService, searchNamespace string) []*model.EnrichServiceContract { - values := make([]*model.EnrichServiceContract, 0, 64) - sc.contracts.Range(func(namespace string, services *utils.SyncMap[string, *utils.SyncMap[string, *model.EnrichServiceContract]]) { - if searchNamespace != namespace { - return + val, err := sc.Store().GetServiceContract(id) + if err != nil { + atomic.StoreInt64(&sc.lastStoreErrTime, time.Now().Unix()) + return types.EmptyExpireEntry(&model.EnrichServiceContract{}, faultAliveDuration) } - - services.Range(func(service string, contracts *utils.SyncMap[string, *model.EnrichServiceContract]) { - if searchService != service { - return - } - contracts.Range(func(_ string, val *model.EnrichServiceContract) { - values = append(values, &model.EnrichServiceContract{ - ServiceContract: &model.ServiceContract{ - ID: val.ID, - Namespace: val.Namespace, - Service: val.Service, - Name: val.Name, - Protocol: val.Protocol, - Version: val.Version, - Revision: val.Revision, - CreateTime: val.CreateTime, - ModifyTime: val.ModifyTime, - }, - }) - }) - }) + return types.NewExpireEntry(val, maxAliveDuration) }) - sort.Slice(values, func(i, j int) bool { - return values[j].ModifyTime.Before(values[i].ModifyTime) - }) - return values -} -func (sc *serviceContractCache) toPage(values []*model.EnrichServiceContract, offset, - limit uint32) ([]*model.EnrichServiceContract, uint32) { - - // 所有符合条件的服务数量 - amount := uint32(len(values)) - // 判断 offset 和 limit 是否允许返回对应的服务 - if offset >= amount || limit == 0 { - return nil, amount - } - - endIdx := offset + limit - if endIdx > amount { - endIdx = amount - } - return values[offset:endIdx], amount + return ret.Get() } diff --git a/common/api/v1/config_response.go b/common/api/v1/config_response.go index f726bf70b..ccf34820b 100644 --- a/common/api/v1/config_response.go +++ b/common/api/v1/config_response.go @@ -43,6 +43,13 @@ func NewConfigClientListResponse(code apimodel.Code) *apiconfig.ConfigClientList } } +func NewConfigClientListResponseWithInfo(code apimodel.Code, msg string) *apiconfig.ConfigClientListResponse { + return &apiconfig.ConfigClientListResponse{ + Code: &wrappers.UInt32Value{Value: uint32(code)}, + Info: &wrappers.StringValue{Value: msg}, + } +} + func NewConfigClientResponse0(code apimodel.Code) *apiconfig.ConfigClientResponse { return &apiconfig.ConfigClientResponse{ Code: &wrappers.UInt32Value{Value: uint32(code)}, diff --git a/common/api/v1/naming_response.go b/common/api/v1/naming_response.go index 9070359a8..94d76e4cd 100644 --- a/common/api/v1/naming_response.go +++ b/common/api/v1/naming_response.go @@ -323,6 +323,16 @@ func NewDiscoverCircuitBreakerResponse(code apimodel.Code, service *apiservice.S } } +// NewDiscoverLaneResponse . +func NewDiscoverLaneResponse(code apimodel.Code, service *apiservice.Service) *apiservice.DiscoverResponse { + return &apiservice.DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: uint32(code)}, + Info: &wrappers.StringValue{Value: code2info[uint32(code)]}, + Type: apiservice.DiscoverResponse_LANE, + Service: service, + } +} + /** * @brief 创建查询探测规则回复 */ diff --git a/common/model/contract.go b/common/model/contract.go index 6f7ecad2a..cfb8ed1d6 100644 --- a/common/model/contract.go +++ b/common/model/contract.go @@ -32,8 +32,8 @@ type ServiceContract struct { Namespace string // 所属服务名称 Service string - // 契约名称 - Name string + // Type 类型 + Type string // 协议,http/grpc/dubbo/thrift Protocol string // 契约版本 @@ -98,7 +98,8 @@ func (e *EnrichServiceContract) ToSpec() *apiservice.ServiceContract { interfaces = append(interfaces, &apiservice.InterfaceDescriptor{ Id: item.ID, Path: item.Path, - Name: item.Name, + Name: item.Type, + Type: item.Type, Method: item.Method, Source: item.Source, Content: item.Content, @@ -109,7 +110,8 @@ func (e *EnrichServiceContract) ToSpec() *apiservice.ServiceContract { } return &apiservice.ServiceContract{ Id: e.ID, - Name: e.Name, + Name: e.Type, + Type: e.Type, Namespace: e.Namespace, Service: e.Service, Protocol: e.Protocol, @@ -123,20 +125,28 @@ func (e *EnrichServiceContract) ToSpec() *apiservice.ServiceContract { } func (s *ServiceContract) GetResourceName() string { - return fmt.Sprintf("%s/%s/%s/%s", s.Service, s.Name, s.Protocol, s.Version) + return fmt.Sprintf("%s/%s/%s/%s", s.Service, s.Type, s.Protocol, s.Version) } func (s *ServiceContract) GetCacheKey() string { - return fmt.Sprintf("%s/%s/%s/%s/%s", s.Namespace, s.Service, s.Name, s.Protocol, s.Version) + return fmt.Sprintf("%s/%s/%s/%s/%s", s.Namespace, s.Service, s.Type, s.Protocol, s.Version) } type InterfaceDescriptor struct { // ID ID string - // Name 接口名称 - Name string // ContractID ContractID string + // 所属命名空间 + Namespace string + // 所属服务名称 + Service string + // 协议,http/grpc/dubbo/thrift + Protocol string + // 契约版本 + Version string + // Type 类型 + Type string // 方法名称,对应 http method/ dubbo interface func/grpc service func Method string // 接口名称,http path/dubbo interface/grpc service diff --git a/common/model/lane.go b/common/model/lane.go new file mode 100644 index 000000000..db5539a7f --- /dev/null +++ b/common/model/lane.go @@ -0,0 +1,202 @@ +/** + * 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 ( + "encoding/json" + "time" + + "github.com/golang/protobuf/proto" + apitraffic "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" + + commontime "github.com/polarismesh/polaris/common/time" + "github.com/polarismesh/polaris/common/utils" +) + +type TrafficEntryType string + +const ( + TrafficEntry_TSEGateway = "polarismesh.cn/gateway/tse-gateway" + TrafficEntry_SpringCloudGateway = "polarismesh.cn/gateway/spring-cloud-gateway" + TrafficEntry_MicroService = "polarismesh.cn/service" +) + +type LaneGroupProto struct { + *LaneGroup + Proto *apitraffic.LaneGroup +} + +// LaneGroup 泳道分组 +type LaneGroup struct { + ID string + Name string + Rule string + Revision string + Description string + Valid bool + CreateTime time.Time + ModifyTime time.Time + // LaneRules id -> *LaneRule + LaneRules map[string]*LaneRule +} + +func (l *LaneGroup) FromSpec(item *apitraffic.LaneGroup) error { + l.ID = item.GetId() + l.Name = item.GetName() + l.Description = item.GetDescription() + l.LaneRules = map[string]*LaneRule{} + for i := range item.Rules { + laneRule := &LaneRule{} + if err := laneRule.FromSpec(item.Rules[i]); err != nil { + return err + } + // 这里泳道所属泳道组的名称需要这里手动设置 + laneRule.LaneGroup = l.Name + laneRule.ID = utils.DefaultString(laneRule.ID, utils.NewUUID()) + l.LaneRules[laneRule.ID] = laneRule + } + copyItem := proto.Clone(item).(*apitraffic.LaneGroup) + copyItem.Rules = make([]*apitraffic.LaneRule, 0) + rule, err := json.Marshal(copyItem) + if err != nil { + return err + } + l.Rule = string(rule) + return nil +} + +func (l *LaneGroup) ToProto() (*LaneGroupProto, error) { + ret := &apitraffic.LaneGroup{} + if err := json.Unmarshal([]byte(l.Rule), ret); err != nil { + return nil, err + } + + ret.Id = l.ID + ret.Revision = l.Revision + ret.Description = l.Description + ret.Rules = make([]*apitraffic.LaneRule, 0, 32) + ret.Ctime = commontime.Time2String(l.CreateTime) + ret.Mtime = commontime.Time2String(l.ModifyTime) + protoG := &LaneGroupProto{ + LaneGroup: l, + Proto: ret, + } + + for k := range l.LaneRules { + protoRule, err := l.LaneRules[k].ToProto() + if err != nil { + return nil, err + } + protoG.Proto.Rules = append(protoG.Proto.Rules, protoRule.Proto) + } + + return protoG, nil +} + +func (l *LaneGroup) ToSpec() (*apitraffic.LaneGroup, error) { + ret := &apitraffic.LaneGroup{} + if err := json.Unmarshal([]byte(l.Rule), ret); err != nil { + return nil, err + } + return ret, nil +} + +// LaneRule 泳道规则实体 +type LaneRule struct { + ID string + LaneGroup string + Name string + Rule string + Priority uint32 + Revision string + Description string + Enable bool + Valid bool + CreateTime time.Time + EnableTime time.Time + ModifyTime time.Time + + // 仅仅用于修改 flag 标记 + changeEnable bool + add bool +} + +func (l *LaneRule) SetChangeEnable(v bool) { + l.changeEnable = v +} + +func (l *LaneRule) IsChangeEnable() bool { + return l.changeEnable +} + +func (l *LaneRule) SetAddFlag(v bool) { + l.add = v +} + +func (l *LaneRule) IsAdd() bool { + return l.add +} + +func (l *LaneRule) FromSpec(item *apitraffic.LaneRule) error { + l.ID = item.GetId() + l.Name = item.GetName() + l.LaneGroup = item.GetGroupName() + l.Priority = item.GetPriority() + l.Revision = utils.DefaultString(item.GetRevision(), utils.NewUUID()) + l.Description = item.GetDescription() + l.Enable = item.GetEnable() + + rule, err := json.Marshal(item) + if err != nil { + return err + } + l.Rule = string(rule) + return nil +} + +func (l *LaneRule) ToProto() (*LaneRuleProto, error) { + rule := &apitraffic.LaneRule{} + if err := json.Unmarshal([]byte(l.Rule), rule); err != nil { + return nil, err + } + rule.Id = l.ID + rule.Name = l.Name + rule.Enable = l.Enable + rule.GroupName = l.LaneGroup + rule.Description = l.Description + rule.Priority = l.Priority + rule.Revision = l.Revision + rule.Ctime = commontime.Time2String(l.CreateTime) + rule.Mtime = commontime.Time2String(l.ModifyTime) + + if l.EnableTime.Year() > 2000 { + rule.Etime = commontime.Time2String(l.EnableTime) + } else { + rule.Etime = "" + } + + return &LaneRuleProto{ + LaneRule: l, + Proto: rule, + }, nil +} + +type LaneRuleProto struct { + *LaneRule + Proto *apitraffic.LaneRule +} diff --git a/common/utils/common.go b/common/utils/common.go index ac5d56bda..176fa1f05 100644 --- a/common/utils/common.go +++ b/common/utils/common.go @@ -584,3 +584,15 @@ func CheckContractInterfaceTetrad(contractId string, source apiservice.Interface out := hex.EncodeToString(h.Sum(nil)) return out, nil } + +func CalculateContractID(namespace, service, name, protocol, version string) (string, error) { + h := sha1.New() + str := fmt.Sprintf("%s##%s##%s##%s##%d", namespace, service, name, protocol, version) + + if _, err := io.WriteString(h, str); err != nil { + return "", err + } + + out := hex.EncodeToString(h.Sum(nil)) + return out, nil +} diff --git a/config/api.go b/config/api.go index a20ff72c3..9769462a6 100644 --- a/config/api.go +++ b/config/api.go @@ -77,10 +77,14 @@ type ConfigFileReleaseOperate interface { PublishConfigFile(ctx context.Context, configFileRelease *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse // GetConfigFileRelease 获取配置文件发布 GetConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse - // DeleteConfigFileReleases 删除配置文件发布内容 + // DeleteConfigFileReleases 批量删除配置文件发布内容 DeleteConfigFileReleases(ctx context.Context, reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse + // DeleteConfigFileRelease 删除配置文件发布 + DeleteConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse // RollbackConfigFileReleases 批量回滚配置到指定版本 RollbackConfigFileReleases(ctx context.Context, releases []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse + // RollbackConfigFileRelease 回滚配置到指定版本 + RollbackConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse // GetConfigFileReleases 查询所有的配置发布版本信息 GetConfigFileReleases(ctx context.Context, filters map[string]string) *apiconfig.ConfigBatchQueryResponse // GetConfigFileReleaseVersions 查询所有的配置发布版本信息 diff --git a/config/client.go b/config/client.go index 5f4076ddf..eec6707e6 100644 --- a/config/client.go +++ b/config/client.go @@ -45,10 +45,6 @@ func (s *Server) GetConfigFileWithCache(ctx context.Context, group := req.GetGroup().GetValue() fileName := req.GetFileName().GetValue() - if namespace == "" || group == "" || fileName == "" { - return api.NewConfigClientResponseWithInfo( - apimodel.Code_BadRequest, "namespace & group & fileName can not be empty") - } req = formatClientRequest(ctx, req) // 从缓存中获取灰度文件 var release *model.ConfigFileRelease @@ -142,10 +138,6 @@ func (s *Server) GetConfigFileNamesWithCache(ctx context.Context, namespace := req.GetConfigFileGroup().GetNamespace().GetValue() group := req.GetConfigFileGroup().GetName().GetValue() - if namespace == "" || group == "" { - return api.NewConfigClientListResponse(apimodel.Code_BadRequest) - } - releases, revision := s.fileCache.GetGroupActiveReleases(namespace, group) if revision == "" { return api.NewConfigClientListResponse(apimodel.Code_ExecuteSuccess) @@ -179,11 +171,6 @@ 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 == "" { @@ -217,6 +204,7 @@ func CompareByVersion(clientInfo *apiconfig.ClientConfigFileInfo, file *model.Co return clientInfo.GetVersion().GetValue() < file.Version } +// only for unit test func (s *Server) checkClientConfigFile(ctx context.Context, files []*apiconfig.ClientConfigFileInfo, compartor CompareFunction) (*apiconfig.ConfigClientResponse, bool) { if len(files) == 0 { diff --git a/config/config_file.go b/config/config_file.go index 873f8c142..3f91daa44 100644 --- a/config/config_file.go +++ b/config/config_file.go @@ -94,9 +94,6 @@ func (s *Server) handleCreateConfigFile(ctx context.Context, tx store.Tx, // UpdateConfigFile 更新配置文件 func (s *Server) UpdateConfigFile(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { - if checkRsp := s.checkConfigFileParams(req); checkRsp != nil { - return checkRsp - } tx, err := s.storage.StartTx() if err != nil { log.Error("[Config][File] update config file begin tx.", utils.RequestID(ctx), zap.Error(err)) @@ -227,10 +224,6 @@ func (s *Server) BatchDeleteConfigFile(ctx context.Context, req []*apiconfig.Con // DeleteConfigFile 删除配置文件,删除配置文件同时会通知客户端 Not_Found func (s *Server) DeleteConfigFile(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { - if errResp := checkReadFileParameter(req); errResp != nil { - return errResp - } - namespace := req.GetNamespace().GetValue() group := req.GetGroup().GetValue() fileName := req.GetName().GetValue() @@ -282,10 +275,6 @@ func (s *Server) DeleteConfigFile(ctx context.Context, req *apiconfig.ConfigFile // GetConfigFileRichInfo 获取单个配置文件基础信息,包含发布状态等信息 func (s *Server) GetConfigFileRichInfo(ctx context.Context, req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { - if errResp := checkReadFileParameter(req); errResp != nil { - return errResp - } - namespace := req.GetNamespace().GetValue() group := req.GetGroup().GetValue() fileName := req.GetName().GetValue() @@ -310,23 +299,8 @@ func (s *Server) GetConfigFileRichInfo(ctx context.Context, req *apiconfig.Confi } // SearchConfigFile 查询配置文件 -func (s *Server) SearchConfigFile(ctx context.Context, filter map[string]string) *apiconfig.ConfigBatchQueryResponse { - offset, limit, err := utils.ParseOffsetAndLimit(filter) - if err != nil { - out := api.NewConfigBatchQueryResponse(apimodel.Code_BadRequest) - out.Info = utils.NewStringValue(err.Error()) - return out - } - searchFilters := map[string]string{} - for k, v := range filter { - // 无效查询参数自动忽略 - if v == "" { - continue - } - if _, ok := availableSearch["config_file"][k]; ok { - searchFilters[k] = v - } - } +func (s *Server) SearchConfigFile(ctx context.Context, searchFilters map[string]string) *apiconfig.ConfigBatchQueryResponse { + offset, limit, _ := utils.ParseOffsetAndLimit(searchFilters) count, files, err := s.storage.QueryConfigFiles(searchFilters, offset, limit) if err != nil { log.Error("[Config][File] search config files.", utils.RequestID(ctx), zap.Error(err)) @@ -436,9 +410,6 @@ func (s *Server) ImportConfigFile(ctx context.Context, configFiles []*apiconfig.ConfigFile, conflictHandling string) *apiconfig.ConfigImportResponse { // 预创建命名空间和分组 for _, configFile := range configFiles { - if checkRsp := s.checkConfigFileParams(configFile); checkRsp != nil { - return api.NewConfigFileImportResponse(apimodel.Code(checkRsp.Code.GetValue()), nil, nil, nil) - } if rsp := s.prepareCreateConfigFile(ctx, configFile); rsp.Code.Value != api.ExecuteSuccess { return api.NewConfigFileImportResponse(apimodel.Code(rsp.Code.GetValue()), nil, nil, nil) } @@ -526,29 +497,6 @@ func (s *Server) getGroupAllConfigFiles(namespace, group string) ([]*model.Confi return configFiles, nil } -func (s *Server) checkConfigFileParams(configFile *apiconfig.ConfigFile) *apiconfig.ConfigResponse { - if configFile == nil { - return api.NewConfigFileResponse(apimodel.Code_InvalidParameter, configFile) - } - if err := CheckFileName(configFile.Name); err != nil { - return api.NewConfigFileResponse(apimodel.Code_InvalidConfigFileName, configFile) - } - if err := utils.CheckResourceName(configFile.Namespace); err != nil { - return api.NewConfigFileResponse(apimodel.Code_InvalidNamespaceName, configFile) - } - if err := CheckContentLength(configFile.Content.GetValue(), int(s.cfg.ContentMaxLength)); err != nil { - return api.NewConfigResponseWithInfo(apimodel.Code_InvalidConfigFileContentLength, err.Error()) - } - if len(configFile.Tags) > 0 { - for _, tag := range configFile.Tags { - if tag.Key.GetValue() == "" || tag.Value.GetValue() == "" { - return api.NewConfigFileResponse(apimodel.Code_InvalidConfigFileTags, configFile) - } - } - } - return nil -} - // GetAllConfigEncryptAlgorithms 获取配置加密算法 func (s *Server) GetAllConfigEncryptAlgorithms(ctx context.Context) *apiconfig.ConfigEncryptAlgorithmResponse { if s.cryptoManager == nil { diff --git a/config/config_file_group.go b/config/config_file_group.go index eae395e15..6077366d7 100644 --- a/config/config_file_group.go +++ b/config/config_file_group.go @@ -36,10 +36,6 @@ import ( // CreateConfigFileGroup 创建配置文件组 func (s *Server) CreateConfigFileGroup(ctx context.Context, req *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { - if checkError := checkConfigFileGroupParams(req); checkError != nil { - return checkError - } - namespace := req.Namespace.GetValue() groupName := req.Name.GetValue() @@ -89,10 +85,6 @@ func (s *Server) CreateConfigFileGroup(ctx context.Context, req *apiconfig.Confi // UpdateConfigFileGroup 更新配置文件组 func (s *Server) UpdateConfigFileGroup(ctx context.Context, req *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { - if resp := checkConfigFileGroupParams(req); resp != nil { - return resp - } - namespace := req.Namespace.GetValue() groupName := req.Name.GetValue() @@ -173,13 +165,6 @@ func (s *Server) createConfigFileGroupIfAbsent(ctx context.Context, // DeleteConfigFileGroup 删除配置文件组 func (s *Server) DeleteConfigFileGroup(ctx context.Context, namespace, name string) *apiconfig.ConfigResponse { - if err := utils.CheckResourceName(utils.NewStringValue(namespace)); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) - } - if err := utils.CheckResourceName(utils.NewStringValue(name)); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) - } - log.Info("[Config][Group] delete config file group. ", utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(name)) @@ -242,21 +227,9 @@ func (s *Server) hasResourceInConfigGroup(ctx context.Context, namespace, name s // QueryConfigFileGroups 查询配置文件组 func (s *Server) QueryConfigFileGroups(ctx context.Context, - filter map[string]string) *apiconfig.ConfigBatchQueryResponse { - - offset, limit, err := utils.ParseOffsetAndLimit(filter) - if err != nil { - resp := api.NewConfigBatchQueryResponse(apimodel.Code_BadRequest) - resp.Info = utils.NewStringValue(err.Error()) - return resp - } + searchFilters map[string]string) *apiconfig.ConfigBatchQueryResponse { - searchFilters := map[string]string{} - for k, v := range filter { - if newK, ok := availableSearch["config_file_group"][k]; ok { - searchFilters[newK] = v - } - } + offset, limit, _ := utils.ParseOffsetAndLimit(searchFilters) args := &cachetypes.ConfigGroupArgs{ Namespace: searchFilters["namespace"], @@ -293,22 +266,6 @@ func (s *Server) QueryConfigFileGroups(ctx context.Context, return resp } -func checkConfigFileGroupParams(configFileGroup *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { - if configFileGroup == nil { - return api.NewConfigResponse(apimodel.Code_InvalidParameter) - } - if err := utils.CheckResourceName(configFileGroup.Name); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) - } - if err := utils.CheckResourceName(configFileGroup.Namespace); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) - } - if len(configFileGroup.GetMetadata()) > utils.MaxMetadataLength { - return api.NewConfigResponse(apimodel.Code_InvalidMetadata) - } - return nil -} - // configGroupRecordEntry 生成服务的记录entry func configGroupRecordEntry(ctx context.Context, req *apiconfig.ConfigFileGroup, md *model.ConfigFileGroup, operationType model.OperationType) *model.RecordEntry { diff --git a/config/config_file_release.go b/config/config_file_release.go index cb79a7348..e1efa3d03 100644 --- a/config/config_file_release.go +++ b/config/config_file_release.go @@ -41,22 +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) - } - if err := utils.CheckResourceName(req.GetNamespace()); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) - } - if err := utils.CheckResourceName(req.GetGroup()); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) - } - if !s.checkNamespaceExisted(req.GetNamespace().GetValue()) { - return api.NewConfigResponse(apimodel.Code_NotFoundNamespace) - } - if req.GetReleaseType().GetValue() == model.ReleaseTypeGray && len(req.GetBetaLabels()) == 0 { - return api.NewConfigResponse(apimodel.Code_InvalidMatchRule) - } - tx, err := s.storage.StartTx() if err != nil { log.Error("[Config][Release] publish config file begin tx.", utils.RequestID(ctx), zap.Error(err)) @@ -207,10 +191,6 @@ func (s *Server) GetConfigFileRelease(ctx context.Context, req *apiconfig.Config group := req.GetGroup().GetValue() fileName := req.GetFileName().GetValue() releaseName := req.GetName().GetValue() - - if errCode, errMsg := checkBaseReleaseParam(req, false); errCode != apimodel.Code_ExecuteSuccess { - return api.NewConfigResponseWithInfo(errCode, errMsg) - } var ( ret *model.ConfigFileRelease err error @@ -263,7 +243,7 @@ func (s *Server) DeleteConfigFileReleases(ctx context.Context, for i, instance := range reqs { chs = append(chs, make(chan *apiconfig.ConfigResponse)) go func(index int, ins *apiconfig.ConfigFileRelease) { - chs[index] <- s.handleDeleteConfigFileRelease(ctx, ins) + chs[index] <- s.DeleteConfigFileRelease(ctx, ins) }(i, instance) } @@ -274,12 +254,8 @@ func (s *Server) DeleteConfigFileReleases(ctx context.Context, return responses } -func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, +func (s *Server) DeleteConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { - - if errCode, errMsg := checkBaseReleaseParam(req, true); errCode != apimodel.Code_ExecuteSuccess { - return api.NewConfigResponseWithInfo(errCode, errMsg) - } release := &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ @@ -348,33 +324,14 @@ func (s *Server) handleDeleteConfigFileRelease(ctx context.Context, } func (s *Server) GetConfigFileReleaseVersions(ctx context.Context, - filters map[string]string) *apiconfig.ConfigBatchQueryResponse { - - searchFilters := map[string]string{} - for k, v := range filters { - if nk, ok := availableSearch["config_file_release"][k]; ok { - searchFilters[nk] = v - } - } + searchFilters map[string]string) *apiconfig.ConfigBatchQueryResponse { - namespace := searchFilters["namespace"] - group := searchFilters["group"] - fileName := searchFilters["file_name"] - if namespace == "" { - return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, "invalid namespace") - } - if group == "" { - return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, "invalid config group") - } - if fileName == "" { - return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, "invalid config file name") - } args := cachetypes.ConfigReleaseArgs{ BaseConfigArgs: cachetypes.BaseConfigArgs{ - Namespace: filters["namespace"], - Group: filters["group"], + Namespace: searchFilters["namespace"], + Group: searchFilters["group"], }, - FileName: filters["file_name"], + FileName: searchFilters["file_name"], OnlyActive: false, NoPage: true, } @@ -382,19 +339,9 @@ func (s *Server) GetConfigFileReleaseVersions(ctx context.Context, } func (s *Server) GetConfigFileReleases(ctx context.Context, - filter map[string]string) *apiconfig.ConfigBatchQueryResponse { - - searchFilters := map[string]string{} - for k, v := range filter { - if nK, ok := availableSearch["config_file_release"][k]; ok { - searchFilters[nK] = v - } - } + searchFilters map[string]string) *apiconfig.ConfigBatchQueryResponse { - offset, limit, err := utils.ParseOffsetAndLimit(filter) - if err != nil { - return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, err.Error()) - } + offset, limit, _ := utils.ParseOffsetAndLimit(searchFilters) args := cachetypes.ConfigReleaseArgs{ BaseConfigArgs: cachetypes.BaseConfigArgs{ @@ -475,9 +422,6 @@ func (s *Server) RollbackConfigFileReleases(ctx context.Context, // RollbackConfigFileRelease 回滚配置 func (s *Server) RollbackConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { - if errCode, errMsg := checkBaseReleaseParam(req, true); errCode != apimodel.Code_ExecuteSuccess { - return api.NewConfigResponseWithInfo(errCode, errMsg) - } data := &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ @@ -546,16 +490,6 @@ func (s *Server) handleRollbackConfigFileRelease(ctx context.Context, tx store.T // CasUpsertAndReleaseConfigFile 根据版本比对决定是否允许进行配置修改发布 func (s *Server) CasUpsertAndReleaseConfigFile(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { - if err := CheckFileName(req.GetFileName()); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidConfigFileName) - } - 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") - } - upsertFileReq := &apiconfig.ConfigFile{ Name: req.GetFileName(), Namespace: req.GetNamespace(), @@ -648,17 +582,6 @@ func (s *Server) CasUpsertAndReleaseConfigFile(ctx context.Context, func (s *Server) UpsertAndReleaseConfigFile(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(), @@ -849,25 +772,3 @@ func configFileReleaseRecordEntry(ctx context.Context, req *apiconfig.ConfigFile return entry } - -func checkBaseReleaseParam(req *apiconfig.ConfigFileRelease, checkRelease bool) (apimodel.Code, string) { - namespace := req.GetNamespace().GetValue() - group := req.GetGroup().GetValue() - fileName := req.GetFileName().GetValue() - releaseName := req.GetName().GetValue() - if namespace == "" { - return apimodel.Code_BadRequest, "invalid namespace" - } - if group == "" { - return apimodel.Code_BadRequest, "invalid config group" - } - if fileName == "" { - return apimodel.Code_BadRequest, "invalid config file name" - } - if checkRelease { - if releaseName == "" { - return apimodel.Code_BadRequest, "invalid config release name" - } - } - return apimodel.Code_ExecuteSuccess, "" -} diff --git a/config/config_file_release_history.go b/config/config_file_release_history.go index 094ce0f70..8e8e2b997 100644 --- a/config/config_file_release_history.go +++ b/config/config_file_release_history.go @@ -62,24 +62,14 @@ func (s *Server) recordReleaseHistory(ctx context.Context, fileRelease *model.Co // GetConfigFileReleaseHistories 获取配置文件发布历史记录 func (s *Server) GetConfigFileReleaseHistories(ctx context.Context, - filter map[string]string) *apiconfig.ConfigBatchQueryResponse { + searchFilter map[string]string) *apiconfig.ConfigBatchQueryResponse { - offset, limit, err := utils.ParseOffsetAndLimit(filter) - if err != nil { - return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, err.Error()) - } - - searchFilter := map[string]string{} - for k, v := range filter { - if nk, ok := availableSearch["config_file_release_history"][k]; ok { - searchFilter[nk] = v - } - } + offset, limit, _ := utils.ParseOffsetAndLimit(searchFilter) count, saveDatas, err := s.storage.QueryConfigFileReleaseHistories(searchFilter, offset, limit) if err != nil { log.Error("[Config][History] get config file release history error.", utils.RequestID(ctx), - zap.Any("filter", filter), zap.Error(err)) + zap.Any("filter", searchFilter), zap.Error(err)) return api.NewConfigBatchQueryResponseWithInfo(commonstore.StoreCode2APICode(err), err.Error()) } diff --git a/config/config_file_template.go b/config/config_file_template.go index e903a983c..e7f78a49f 100644 --- a/config/config_file_template.go +++ b/config/config_file_template.go @@ -33,9 +33,6 @@ import ( // CreateConfigFileTemplate create config file template func (s *Server) CreateConfigFileTemplate( ctx context.Context, template *apiconfig.ConfigFileTemplate) *apiconfig.ConfigResponse { - if checkRsp := s.checkConfigFileTemplateParam(template); checkRsp != nil { - return checkRsp - } name := template.GetName().GetValue() saveData, err := s.storage.GetConfigFileTemplate(name) @@ -95,16 +92,3 @@ func (s *Server) GetAllConfigFileTemplates(ctx context.Context) *apiconfig.Confi return api.NewConfigFileTemplateBatchQueryResponse(apimodel.Code_ExecuteSuccess, uint32(len(templates)), apiTemplates) } - -func (s *Server) checkConfigFileTemplateParam(template *apiconfig.ConfigFileTemplate) *apiconfig.ConfigResponse { - if err := CheckFileName(template.GetName()); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidConfigFileTemplateName) - } - if err := CheckContentLength(template.Content.GetValue(), int(s.cfg.ContentMaxLength)); err != nil { - return api.NewConfigResponse(apimodel.Code_InvalidConfigFileContentLength) - } - if len(template.Content.GetValue()) == 0 { - return api.NewConfigFileTemplateResponseWithMessage(apimodel.Code_BadRequest, "content can not be blank.") - } - return nil -} diff --git a/config/interceptor/auth/config_file_release_authibility.go b/config/interceptor/auth/config_file_release_authibility.go index 7dd0879ec..1af06424c 100644 --- a/config/interceptor/auth/config_file_release_authibility.go +++ b/config/interceptor/auth/config_file_release_authibility.go @@ -73,6 +73,20 @@ func (s *ServerAuthability) DeleteConfigFileReleases(ctx context.Context, return s.nextServer.DeleteConfigFileReleases(ctx, reqs) } +// DeleteConfigFileRelease implements ConfigCenterServer. +func (s *ServerAuthability) DeleteConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + authCtx := s.collectConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{ + req, + }, model.Delete, "DeleteConfigFileRelease") + + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.nextServer.DeleteConfigFileRelease(ctx, req) +} + // GetConfigFileReleaseVersions implements ConfigCenterServer. func (s *ServerAuthability) GetConfigFileReleaseVersions(ctx context.Context, filters map[string]string) *apiconfig.ConfigBatchQueryResponse { @@ -115,6 +129,21 @@ func (s *ServerAuthability) RollbackConfigFileReleases(ctx context.Context, return s.nextServer.RollbackConfigFileReleases(ctx, reqs) } +func (s *ServerAuthability) RollbackConfigFileRelease(ctx context.Context, + req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + + authCtx := s.collectConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{ + req, + }, model.Modify, "RollbackConfigFileRelease") + + if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + return s.nextServer.RollbackConfigFileRelease(ctx, req) +} + // UpsertAndReleaseConfigFile . func (s *ServerAuthability) UpsertAndReleaseConfigFile(ctx context.Context, req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { diff --git a/config/interceptor/auth/server_authability.go b/config/interceptor/auth/server_authability.go index 2117e8fe0..c6e27cdfb 100644 --- a/config/interceptor/auth/server_authability.go +++ b/config/interceptor/auth/server_authability.go @@ -50,7 +50,7 @@ func New(nextServer config.ConfigCenterServer, cacheMgr cachetypes.CacheManager, userMgn: userMgr, strategyMgn: strategyMgr, } - if val, ok := nextServer.(*config.Server); !ok { + if val, ok := nextServer.(*config.Server); ok { val.SetResourceHooks(proxy) } return proxy diff --git a/config/interceptor/paramcheck/client_check.go b/config/interceptor/paramcheck/client_check.go new file mode 100644 index 000000000..7b8ace8eb --- /dev/null +++ b/config/interceptor/paramcheck/client_check.go @@ -0,0 +1,219 @@ +/** + * 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 paramcheck + +import ( + "context" + + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + 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 *Server) UpsertAndReleaseConfigFileFromClient(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") + } + return s.nextServer.UpsertAndReleaseConfigFileFromClient(ctx, req) +} + +// CreateConfigFileFromClient 调用config_file的方法创建配置文件 +func (s *Server) CreateConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { + if checkRsp := s.checkConfigFileParams(req); checkRsp != nil { + return api.NewConfigClientResponseFromConfigResponse(checkRsp) + } + return s.nextServer.CreateConfigFileFromClient(ctx, req) +} + +// UpdateConfigFileFromClient 调用config_file的方法更新配置文件 +func (s *Server) UpdateConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse { + if checkRsp := s.checkConfigFileParams(req); checkRsp != nil { + return api.NewConfigClientResponseFromConfigResponse(checkRsp) + } + return s.nextServer.UpdateConfigFileFromClient(ctx, req) +} + +// DeleteConfigFileFromClient 删除配置文件,删除配置文件同时会通知客户端 Not_Found +func (s *Server) DeleteConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + + if req.GetNamespace().GetValue() == "" { + return api.NewConfigResponseWithInfo( + apimodel.Code_BadRequest, "namespace is empty") + } + + if req.GetGroup().GetValue() == "" { + return api.NewConfigResponseWithInfo( + apimodel.Code_BadRequest, "file group is empty") + } + + if req.GetName().GetValue() == "" { + return api.NewConfigResponseWithInfo( + apimodel.Code_BadRequest, "filename is empty") + } + + return s.nextServer.DeleteConfigFileFromClient(ctx, req) +} + +// PublishConfigFileFromClient 调用config_file_release的方法发布配置文件 +func (s *Server) PublishConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFileRelease) *apiconfig.ConfigClientResponse { + + if err := CheckFileName(req.GetFileName()); err != nil { + ret := api.NewConfigResponse(apimodel.Code_InvalidConfigFileName) + return api.NewConfigClientResponseFromConfigResponse(ret) + } + if err := utils.CheckResourceName(req.GetNamespace()); err != nil { + ret := api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) + return api.NewConfigClientResponseFromConfigResponse(ret) + } + if err := utils.CheckResourceName(req.GetGroup()); err != nil { + ret := api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) + return api.NewConfigClientResponseFromConfigResponse(ret) + } + if !s.checkNamespaceExisted(req.GetNamespace().GetValue()) { + ret := api.NewConfigResponse(apimodel.Code_NotFoundNamespace) + return api.NewConfigClientResponseFromConfigResponse(ret) + } + if req.GetReleaseType().GetValue() == model.ReleaseTypeGray && len(req.GetBetaLabels()) == 0 { + ret := api.NewConfigResponse(apimodel.Code_InvalidMatchRule) + return api.NewConfigClientResponseFromConfigResponse(ret) + } + + return s.nextServer.PublishConfigFileFromClient(ctx, req) +} + +// GetConfigFileWithCache 从缓存中获取配置文件,如果客户端的版本号大于服务端,则服务端重新加载缓存 +func (s *Server) GetConfigFileWithCache(ctx context.Context, + req *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigClientResponse { + + if req.GetNamespace().GetValue() == "" { + return api.NewConfigClientResponseWithInfo( + apimodel.Code_BadRequest, "namespace is empty") + } + + if req.GetGroup().GetValue() == "" { + return api.NewConfigClientResponseWithInfo( + apimodel.Code_BadRequest, "file group is empty") + } + + if req.GetFileName().GetValue() == "" { + return api.NewConfigClientResponseWithInfo( + apimodel.Code_BadRequest, "filename is empty") + } + + return s.nextServer.GetConfigFileWithCache(ctx, req) +} + +// WatchConfigFiles 监听配置文件变化 +func (s *Server) LongPullWatchFile(ctx context.Context, + request *apiconfig.ClientWatchConfigFileRequest) (config.WatchCallback, error) { + + watchFiles := request.WatchFiles + if len(watchFiles) == 0 { + return func() *apiconfig.ConfigClientResponse { + return api.NewConfigClientResponse0(apimodel.Code_InvalidWatchConfigFileFormat) + }, nil + } + + for _, configFile := range watchFiles { + namespace := configFile.GetNamespace().GetValue() + group := configFile.GetGroup().GetValue() + fileName := configFile.GetFileName().GetValue() + if namespace == "" { + return func() *apiconfig.ConfigClientResponse { + return api.NewConfigClientResponseWithInfo( + apimodel.Code_BadRequest, "namespace is empty") + }, nil + } + if group == "" { + return func() *apiconfig.ConfigClientResponse { + return api.NewConfigClientResponseWithInfo( + apimodel.Code_BadRequest, "file group is empty") + }, nil + } + if fileName == "" { + return func() *apiconfig.ConfigClientResponse { + return api.NewConfigClientResponseWithInfo( + apimodel.Code_BadRequest, "filename is empty") + }, nil + } + } + + return s.nextServer.LongPullWatchFile(ctx, request) +} + +// GetConfigFileNamesWithCache 获取某个配置分组下的配置文件 +func (s *Server) GetConfigFileNamesWithCache(ctx context.Context, + req *apiconfig.ConfigFileGroupRequest) *apiconfig.ConfigClientListResponse { + + if req.GetConfigFileGroup().GetNamespace().GetValue() == "" { + return api.NewConfigClientListResponseWithInfo( + apimodel.Code_BadRequest, "namespace is empty") + } + + if req.GetConfigFileGroup().GetName().GetValue() == "" { + return api.NewConfigClientListResponseWithInfo( + apimodel.Code_BadRequest, "file group is empty") + } + + return s.nextServer.GetConfigFileNamesWithCache(ctx, req) +} + +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 + } + + return s.nextServer.GetConfigGroupsWithCache(ctx, req) +} + +// CasUpsertAndReleaseConfigFileFromClient 创建/更新配置文件并发布 +func (s *Server) CasUpsertAndReleaseConfigFileFromClient(ctx context.Context, + req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { + if err := CheckFileName(req.GetFileName()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileName) + } + if err := utils.CheckResourceName(req.GetNamespace()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) + } + if err := utils.CheckResourceName(req.GetGroup()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) + } + return s.nextServer.CasUpsertAndReleaseConfigFileFromClient(ctx, req) +} diff --git a/config/interceptor/paramcheck/config_file_check.go b/config/interceptor/paramcheck/config_file_check.go new file mode 100644 index 000000000..ba1f42d67 --- /dev/null +++ b/config/interceptor/paramcheck/config_file_check.go @@ -0,0 +1,113 @@ +/** + * 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 paramcheck + +import ( + "context" + "strconv" + + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/utils" +) + +// CreateConfigFile 创建配置文件 +func (s *Server) CreateConfigFile(ctx context.Context, + configFile *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + + return s.nextServer.CreateConfigFile(ctx, configFile) +} + +// GetConfigFileRichInfo 获取单个配置文件基础信息,包含发布状态等信息 +func (s *Server) GetConfigFileRichInfo(ctx context.Context, + req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + if errResp := checkReadFileParameter(req); errResp != nil { + return errResp + } + return s.nextServer.GetConfigFileRichInfo(ctx, req) +} + +// SearchConfigFile 查询配置文件 +func (s *Server) SearchConfigFile(ctx context.Context, + filter map[string]string) *apiconfig.ConfigBatchQueryResponse { + + offset, limit, err := utils.ParseOffsetAndLimit(filter) + if err != nil { + out := api.NewConfigBatchQueryResponse(apimodel.Code_BadRequest) + out.Info = utils.NewStringValue(err.Error()) + return out + } + searchFilters := map[string]string{ + "offset": strconv.Itoa(int(offset)), + "limit": strconv.Itoa(int(limit)), + } + for k, v := range filter { + // 无效查询参数自动忽略 + if v == "" { + continue + } + if _, ok := availableSearch["config_file"][k]; ok { + searchFilters[k] = v + } + } + return s.nextServer.SearchConfigFile(ctx, filter) +} + +// UpdateConfigFile 更新配置文件 +func (s *Server) UpdateConfigFile( + ctx context.Context, configFile *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + + return s.nextServer.UpdateConfigFile(ctx, configFile) +} + +// DeleteConfigFile 删除配置文件,删除配置文件同时会通知客户端 Not_Found +func (s *Server) DeleteConfigFile(ctx context.Context, + req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + + return s.nextServer.DeleteConfigFile(ctx, req) +} + +// BatchDeleteConfigFile 批量删除配置文件 +func (s *Server) BatchDeleteConfigFile(ctx context.Context, + req []*apiconfig.ConfigFile) *apiconfig.ConfigResponse { + + return s.nextServer.BatchDeleteConfigFile(ctx, req) +} + +func (s *Server) ExportConfigFile(ctx context.Context, + configFileExport *apiconfig.ConfigFileExportRequest) *apiconfig.ConfigExportResponse { + + return s.nextServer.ExportConfigFile(ctx, configFileExport) +} + +func (s *Server) ImportConfigFile(ctx context.Context, + configFiles []*apiconfig.ConfigFile, conflictHandling string) *apiconfig.ConfigImportResponse { + for _, configFile := range configFiles { + if checkRsp := s.checkConfigFileParams(configFile); checkRsp != nil { + return api.NewConfigFileImportResponse(apimodel.Code(checkRsp.Code.GetValue()), nil, nil, nil) + } + } + return s.nextServer.ImportConfigFile(ctx, configFiles, conflictHandling) +} + +func (s *Server) GetAllConfigEncryptAlgorithms( + ctx context.Context) *apiconfig.ConfigEncryptAlgorithmResponse { + return s.nextServer.GetAllConfigEncryptAlgorithms(ctx) +} diff --git a/config/interceptor/paramcheck/config_file_group_check.go b/config/interceptor/paramcheck/config_file_group_check.go new file mode 100644 index 000000000..4dc649c10 --- /dev/null +++ b/config/interceptor/paramcheck/config_file_group_check.go @@ -0,0 +1,99 @@ +/** + * 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 paramcheck + +import ( + "context" + "strconv" + + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/utils" +) + +// CreateConfigFileGroup 创建配置文件组 +func (s *Server) CreateConfigFileGroup(ctx context.Context, + req *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { + if checkError := checkConfigFileGroupParams(req); checkError != nil { + return checkError + } + return s.nextServer.CreateConfigFileGroup(ctx, req) +} + +// QueryConfigFileGroups 查询配置文件组 +func (s *Server) QueryConfigFileGroups(ctx context.Context, + filter map[string]string) *apiconfig.ConfigBatchQueryResponse { + + offset, limit, err := utils.ParseOffsetAndLimit(filter) + if err != nil { + resp := api.NewConfigBatchQueryResponse(apimodel.Code_BadRequest) + resp.Info = utils.NewStringValue(err.Error()) + return resp + } + + searchFilters := map[string]string{ + "offset": strconv.Itoa(int(offset)), + "limit": strconv.Itoa(int(limit)), + } + for k, v := range filter { + if newK, ok := availableSearch["config_file_group"][k]; ok { + searchFilters[newK] = v + } + } + + return s.nextServer.QueryConfigFileGroups(ctx, searchFilters) +} + +// DeleteConfigFileGroup 删除配置文件组 +func (s *Server) DeleteConfigFileGroup( + ctx context.Context, namespace, name string) *apiconfig.ConfigResponse { + if err := utils.CheckResourceName(utils.NewStringValue(namespace)); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) + } + if err := utils.CheckResourceName(utils.NewStringValue(name)); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) + } + return s.nextServer.DeleteConfigFileGroup(ctx, namespace, name) +} + +// UpdateConfigFileGroup 更新配置文件组 +func (s *Server) UpdateConfigFileGroup(ctx context.Context, + req *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { + if checkError := checkConfigFileGroupParams(req); checkError != nil { + return checkError + } + return s.nextServer.UpdateConfigFileGroup(ctx, req) +} + +func checkConfigFileGroupParams(configFileGroup *apiconfig.ConfigFileGroup) *apiconfig.ConfigResponse { + if configFileGroup == nil { + return api.NewConfigResponse(apimodel.Code_InvalidParameter) + } + if err := utils.CheckResourceName(configFileGroup.Name); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) + } + if err := utils.CheckResourceName(configFileGroup.Namespace); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) + } + if len(configFileGroup.GetMetadata()) > utils.MaxMetadataLength { + return api.NewConfigResponse(apimodel.Code_InvalidMetadata) + } + return nil +} diff --git a/config/interceptor/paramcheck/config_file_release_check.go b/config/interceptor/paramcheck/config_file_release_check.go new file mode 100644 index 000000000..6b1f93e6e --- /dev/null +++ b/config/interceptor/paramcheck/config_file_release_check.go @@ -0,0 +1,183 @@ +/** + * 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 paramcheck + +import ( + "context" + "strconv" + + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/utils" +) + +// PublishConfigFile 发布配置文件 +func (s *Server) PublishConfigFile(ctx context.Context, + configFileRelease *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + + return s.nextServer.PublishConfigFile(ctx, configFileRelease) +} + +// GetConfigFileRelease 获取配置文件发布内容 +func (s *Server) GetConfigFileRelease(ctx context.Context, + req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + if errCode, errMsg := checkBaseReleaseParam(req, false); errCode != apimodel.Code_ExecuteSuccess { + return api.NewConfigResponseWithInfo(errCode, errMsg) + } + return s.nextServer.GetConfigFileRelease(ctx, req) +} + +// DeleteConfigFileReleases implements ConfigCenterServer. +func (s *Server) DeleteConfigFileReleases(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.DeleteConfigFileRelease(ctx, ins) + }(i, instance) + } + + for _, ch := range chs { + resp := <-ch + api.ConfigCollect(responses, resp) + } + return responses +} + +func (s *Server) DeleteConfigFileRelease(ctx context.Context, req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + if errCode, errMsg := checkBaseReleaseParam(req, true); errCode != apimodel.Code_ExecuteSuccess { + return api.NewConfigResponseWithInfo(errCode, errMsg) + } + return s.nextServer.DeleteConfigFileRelease(ctx, req) +} + +// GetConfigFileReleaseVersions implements ConfigCenterServer. +func (s *Server) GetConfigFileReleaseVersions(ctx context.Context, + filters map[string]string) *apiconfig.ConfigBatchQueryResponse { + + searchFilters := map[string]string{} + for k, v := range filters { + if nk, ok := availableSearch["config_file_release"][k]; ok { + searchFilters[nk] = v + } + } + + namespace := searchFilters["namespace"] + group := searchFilters["group"] + fileName := searchFilters["file_name"] + if namespace == "" { + return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, "invalid namespace") + } + if group == "" { + return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, "invalid config group") + } + if fileName == "" { + return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, "invalid config file name") + } + + return s.nextServer.GetConfigFileReleaseVersions(ctx, searchFilters) +} + +// GetConfigFileReleases implements ConfigCenterServer. +func (s *Server) GetConfigFileReleases(ctx context.Context, + filters map[string]string) *apiconfig.ConfigBatchQueryResponse { + + offset, limit, err := utils.ParseOffsetAndLimit(filters) + if err != nil { + return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, err.Error()) + } + + searchFilters := map[string]string{ + "offset": strconv.Itoa(int(offset)), + "limit": strconv.Itoa(int(limit)), + } + for k, v := range filters { + if nK, ok := availableSearch["config_file_release"][k]; ok { + searchFilters[nK] = v + } + } + + return s.nextServer.GetConfigFileReleases(ctx, searchFilters) +} + +// RollbackConfigFileReleases implements ConfigCenterServer. +func (s *Server) RollbackConfigFileReleases(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.RollbackConfigFileRelease(ctx, ins) + }(i, instance) + } + + for _, ch := range chs { + resp := <-ch + api.ConfigCollect(responses, resp) + } + return responses +} + +func (s *Server) RollbackConfigFileRelease(ctx context.Context, + req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + if errCode, errMsg := checkBaseReleaseParam(req, true); errCode != apimodel.Code_ExecuteSuccess { + return api.NewConfigResponseWithInfo(errCode, errMsg) + } + return s.nextServer.RollbackConfigFileRelease(ctx, req) +} + +// UpsertAndReleaseConfigFile . +func (s *Server) UpsertAndReleaseConfigFile(ctx context.Context, + req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { + + return s.nextServer.UpsertAndReleaseConfigFile(ctx, req) +} + +func (s *Server) StopGrayConfigFileReleases(ctx context.Context, + reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse { + + return s.nextServer.StopGrayConfigFileReleases(ctx, reqs) +} + +func checkBaseReleaseParam(req *apiconfig.ConfigFileRelease, checkRelease bool) (apimodel.Code, string) { + namespace := req.GetNamespace().GetValue() + group := req.GetGroup().GetValue() + fileName := req.GetFileName().GetValue() + releaseName := req.GetName().GetValue() + if namespace == "" { + return apimodel.Code_BadRequest, "invalid namespace" + } + if group == "" { + return apimodel.Code_BadRequest, "invalid config group" + } + if fileName == "" { + return apimodel.Code_BadRequest, "invalid config file name" + } + if checkRelease { + if releaseName == "" { + return apimodel.Code_BadRequest, "invalid config release name" + } + } + return apimodel.Code_ExecuteSuccess, "" +} diff --git a/config/interceptor/paramcheck/config_file_release_history_check.go b/config/interceptor/paramcheck/config_file_release_history_check.go new file mode 100644 index 000000000..144bee2a8 --- /dev/null +++ b/config/interceptor/paramcheck/config_file_release_history_check.go @@ -0,0 +1,52 @@ +/** + * 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 paramcheck + +import ( + "context" + "strconv" + + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/utils" +) + +// GetConfigFileReleaseHistory 获取配置文件发布历史记录 +func (s *Server) GetConfigFileReleaseHistories(ctx context.Context, + filter map[string]string) *apiconfig.ConfigBatchQueryResponse { + + offset, limit, err := utils.ParseOffsetAndLimit(filter) + if err != nil { + return api.NewConfigBatchQueryResponseWithInfo(apimodel.Code_BadRequest, err.Error()) + } + + searchFilters := map[string]string{ + "offset": strconv.Itoa(int(offset)), + "limit": strconv.Itoa(int(limit)), + } + + for k, v := range filter { + if nk, ok := availableSearch["config_file_release_history"][k]; ok { + searchFilters[nk] = v + } + } + + return s.nextServer.GetConfigFileReleaseHistories(ctx, searchFilters) +} diff --git a/config/interceptor/paramcheck/config_file_template_check.go b/config/interceptor/paramcheck/config_file_template_check.go new file mode 100644 index 000000000..bd8a85232 --- /dev/null +++ b/config/interceptor/paramcheck/config_file_template_check.go @@ -0,0 +1,61 @@ +/** + * 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 paramcheck + +import ( + "context" + + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + api "github.com/polarismesh/polaris/common/api/v1" +) + +// GetAllConfigFileTemplates get all config file templates +func (s *Server) GetAllConfigFileTemplates(ctx context.Context) *apiconfig.ConfigBatchQueryResponse { + + return s.nextServer.GetAllConfigFileTemplates(ctx) +} + +// GetConfigFileTemplate get config file template +func (s *Server) GetConfigFileTemplate(ctx context.Context, name string) *apiconfig.ConfigResponse { + + return s.nextServer.GetConfigFileTemplate(ctx, name) +} + +// CreateConfigFileTemplate create config file template +func (s *Server) CreateConfigFileTemplate(ctx context.Context, + template *apiconfig.ConfigFileTemplate) *apiconfig.ConfigResponse { + if checkRsp := s.checkConfigFileTemplateParam(template); checkRsp != nil { + return checkRsp + } + return s.nextServer.CreateConfigFileTemplate(ctx, template) +} + +func (s *Server) checkConfigFileTemplateParam(template *apiconfig.ConfigFileTemplate) *apiconfig.ConfigResponse { + if err := CheckFileName(template.GetName()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileTemplateName) + } + if err := CheckContentLength(template.Content.GetValue(), int(s.cfg.ContentMaxLength)); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileContentLength) + } + if len(template.Content.GetValue()) == 0 { + return api.NewConfigFileTemplateResponseWithMessage(apimodel.Code_BadRequest, "content can not be blank.") + } + return nil +} diff --git a/config/interceptor/paramcheck/log.go b/config/interceptor/paramcheck/log.go new file mode 100644 index 000000000..627e72a9c --- /dev/null +++ b/config/interceptor/paramcheck/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 paramcheck + +import commonLog "github.com/polarismesh/polaris/common/log" + +var ( + log = commonLog.GetScopeOrDefaultByName(commonLog.ConfigLoggerName) + authLog = commonLog.GetScopeOrDefaultByName(commonLog.AuthLoggerName) +) diff --git a/config/interceptor/paramcheck/server.go b/config/interceptor/paramcheck/server.go new file mode 100644 index 000000000..05b264c7d --- /dev/null +++ b/config/interceptor/paramcheck/server.go @@ -0,0 +1,176 @@ +/** + * 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 paramcheck + +import ( + "errors" + "fmt" + "unicode/utf8" + + "github.com/golang/protobuf/ptypes/wrappers" + apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + + cachetypes "github.com/polarismesh/polaris/cache/api" + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/config" + "github.com/polarismesh/polaris/store" +) + +var _ config.ConfigCenterServer = (*Server)(nil) + +// Config 配置中心模块启动参数 +type Config struct { + ContentMaxLength int64 `yaml:"contentMaxLength"` +} + +// Server 配置中心核心服务 +type Server struct { + cacheMgr cachetypes.CacheManager + nextServer config.ConfigCenterServer + storage store.Store + cfg Config +} + +func New(nextServer config.ConfigCenterServer, cacheMgr cachetypes.CacheManager, + storage store.Store, cfg config.Config) config.ConfigCenterServer { + proxy := &Server{ + nextServer: nextServer, + cacheMgr: cacheMgr, + storage: storage, + cfg: Config{ + ContentMaxLength: cfg.ContentMaxLength, + }, + } + return proxy +} + +func (s *Server) checkNamespaceExisted(namespaceName string) bool { + if val := s.cacheMgr.Namespace().GetNamespace(namespaceName); val != nil { + return true + } + namespace, _ := s.storage.GetNamespace(namespaceName) + return namespace != nil +} + +// CheckFileName 校验文件名 +func CheckFileName(name *wrappers.StringValue) error { + if name == nil { + return errors.New(utils.NilErrString) + } + + if name.GetValue() == "" { + return errors.New(utils.EmptyErrString) + } + return nil +} + +// CheckContentLength 校验文件内容长度 +func CheckContentLength(content string, max int) error { + if utf8.RuneCountInString(content) > max { + return fmt.Errorf("content length too long. max length =%d", max) + } + + return nil +} + +func checkReadFileParameter(req *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + if req.GetNamespace().GetValue() == "" { + return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) + } + if req.GetGroup().GetValue() == "" { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) + } + if req.GetName().GetValue() == "" { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileName) + } + return nil +} + +func (s *Server) checkConfigFileParams(configFile *apiconfig.ConfigFile) *apiconfig.ConfigResponse { + if configFile == nil { + return api.NewConfigFileResponse(apimodel.Code_InvalidParameter, configFile) + } + if err := CheckFileName(configFile.Name); err != nil { + return api.NewConfigFileResponse(apimodel.Code_InvalidConfigFileName, configFile) + } + if err := utils.CheckResourceName(configFile.Namespace); err != nil { + return api.NewConfigFileResponse(apimodel.Code_InvalidNamespaceName, configFile) + } + if err := CheckContentLength(configFile.Content.GetValue(), int(s.cfg.ContentMaxLength)); err != nil { + return api.NewConfigResponseWithInfo(apimodel.Code_InvalidConfigFileContentLength, err.Error()) + } + if len(configFile.Tags) > 0 { + for _, tag := range configFile.Tags { + if tag.Key.GetValue() == "" || tag.Value.GetValue() == "" { + return api.NewConfigFileResponse(apimodel.Code_InvalidConfigFileTags, configFile) + } + } + } + return nil +} + +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", + }, + } +) diff --git a/config/interceptor/register.go b/config/interceptor/register.go index 8f048731b..cd235a0a1 100644 --- a/config/interceptor/register.go +++ b/config/interceptor/register.go @@ -22,11 +22,25 @@ import ( cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/config" config_auth "github.com/polarismesh/polaris/config/interceptor/auth" + "github.com/polarismesh/polaris/config/interceptor/paramcheck" + "github.com/polarismesh/polaris/store" ) func init() { - err := config.RegisterServerProxy("auth", func(cacheMgr cachetypes.CacheManager, - pre config.ConfigCenterServer) (config.ConfigCenterServer, error) { + err := config.RegisterServerProxy("paramcheck", func(cacheMgr cachetypes.CacheManager, + next config.ConfigCenterServer, cfg config.Config) (config.ConfigCenterServer, error) { + storage, err := store.GetStore() + if err != nil { + return nil, err + } + return paramcheck.New(next, cacheMgr, storage, cfg), nil + }) + if err != nil { + panic(err) + } + + err = config.RegisterServerProxy("auth", func(cacheMgr cachetypes.CacheManager, + next config.ConfigCenterServer, cfg config.Config) (config.ConfigCenterServer, error) { userMgr, err := auth.GetUserServer() if err != nil { return nil, err @@ -36,7 +50,7 @@ func init() { return nil, err } - return config_auth.New(pre, cacheMgr, userMgr, strategyMgr), nil + return config_auth.New(next, cacheMgr, userMgr, strategyMgr), nil }) if err != nil { panic(err) diff --git a/config/server.go b/config/server.go index 2cf71588f..63169eb4a 100644 --- a/config/server.go +++ b/config/server.go @@ -46,7 +46,7 @@ var ( serverProxyFactories = map[string]ServerProxyFactory{} ) -type ServerProxyFactory func(cacheMgr cachetypes.CacheManager, pre ConfigCenterServer) (ConfigCenterServer, error) +type ServerProxyFactory func(cacheMgr cachetypes.CacheManager, pre ConfigCenterServer, cfg Config) (ConfigCenterServer, error) func RegisterServerProxy(name string, factor ServerProxyFactory) error { if _, ok := serverProxyFactories[name]; ok { @@ -101,12 +101,12 @@ func Initialize(ctx context.Context, config Config, s store.Store, cacheMgr cach return nil } -func doInitialize(ctx context.Context, config Config, s store.Store, cacheMgr cachetypes.CacheManager, +func doInitialize(ctx context.Context, svcConf Config, s store.Store, cacheMgr cachetypes.CacheManager, namespaceOperator namespace.NamespaceOperateServer) (ConfigCenterServer, *Server, error) { var proxySvr ConfigCenterServer originSvr := &Server{} - if !config.Open { + if !svcConf.Open { originSvr.initialized = true return nil, nil, nil } @@ -114,21 +114,21 @@ func doInitialize(ctx context.Context, config Config, s store.Store, cacheMgr ca if err := cacheMgr.OpenResourceCache(configCacheEntries...); err != nil { return nil, nil, err } - err := originSvr.initialize(ctx, config, s, namespaceOperator, cacheMgr) + err := originSvr.initialize(ctx, svcConf, s, namespaceOperator, cacheMgr) if err != nil { return nil, nil, err } proxySvr = originSvr // 需要返回包装代理的 DiscoverServer - order := config.Interceptors + order := svcConf.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]) } - tmpSvr, err := factory(cacheMgr, proxySvr) + tmpSvr, err := factory(cacheMgr, proxySvr, svcConf) if err != nil { return nil, nil, err } @@ -329,6 +329,7 @@ func (cc *ConfigChains) AfterGetFileHistory(ctx context.Context, func GetChainOrder() []string { return []string{ + "paramcheck", "auth", } } diff --git a/config/test_export.go b/config/test_export.go index b2f033f19..0af5f5d5b 100644 --- a/config/test_export.go +++ b/config/test_export.go @@ -56,7 +56,7 @@ func TestInitialize(ctx context.Context, config Config, s store.Store, cacheMgn return nil, nil, fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) } - tmpSvr, err := factory(cacheMgn, proxySvr) + tmpSvr, err := factory(cacheMgn, proxySvr, config) if err != nil { return nil, nil, err } diff --git a/config/watcher.go b/config/watcher.go index 7934fc5c4..a86bfd92a 100644 --- a/config/watcher.go +++ b/config/watcher.go @@ -216,11 +216,6 @@ func (wc *watchCenter) OnEvent(ctx context.Context, arg any) error { } func (wc *watchCenter) checkQuickResponseClient(watchCtx WatchContext) *apiconfig.ConfigClientResponse { - watchFiles := watchCtx.ListWatchFiles() - if len(watchFiles) == 0 { - return api.NewConfigClientResponse0(apimodel.Code_InvalidWatchConfigFileFormat) - } - buildRet := func(release *model.ConfigFileRelease) *apiconfig.ConfigClientResponse { ret := &apiconfig.ClientConfigFileInfo{ Namespace: utils.NewStringValue(release.Namespace), @@ -233,14 +228,10 @@ func (wc *watchCenter) checkQuickResponseClient(watchCtx WatchContext) *apiconfi return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, ret) } - for _, configFile := range watchFiles { + for _, configFile := range watchCtx.ListWatchFiles() { namespace := configFile.GetNamespace().GetValue() group := configFile.GetGroup().GetValue() fileName := configFile.GetFileName().GetValue() - if namespace == "" || group == "" || fileName == "" { - return api.NewConfigClientResponseWithInfo(apimodel.Code_BadRequest, - "namespace & group & fileName can not be empty") - } // 从缓存中获取灰度文件 if len(watchCtx.ClientLabels()) > 0 { if release := wc.fileCache.GetActiveGrayRelease(namespace, group, fileName); release != nil { @@ -375,9 +366,6 @@ func (wc *watchCenter) startHandleTimeoutRequestWorker(ctx context.Context) { case <-ctx.Done(): return case <-t.C: - if wc.clients.Len() == 0 { - continue - } tNow := time.Now() waitRemove := make([]WatchContext, 0, 32) wc.clients.Range(func(client string, watchCtx WatchContext) { @@ -385,6 +373,9 @@ func (wc *watchCenter) startHandleTimeoutRequestWorker(ctx context.Context) { waitRemove = append(waitRemove, watchCtx) } }) + if len(waitRemove) > 0 { + log.Info("remove expire watch context", zap.Any("client-ids", waitRemove)) + } for i := range waitRemove { watchCtx := waitRemove[i] diff --git a/go.mod b/go.mod index b4fc41c15..c3ac69915 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,7 @@ require ( require ( github.com/DATA-DOG/go-sqlmock v1.5.0 - github.com/polarismesh/specification v1.4.2 + github.com/polarismesh/specification v1.5.0 ) require github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 295e0b11c..a77ff7b70 100644 --- a/go.sum +++ b/go.sum @@ -296,8 +296,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 h1:Y54jc86sdggM5DAbvxDNeEJxjN1uc8R6g5mV+i74e0E= -github.com/polarismesh/specification v1.4.2/go.mod h1:rDvMMtl5qebPmqiBLNa5Ps0XtwkP31ZLirbH4kXA0YU= +github.com/polarismesh/specification v1.5.0 h1:GzPtvqXCdiZ3tTKSenROrwSi0Bam2U2dM2opsBvP+mM= +github.com/polarismesh/specification v1.5.0/go.mod h1:rDvMMtl5qebPmqiBLNa5Ps0XtwkP31ZLirbH4kXA0YU= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= diff --git a/release/conf/polaris-server.yaml b/release/conf/polaris-server.yaml index cd037172e..26a523f3b 100644 --- a/release/conf/polaris-server.yaml +++ b/release/conf/polaris-server.yaml @@ -31,7 +31,7 @@ bootstrap: # The maximum preservation days of a single log file, default 7 rotationMaxAge: 7 # Log output level,debug/info/warn/error - outputLevel: info + outputLevel: debug # Open the log file compression compress: true # onlyContent just print log content, not print log timestamp @@ -448,22 +448,22 @@ maintain: # Storage configuration store: # # Standalone file storage plugin - name: boltdbStore - option: - path: ./polaris.bolt - ## Database storage plugin - # name: defaultStore + # name: boltdbStore # option: - # master: - # dbType: mysql - # dbName: polaris_server - # dbUser: ${MYSQL_USER} ##DB_USER## - # dbPwd: ${MYSQL_PWD} ##DB_PWD## - # dbAddr: ${MYSQL_HOST} ##DB_ADDR## - # maxOpenConns: 300 - # maxIdleConns: 50 - # connMaxLifetime: 300 # Unit second - # txIsolationLevel: 2 #LevelReadCommitted + # path: ./polaris.bolt + ## Database storage plugin + name: defaultStore + option: + master: + dbType: mysql + dbName: polaris_server + dbUser: ${MYSQL_USER} ##DB_USER## + dbPwd: ${MYSQL_PWD} ##DB_PWD## + dbAddr: ${MYSQL_HOST} ##DB_ADDR## + maxOpenConns: 300 + maxIdleConns: 50 + connMaxLifetime: 300 # Unit second + txIsolationLevel: 2 #LevelReadCommitted # polaris-server plugin settings plugin: crypto: diff --git a/service/api_v1.go b/service/api_v1.go index 236aaa734..20ac16ccd 100644 --- a/service/api_v1.go +++ b/service/api_v1.go @@ -186,6 +186,8 @@ type ClientServer interface { UpdateInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response // ReportServiceContract client report service_contract ReportServiceContract(ctx context.Context, req *apiservice.ServiceContract) *apiservice.Response + // GetLaneRuleWithCache fetch lane rules by client + GetLaneRuleWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse } // L5OperateServer L5 related operations diff --git a/service/client_v1.go b/service/client_v1.go index 74d8314ba..c40b845c1 100644 --- a/service/client_v1.go +++ b/service/client_v1.go @@ -51,10 +51,10 @@ func (s *Server) DeregisterInstance(ctx context.Context, req *apiservice.Instanc // ReportServiceContract report client service interface info func (s *Server) ReportServiceContract(ctx context.Context, req *apiservice.ServiceContract) *apiservice.Response { ctx = context.WithValue(ctx, utils.ContextIsFromClient, true) - cacheData := s.caches.ServiceContract().Get(&model.ServiceContract{ + cacheData := s.caches.ServiceContract().Get(ctx, &model.ServiceContract{ Namespace: req.GetNamespace(), Service: req.GetService(), - Name: req.GetName(), + Type: req.GetName(), Version: req.GetVersion(), Protocol: req.GetProtocol(), }) @@ -83,10 +83,6 @@ func isSuccessReportContract(rsp *apiservice.Response) bool { // ReportClient 客户端上报信息 func (s *Server) ReportClient(ctx context.Context, req *apiservice.Client) *apiservice.Response { - if s.caches == nil { - return api.NewResponse(apimodel.Code_ClientAPINotOpen) - } - // 客户端信息不写入到DB中 host := req.GetHost().GetValue() // 从CMDB查询地理位置信息 @@ -175,13 +171,6 @@ func (s *Server) GetPrometheusTargets(ctx context.Context, // GetServiceWithCache 查询服务列表 func (s *Server) GetServiceWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { - if s.caches == nil { - return api.NewDiscoverServiceResponse(apimodel.Code_ClientAPINotOpen, req) - } - if req == nil { - return api.NewDiscoverServiceResponse(apimodel.Code_EmptyRequest, req) - } - resp := api.NewDiscoverServiceResponse(apimodel.Code_ExecuteSuccess, req) var ( revision string @@ -229,15 +218,6 @@ func (s *Server) ServiceInstancesCache(ctx context.Context, filter *apiservice.D serviceName := req.GetName().GetValue() namespaceName := req.GetNamespace().GetValue() - // 消费服务为了兼容,可以不带namespace,server端使用默认的namespace - if namespaceName == "" { - namespaceName = DefaultNamespace - req.Namespace = utils.NewStringValue(namespaceName) - } - if !s.commonCheckDiscoverRequest(req, resp) { - return resp - } - // 数据源都来自Cache,这里拿到的service,已经是源服务 aliasFor, visibleServices := s.findVisibleServices(serviceName, namespaceName, req) if len(visibleServices) == 0 { @@ -320,23 +300,7 @@ func (s *Server) findVisibleServices(serviceName, namespaceName string, req *api // GetRoutingConfigWithCache 获取缓存中的路由配置信息 func (s *Server) GetRoutingConfigWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { resp := createCommonDiscoverResponse(req, apiservice.DiscoverResponse_ROUTING) - if !s.commonCheckDiscoverRequest(req, resp) { - return resp - } - - resp.Service = &apiservice.Service{ - Name: req.GetName(), - Namespace: req.GetNamespace(), - } - - // 先从缓存获取ServiceID,这里返回的是源服务 - aliasFor := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) - if aliasFor == nil { - aliasFor = &model.Service{ - Namespace: req.GetNamespace().GetValue(), - Name: req.GetName().GetValue(), - } - } + aliasFor := s.findServiceAlias(req) out, err := s.caches.RoutingConfig().GetRouterConfig(aliasFor.ID, aliasFor.Name, aliasFor.Namespace) if err != nil { @@ -366,18 +330,7 @@ func (s *Server) GetRoutingConfigWithCache(ctx context.Context, req *apiservice. // GetRateLimitWithCache 获取缓存中的限流规则信息 func (s *Server) GetRateLimitWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { resp := createCommonDiscoverResponse(req, apiservice.DiscoverResponse_RATE_LIMIT) - if !s.commonCheckDiscoverRequest(req, resp) { - return resp - } - - // 获取源服务 - aliasFor := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) - if aliasFor == nil { - aliasFor = &model.Service{ - Name: req.GetName().GetValue(), - Namespace: req.GetNamespace().GetValue(), - } - } + aliasFor := s.findServiceAlias(req) rules, revision := s.caches.RateLimit().GetRateLimitRules(model.ServiceKey{ Namespace: aliasFor.Namespace, @@ -417,23 +370,7 @@ func (s *Server) GetRateLimitWithCache(ctx context.Context, req *apiservice.Serv func (s *Server) GetFaultDetectWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { resp := createCommonDiscoverResponse(req, apiservice.DiscoverResponse_FAULT_DETECTOR) - if !s.commonCheckDiscoverRequest(req, resp) { - return resp - } - // 服务名和request保持一致 - resp.Service = &apiservice.Service{ - Name: req.GetName(), - Namespace: req.GetNamespace(), - } - - // 获取源服务 - aliasFor := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) - if aliasFor == nil { - aliasFor = &model.Service{ - Namespace: req.GetNamespace().GetValue(), - Name: req.GetName().GetValue(), - } - } + aliasFor := s.findServiceAlias(req) out := s.caches.FaultDetector().GetFaultDetectConfig(aliasFor.Name, aliasFor.Namespace) if out == nil || out.Revision == "" { @@ -462,24 +399,8 @@ func (s *Server) GetFaultDetectWithCache(ctx context.Context, req *apiservice.Se // GetCircuitBreakerWithCache 获取缓存中的熔断规则信息 func (s *Server) GetCircuitBreakerWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { resp := createCommonDiscoverResponse(req, apiservice.DiscoverResponse_CIRCUIT_BREAKER) - if !s.commonCheckDiscoverRequest(req, resp) { - return resp - } - - // 服务名和request保持一致 - resp.Service = &apiservice.Service{ - Name: req.GetName(), - Namespace: req.GetNamespace(), - } - // 获取源服务 - aliasFor := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) - if aliasFor == nil { - aliasFor = &model.Service{ - Namespace: req.GetNamespace().GetValue(), - Name: req.GetName().GetValue(), - } - } + aliasFor := s.findServiceAlias(req) out := s.caches.CircuitBreaker().GetCircuitBreakerConfig(aliasFor.Name, aliasFor.Namespace) if out == nil || out.Revision == "" { return resp @@ -509,10 +430,6 @@ func (s *Server) GetCircuitBreakerWithCache(ctx context.Context, req *apiservice func (s *Server) GetServiceContractWithCache(ctx context.Context, req *apiservice.ServiceContract) *apiservice.Response { resp := api.NewResponse(apimodel.Code_ExecuteSuccess) - if !s.serviceContractCheckDiscoverRequest(req, resp) { - return resp - } - // 服务名和request保持一致 resp.Service = &apiservice.Service{ Name: wrapperspb.String(req.GetService()), @@ -520,19 +437,13 @@ func (s *Server) GetServiceContractWithCache(ctx context.Context, } // 获取源服务 - aliasFor := s.getServiceCache(req.GetService(), req.GetNamespace()) - if aliasFor == nil { - aliasFor = &model.Service{ - Namespace: req.GetNamespace(), - Name: req.GetService(), - } - } + aliasFor := s.findServiceAlias(resp.Service) - out := s.caches.ServiceContract().Get(&model.ServiceContract{ + out := s.caches.ServiceContract().Get(ctx, &model.ServiceContract{ Namespace: aliasFor.Namespace, Service: aliasFor.Name, Version: req.Version, - Name: req.Name, + Type: req.Name, Protocol: req.Protocol, }) if out == nil { @@ -553,6 +464,50 @@ func (s *Server) GetServiceContractWithCache(ctx context.Context, return resp } +// GetLaneRuleWithCache fetch lane rule by client +func (s *Server) GetLaneRuleWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := createCommonDiscoverResponse(req, apiservice.DiscoverResponse_LANE) + // 获取源服务 + aliasFor := s.findServiceAlias(req) + out, revision := s.caches.LaneRule().GetLaneRules(aliasFor) + if out == nil || revision == "" { + return resp + } + + // 获取泳道规则数据,并对比revision + if len(req.GetRevision().GetValue()) > 0 && req.GetRevision().GetValue() == revision { + return api.NewDiscoverLaneResponse(apimodel.Code_DataNoChange, req) + } + + resp.AliasFor = &apiservice.Service{ + Name: utils.NewStringValue(aliasFor.Name), + Namespace: utils.NewStringValue(aliasFor.Namespace), + } + resp.Service.Revision = utils.NewStringValue(revision) + resp.Lanes = make([]*apitraffic.LaneGroup, 0, len(out)) + for i := range out { + resp.Lanes = append(resp.Lanes, out[i].Proto) + } + return resp +} + +func (s *Server) findServiceAlias(req *apiservice.Service) *model.Service { + // 获取源服务 + aliasFor := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) + if aliasFor == nil { + aliasFor = &model.Service{ + Namespace: req.GetNamespace().GetValue(), + Name: req.GetName().GetValue(), + } + } + return aliasFor +} + +func CreateCommonDiscoverResponse(req *apiservice.Service, + dT apiservice.DiscoverResponse_DiscoverResponseType) *apiservice.DiscoverResponse { + return createCommonDiscoverResponse(req, dT) +} + func createCommonDiscoverResponse(req *apiservice.Service, dT apiservice.DiscoverResponse_DiscoverResponseType) *apiservice.DiscoverResponse { return &apiservice.DiscoverResponse{ @@ -608,77 +563,3 @@ func (s *Server) getServiceCache(name string, namespace string) *model.Service { } return service } - -func (s *Server) commonCheckDiscoverRequest(req *apiservice.Service, resp *apiservice.DiscoverResponse) bool { - if s.caches == nil { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_ClientAPINotOpen)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = req - return false - } - if req == nil { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_EmptyRequest)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = req - return false - } - - if req.GetName().GetValue() == "" { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidServiceName)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = req - return false - } - if req.GetNamespace().GetValue() == "" { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidNamespaceName)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = req - return false - } - - return true -} - -func (s *Server) serviceContractCheckDiscoverRequest(req *apiservice.ServiceContract, resp *apiservice.Response) bool { - svc := &apiservice.Service{ - Name: wrapperspb.String(req.GetService()), - Namespace: wrapperspb.String(req.GetNamespace()), - } - - if s.caches == nil { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_ClientAPINotOpen)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = svc - resp.ServiceContract = req - return false - } - if req == nil { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_EmptyRequest)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = svc - return false - } - - if req.GetName() == "" { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidParameter)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = svc - resp.ServiceContract = req - return false - } - if req.GetNamespace() == "" { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidNamespaceName)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = svc - resp.ServiceContract = req - return false - } - if req.GetProtocol() == "" { - resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidParameter)) - resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) - resp.Service = svc - resp.ServiceContract = req - return false - } - return true -} diff --git a/service/interceptor/auth/circuitbreaker_config_authability.go b/service/interceptor/auth/circuitbreaker_config_authability.go index dd98af254..e8c215711 100644 --- a/service/interceptor/auth/circuitbreaker_config_authability.go +++ b/service/interceptor/auth/circuitbreaker_config_authability.go @@ -27,71 +27,71 @@ import ( // CreateCircuitBreakers creates circuit breakers func (svr *ServerAuthAbility) CreateCircuitBreakers(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { - return svr.targetServer.CreateCircuitBreakers(ctx, reqs) + return svr.nextSvr.CreateCircuitBreakers(ctx, reqs) } // CreateCircuitBreakerVersions creates circuit breaker versions func (svr *ServerAuthAbility) CreateCircuitBreakerVersions(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { - return svr.targetServer.CreateCircuitBreakerVersions(ctx, reqs) + return svr.nextSvr.CreateCircuitBreakerVersions(ctx, reqs) } // DeleteCircuitBreakers delete circuit breakers func (svr *ServerAuthAbility) DeleteCircuitBreakers(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { - return svr.targetServer.DeleteCircuitBreakers(ctx, reqs) + return svr.nextSvr.DeleteCircuitBreakers(ctx, reqs) } // UpdateCircuitBreakers update circuit breakers func (svr *ServerAuthAbility) UpdateCircuitBreakers(ctx context.Context, reqs []*apifault.CircuitBreaker) *apiservice.BatchWriteResponse { - return svr.targetServer.UpdateCircuitBreakers(ctx, reqs) + return svr.nextSvr.UpdateCircuitBreakers(ctx, reqs) } // ReleaseCircuitBreakers release circuit breakers func (svr *ServerAuthAbility) ReleaseCircuitBreakers(ctx context.Context, reqs []*apiservice.ConfigRelease) *apiservice.BatchWriteResponse { - return svr.targetServer.ReleaseCircuitBreakers(ctx, reqs) + return svr.nextSvr.ReleaseCircuitBreakers(ctx, reqs) } // UnBindCircuitBreakers unbind circuit breakers func (svr *ServerAuthAbility) UnBindCircuitBreakers(ctx context.Context, reqs []*apiservice.ConfigRelease) *apiservice.BatchWriteResponse { - return svr.targetServer.UnBindCircuitBreakers(ctx, reqs) + return svr.nextSvr.UnBindCircuitBreakers(ctx, reqs) } // GetCircuitBreaker get circuit breaker func (svr *ServerAuthAbility) GetCircuitBreaker(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - return svr.targetServer.GetCircuitBreaker(ctx, query) + return svr.nextSvr.GetCircuitBreaker(ctx, query) } // GetCircuitBreakerVersions get circuit breaker versions func (svr *ServerAuthAbility) GetCircuitBreakerVersions(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - return svr.targetServer.GetCircuitBreakerVersions(ctx, query) + return svr.nextSvr.GetCircuitBreakerVersions(ctx, query) } // GetMasterCircuitBreakers get master circuit breakers func (svr *ServerAuthAbility) GetMasterCircuitBreakers(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - return svr.targetServer.GetMasterCircuitBreakers(ctx, query) + return svr.nextSvr.GetMasterCircuitBreakers(ctx, query) } // GetReleaseCircuitBreakers get release circuit breakers func (svr *ServerAuthAbility) GetReleaseCircuitBreakers(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - return svr.targetServer.GetReleaseCircuitBreakers(ctx, query) + return svr.nextSvr.GetReleaseCircuitBreakers(ctx, query) } // GetCircuitBreakerByService get circuit breaker by service func (svr *ServerAuthAbility) GetCircuitBreakerByService(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - return svr.targetServer.GetCircuitBreakerByService(ctx, query) + return svr.nextSvr.GetCircuitBreakerByService(ctx, query) } // GetCircuitBreakerToken get circuit breaker token func (svr *ServerAuthAbility) GetCircuitBreakerToken( ctx context.Context, req *apifault.CircuitBreaker) *apiservice.Response { - return svr.targetServer.GetCircuitBreakerToken(ctx, req) + return svr.nextSvr.GetCircuitBreakerToken(ctx, req) } diff --git a/service/interceptor/auth/circuitbreaker_rule_authability.go b/service/interceptor/auth/circuitbreaker_rule_authability.go index 79a1b98f2..2f1137fe3 100644 --- a/service/interceptor/auth/circuitbreaker_rule_authability.go +++ b/service/interceptor/auth/circuitbreaker_rule_authability.go @@ -40,7 +40,7 @@ func (svr *ServerAuthAbility) CreateCircuitBreakerRules( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateCircuitBreakerRules(ctx, request) + return svr.nextSvr.CreateCircuitBreakerRules(ctx, request) } func (svr *ServerAuthAbility) DeleteCircuitBreakerRules( @@ -53,7 +53,7 @@ func (svr *ServerAuthAbility) DeleteCircuitBreakerRules( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteCircuitBreakerRules(ctx, request) + return svr.nextSvr.DeleteCircuitBreakerRules(ctx, request) } func (svr *ServerAuthAbility) EnableCircuitBreakerRules( @@ -66,7 +66,7 @@ func (svr *ServerAuthAbility) EnableCircuitBreakerRules( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.EnableCircuitBreakerRules(ctx, request) + return svr.nextSvr.EnableCircuitBreakerRules(ctx, request) } func (svr *ServerAuthAbility) UpdateCircuitBreakerRules( @@ -79,7 +79,7 @@ func (svr *ServerAuthAbility) UpdateCircuitBreakerRules( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateCircuitBreakerRules(ctx, request) + return svr.nextSvr.UpdateCircuitBreakerRules(ctx, request) } func (svr *ServerAuthAbility) GetCircuitBreakerRules( @@ -92,5 +92,5 @@ func (svr *ServerAuthAbility) GetCircuitBreakerRules( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetCircuitBreakerRules(ctx, query) + return svr.nextSvr.GetCircuitBreakerRules(ctx, query) } diff --git a/service/interceptor/auth/client_v1_authability.go b/service/interceptor/auth/client_v1_authability.go index 5a3cd7221..b486000c4 100644 --- a/service/interceptor/auth/client_v1_authability.go +++ b/service/interceptor/auth/client_v1_authability.go @@ -42,7 +42,7 @@ func (svr *ServerAuthAbility) RegisterInstance(ctx context.Context, req *apiserv ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.RegisterInstance(ctx, req) + return svr.nextSvr.RegisterInstance(ctx, req) } // DeregisterInstance delete onr instance @@ -59,12 +59,12 @@ func (svr *ServerAuthAbility) DeregisterInstance(ctx context.Context, req *apise ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeregisterInstance(ctx, req) + return svr.nextSvr.DeregisterInstance(ctx, req) } // ReportClient is the interface for reporting client authability func (svr *ServerAuthAbility) ReportClient(ctx context.Context, req *apiservice.Client) *apiservice.Response { - return svr.targetServer.ReportClient(ctx, req) + return svr.nextSvr.ReportClient(ctx, req) } // ReportServiceContract . @@ -83,14 +83,14 @@ func (svr *ServerAuthAbility) ReportServiceContract(ctx context.Context, req *ap ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.ReportServiceContract(ctx, req) + return svr.nextSvr.ReportServiceContract(ctx, req) } // GetPrometheusTargets Used for client acquisition service information func (svr *ServerAuthAbility) GetPrometheusTargets(ctx context.Context, query map[string]string) *model.PrometheusDiscoveryResponse { - return svr.targetServer.GetPrometheusTargets(ctx, query) + return svr.nextSvr.GetPrometheusTargets(ctx, query) } // GetServiceWithCache is the interface for getting service with cache @@ -108,7 +108,7 @@ func (svr *ServerAuthAbility) GetServiceWithCache( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServiceWithCache(ctx, req) + return svr.nextSvr.GetServiceWithCache(ctx, req) } // ServiceInstancesCache is the interface for getting service instances cache @@ -126,7 +126,7 @@ func (svr *ServerAuthAbility) ServiceInstancesCache( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.ServiceInstancesCache(ctx, filter, req) + return svr.nextSvr.ServiceInstancesCache(ctx, filter, req) } // GetRoutingConfigWithCache is the interface for getting routing config with cache @@ -144,7 +144,7 @@ func (svr *ServerAuthAbility) GetRoutingConfigWithCache( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetRoutingConfigWithCache(ctx, req) + return svr.nextSvr.GetRoutingConfigWithCache(ctx, req) } // GetRateLimitWithCache is the interface for getting rate limit with cache @@ -162,7 +162,7 @@ func (svr *ServerAuthAbility) GetRateLimitWithCache( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetRateLimitWithCache(ctx, req) + return svr.nextSvr.GetRateLimitWithCache(ctx, req) } // GetCircuitBreakerWithCache is the interface for getting a circuit breaker with cache @@ -180,7 +180,7 @@ func (svr *ServerAuthAbility) GetCircuitBreakerWithCache( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetCircuitBreakerWithCache(ctx, req) + return svr.nextSvr.GetCircuitBreakerWithCache(ctx, req) } func (svr *ServerAuthAbility) GetFaultDetectWithCache( @@ -197,7 +197,7 @@ func (svr *ServerAuthAbility) GetFaultDetectWithCache( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetFaultDetectWithCache(ctx, req) + return svr.nextSvr.GetFaultDetectWithCache(ctx, req) } // UpdateInstance update single instance @@ -214,7 +214,7 @@ func (svr *ServerAuthAbility) UpdateInstance(ctx context.Context, req *apiservic ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateInstance(ctx, req) + return svr.nextSvr.UpdateInstance(ctx, req) } // GetServiceContractWithCache User Client Get ServiceContract Rule Information @@ -235,5 +235,21 @@ func (svr *ServerAuthAbility) GetServiceContractWithCache(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServiceContractWithCache(ctx, req) + return svr.nextSvr.GetServiceContractWithCache(ctx, req) +} + +// GetLaneRuleWithCache fetch lane rules by client +func (svr *ServerAuthAbility) GetLaneRuleWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + authCtx := svr.collectServiceAuthContext( + ctx, []*apiservice.Service{req}, model.Read, "DiscoverLaneRule") + _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + if err != nil { + resp := api.NewDiscoverResponse(convertToErrCode(err)) + resp.Info = utils.NewStringValue(err.Error()) + return resp + } + ctx = authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) + + return svr.nextSvr.GetLaneRuleWithCache(ctx, req) } diff --git a/service/interceptor/auth/faultdetect_config_authability.go b/service/interceptor/auth/faultdetect_config_authability.go index dac4979aa..48e7a3466 100644 --- a/service/interceptor/auth/faultdetect_config_authability.go +++ b/service/interceptor/auth/faultdetect_config_authability.go @@ -37,7 +37,7 @@ func (svr *ServerAuthAbility) CreateFaultDetectRules( } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateFaultDetectRules(ctx, request) + return svr.nextSvr.CreateFaultDetectRules(ctx, request) } func (svr *ServerAuthAbility) DeleteFaultDetectRules( @@ -49,7 +49,7 @@ func (svr *ServerAuthAbility) DeleteFaultDetectRules( } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteFaultDetectRules(ctx, request) + return svr.nextSvr.DeleteFaultDetectRules(ctx, request) } func (svr *ServerAuthAbility) UpdateFaultDetectRules( @@ -61,7 +61,7 @@ func (svr *ServerAuthAbility) UpdateFaultDetectRules( } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateFaultDetectRules(ctx, request) + return svr.nextSvr.UpdateFaultDetectRules(ctx, request) } func (svr *ServerAuthAbility) GetFaultDetectRules( @@ -72,5 +72,5 @@ func (svr *ServerAuthAbility) GetFaultDetectRules( } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetFaultDetectRules(ctx, query) + return svr.nextSvr.GetFaultDetectRules(ctx, query) } diff --git a/service/interceptor/auth/instance_authability.go b/service/interceptor/auth/instance_authability.go index e208bbd0c..ecde3b62c 100644 --- a/service/interceptor/auth/instance_authability.go +++ b/service/interceptor/auth/instance_authability.go @@ -45,7 +45,7 @@ func (svr *ServerAuthAbility) CreateInstances(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateInstances(ctx, reqs) + return svr.nextSvr.CreateInstances(ctx, reqs) } // DeleteInstances delete instances @@ -64,7 +64,7 @@ func (svr *ServerAuthAbility) DeleteInstances(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteInstances(ctx, reqs) + return svr.nextSvr.DeleteInstances(ctx, reqs) } // DeleteInstancesByHost 目前只允许 super account 进行数据删除 @@ -83,7 +83,7 @@ func (svr *ServerAuthAbility) DeleteInstancesByHost(ctx context.Context, return ret } - return svr.targetServer.DeleteInstancesByHost(ctx, reqs) + return svr.nextSvr.DeleteInstancesByHost(ctx, reqs) } // UpdateInstances update instances @@ -99,7 +99,7 @@ func (svr *ServerAuthAbility) UpdateInstances(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateInstances(ctx, reqs) + return svr.nextSvr.UpdateInstances(ctx, reqs) } // UpdateInstancesIsolate update instances @@ -115,7 +115,7 @@ func (svr *ServerAuthAbility) UpdateInstancesIsolate(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateInstancesIsolate(ctx, reqs) + return svr.nextSvr.UpdateInstancesIsolate(ctx, reqs) } // GetInstances get instances @@ -130,7 +130,7 @@ func (svr *ServerAuthAbility) GetInstances(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetInstances(ctx, query) + return svr.nextSvr.GetInstances(ctx, query) } // GetInstancesCount get instances to count @@ -143,7 +143,7 @@ func (svr *ServerAuthAbility) GetInstancesCount(ctx context.Context) *apiservice ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetInstancesCount(ctx) + return svr.nextSvr.GetInstancesCount(ctx) } func (svr *ServerAuthAbility) GetInstanceLabels(ctx context.Context, @@ -156,5 +156,5 @@ func (svr *ServerAuthAbility) GetInstanceLabels(ctx context.Context, } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetInstanceLabels(ctx, query) + return svr.nextSvr.GetInstanceLabels(ctx, query) } diff --git a/service/interceptor/auth/l5_service_authability.go b/service/interceptor/auth/l5_service_authability.go index 7f1b4ea81..4b3eac1ee 100644 --- a/service/interceptor/auth/l5_service_authability.go +++ b/service/interceptor/auth/l5_service_authability.go @@ -30,10 +30,10 @@ import ( // Stat::instance()->add_agent(sbac.agent_ip()); func (svr *ServerAuthAbility) SyncByAgentCmd(ctx context.Context, sbac *l5.Cl5SyncByAgentCmd) ( *l5.Cl5SyncByAgentAckCmd, error) { - return svr.targetServer.SyncByAgentCmd(ctx, sbac) + return svr.nextSvr.SyncByAgentCmd(ctx, sbac) } // RegisterByNameCmd 根据名字获取sid信息 func (svr *ServerAuthAbility) RegisterByNameCmd(rbnc *l5.Cl5RegisterByNameCmd) (*l5.Cl5RegisterByNameAckCmd, error) { - return svr.targetServer.RegisterByNameCmd(rbnc) + return svr.nextSvr.RegisterByNameCmd(rbnc) } diff --git a/service/interceptor/auth/ratelimit_config_authability.go b/service/interceptor/auth/ratelimit_config_authability.go index a0f2be54c..7ab61bc90 100644 --- a/service/interceptor/auth/ratelimit_config_authability.go +++ b/service/interceptor/auth/ratelimit_config_authability.go @@ -42,7 +42,7 @@ func (svr *ServerAuthAbility) CreateRateLimits( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateRateLimits(ctx, reqs) + return svr.nextSvr.CreateRateLimits(ctx, reqs) } // DeleteRateLimits deletes rate limits for a namespace. @@ -58,7 +58,7 @@ func (svr *ServerAuthAbility) DeleteRateLimits( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteRateLimits(ctx, reqs) + return svr.nextSvr.DeleteRateLimits(ctx, reqs) } // UpdateRateLimits updates rate limits for a namespace. @@ -74,7 +74,7 @@ func (svr *ServerAuthAbility) UpdateRateLimits( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateRateLimits(ctx, reqs) + return svr.nextSvr.UpdateRateLimits(ctx, reqs) } // EnableRateLimits 启用限流规则 @@ -90,7 +90,7 @@ func (svr *ServerAuthAbility) EnableRateLimits( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.EnableRateLimits(ctx, reqs) + return svr.nextSvr.EnableRateLimits(ctx, reqs) } // GetRateLimits gets rate limits for a namespace. @@ -106,5 +106,5 @@ func (svr *ServerAuthAbility) GetRateLimits( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetRateLimits(ctx, query) + return svr.nextSvr.GetRateLimits(ctx, query) } diff --git a/service/interceptor/auth/routing_config_v1_authability.go b/service/interceptor/auth/routing_config_v1_authability.go index b20cabe6e..c73af7fb4 100644 --- a/service/interceptor/auth/routing_config_v1_authability.go +++ b/service/interceptor/auth/routing_config_v1_authability.go @@ -42,7 +42,7 @@ func (svr *ServerAuthAbility) CreateRoutingConfigs( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateRoutingConfigs(ctx, reqs) + return svr.nextSvr.CreateRoutingConfigs(ctx, reqs) } // DeleteRoutingConfigs deletes routing configs @@ -58,7 +58,7 @@ func (svr *ServerAuthAbility) DeleteRoutingConfigs( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteRoutingConfigs(ctx, reqs) + return svr.nextSvr.DeleteRoutingConfigs(ctx, reqs) } // UpdateRoutingConfigs updates routing configs @@ -74,7 +74,7 @@ func (svr *ServerAuthAbility) UpdateRoutingConfigs( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateRoutingConfigs(ctx, reqs) + return svr.nextSvr.UpdateRoutingConfigs(ctx, reqs) } // GetRoutingConfigs gets routing configs @@ -90,5 +90,5 @@ func (svr *ServerAuthAbility) GetRoutingConfigs( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetRoutingConfigs(ctx, query) + return svr.nextSvr.GetRoutingConfigs(ctx, query) } diff --git a/service/interceptor/auth/routing_config_v2_authability.go b/service/interceptor/auth/routing_config_v2_authability.go index 94e703ab2..57551cfd0 100644 --- a/service/interceptor/auth/routing_config_v2_authability.go +++ b/service/interceptor/auth/routing_config_v2_authability.go @@ -39,7 +39,7 @@ func (svr *ServerAuthAbility) CreateRoutingConfigsV2(ctx context.Context, } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateRoutingConfigsV2(ctx, req) + return svr.nextSvr.CreateRoutingConfigsV2(ctx, req) } // DeleteRoutingConfigsV2 批量删除路由配置 @@ -52,7 +52,7 @@ func (svr *ServerAuthAbility) DeleteRoutingConfigsV2(ctx context.Context, } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteRoutingConfigsV2(ctx, req) + return svr.nextSvr.DeleteRoutingConfigsV2(ctx, req) } // UpdateRoutingConfigsV2 批量更新路由配置 @@ -65,7 +65,7 @@ func (svr *ServerAuthAbility) UpdateRoutingConfigsV2(ctx context.Context, } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateRoutingConfigsV2(ctx, req) + return svr.nextSvr.UpdateRoutingConfigsV2(ctx, req) } // EnableRoutings batch enable routing rules @@ -78,12 +78,12 @@ func (svr *ServerAuthAbility) EnableRoutings(ctx context.Context, } ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.EnableRoutings(ctx, req) + return svr.nextSvr.EnableRoutings(ctx, req) } // QueryRoutingConfigsV2 提供给OSS的查询路由配置的接口 func (svr *ServerAuthAbility) QueryRoutingConfigsV2(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - return svr.targetServer.QueryRoutingConfigsV2(ctx, query) + return svr.nextSvr.QueryRoutingConfigsV2(ctx, query) } diff --git a/service/interceptor/auth/server_authability.go b/service/interceptor/auth/server_authability.go index 60b9a0c17..9efa6b8e3 100644 --- a/service/interceptor/auth/server_authability.go +++ b/service/interceptor/auth/server_authability.go @@ -39,33 +39,35 @@ import ( // // 该层会对请求参数做一些调整,根据具体的请求发起人,设置为数据对应的 owner,不可为为别人进行创建资源 type ServerAuthAbility struct { - targetServer *service.Server - userMgn auth.UserServer - strategyMgn auth.StrategyServer + nextSvr service.DiscoverServer + userMgn auth.UserServer + strategyMgn auth.StrategyServer } -func NewServerAuthAbility(targetServer *service.Server, +func NewServerAuthAbility(nextSvr service.DiscoverServer, userMgn auth.UserServer, strategyMgn auth.StrategyServer) service.DiscoverServer { proxy := &ServerAuthAbility{ - targetServer: targetServer, - userMgn: userMgn, - strategyMgn: strategyMgn, + nextSvr: nextSvr, + userMgn: userMgn, + strategyMgn: strategyMgn, } - targetServer.SetResourceHooks(proxy) - + actualSvr, ok := nextSvr.(*service.Server) + if ok { + actualSvr.SetResourceHooks(proxy) + } return proxy } // Cache Get cache management func (svr *ServerAuthAbility) Cache() *cache.CacheManager { - return svr.targetServer.Cache() + return svr.nextSvr.Cache() } // GetServiceInstanceRevision 获取服务实例的版本号 func (svr *ServerAuthAbility) GetServiceInstanceRevision(serviceID string, instances []*model.Instance) (string, error) { - return svr.targetServer.GetServiceInstanceRevision(serviceID, instances) + return svr.nextSvr.GetServiceInstanceRevision(serviceID, instances) } // collectServiceAuthContext 对于服务的处理,收集所有的与鉴权的相关信息 diff --git a/service/interceptor/auth/service_alias_authability.go b/service/interceptor/auth/service_alias_authability.go index 644d9bbbf..84881e868 100644 --- a/service/interceptor/auth/service_alias_authability.go +++ b/service/interceptor/auth/service_alias_authability.go @@ -47,7 +47,7 @@ func (svr *ServerAuthAbility) CreateServiceAlias( req.Owners = utils.NewStringValue(ownerId) } - return svr.targetServer.CreateServiceAlias(ctx, req) + return svr.nextSvr.CreateServiceAlias(ctx, req) } // DeleteServiceAliases deletes service aliases @@ -62,7 +62,7 @@ func (svr *ServerAuthAbility) DeleteServiceAliases(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteServiceAliases(ctx, reqs) + return svr.nextSvr.DeleteServiceAliases(ctx, reqs) } // UpdateServiceAlias updates service alias @@ -78,7 +78,7 @@ func (svr *ServerAuthAbility) UpdateServiceAlias( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateServiceAlias(ctx, req) + return svr.nextSvr.UpdateServiceAlias(ctx, req) } // GetServiceAliases gets service aliases @@ -93,7 +93,7 @@ func (svr *ServerAuthAbility) GetServiceAliases(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - resp := svr.targetServer.GetServiceAliases(ctx, query) + resp := svr.nextSvr.GetServiceAliases(ctx, query) if len(resp.Aliases) != 0 { // 对于服务别名,则是参考源服务是否有编辑权限 principal := model.Principal{ diff --git a/service/interceptor/auth/service_authability.go b/service/interceptor/auth/service_authability.go index 52c212646..a8229d9bd 100644 --- a/service/interceptor/auth/service_authability.go +++ b/service/interceptor/auth/service_authability.go @@ -50,7 +50,7 @@ func (svr *ServerAuthAbility) CreateServices( } } - resp := svr.targetServer.CreateServices(ctx, reqs) + resp := svr.nextSvr.CreateServices(ctx, reqs) return resp } @@ -69,7 +69,7 @@ func (svr *ServerAuthAbility) DeleteServices( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - resp := svr.targetServer.DeleteServices(ctx, reqs) + resp := svr.nextSvr.DeleteServices(ctx, reqs) return resp } @@ -89,7 +89,7 @@ func (svr *ServerAuthAbility) UpdateServices( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateServices(ctx, reqs) + return svr.nextSvr.UpdateServices(ctx, reqs) } // UpdateServiceToken 更新服务的 token @@ -108,7 +108,7 @@ func (svr *ServerAuthAbility) UpdateServiceToken( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.UpdateServiceToken(ctx, req) + return svr.nextSvr.UpdateServiceToken(ctx, req) } func (svr *ServerAuthAbility) GetAllServices(ctx context.Context, @@ -122,7 +122,7 @@ func (svr *ServerAuthAbility) GetAllServices(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetAllServices(ctx, query) + return svr.nextSvr.GetAllServices(ctx, query) } // GetServices 批量获取服务 @@ -137,7 +137,7 @@ func (svr *ServerAuthAbility) GetServices( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - resp := svr.targetServer.GetServices(ctx, query) + resp := svr.nextSvr.GetServices(ctx, query) if len(resp.Services) != 0 { principal := model.Principal{ PrincipalID: utils.ParseUserID(ctx), @@ -167,7 +167,7 @@ func (svr *ServerAuthAbility) GetServicesCount(ctx context.Context) *apiservice. ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServicesCount(ctx) + return svr.nextSvr.GetServicesCount(ctx) } // GetServiceToken 获取服务的 token @@ -180,7 +180,7 @@ func (svr *ServerAuthAbility) GetServiceToken(ctx context.Context, req *apiservi ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServiceToken(ctx, req) + return svr.nextSvr.GetServiceToken(ctx, req) } // GetServiceOwner 获取服务的 owner @@ -194,5 +194,5 @@ func (svr *ServerAuthAbility) GetServiceOwner( ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServiceOwner(ctx, req) + return svr.nextSvr.GetServiceOwner(ctx, req) } diff --git a/service/interceptor/auth/service_contract_authability.go b/service/interceptor/auth/service_contract_authability.go index f6dd2771a..a17f40f24 100644 --- a/service/interceptor/auth/service_contract_authability.go +++ b/service/interceptor/auth/service_contract_authability.go @@ -45,7 +45,7 @@ func (svr *ServerAuthAbility) CreateServiceContracts(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateServiceContracts(ctx, req) + return svr.nextSvr.CreateServiceContracts(ctx, req) } // GetServiceContracts . @@ -58,7 +58,7 @@ func (svr *ServerAuthAbility) GetServiceContracts(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServiceContracts(ctx, query) + return svr.nextSvr.GetServiceContracts(ctx, query) } // GetServiceContractVersions . @@ -72,7 +72,7 @@ func (svr *ServerAuthAbility) GetServiceContractVersions(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.GetServiceContractVersions(ctx, filter) + return svr.nextSvr.GetServiceContractVersions(ctx, filter) } // DeleteServiceContracts . @@ -93,7 +93,7 @@ func (svr *ServerAuthAbility) DeleteServiceContracts(ctx context.Context, ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteServiceContracts(ctx, req) + return svr.nextSvr.DeleteServiceContracts(ctx, req) } // CreateServiceContractInterfaces . @@ -111,7 +111,7 @@ func (svr *ServerAuthAbility) CreateServiceContractInterfaces(ctx context.Contex ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.CreateServiceContractInterfaces(ctx, contract, source) + return svr.nextSvr.CreateServiceContractInterfaces(ctx, contract, source) } // AppendServiceContractInterfaces . @@ -129,7 +129,7 @@ func (svr *ServerAuthAbility) AppendServiceContractInterfaces(ctx context.Contex ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.AppendServiceContractInterfaces(ctx, contract, source) + return svr.nextSvr.AppendServiceContractInterfaces(ctx, contract, source) } // DeleteServiceContractInterfaces . @@ -147,5 +147,5 @@ func (svr *ServerAuthAbility) DeleteServiceContractInterfaces(ctx context.Contex ctx = authCtx.GetRequestContext() ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx) - return svr.targetServer.DeleteServiceContractInterfaces(ctx, contract) + return svr.nextSvr.DeleteServiceContractInterfaces(ctx, contract) } diff --git a/service/interceptor/paramcheck/check.go b/service/interceptor/paramcheck/check.go new file mode 100644 index 000000000..908767286 --- /dev/null +++ b/service/interceptor/paramcheck/check.go @@ -0,0 +1,341 @@ +package paramcheck + +import ( + "context" + + "github.com/polarismesh/polaris/common/api/l5" + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/specification/source/go/api/v1/fault_tolerance" + "github.com/polarismesh/specification/source/go/api/v1/service_manage" + "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" +) + +// AppendServiceContractInterfaces implements service.DiscoverServer. +func (svr *Server) AppendServiceContractInterfaces(ctx context.Context, contract *service_manage.ServiceContract, source service_manage.InterfaceDescriptor_Source) *service_manage.Response { + return svr.nextSvr.AppendServiceContractInterfaces(ctx, contract, source) +} + +// CreateCircuitBreakerRules implements service.DiscoverServer. +func (svr *Server) CreateCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateCircuitBreakerRules(ctx, request) +} + +// CreateCircuitBreakerVersions implements service.DiscoverServer. +func (svr *Server) CreateCircuitBreakerVersions(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateCircuitBreakerVersions(ctx, req) +} + +// CreateCircuitBreakers implements service.DiscoverServer. +func (svr *Server) CreateCircuitBreakers(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateCircuitBreakers(ctx, req) +} + +// CreateFaultDetectRules implements service.DiscoverServer. +func (svr *Server) CreateFaultDetectRules(ctx context.Context, request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateFaultDetectRules(ctx, request) +} + +// CreateInstances implements service.DiscoverServer. +func (svr *Server) CreateInstances(ctx context.Context, reqs []*service_manage.Instance) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateInstances(ctx, reqs) +} + +// CreateRateLimits implements service.DiscoverServer. +func (svr *Server) CreateRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateRateLimits(ctx, request) +} + +// CreateRoutingConfigs implements service.DiscoverServer. +func (svr *Server) CreateRoutingConfigs(ctx context.Context, req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateRoutingConfigs(ctx, req) +} + +// CreateRoutingConfigsV2 implements service.DiscoverServer. +func (svr *Server) CreateRoutingConfigsV2(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateRoutingConfigsV2(ctx, req) +} + +// CreateServiceAlias implements service.DiscoverServer. +func (svr *Server) CreateServiceAlias(ctx context.Context, req *service_manage.ServiceAlias) *service_manage.Response { + return svr.nextSvr.CreateServiceAlias(ctx, req) +} + +// CreateServiceContractInterfaces implements service.DiscoverServer. +func (svr *Server) CreateServiceContractInterfaces(ctx context.Context, contract *service_manage.ServiceContract, source service_manage.InterfaceDescriptor_Source) *service_manage.Response { + return svr.nextSvr.CreateServiceContractInterfaces(ctx, contract, source) +} + +// CreateServiceContracts implements service.DiscoverServer. +func (svr *Server) CreateServiceContracts(ctx context.Context, req []*service_manage.ServiceContract) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateServiceContracts(ctx, req) +} + +// CreateServices implements service.DiscoverServer. +func (svr *Server) CreateServices(ctx context.Context, req []*service_manage.Service) *service_manage.BatchWriteResponse { + return svr.nextSvr.CreateServices(ctx, req) +} + +// DeleteCircuitBreakerRules implements service.DiscoverServer. +func (svr *Server) DeleteCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteCircuitBreakerRules(ctx, request) +} + +// DeleteCircuitBreakers implements service.DiscoverServer. +func (svr *Server) DeleteCircuitBreakers(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteCircuitBreakers(ctx, req) +} + +// DeleteFaultDetectRules implements service.DiscoverServer. +func (svr *Server) DeleteFaultDetectRules(ctx context.Context, request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteFaultDetectRules(ctx, request) +} + +// DeleteInstances implements service.DiscoverServer. +func (svr *Server) DeleteInstances(ctx context.Context, req []*service_manage.Instance) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteInstances(ctx, req) +} + +// DeleteInstancesByHost implements service.DiscoverServer. +func (svr *Server) DeleteInstancesByHost(ctx context.Context, req []*service_manage.Instance) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteInstancesByHost(ctx, req) +} + +// DeleteRateLimits implements service.DiscoverServer. +func (svr *Server) DeleteRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteRateLimits(ctx, request) +} + +// DeleteRoutingConfigs implements service.DiscoverServer. +func (svr *Server) DeleteRoutingConfigs(ctx context.Context, req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteRoutingConfigs(ctx, req) +} + +// DeleteRoutingConfigsV2 implements service.DiscoverServer. +func (svr *Server) DeleteRoutingConfigsV2(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteRoutingConfigsV2(ctx, req) +} + +// DeleteServiceAliases implements service.DiscoverServer. +func (svr *Server) DeleteServiceAliases(ctx context.Context, req []*service_manage.ServiceAlias) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteServiceAliases(ctx, req) +} + +// DeleteServiceContractInterfaces implements service.DiscoverServer. +func (svr *Server) DeleteServiceContractInterfaces(ctx context.Context, contract *service_manage.ServiceContract) *service_manage.Response { + return svr.nextSvr.DeleteServiceContractInterfaces(ctx, contract) +} + +// DeleteServiceContracts implements service.DiscoverServer. +func (svr *Server) DeleteServiceContracts(ctx context.Context, req []*service_manage.ServiceContract) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteServiceContracts(ctx, req) +} + +// DeleteServices implements service.DiscoverServer. +func (svr *Server) DeleteServices(ctx context.Context, req []*service_manage.Service) *service_manage.BatchWriteResponse { + return svr.nextSvr.DeleteServices(ctx, req) +} + +// EnableCircuitBreakerRules implements service.DiscoverServer. +func (svr *Server) EnableCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.EnableCircuitBreakerRules(ctx, request) +} + +// EnableRateLimits implements service.DiscoverServer. +func (svr *Server) EnableRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { + return svr.nextSvr.EnableRateLimits(ctx, request) +} + +// EnableRoutings implements service.DiscoverServer. +func (svr *Server) EnableRoutings(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.EnableRoutings(ctx, req) +} + +// GetAllServices implements service.DiscoverServer. +func (svr *Server) GetAllServices(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetAllServices(ctx, query) +} + +// GetCircuitBreaker implements service.DiscoverServer. +func (svr *Server) GetCircuitBreaker(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetCircuitBreaker(ctx, query) +} + +// GetCircuitBreakerByService implements service.DiscoverServer. +func (svr *Server) GetCircuitBreakerByService(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetCircuitBreakerByService(ctx, query) +} + +// GetCircuitBreakerRules implements service.DiscoverServer. +func (svr *Server) GetCircuitBreakerRules(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetCircuitBreakerRules(ctx, query) +} + +// GetCircuitBreakerToken implements service.DiscoverServer. +func (svr *Server) GetCircuitBreakerToken(ctx context.Context, req *fault_tolerance.CircuitBreaker) *service_manage.Response { + return svr.nextSvr.GetCircuitBreakerToken(ctx, req) +} + +// GetCircuitBreakerVersions implements service.DiscoverServer. +func (svr *Server) GetCircuitBreakerVersions(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetCircuitBreakerVersions(ctx, query) +} + +// GetFaultDetectRules implements service.DiscoverServer. +func (svr *Server) GetFaultDetectRules(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetFaultDetectRules(ctx, query) +} + +// GetInstanceLabels implements service.DiscoverServer. +func (svr *Server) GetInstanceLabels(ctx context.Context, query map[string]string) *service_manage.Response { + return svr.nextSvr.GetInstanceLabels(ctx, query) +} + +// GetInstances implements service.DiscoverServer. +func (svr *Server) GetInstances(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetInstances(ctx, query) +} + +// GetInstancesCount implements service.DiscoverServer. +func (svr *Server) GetInstancesCount(ctx context.Context) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetInstancesCount(ctx) +} + +// GetMasterCircuitBreakers implements service.DiscoverServer. +func (svr *Server) GetMasterCircuitBreakers(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetMasterCircuitBreakers(ctx, query) +} + +// GetPrometheusTargets implements service.DiscoverServer. +func (svr *Server) GetPrometheusTargets(ctx context.Context, query map[string]string) *model.PrometheusDiscoveryResponse { + return svr.nextSvr.GetPrometheusTargets(ctx, query) +} + +// GetRateLimits implements service.DiscoverServer. +func (svr *Server) GetRateLimits(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetRateLimits(ctx, query) +} + +// GetReleaseCircuitBreakers implements service.DiscoverServer. +func (svr *Server) GetReleaseCircuitBreakers(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetReleaseCircuitBreakers(ctx, query) +} + +// GetRoutingConfigs implements service.DiscoverServer. +func (svr *Server) GetRoutingConfigs(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetRoutingConfigs(ctx, query) +} + +// GetServiceAliases implements service.DiscoverServer. +func (svr *Server) GetServiceAliases(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetServiceAliases(ctx, query) +} + +// GetServiceContractVersions implements service.DiscoverServer. +func (svr *Server) GetServiceContractVersions(ctx context.Context, filter map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetServiceContractVersions(ctx, filter) +} + +// GetServiceContracts implements service.DiscoverServer. +func (svr *Server) GetServiceContracts(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetServiceContracts(ctx, query) +} + +// GetServiceOwner implements service.DiscoverServer. +func (svr *Server) GetServiceOwner(ctx context.Context, req []*service_manage.Service) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetServiceOwner(ctx, req) +} + +// GetServiceToken implements service.DiscoverServer. +func (svr *Server) GetServiceToken(ctx context.Context, req *service_manage.Service) *service_manage.Response { + return svr.nextSvr.GetServiceToken(ctx, req) +} + +// GetServices implements service.DiscoverServer. +func (svr *Server) GetServices(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetServices(ctx, query) +} + +// GetServicesCount implements service.DiscoverServer. +func (svr *Server) GetServicesCount(ctx context.Context) *service_manage.BatchQueryResponse { + return svr.nextSvr.GetServicesCount(ctx) +} + +// QueryRoutingConfigsV2 implements service.DiscoverServer. +func (svr *Server) QueryRoutingConfigsV2(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { + return svr.nextSvr.QueryRoutingConfigsV2(ctx, query) +} + +// RegisterByNameCmd implements service.DiscoverServer. +func (svr *Server) RegisterByNameCmd(rbnc *l5.Cl5RegisterByNameCmd) (*l5.Cl5RegisterByNameAckCmd, error) { + return svr.nextSvr.RegisterByNameCmd(rbnc) +} + +// ReleaseCircuitBreakers implements service.DiscoverServer. +func (svr *Server) ReleaseCircuitBreakers(ctx context.Context, req []*service_manage.ConfigRelease) *service_manage.BatchWriteResponse { + return svr.nextSvr.ReleaseCircuitBreakers(ctx, req) +} + +// SyncByAgentCmd implements service.DiscoverServer. +func (svr *Server) SyncByAgentCmd(ctx context.Context, sbac *l5.Cl5SyncByAgentCmd) (*l5.Cl5SyncByAgentAckCmd, error) { + return svr.nextSvr.SyncByAgentCmd(ctx, sbac) +} + +// UnBindCircuitBreakers implements service.DiscoverServer. +func (svr *Server) UnBindCircuitBreakers(ctx context.Context, req []*service_manage.ConfigRelease) *service_manage.BatchWriteResponse { + return svr.nextSvr.UnBindCircuitBreakers(ctx, req) +} + +// UpdateCircuitBreakerRules implements service.DiscoverServer. +func (svr *Server) UpdateCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateCircuitBreakerRules(ctx, request) +} + +// UpdateCircuitBreakers implements service.DiscoverServer. +func (svr *Server) UpdateCircuitBreakers(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateCircuitBreakers(ctx, req) +} + +// UpdateFaultDetectRules implements service.DiscoverServer. +func (svr *Server) UpdateFaultDetectRules(ctx context.Context, request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateFaultDetectRules(ctx, request) +} + +// UpdateInstances implements service.DiscoverServer. +func (svr *Server) UpdateInstances(ctx context.Context, req []*service_manage.Instance) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateInstances(ctx, req) +} + +// UpdateInstancesIsolate implements service.DiscoverServer. +func (svr *Server) UpdateInstancesIsolate(ctx context.Context, req []*service_manage.Instance) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateInstancesIsolate(ctx, req) +} + +// UpdateRateLimits implements service.DiscoverServer. +func (svr *Server) UpdateRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateRateLimits(ctx, request) +} + +// UpdateRoutingConfigs implements service.DiscoverServer. +func (svr *Server) UpdateRoutingConfigs(ctx context.Context, req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateRoutingConfigs(ctx, req) +} + +// UpdateRoutingConfigsV2 implements service.DiscoverServer. +func (svr *Server) UpdateRoutingConfigsV2(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateRoutingConfigsV2(ctx, req) +} + +// UpdateServiceAlias implements service.DiscoverServer. +func (svr *Server) UpdateServiceAlias(ctx context.Context, req *service_manage.ServiceAlias) *service_manage.Response { + return svr.nextSvr.UpdateServiceAlias(ctx, req) +} + +// UpdateServiceToken implements service.DiscoverServer. +func (svr *Server) UpdateServiceToken(ctx context.Context, req *service_manage.Service) *service_manage.Response { + return svr.nextSvr.UpdateServiceToken(ctx, req) +} + +// UpdateServices implements service.DiscoverServer. +func (svr *Server) UpdateServices(ctx context.Context, req []*service_manage.Service) *service_manage.BatchWriteResponse { + return svr.nextSvr.UpdateServices(ctx, req) +} diff --git a/service/interceptor/paramcheck/client.go b/service/interceptor/paramcheck/client.go new file mode 100644 index 000000000..74a12b9fa --- /dev/null +++ b/service/interceptor/paramcheck/client.go @@ -0,0 +1,198 @@ +package paramcheck + +import ( + "context" + + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" + "google.golang.org/protobuf/types/known/wrapperspb" + + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/service" +) + +// RegisterInstance create one instance by client +func (s *Server) RegisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { + return s.nextSvr.RegisterInstance(ctx, req) +} + +// DeregisterInstance delete onr instance by client +func (s *Server) DeregisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { + return s.nextSvr.DeregisterInstance(ctx, req) +} + +// ReportClient Client gets geographic location information +func (s *Server) ReportClient(ctx context.Context, req *apiservice.Client) *apiservice.Response { + if s.nextSvr.Cache() == nil { + return api.NewResponse(apimodel.Code_ClientAPINotOpen) + } + return s.nextSvr.ReportClient(ctx, req) +} + +// GetServiceWithCache Used for client acquisition service information +func (s *Server) GetServiceWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + if s.nextSvr.Cache() == nil { + return api.NewDiscoverServiceResponse(apimodel.Code_ClientAPINotOpen, req) + } + if req == nil { + return api.NewDiscoverServiceResponse(apimodel.Code_EmptyRequest, req) + } + return s.nextSvr.GetServiceWithCache(ctx, req) +} + +// ServiceInstancesCache Used for client acquisition service instance information +func (s *Server) ServiceInstancesCache(ctx context.Context, filter *apiservice.DiscoverFilter, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_INSTANCE) + + namespaceName := req.GetNamespace().GetValue() + + // 消费服务为了兼容,可以不带namespace,server端使用默认的namespace + if namespaceName == "" { + namespaceName = service.DefaultNamespace + req.Namespace = utils.NewStringValue(namespaceName) + } + if !s.commonCheckDiscoverRequest(req, resp) { + return resp + } + return s.nextSvr.ServiceInstancesCache(ctx, filter, req) +} + +// GetRoutingConfigWithCache User Client Get Service Routing Configuration Information +func (s *Server) GetRoutingConfigWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_ROUTING) + if !s.commonCheckDiscoverRequest(req, resp) { + return resp + } + return s.nextSvr.GetRoutingConfigWithCache(ctx, req) +} + +// GetRateLimitWithCache User Client Get Service Limit Configuration Information +func (s *Server) GetRateLimitWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_RATE_LIMIT) + if !s.commonCheckDiscoverRequest(req, resp) { + return resp + } + return s.nextSvr.GetRateLimitWithCache(ctx, req) +} + +// GetCircuitBreakerWithCache Fuse configuration information for obtaining services for clients +func (s *Server) GetCircuitBreakerWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_CIRCUIT_BREAKER) + if !s.commonCheckDiscoverRequest(req, resp) { + return resp + } + return s.nextSvr.GetCircuitBreakerWithCache(ctx, req) +} + +// GetFaultDetectWithCache User Client Get FaultDetect Rule Information +func (s *Server) GetFaultDetectWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_FAULT_DETECTOR) + if !s.commonCheckDiscoverRequest(req, resp) { + return resp + } + return s.nextSvr.GetFaultDetectWithCache(ctx, req) +} + +// GetServiceContractWithCache User Client Get ServiceContract Rule Information +func (s *Server) GetServiceContractWithCache(ctx context.Context, req *apiservice.ServiceContract) *apiservice.Response { + resp := api.NewResponse(apimodel.Code_ExecuteSuccess) + if !s.serviceContractCheckDiscoverRequest(req, resp) { + return resp + } + + return s.nextSvr.GetServiceContractWithCache(ctx, req) +} + +// GetLaneRuleWithCache fetch lane rule by client +func (s *Server) GetLaneRuleWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { + resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_LANE) + if !s.commonCheckDiscoverRequest(req, resp) { + return resp + } + return s.nextSvr.GetLaneRuleWithCache(ctx, req) +} + +// UpdateInstance update one instance by client +func (s *Server) UpdateInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response { + return s.nextSvr.UpdateInstance(ctx, req) +} + +// ReportServiceContract client report service_contract +func (s *Server) ReportServiceContract(ctx context.Context, req *apiservice.ServiceContract) *apiservice.Response { + return s.nextSvr.ReportServiceContract(ctx, req) +} + +func (s *Server) commonCheckDiscoverRequest(req *apiservice.Service, resp *apiservice.DiscoverResponse) bool { + if s.nextSvr.Cache() == nil { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_ClientAPINotOpen)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = req + return false + } + if req == nil { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_EmptyRequest)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = req + return false + } + + if req.GetName().GetValue() == "" { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidServiceName)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = req + return false + } + if req.GetNamespace().GetValue() == "" { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidNamespaceName)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = req + return false + } + + return true +} + +func (s *Server) serviceContractCheckDiscoverRequest(req *apiservice.ServiceContract, resp *apiservice.Response) bool { + svc := &apiservice.Service{ + Name: wrapperspb.String(req.GetService()), + Namespace: wrapperspb.String(req.GetNamespace()), + } + + if s.nextSvr.Cache() == nil { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_ClientAPINotOpen)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = svc + resp.ServiceContract = req + return false + } + if req == nil { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_EmptyRequest)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = svc + return false + } + + if req.GetName() == "" { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidParameter)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = svc + resp.ServiceContract = req + return false + } + if req.GetNamespace() == "" { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidNamespaceName)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = svc + resp.ServiceContract = req + return false + } + if req.GetProtocol() == "" { + resp.Code = utils.NewUInt32Value(uint32(apimodel.Code_InvalidParameter)) + resp.Info = utils.NewStringValue(api.Code2Info(resp.GetCode().GetValue())) + resp.Service = svc + resp.ServiceContract = req + return false + } + return true +} diff --git a/service/interceptor/paramcheck/server.go b/service/interceptor/paramcheck/server.go new file mode 100644 index 000000000..83f62ecdd --- /dev/null +++ b/service/interceptor/paramcheck/server.go @@ -0,0 +1,49 @@ +/** + * 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 paramcheck + +import ( + "github.com/polarismesh/polaris/cache" + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/service" +) + +// Server 带有鉴权能力的 discoverServer +// +// 该层会对请求参数做一些调整,根据具体的请求发起人,设置为数据对应的 owner,不可为为别人进行创建资源 +type Server struct { + nextSvr service.DiscoverServer +} + +func NewServer(nextSvr service.DiscoverServer) service.DiscoverServer { + proxy := &Server{ + nextSvr: nextSvr, + } + return proxy +} + +// Cache Get cache management +func (svr *Server) Cache() *cache.CacheManager { + return svr.nextSvr.Cache() +} + +// GetServiceInstanceRevision 获取服务实例的版本号 +func (svr *Server) GetServiceInstanceRevision(serviceID string, + instances []*model.Instance) (string, error) { + return svr.nextSvr.GetServiceInstanceRevision(serviceID, instances) +} diff --git a/service/interceptor/register.go b/service/interceptor/register.go index 860b59ad6..9c301cbda 100644 --- a/service/interceptor/register.go +++ b/service/interceptor/register.go @@ -21,10 +21,18 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/service" service_auth "github.com/polarismesh/polaris/service/interceptor/auth" + "github.com/polarismesh/polaris/service/interceptor/paramcheck" ) func init() { - err := service.RegisterServerProxy("auth", func(svr *service.Server, pre service.DiscoverServer) (service.DiscoverServer, error) { + err := service.RegisterServerProxy("paramcheck", func(svr *service.Server, pre service.DiscoverServer) (service.DiscoverServer, error) { + return paramcheck.NewServer(svr), nil + }) + if err != nil { + panic(err) + } + + err = service.RegisterServerProxy("auth", func(svr *service.Server, pre service.DiscoverServer) (service.DiscoverServer, error) { userMgn, err := auth.GetUserServer() if err != nil { return nil, err diff --git a/service/service_contract.go b/service/service_contract.go index 1da61b1b1..d78536928 100644 --- a/service/service_contract.go +++ b/service/service_contract.go @@ -99,7 +99,7 @@ func (s *Server) CreateServiceContract(ctx context.Context, contract *apiservice saveData := &model.ServiceContract{ ID: contractId, - Name: contract.GetName(), + Type: utils.DefaultString(contract.GetType(), contract.GetName()), Namespace: contract.GetNamespace(), Service: contract.GetService(), Protocol: contract.GetProtocol(), @@ -140,7 +140,7 @@ func (s *Server) GetServiceContracts(ctx context.Context, query map[string]strin return out } - ret, totalCount, err := s.caches.ServiceContract().Query(searchFilters, offset, limit) + totalCount, ret, err := s.storage.GetServiceContracts(ctx, searchFilters, offset, limit) if err != nil { out = api.NewBatchQueryResponseWithMsg(commonstore.StoreCode2APICode(err), err.Error()) return out @@ -172,7 +172,8 @@ func (s *Server) GetServiceContracts(ctx context.Context, query map[string]strin contract := &apiservice.ServiceContract{ Id: item.ID, - Name: item.Name, + Name: item.Type, + Type: item.Type, Namespace: item.Namespace, Service: item.Service, Protocol: item.Protocol, @@ -231,7 +232,7 @@ func (s *Server) DeleteServiceContract(ctx context.Context, deleteData := &model.ServiceContract{ ID: contract.Id, - Name: contract.Name, + Type: utils.DefaultString(contract.Type, contract.Name), Namespace: contract.Namespace, Service: contract.Service, Protocol: contract.Protocol, @@ -255,14 +256,20 @@ func (s *Server) GetServiceContractVersions(ctx context.Context, filter map[stri return api.NewBatchQueryResponseWithMsg(apimodel.Code_InvalidParameter, "namespace is empty") } - ret := s.caches.ServiceContract().ListVersions(serviceName, namespace) + ret, err := s.storage.ListVersions(ctx, serviceName, namespace) + if err != nil { + log.Error("[Service][Contract] list save service_contract versions", utils.RequestID(ctx), zap.Error(err)) + return api.NewBatchQueryResponse(store.StoreCode2APICode(err)) + } + resp := api.NewBatchQueryResponse(apimodel.Code_ExecuteSuccess) resp.Data = make([]*anypb.Any, 0, len(ret)) for i := range ret { item := ret[i] if err := api.AddAnyDataIntoBatchQuery(resp, &apiservice.ServiceContract{ Id: item.ID, - Name: item.Name, + Name: item.Type, + Type: item.Type, Namespace: item.Namespace, Service: item.Service, Version: item.Version, @@ -305,7 +312,7 @@ func (s *Server) CreateServiceContractInterfaces(ctx context.Context, createData.Interfaces = append(createData.Interfaces, &model.InterfaceDescriptor{ ID: interfaceId, ContractID: contract.Id, - Name: item.Name, + Type: utils.DefaultString(item.Type, item.Name), Method: item.Method, Path: item.Path, Content: item.Content, @@ -356,7 +363,7 @@ func (s *Server) AppendServiceContractInterfaces(ctx context.Context, appendData.Interfaces = append(appendData.Interfaces, &model.InterfaceDescriptor{ ID: interfaceId, ContractID: contract.Id, - Name: item.Name, + Type: utils.DefaultString(item.Type, item.Name), Method: item.Method, Path: item.Path, Content: item.Content, @@ -408,7 +415,7 @@ func (s *Server) DeleteServiceContractInterfaces(ctx context.Context, ID: interfaceId, ContractID: contract.Id, Method: item.Method, - Name: item.Name, + Type: utils.DefaultString(item.Type, item.Name), Path: item.Path, }) } diff --git a/store/boltdb/default.go b/store/boltdb/default.go index a5069fff7..85ca9eea3 100644 --- a/store/boltdb/default.go +++ b/store/boltdb/default.go @@ -72,6 +72,7 @@ type boltStore struct { *faultDetectStore *routingStoreV2 *serviceContractStore + *laneStore // 配置中心stores *configFileGroupStore @@ -330,6 +331,7 @@ func (m *boltStore) newDiscoverModuleStore() { m.faultDetectStore = &faultDetectStore{handler: m.handler} m.routingStoreV2 = &routingStoreV2{handler: m.handler} m.serviceContractStore = &serviceContractStore{handler: m.handler} + m.laneStore = &laneStore{handler: m.handler} } func (m *boltStore) newAuthModuleStore() { diff --git a/store/boltdb/lane.go b/store/boltdb/lane.go new file mode 100644 index 000000000..0a0235e18 --- /dev/null +++ b/store/boltdb/lane.go @@ -0,0 +1,371 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package boltdb + +import ( + "encoding/json" + "sort" + "strings" + "time" + + bolt "go.etcd.io/bbolt" + "go.uber.org/zap" + + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/store" +) + +const ( + tblLaneGroup string = "lane_group" + + FieldLaneRuleText = "Rule" + FieldLaneDescription = "Description" + FieldLaneGroupName = "LaneGroup" + FieldLaneRules = "LaneRules" +) + +type laneStore struct { + handler BoltHandler +} + +// AddLaneGroup 添加泳道组 +func (l *laneStore) AddLaneGroup(tx store.Tx, item *model.LaneGroup) error { + dbTx := tx.GetDelegateTx().(*bolt.Tx) + // Before adding new data, you must clean up the old data + if err := deleteValues(dbTx, tblLaneGroup, []string{item.ID}); err != nil { + log.Errorf("[Store][Lane] delete lane group to kv error, %v", err) + return store.Error(err) + } + + for i := range item.LaneRules { + rule := item.LaneRules[i] + if rule.IsAdd() { + rule.CreateTime = time.Now() + rule.ModifyTime = time.Now() + } else { + rule.ModifyTime = time.Now() + } + + if rule.IsChangeEnable() { + rule.EnableTime = time.Now() + } else { + rule.EnableTime = time.Unix(0, 1) + } + } + + tn := time.Now() + item.CreateTime = tn + item.ModifyTime = tn + item.Valid = true + + if err := saveValue(dbTx, tblLaneGroup, item.ID, toLaneGroupStore(item)); err != nil { + log.Errorf("[Store][Lane] save lane group to kv error, %v", err) + return store.Error(err) + } + return nil +} + +// UpdateLaneGroup 更新泳道组 +func (l *laneStore) UpdateLaneGroup(tx store.Tx, item *model.LaneGroup) error { + dbTx := tx.GetDelegateTx().(*bolt.Tx) + + for i := range item.LaneRules { + rule := item.LaneRules[i] + if rule.IsAdd() { + rule.CreateTime = time.Now() + rule.ModifyTime = time.Now() + } else { + rule.ModifyTime = time.Now() + } + + if rule.IsChangeEnable() { + rule.EnableTime = time.Now() + } else { + rule.EnableTime = time.Unix(0, 1) + } + } + + properties := map[string]interface{}{ + FieldLaneDescription: item.Description, + FieldLaneRuleText: item.Rule, + CommonFieldModifyTime: time.Now(), + CommonFieldRevision: item.Revision, + FieldLaneRules: utils.MustJson(item.LaneRules), + } + + if err := updateValue(dbTx, tblLaneGroup, item.ID, properties); err != nil { + log.Error("[Store][Lane] update lane group to kv", zap.String("name", item.Name), zap.Error(err)) + return store.Error(err) + } + + return nil +} + +// LockLaneGroup 锁住一个泳道分组 +func (l *laneStore) LockLaneGroup(tx store.Tx, name string) (*model.LaneGroup, error) { + dbTx := tx.GetDelegateTx().(*bolt.Tx) + ret, err := l.getLaneGroup(dbTx, name, false) + if err != nil { + return nil, err + } + return ret, nil +} + +func (l *laneStore) GetLaneGroup(name string) (*model.LaneGroup, error) { + var ret *model.LaneGroup + var err error + err = l.handler.Execute(false, func(tx *bolt.Tx) error { + ret, err = l.getLaneGroup(tx, name, true) + return err + }) + if err != nil { + return nil, err + } + return ret, nil +} + +func (l *laneStore) GetLaneGroupByID(id string) (*model.LaneGroup, error) { + var ret *model.LaneGroup + var err error + err = l.handler.Execute(false, func(tx *bolt.Tx) error { + ret, err = l.getLaneGroup(tx, id, true) + return err + }) + if err != nil { + return nil, err + } + return ret, nil +} + +func (l *laneStore) getLaneGroup(tx *bolt.Tx, name string, brief bool) (*model.LaneGroup, error) { + fields := []string{ + CommonFieldValid, CommonFieldName, CommonFieldID, + } + result := make(map[string]interface{}) + err := loadValuesByFilter(tx, tblLaneGroup, fields, &LaneGroup{}, + func(m map[string]interface{}) bool { + validVal, ok := m[CommonFieldValid] + if ok && !validVal.(bool) { + return false + } + saveId, _ := m[CommonFieldID].(string) + saveName, _ := m[CommonFieldName].(string) + return name == saveName || saveId == name + }, result) + if err != nil { + log.Errorf("[Store][Lane] select one lane group to kv error, %v", err) + return nil, store.Error(err) + } + for _, v := range result { + saveData := toLaneGroupModel(v.(*LaneGroup)) + return saveData, nil + } + return nil, nil +} + +// GetLaneGroups 查询泳道组 +func (l *laneStore) GetLaneGroups(filter map[string]string, offset, limit uint32) (uint32, []*model.LaneGroup, error) { + fields := []string{ + CommonFieldValid, CommonFieldID, CommonFieldName, + } + searchName, hasName := filter["name"] + searchId, hasId := filter["id"] + result, err := l.handler.LoadValuesByFilter(tblLaneGroup, fields, &LaneGroup{}, func(m map[string]interface{}) bool { + validVal, ok := m[CommonFieldValid] + if ok && !validVal.(bool) { + return false + } + if hasName { + if !utils.IsWildMatch(m[CommonFieldName].(string), searchName) { + return false + } + } + if hasId { + if m[CommonFieldID].(string) != searchId { + return false + } + } + return true + }) + if err != nil { + log.Errorf("[Store][Lane] select lane group to kv error, %v", err) + return 0, nil, store.Error(err) + } + groups := make([]*model.LaneGroup, 0, len(result)) + for _, v := range result { + group := toLaneGroupModel(v.(*LaneGroup)) + groups = append(groups, group) + } + return uint32(len(result)), pageLaneGroups(groups, offset, limit, filter), nil +} + +func pageLaneGroups(items []*model.LaneGroup, offset, limit uint32, order map[string]string) []*model.LaneGroup { + orderField := order["order_field"] + asc := strings.ToLower(order["order_type"]) == "asc" + + switch orderField { + case "name": + // 按照名称排序 + sort.Slice(items, func(i, j int) bool { + if asc { + return items[i].Name < items[j].Name + } + return items[i].Name > items[j].Name + }) + default: + // 默认按照更新时间排序 + sort.Slice(items, func(i, j int) bool { + if asc { + return items[i].ModifyTime.Before(items[j].ModifyTime) + } + return items[i].ModifyTime.After(items[j].ModifyTime) + }) + } + + amount := uint32(len(items)) + endIdx := offset + limit + if endIdx > amount { + endIdx = amount + } + + return items[offset:endIdx] +} + +// DeleteLaneGroup 删除泳道组 +func (l *laneStore) DeleteLaneGroup(id string) error { + err := l.handler.Execute(true, func(tx *bolt.Tx) error { + properties := map[string]interface{}{ + CommonFieldModifyTime: time.Now(), + CommonFieldValid: false, + } + if err := updateValue(tx, tblLaneGroup, id, properties); err != nil { + log.Error("[Store][Lane] delete lane_group from kv", zap.Error(err)) + return err + } + return nil + }) + return store.Error(err) +} + +// GetMoreLaneGroups 获取泳道规则列表到缓存层 +func (l *laneStore) GetMoreLaneGroups(mtime time.Time, firstUpdate bool) (map[string]*model.LaneGroup, error) { + if firstUpdate { + mtime = time.Unix(0, 0) + } + fields := []string{ + CommonFieldModifyTime, + } + + groups := make(map[string]*model.LaneGroup, 32) + err := l.handler.Execute(false, func(tx *bolt.Tx) error { + result := make(map[string]interface{}) + err := loadValuesByFilter(tx, tblLaneGroup, fields, &LaneGroup{}, func(m map[string]interface{}) bool { + val, ok := m[CommonFieldModifyTime] + if !ok { + return true + } + saveMtime := val.(time.Time) + return !saveMtime.Before(mtime) + }, result) + if err != nil { + log.Errorf("[Store][Lane] get more lane rule for cache, %v", err) + return store.Error(err) + } + for _, v := range result { + item := toLaneGroupModel(v.(*LaneGroup)) + groups[item.ID] = item + } + return nil + }) + if err != nil { + log.Error("[Store][Lane] get more lane_group for cache update", zap.Error(err)) + return nil, store.Error(err) + } + return groups, nil +} + +func (l *laneStore) GetLaneRuleMaxPriority() (int32, error) { + var maxPriority int32 + fields := []string{ + FieldLaneRules, + CommonFieldValid, + } + _, err := l.handler.LoadValuesByFilter(tblLaneGroup, fields, &model.LaneGroup{}, func(m map[string]interface{}) bool { + valid, _ := m[CommonFieldValid].(bool) + if !valid { + return false + } + rules := make(map[string]*model.LaneRule) + _ = json.Unmarshal([]byte(m[FieldLaneRules].(string)), &rules) + for _, rule := range rules { + curPriority := rule.Priority + if maxPriority <= int32(curPriority) { + maxPriority = int32(curPriority) + } + } + return false + }) + if err != nil { + log.Error("[Store][Lane] get current lane_rule max priority", zap.Error(err)) + } + return maxPriority, err +} + +func toLaneGroupStore(data *model.LaneGroup) *LaneGroup { + return &LaneGroup{ + ID: data.ID, + Name: data.Name, + Rule: data.Rule, + Revision: data.Revision, + Description: data.Description, + Valid: data.Valid, + CreateTime: data.CreateTime, + ModifyTime: data.ModifyTime, + LaneRules: utils.MustJson(data.LaneRules), + } +} + +func toLaneGroupModel(data *LaneGroup) *model.LaneGroup { + ret := &model.LaneGroup{ + ID: data.ID, + Name: data.Name, + Rule: data.Rule, + Revision: data.Revision, + Description: data.Description, + Valid: data.Valid, + CreateTime: data.CreateTime, + ModifyTime: data.ModifyTime, + LaneRules: map[string]*model.LaneRule{}, + } + + _ = json.Unmarshal([]byte(data.Name), &ret.LaneRules) + return ret +} + +type LaneGroup struct { + ID string + Name string + Rule string + Revision string + Description string + Valid bool + CreateTime time.Time + ModifyTime time.Time + LaneRules string +} diff --git a/store/boltdb/service_contract.go b/store/boltdb/service_contract.go index f3b6d720e..e683ed1d8 100644 --- a/store/boltdb/service_contract.go +++ b/store/boltdb/service_contract.go @@ -18,7 +18,9 @@ package boltdb import ( + "context" "encoding/json" + "sort" "time" bolt "go.etcd.io/bbolt" @@ -34,13 +36,12 @@ const ( ContractFieldID = "ID" ContractFieldNamespace = "Namespace" ContractFieldService = "Service" - ContractFieldName = "Name" + ContractFieldType = "Type" ContractFieldProtocol = "Protocol" ContractFieldVersion = "Version" ContractFieldRevision = "Revision" ContractFieldContent = "Content" ContractFieldInterfaces = "Interfaces" - ContractFieldCreateTime = "CreateTime" ContractFieldModifyTime = "ModifyTime" ContractFieldValid = "Valid" ) @@ -91,35 +92,6 @@ func (s *serviceContractStore) DeleteServiceContract(contract *model.ServiceCont return nil } -// GetMoreServiceContracts 查询服务契约数据 -func (s *serviceContractStore) GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) { - if firstUpdate { - mtime = time.Unix(0, 0) - } - - fields := []string{ContractFieldValid, ContractFieldModifyTime} - values, err := s.handler.LoadValuesByFilter(tblServiceContract, fields, &ServiceContract{}, - func(m map[string]interface{}) bool { - if firstUpdate { - valid, _ := m[ContractFieldValid].(bool) - if !valid { - return false - } - } - saveMtime, _ := m[ContractFieldModifyTime].(time.Time) - return !saveMtime.Before(mtime) - }) - if err != nil { - return nil, store.Error(err) - } - - ret := make([]*model.EnrichServiceContract, 0, len(values)) - for _, v := range values { - ret = append(ret, s.toModel(v.(*ServiceContract))) - } - return ret, nil -} - // GetServiceContract . func (s *serviceContractStore) GetServiceContract(id string) (*model.EnrichServiceContract, error) { values, err := s.handler.LoadValues(tblServiceContract, []string{id}, &ServiceContract{}) @@ -233,6 +205,213 @@ func (s *serviceContractStore) DeleteServiceContractInterfaces(contract *model.E }) } +var ( + searchContractFields = []string{ + ContractFieldType, + ContractFieldNamespace, + ContractFieldService, + ContractFieldVersion, + ContractFieldProtocol, + ContractFieldValid, + } + + searchInterfaceFields = []string{ + ContractFieldType, + ContractFieldNamespace, + ContractFieldService, + ContractFieldVersion, + ContractFieldProtocol, + ContractFieldValid, + ContractFieldInterfaces, + } +) + +func (s *serviceContractStore) GetServiceContracts(ctx context.Context, filter map[string]string, offset, + limit uint32) (uint32, []*model.EnrichServiceContract, error) { + + values, err := s.handler.LoadValuesByFilter(tblServiceContract, searchContractFields, &ServiceContract{}, + func(m map[string]interface{}) bool { + valid, _ := m[ContractFieldValid].(bool) + if !valid { + return false + } + if searchNs, ok := filter["namespace"]; ok { + saveNs, _ := m[ContractFieldNamespace].(string) + if saveNs != searchNs { + return false + } + } + if searchSvc, ok := filter["service"]; ok { + saveSvc, _ := m[ContractFieldService].(string) + if saveSvc != searchSvc { + return false + } + } + if searchProtocol, ok := filter["protocol"]; ok { + saveProtocol, _ := m[ContractFieldProtocol].(string) + if saveProtocol != searchProtocol { + return false + } + } + if searchVer, ok := filter["version"]; ok { + saveVer, _ := m[ContractFieldVersion].(string) + if searchVer != saveVer { + return false + } + } + if searchType, ok := filter["type"]; ok { + saveType, _ := m[ContractFieldType].(string) + if searchType != saveType { + return false + } + } + return true + }) + if err != nil { + return 0, nil, store.Error(err) + } + + ret := make([]*model.EnrichServiceContract, 0, len(values)) + for _, v := range values { + ret = append(ret, s.toModel(v.(*ServiceContract))) + } + sort.Slice(ret, func(i, j int) bool { + return ret[j].ModifyTime.Before(ret[i].ModifyTime) + }) + return uint32(len(values)), toServiceContractPage(ret, offset, limit), nil +} + +func toServiceContractPage(result []*model.EnrichServiceContract, offset, limit uint32) []*model.EnrichServiceContract { + // 所有符合条件的服务数量 + amount := uint32(len(result)) + // 判断 offset 和 limit 是否允许返回对应的服务 + if offset >= amount || limit == 0 { + return nil + } + + endIdx := offset + limit + if endIdx > amount { + endIdx = amount + } + return result[offset:endIdx] +} + +// GetInterfaceDescriptors 查询服务接口列表 +func (s *serviceContractStore) GetInterfaceDescriptors(ctx context.Context, filter map[string]string, offset, + limit uint32) (uint32, []*model.InterfaceDescriptor, error) { + + result := make([]*model.InterfaceDescriptor, 0, limit) + _, err := s.handler.LoadValuesByFilter(tblServiceContract, searchInterfaceFields, &ServiceContract{}, + func(m map[string]interface{}) bool { + valid, _ := m[ContractFieldValid].(bool) + if !valid { + return false + } + if searchNs, ok := filter["namespace"]; ok { + saveNs, _ := m[ContractFieldNamespace].(string) + if saveNs != searchNs { + return false + } + } + if searchSvc, ok := filter["service"]; ok { + saveSvc, _ := m[ContractFieldService].(string) + if saveSvc != searchSvc { + return false + } + } + if searchProtocol, ok := filter["protocol"]; ok { + saveProtocol, _ := m[ContractFieldProtocol].(string) + if saveProtocol != searchProtocol { + return false + } + } + if searchVer, ok := filter["version"]; ok { + saveVer, _ := m[ContractFieldVersion].(string) + if searchVer != saveVer { + return false + } + } + interfaces := make([]*model.InterfaceDescriptor, 0, 4) + _ = json.Unmarshal([]byte(m[ContractFieldInterfaces].(string)), &interfaces) + for i := range interfaces { + if searchType, ok := filter["type"]; ok { + if searchType != interfaces[i].Type { + continue + } + } + if searchPath, ok := filter["path"]; ok { + if searchPath != interfaces[i].Path { + continue + } + } + if searchMethod, ok := filter["method"]; ok { + if searchMethod != interfaces[i].Method { + continue + } + } + if searchSource, ok := filter["source"]; ok { + if searchSource != interfaces[i].Source.String() { + continue + } + } + result = append(result, interfaces[i]) + } + + return true + }) + if err != nil { + return 0, nil, store.Error(err) + } + sort.Slice(result, func(i, j int) bool { + return result[j].ModifyTime.Before(result[i].ModifyTime) + }) + return uint32(len(result)), toServiceInterfacesPage(result, offset, limit), err +} + +func toServiceInterfacesPage(result []*model.InterfaceDescriptor, offset, limit uint32) []*model.InterfaceDescriptor { + // 所有符合条件的服务数量 + amount := uint32(len(result)) + // 判断 offset 和 limit 是否允许返回对应的服务 + if offset >= amount || limit == 0 { + return nil + } + + endIdx := offset + limit + if endIdx > amount { + endIdx = amount + } + return result[offset:endIdx] +} + +// ListVersions . +func (s *serviceContractStore) ListVersions(ctx context.Context, service, namespace string) ([]*model.ServiceContract, error) { + fields := []string{ContractFieldValid, ContractFieldNamespace, ContractFieldService} + values, err := s.handler.LoadValuesByFilter(tblServiceContract, fields, &ServiceContract{}, + func(m map[string]interface{}) bool { + valid, _ := m[ContractFieldValid].(bool) + if !valid { + return false + } + + saveNs, _ := m[ContractFieldNamespace].(string) + saveSvc, _ := m[ContractFieldService].(string) + + return saveNs == namespace && saveSvc == service + }) + if err != nil { + return nil, store.Error(err) + } + + ret := make([]*model.EnrichServiceContract, 0, len(values)) + for _, v := range values { + data := s.toModel(v.(*ServiceContract)) + data.Interfaces = nil + data.Content = "" + ret = append(ret, data) + } + return nil, nil +} + func (s *serviceContractStore) toModel(data *ServiceContract) *model.EnrichServiceContract { interfaces := make([]*model.InterfaceDescriptor, 0, 4) _ = json.Unmarshal([]byte(data.Interfaces), &interfaces) @@ -241,7 +420,7 @@ func (s *serviceContractStore) toModel(data *ServiceContract) *model.EnrichServi ID: data.ID, Namespace: data.Namespace, Service: data.Service, - Name: data.Name, + Type: data.Type, Protocol: data.Protocol, Version: data.Version, Revision: data.Revision, @@ -261,7 +440,7 @@ func (s *serviceContractStore) toStore(data *model.EnrichServiceContract) *Servi ID: data.ID, Namespace: data.Namespace, Service: data.Service, - Name: data.Name, + Type: data.Type, Protocol: data.Protocol, Version: data.Version, Revision: data.Revision, @@ -280,7 +459,7 @@ type ServiceContract struct { // 所属服务名称 Service string // 契约名称 - Name string + Type string // 协议,http/grpc/dubbo/thrift Protocol string // 契约版本 diff --git a/store/discover_api.go b/store/discover_api.go index 5c3463873..56555dbe4 100644 --- a/store/discover_api.go +++ b/store/discover_api.go @@ -18,6 +18,7 @@ package store import ( + "context" "time" "github.com/polarismesh/polaris/common/model" @@ -51,6 +52,8 @@ type NamingModuleStore interface { FaultDetectRuleStore // ServiceContractStore 服务契约操作接口 ServiceContractStore + // LaneStore 泳道规则存储操作接口 + LaneStore } // ServiceStore 服务存储接口 @@ -277,14 +280,40 @@ type ServiceContractStore interface { UpdateServiceContract(contract *model.ServiceContract) error // DeleteServiceContract 删除服务契约 DeleteServiceContract(contract *model.ServiceContract) error - // GetMoreServiceContracts 用于缓存加载数据 - GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) // GetServiceContract 查询服务契约数据 GetServiceContract(id string) (data *model.EnrichServiceContract, err error) + // GetServiceContracts 查询服务契约公共属性列表 + GetServiceContracts(ctx context.Context, filter map[string]string, offset, limit uint32) (uint32, []*model.EnrichServiceContract, error) // AddServiceContractInterfaces 创建服务契约API接口 AddServiceContractInterfaces(contract *model.EnrichServiceContract) error // AppendServiceContractInterfaces 追加服务契约API接口 AppendServiceContractInterfaces(contract *model.EnrichServiceContract) error // DeleteServiceContractInterfaces 批量删除服务契约API接口 DeleteServiceContractInterfaces(contract *model.EnrichServiceContract) error + // GetInterfaceDescriptors 查询服务接口列表 + GetInterfaceDescriptors(ctx context.Context, filter map[string]string, offset, limit uint32) (uint32, []*model.InterfaceDescriptor, error) + // ListVersions . + ListVersions(ctx context.Context, service, namespace string) ([]*model.ServiceContract, error) +} + +// LaneStore 泳道资源存储操作 +type LaneStore interface { + // AddLaneGroup 添加泳道组 + AddLaneGroup(tx Tx, item *model.LaneGroup) error + // UpdateLaneGroup 更新泳道组 + UpdateLaneGroup(tx Tx, item *model.LaneGroup) error + // GetLaneGroup 按照名称查询泳道组 + GetLaneGroup(name string) (*model.LaneGroup, error) + // GetLaneGroupByID 按照名称查询泳道组 + GetLaneGroupByID(id string) (*model.LaneGroup, error) + // GetLaneGroups 查询泳道组 + GetLaneGroups(filter map[string]string, offset, limit uint32) (uint32, []*model.LaneGroup, error) + // LockLaneGroup 锁住一个泳道分组 + LockLaneGroup(tx Tx, name string) (*model.LaneGroup, error) + // GetMoreLaneGroups 获取泳道规则列表到缓存层 + GetMoreLaneGroups(mtime time.Time, firstUpdate bool) (map[string]*model.LaneGroup, error) + // DeleteLaneGroup 删除泳道组 + DeleteLaneGroup(id string) error + // GetLaneRuleMaxPriority 获取泳道规则中当前最大的泳道规则优先级信息 + GetLaneRuleMaxPriority() (int32, error) } diff --git a/store/mock/api_mock.go b/store/mock/api_mock.go index 2e9ad538f..d7f448876 100644 --- a/store/mock/api_mock.go +++ b/store/mock/api_mock.go @@ -5,6 +5,7 @@ package mock import ( + context "context" reflect "reflect" time "time" @@ -78,6 +79,20 @@ func (mr *MockStoreMockRecorder) AddInstance(instance interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddInstance", reflect.TypeOf((*MockStore)(nil).AddInstance), instance) } +// AddLaneGroup mocks base method. +func (m *MockStore) AddLaneGroup(tx store.Tx, item *model.LaneGroup) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLaneGroup", tx, item) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLaneGroup indicates an expected call of AddLaneGroup. +func (mr *MockStoreMockRecorder) AddLaneGroup(tx, item interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLaneGroup", reflect.TypeOf((*MockStore)(nil).AddLaneGroup), tx, item) +} + // AddNamespace mocks base method. func (m *MockStore) AddNamespace(namespace *model.Namespace) error { m.ctrl.T.Helper() @@ -732,6 +747,20 @@ func (mr *MockStoreMockRecorder) DeleteInstance(instanceID interface{}) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstance", reflect.TypeOf((*MockStore)(nil).DeleteInstance), instanceID) } +// DeleteLaneGroup mocks base method. +func (m *MockStore) DeleteLaneGroup(id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLaneGroup", id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLaneGroup indicates an expected call of DeleteLaneGroup. +func (mr *MockStoreMockRecorder) DeleteLaneGroup(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLaneGroup", reflect.TypeOf((*MockStore)(nil).DeleteLaneGroup), id) +} + // DeleteRateLimit mocks base method. func (m *MockStore) DeleteRateLimit(limiting *model.RateLimit) error { m.ctrl.T.Helper() @@ -1323,6 +1352,22 @@ func (mr *MockStoreMockRecorder) GetInstancesMainByService(serviceID, host inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesMainByService", reflect.TypeOf((*MockStore)(nil).GetInstancesMainByService), serviceID, host) } +// GetInterfaceDescriptors mocks base method. +func (m *MockStore) GetInterfaceDescriptors(ctx context.Context, filter map[string]string, offset, limit uint32) (uint32, []*model.InterfaceDescriptor, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInterfaceDescriptors", ctx, filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.InterfaceDescriptor) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetInterfaceDescriptors indicates an expected call of GetInterfaceDescriptors. +func (mr *MockStoreMockRecorder) GetInterfaceDescriptors(ctx, filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInterfaceDescriptors", reflect.TypeOf((*MockStore)(nil).GetInterfaceDescriptors), ctx, filter, offset, limit) +} + // GetL5Extend mocks base method. func (m *MockStore) GetL5Extend(serviceID string) (map[string]interface{}, error) { m.ctrl.T.Helper() @@ -1338,6 +1383,67 @@ func (mr *MockStoreMockRecorder) GetL5Extend(serviceID interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetL5Extend", reflect.TypeOf((*MockStore)(nil).GetL5Extend), serviceID) } +// GetLaneGroup mocks base method. +func (m *MockStore) GetLaneGroup(name string) (*model.LaneGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLaneGroup", name) + ret0, _ := ret[0].(*model.LaneGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLaneGroup indicates an expected call of GetLaneGroup. +func (mr *MockStoreMockRecorder) GetLaneGroup(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLaneGroup", reflect.TypeOf((*MockStore)(nil).GetLaneGroup), name) +} + +// GetLaneGroupByID mocks base method. +func (m *MockStore) GetLaneGroupByID(id string) (*model.LaneGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLaneGroupByID", id) + ret0, _ := ret[0].(*model.LaneGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLaneGroupByID indicates an expected call of GetLaneGroupByID. +func (mr *MockStoreMockRecorder) GetLaneGroupByID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLaneGroupByID", reflect.TypeOf((*MockStore)(nil).GetLaneGroupByID), id) +} + +// GetLaneGroups mocks base method. +func (m *MockStore) GetLaneGroups(filter map[string]string, offset, limit uint32) (uint32, []*model.LaneGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLaneGroups", filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.LaneGroup) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetLaneGroups indicates an expected call of GetLaneGroups. +func (mr *MockStoreMockRecorder) GetLaneGroups(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLaneGroups", reflect.TypeOf((*MockStore)(nil).GetLaneGroups), filter, offset, limit) +} + +// GetLaneRuleMaxPriority mocks base method. +func (m *MockStore) GetLaneRuleMaxPriority() (int32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLaneRuleMaxPriority") + ret0, _ := ret[0].(int32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLaneRuleMaxPriority indicates an expected call of GetLaneRuleMaxPriority. +func (mr *MockStoreMockRecorder) GetLaneRuleMaxPriority() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLaneRuleMaxPriority", reflect.TypeOf((*MockStore)(nil).GetLaneRuleMaxPriority)) +} + // GetMoreClients mocks base method. func (m *MockStore) GetMoreClients(mtime time.Time, firstUpdate bool) (map[string]*model.Client, error) { m.ctrl.T.Helper() @@ -1473,6 +1579,21 @@ func (mr *MockStoreMockRecorder) GetMoreL5Sections(flow interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Sections", reflect.TypeOf((*MockStore)(nil).GetMoreL5Sections), flow) } +// GetMoreLaneGroups mocks base method. +func (m *MockStore) GetMoreLaneGroups(mtime time.Time, firstUpdate bool) (map[string]*model.LaneGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreLaneGroups", mtime, firstUpdate) + ret0, _ := ret[0].(map[string]*model.LaneGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreLaneGroups indicates an expected call of GetMoreLaneGroups. +func (mr *MockStoreMockRecorder) GetMoreLaneGroups(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreLaneGroups", reflect.TypeOf((*MockStore)(nil).GetMoreLaneGroups), mtime, firstUpdate) +} + // GetMoreNamespaces mocks base method. func (m *MockStore) GetMoreNamespaces(mtime time.Time) ([]*model.Namespace, error) { m.ctrl.T.Helper() @@ -1503,21 +1624,6 @@ func (mr *MockStoreMockRecorder) GetMoreReleaseFile(firstUpdate, modifyTime inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreReleaseFile", reflect.TypeOf((*MockStore)(nil).GetMoreReleaseFile), firstUpdate, modifyTime) } -// GetMoreServiceContracts mocks base method. -func (m *MockStore) GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMoreServiceContracts", firstUpdate, mtime) - ret0, _ := ret[0].([]*model.EnrichServiceContract) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetMoreServiceContracts indicates an expected call of GetMoreServiceContracts. -func (mr *MockStoreMockRecorder) GetMoreServiceContracts(firstUpdate, mtime interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreServiceContracts", reflect.TypeOf((*MockStore)(nil).GetMoreServiceContracts), firstUpdate, mtime) -} - // GetMoreServices mocks base method. func (m *MockStore) GetMoreServices(mtime time.Time, firstUpdate, disableBusiness, needMeta bool) (map[string]*model.Service, error) { m.ctrl.T.Helper() @@ -1761,6 +1867,22 @@ func (mr *MockStoreMockRecorder) GetServiceContract(id interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceContract", reflect.TypeOf((*MockStore)(nil).GetServiceContract), id) } +// GetServiceContracts mocks base method. +func (m *MockStore) GetServiceContracts(ctx context.Context, filter map[string]string, offset, limit uint32) (uint32, []*model.EnrichServiceContract, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceContracts", ctx, filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.EnrichServiceContract) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetServiceContracts indicates an expected call of GetServiceContracts. +func (mr *MockStoreMockRecorder) GetServiceContracts(ctx, filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceContracts", reflect.TypeOf((*MockStore)(nil).GetServiceContracts), ctx, filter, offset, limit) +} + // GetServices mocks base method. func (m *MockStore) GetServices(serviceFilters, serviceMetas map[string]string, instanceFilters *store.InstanceArgs, offset, limit uint32) (uint32, []*model.Service, error) { m.ctrl.T.Helper() @@ -2166,6 +2288,21 @@ func (mr *MockStoreMockRecorder) ListLeaderElections() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLeaderElections", reflect.TypeOf((*MockStore)(nil).ListLeaderElections)) } +// ListVersions mocks base method. +func (m *MockStore) ListVersions(ctx context.Context, service, namespace string) ([]*model.ServiceContract, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVersions", ctx, service, namespace) + ret0, _ := ret[0].([]*model.ServiceContract) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListVersions indicates an expected call of ListVersions. +func (mr *MockStoreMockRecorder) ListVersions(ctx, service, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVersions", reflect.TypeOf((*MockStore)(nil).ListVersions), ctx, service, namespace) +} + // LockConfigFile mocks base method. func (m *MockStore) LockConfigFile(tx store.Tx, file *model.ConfigFileKey) (*model.ConfigFile, error) { m.ctrl.T.Helper() @@ -2181,6 +2318,21 @@ func (mr *MockStoreMockRecorder) LockConfigFile(tx, file interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockConfigFile", reflect.TypeOf((*MockStore)(nil).LockConfigFile), tx, file) } +// LockLaneGroup mocks base method. +func (m *MockStore) LockLaneGroup(tx store.Tx, name string) (*model.LaneGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LockLaneGroup", tx, name) + ret0, _ := ret[0].(*model.LaneGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LockLaneGroup indicates an expected call of LockLaneGroup. +func (mr *MockStoreMockRecorder) LockLaneGroup(tx, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockLaneGroup", reflect.TypeOf((*MockStore)(nil).LockLaneGroup), tx, name) +} + // LooseAddStrategyResources mocks base method. func (m *MockStore) LooseAddStrategyResources(resources []model.StrategyResource) error { m.ctrl.T.Helper() @@ -2441,6 +2593,20 @@ func (mr *MockStoreMockRecorder) UpdateInstance(instance interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInstance", reflect.TypeOf((*MockStore)(nil).UpdateInstance), instance) } +// UpdateLaneGroup mocks base method. +func (m *MockStore) UpdateLaneGroup(tx store.Tx, item *model.LaneGroup) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLaneGroup", tx, item) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLaneGroup indicates an expected call of UpdateLaneGroup. +func (mr *MockStoreMockRecorder) UpdateLaneGroup(tx, item interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLaneGroup", reflect.TypeOf((*MockStore)(nil).UpdateLaneGroup), tx, item) +} + // UpdateNamespace mocks base method. func (m *MockStore) UpdateNamespace(namespace *model.Namespace) error { m.ctrl.T.Helper() diff --git a/store/mysql/common.go b/store/mysql/common.go index b204e1306..fe007dc65 100644 --- a/store/mysql/common.go +++ b/store/mysql/common.go @@ -173,3 +173,11 @@ func toUnderscoreName(name string) string { } return buf.String() } + +func StringsToArgs(s []string) []interface{} { + ret := make([]interface{}, 0, len(s)) + for i := range s { + ret = append(ret, s[i]) + } + return ret +} diff --git a/store/mysql/default.go b/store/mysql/default.go index 4d4eb2cf9..299985033 100644 --- a/store/mysql/default.go +++ b/store/mysql/default.go @@ -58,6 +58,7 @@ type stableStore struct { *faultDetectRuleStore *routingConfigStoreV2 *serviceContractStore + *laneStore // 配置中心 stores *configFileGroupStore @@ -260,6 +261,7 @@ func (s *stableStore) newStore() { s.faultDetectRuleStore = &faultDetectRuleStore{master: s.master, slave: s.slave} s.routingConfigStoreV2 = &routingConfigStoreV2{master: s.master, slave: s.slave} s.serviceContractStore = &serviceContractStore{master: s.master, slave: s.slave} + s.laneStore = &laneStore{master: s.master, slave: s.slave} s.configFileGroupStore = &configFileGroupStore{master: s.master, slave: s.slave} s.configFileStore = &configFileStore{master: s.master, slave: s.slave} diff --git a/store/mysql/lane.go b/store/mysql/lane.go new file mode 100644 index 000000000..b446fafca --- /dev/null +++ b/store/mysql/lane.go @@ -0,0 +1,542 @@ +package sqldb + +import ( + "database/sql" + "fmt" + "strings" + "time" + + "go.uber.org/zap" + + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/store" +) + +type laneStore struct { + master *BaseDB + slave *BaseDB +} + +// AddLaneGroup 添加泳道组 +func (l *laneStore) AddLaneGroup(tx store.Tx, item *model.LaneGroup) error { + if err := l.cleanSoftDeletedRules(); err != nil { + return err + } + + dbTx := tx.GetDelegateTx().(*BaseTx) + // 先清理无效的泳道组 + if _, err := dbTx.Exec("DELETE FROM lane_group WHERE name = ? AND flag = 1", item.Name); err != nil { + log.Error("[Store][Lane] clean invalid lane group", zap.String("id", item.ID), + zap.String("name", item.Name), zap.Error(err)) + return err + } + args := []interface{}{ + item.ID, + item.Name, + item.Rule, + item.Revision, + item.Description, + } + + addSql := ` +INSERT INTO lane_group (id, name, rule, revision, description, flag + , ctime, mtime) +VALUES (?, ?, ?, ?, ?, 0, sysdate(), sysdate()) +` + if _, err := dbTx.Exec(addSql, args...); err != nil { + log.Error("[Store][Lane] add lane group", zap.String("id", item.ID), + zap.String("name", item.Name), zap.Error(err)) + return store.Error(err) + } + return l.upsertLaneRules(dbTx, item, item.LaneRules) +} + +// UpdateLaneGroup 更新泳道组 +func (l *laneStore) UpdateLaneGroup(tx store.Tx, item *model.LaneGroup) error { + if err := l.cleanSoftDeletedRules(); err != nil { + return err + } + + dbTx := tx.GetDelegateTx().(*BaseTx) + args := []interface{}{ + item.Rule, + item.Revision, + item.Description, + item.ID, + } + + addSql := "UPDATE lane_group SET rule = ?, revision = ?, description = ?, mtime = sysdate() WHERE id = ?" + if _, err := dbTx.Exec(addSql, args...); err != nil { + log.Error("[Store][Lane] update lane group", zap.String("id", item.ID), + zap.String("name", item.Name), zap.Error(err)) + return store.Error(err) + } + return l.upsertLaneRules(dbTx, item, item.LaneRules) +} + +// GetLaneGroup 查询泳道组 +func (l *laneStore) GetLaneGroup(name string) (*model.LaneGroup, error) { + querySql := ` +SELECT id, name, rule, description, revision, flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM lane_group WHERE flag = 0 AND name = ? +` + result := make([]*model.LaneGroup, 0, 1) + err := l.master.processWithTransaction("GetLaneGroup", func(tx *BaseTx) error { + rows, err := tx.Query(querySql, name) + if err != nil { + log.Error("[Store][Lane] select one lane group", zap.String("querySql", querySql), zap.Error(err)) + return err + } + if err := transferLaneGroups(rows, func(group *model.LaneGroup) { + result = append(result, group) + }); err != nil { + log.Error("[Store][Lane] transfer one lane group row", zap.Error(err)) + return err + } + return tx.Commit() + }) + if err != nil { + return nil, store.Error(err) + } + if len(result) == 0 { + return nil, nil + } + return result[0], nil +} + +// GetLaneGroupByID . +func (l *laneStore) GetLaneGroupByID(id string) (*model.LaneGroup, error) { + querySql := ` +SELECT id, name, rule, description, revision, flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM lane_group WHERE flag = 0 AND id = ? +` + result := make([]*model.LaneGroup, 0, 1) + err := l.master.processWithTransaction("GetLaneGroupByID", func(tx *BaseTx) error { + rows, err := tx.Query(querySql, id) + if err != nil { + log.Error("[Store][Lane] select one lane group", zap.String("querySql", querySql), zap.Error(err)) + return err + } + if err := transferLaneGroups(rows, func(group *model.LaneGroup) { + result = append(result, group) + }); err != nil { + log.Error("[Store][Lane] transfer one lane group row", zap.Error(err)) + return err + } + return tx.Commit() + }) + if err != nil { + return nil, store.Error(err) + } + if len(result) == 0 { + return nil, nil + } + return result[0], nil +} + +func (l *laneStore) LockLaneGroup(tx store.Tx, name string) (*model.LaneGroup, error) { + querySql := ` +SELECT id, name, rule, description, revision + , flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) +FROM lane_group +WHERE flag = 0 + AND name = ? +FOR UPDATE +` + dbTx := tx.GetDelegateTx().(*BaseTx) + result := make([]*model.LaneGroup, 0, 1) + rows, err := dbTx.Query(querySql, name) + if err != nil { + log.Error("[Store][Lane] select one lane group", zap.String("querySql", querySql), zap.String("name", name), + zap.Error(err)) + return nil, err + } + if err := transferLaneGroups(rows, func(group *model.LaneGroup) { + result = append(result, group) + }); err != nil { + log.Error("[Store][Lane] transfer one lane group row", zap.String("name", name), zap.Error(err)) + return nil, store.Error(err) + } + if len(result) == 0 { + return nil, nil + } + rules, err := l.getLaneRulesByGroup(dbTx, []string{name}) + if err != nil { + log.Error("[Store][Lane] load lane_group all lane_rule", zap.String("name", name), zap.Error(err)) + return nil, store.Error(err) + } + if len(rules) != 0 { + result[0].LaneRules = rules[name] + } + return result[0], nil +} + +// GetLaneGroups 查询泳道组 +func (l *laneStore) GetLaneGroups(filter map[string]string, offset, limit uint32) (uint32, []*model.LaneGroup, error) { + countSql := ` +SELECT COUNT(*) FROM lane_group WHERE flag = 0 +` + querySql := ` +SELECT id, name, rule, description, revision, flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM lane_group WHERE flag = 0 +` + conditions := []string{} + args := []interface{}{} + for k, v := range filter { + switch k { + case "name": + if v, ok := utils.ParseWildName(v); ok { + conditions = append(conditions, "name = ?") + args = append(args, v) + } else { + conditions = append(conditions, "name LIKE ?") + args = append(args, "%"+v+"%") + } + case "id": + conditions = append(conditions, "id = ?") + args = append(args, v) + } + } + if len(conditions) > 0 { + countSql += " AND " + strings.Join(conditions, " AND ") + querySql += " AND " + strings.Join(conditions, " AND ") + } + + querySql += fmt.Sprintf(" ORDER BY %s %s LIMIT ?, ? ", filter["order_field"], filter["order_type"]) + + var count int64 + var result []*model.LaneGroup + + err := l.master.processWithTransaction("GetLaneGroups", func(tx *BaseTx) error { + row := tx.QueryRow(countSql, args...) + if err := row.Scan(&count); err != nil { + log.Error("[Store][Lane] count lane group", zap.String("countSql", countSql), zap.Error(err)) + return err + } + + // count 阶段不需要分页参数,因此留到这里在进行追加 + args = append(args, offset, limit) + rows, err := tx.Query(querySql, args...) + if err != nil { + log.Error("[Store][Lane] select lane group", zap.String("querySql", querySql), zap.Error(err)) + return err + } + if err := transferLaneGroups(rows, func(group *model.LaneGroup) { + result = append(result, group) + }); err != nil { + log.Error("[Store][Lane] transfer lane group row", zap.Error(err)) + return err + } + brief := filter[briefSearch] == "true" + if !brief { + names := make([]string, 0, len(result)) + for i := range result { + names = append(names, result[i].Name) + } + rules, err := l.getLaneRulesByGroup(tx, names) + if err != nil { + return err + } + for i := range result { + item := result[i] + item.LaneRules = rules[item.Name] + } + } + return tx.Commit() + }) + if err != nil { + return 0, nil, store.Error(err) + } + return uint32(count), result, nil +} + +// DeleteLaneGroup 删除泳道组 +func (l *laneStore) DeleteLaneGroup(id string) error { + err := l.master.processWithTransaction("DeleteLaneGroup", func(tx *BaseTx) error { + args := []interface{}{ + id, + } + + addSql := "UPDATE lane_rule SET flag = 1, mtime = sysdate() WHERE group_name IN (SELECT name FROM lane_group WHERE id = ?)" + if _, err := tx.Exec(addSql, args...); err != nil { + log.Error("[Store][Lane] delete lane group", zap.String("id", id), zap.Error(err)) + return err + } + + addSql = "UPDATE lane_group SET flag = 1, mtime = sysdate() WHERE id = ?" + if _, err := tx.Exec(addSql, args...); err != nil { + log.Error("[Store][Lane] delete lane group", zap.String("id", id), zap.Error(err)) + return err + } + return tx.Commit() + }) + return store.Error(err) +} + +// getLaneRulesByGroup . +func (l *laneStore) getLaneRulesByGroup(tx *BaseTx, names []string) (map[string]map[string]*model.LaneRule, error) { + if len(names) == 0 { + return map[string]map[string]*model.LaneRule{}, nil + } + + querySql := ` +SELECT id, name, group_name, rule, revision, priority, description, enable, flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(etime), UNIX_TIMESTAMP(mtime) + FROM lane_rule WHERE flag = 0 AND group_name IN (%s) +` + querySql = fmt.Sprintf(querySql, placeholders(len(names))) + + rows, err := tx.Query(querySql, StringsToArgs(names)...) + if err != nil { + log.Error("[Store][Lane] fetch lane group all lane_rules", zap.String("sql", querySql), zap.Error(err)) + return nil, store.Error(err) + } + result := make(map[string]map[string]*model.LaneRule, len(names)) + if err := transferLaneRules(rows, func(rule *model.LaneRule) { + if _, ok := result[rule.LaneGroup]; !ok { + result[rule.LaneGroup] = make(map[string]*model.LaneRule, 32) + } + result[rule.LaneGroup][rule.ID] = rule + }); err != nil { + return nil, store.Error(err) + } + return result, nil +} + +// upsertLaneRules 添加通道规则 +func (l *laneStore) upsertLaneRules(tx *BaseTx, group *model.LaneGroup, items map[string]*model.LaneRule) error { + // 先清理到不再 model.Lane[] 中的泳道规则 + if len(items) > 0 { + // 如果 items.size > 0,只清理不再 items 里面的泳道规则 + args := make([]interface{}, 0, len(items)) + args = append(args, group.Name) + for i := range items { + args = append(args, items[i].Name) + } + + cleanSql := fmt.Sprintf("UPDATE lane_rule SET flag = 1 WHERE group_name = ? AND name NOT IN (%s)", placeholders(len(items))) + if _, err := tx.Exec(cleanSql, args...); err != nil { + log.Error("[Store][Lane] clean invalid lane rule", zap.String("sql", cleanSql), zap.Any("args", args), zap.Error(err)) + return store.Error(err) + } + } else { + // 如果 items.size == 0, 则直接清空所有的泳道规则 + if _, err := tx.Exec("UPDATE lane_rule SET flag = 1 WHERE group_name = ?", group.Name); err != nil { + log.Error("[Store][Lane] clean invalid lane rule", zap.String("group", group.Name), zap.Error(err)) + return store.Error(err) + } + } + + for i := range items { + item := items[i] + var args []interface{} + + var upsertSql string + if item.IsAdd() { + args = []interface{}{ + item.ID, + item.Name, + item.LaneGroup, + item.Rule, + item.Revision, + item.Priority, + item.Description, + item.Enable, + } + addSql := ` +INSERT INTO lane_rule (id, name, group_name, rule, revision, priority, description, enable, flag + , ctime, etime, mtime) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0 + , sysdate(), %s, sysdate()) +` + etimeStr := "sysdate()" + if !item.Enable { + etimeStr = emptyEnableTime + } + upsertSql = fmt.Sprintf(addSql, etimeStr) + } else { + args = []interface{}{ + item.Rule, + item.Revision, + item.Priority, + item.Description, + item.Enable, + item.ID, + } + if item.IsChangeEnable() { + addSql := ` +UPDATE lane_rule SET rule = ?, revision = ?, priority = ?, description = ?, enable = ? + , etime = %s, mtime = sysdate() WHERE id = ? +` + etimeStr := "sysdate()" + if !item.Enable { + etimeStr = emptyEnableTime + } + upsertSql = fmt.Sprintf(addSql, etimeStr) + } else { + upsertSql = ` +UPDATE lane_rule SET rule = ?, revision = ?, priority = ?, description = ?, enable = ? + , mtime = sysdate() WHERE id = ? +` + } + } + if _, err := tx.Exec(upsertSql, args...); err != nil { + log.Error("[Store][Lane] add lane rule", zap.String("id", item.ID), zap.String("sql", upsertSql), + zap.String("group", item.LaneGroup), zap.String("name", item.Name), zap.Error(err)) + return store.Error(err) + } + } + return nil +} + +// GetMoreLaneGroups 获取泳道规则列表到缓存层 +func (l *laneStore) GetMoreLaneGroups(mtime time.Time, firstUpdate bool) (map[string]*model.LaneGroup, error) { + if firstUpdate { + mtime = time.Unix(0, 1) + } + deltaGroupSql := ` +SELECT id, name, rule, description + , revision, flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) +FROM lane_group +WHERE mtime >= FROM_UNIXTIME(?) +` + + deltaRuleSql := ` +SELECT + lr.id, + lr.name, + group_name, + lr.rule, + lr.revision, + lr.priority, + lr.description, + enable, + lr.flag, + UNIX_TIMESTAMP(lr.ctime), + UNIX_TIMESTAMP(lr.etime), + UNIX_TIMESTAMP(lr.mtime) +FROM + lane_rule lr + LEFT JOIN lane_group lg ON lr.group_name = lg.name +WHERE + lg.mtime >= FROM_UNIXTIME(?) +` + deltaGroups := map[string]*model.LaneGroup{} + var deltaRules []*model.LaneRule + + err := l.slave.processWithTransaction("GetMoreLaneGroups", func(tx *BaseTx) error { + rows, err := tx.Query(deltaGroupSql, mtime) + if err != nil { + log.Error("[Store][Lane] delta lane group", zap.String("querySql", deltaGroupSql), zap.Error(err)) + return err + } + if err := transferLaneGroups(rows, func(group *model.LaneGroup) { + group.LaneRules = make(map[string]*model.LaneRule) + deltaGroups[group.Name] = group + }); err != nil { + log.Error("[Store][Lane] transfer lane group row", zap.Error(err)) + return err + } + if len(deltaGroups) > 0 { + // 走 join 操作获取每个 group 下的 lane_rule 列表 + rows, err := tx.Query(deltaRuleSql, mtime) + if err != nil { + log.Error("[Store][Lane] delta lane rule", zap.String("querySql", deltaRuleSql), zap.Error(err)) + return err + } + if err := transferLaneRules(rows, func(rule *model.LaneRule) { + deltaRules = append(deltaRules, rule) + }); err != nil { + log.Error("[Store][Lane] transfer lane rule row", zap.Error(err)) + return err + } + } + return nil + }) + if err != nil { + return nil, store.Error(err) + } + for i := range deltaRules { + item := deltaRules[i] + group, ok := deltaGroups[item.LaneGroup] + if !ok { + continue + } + group.LaneRules[item.ID] = item + } + return deltaGroups, nil +} + +// GetLaneRuleMaxPriority 获取当前泳道规则的最大优先级 ID +func (l *laneStore) GetLaneRuleMaxPriority() (int32, error) { + var maxPriority int32 + err := l.master.processWithTransaction("GetLaneRuleMaxPriority", func(tx *BaseTx) error { + addSql := "SELECT IFNULL(max(priority), 0) FROM lane_rule WHERE flag = 0" + row := tx.QueryRow(addSql) + if err := row.Scan(&maxPriority); err != nil { + log.Error("[Store][Lane] get current lane_rule max priority", zap.Error(err)) + } + return tx.Commit() + }) + return maxPriority, store.Error(err) +} + +// cleanSoftDeletedRules . +func (l *laneStore) cleanSoftDeletedRules() error { + err := l.master.processWithTransaction("cleanSoftDeletedRules", func(tx *BaseTx) error { + if _, err := tx.Exec("DELETE FROM lane_rule WHERE flag = 1"); err != nil { + log.Error("[Store][Lane] clean soft delete lane_rule", zap.Error(err)) + } + return tx.Commit() + }) + return store.Error(err) +} + +func transferLaneGroups(rows *sql.Rows, op func(group *model.LaneGroup)) error { + if rows == nil { + return nil + } + defer func() { + _ = rows.Close() + }() + + for rows.Next() { + item := &model.LaneGroup{} + var ctime, mtime int64 + var flag int + + if err := rows.Scan(&item.ID, &item.Name, &item.Rule, &item.Description, &item.Revision, &flag, &ctime, &mtime); err != nil { + return err + } + item.Valid = flag == 0 + item.CreateTime = time.Unix(ctime, 0) + item.ModifyTime = time.Unix(mtime, 0) + op(item) + } + return nil +} + +func transferLaneRules(rows *sql.Rows, op func(rule *model.LaneRule)) error { + if rows == nil { + return nil + } + defer func() { + _ = rows.Close() + }() + + for rows.Next() { + item := &model.LaneRule{} + var ctime, etime, mtime int64 + var flag, enable int + + if err := rows.Scan(&item.ID, &item.Name, &item.LaneGroup, &item.Rule, &item.Revision, &item.Priority, &item.Description, + &enable, &flag, &ctime, &etime, &mtime); err != nil { + return err + } + item.Valid = flag == 0 + item.Enable = enable == 1 + item.CreateTime = time.Unix(ctime, 0) + item.EnableTime = time.Unix(etime, 0) + item.ModifyTime = time.Unix(mtime, 0) + op(item) + } + + return nil +} diff --git a/store/mysql/scripts/delta/v1_18_0-v1_18_1.sql b/store/mysql/scripts/delta/v1_18_0-v1_18_1.sql new file mode 100644 index 000000000..17d11b466 --- /dev/null +++ b/store/mysql/scripts/delta/v1_18_0-v1_18_1.sql @@ -0,0 +1,71 @@ +/* + * 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. + */ +-- +-- Database: `polaris_server` +-- +USE `polaris_server`; + +-- 服务可见性 +ALTER TABLE service_contract +ADD COLUMN `type` VARCHAR(128) NOT NULL COMMENT '服务契约接口名称'; + +ALTER TABLE service_contract_detail +ADD COLUMN `namespace` VARCHAR(64) NOT NULL COMMENT '命名空间'; + +ALTER TABLE service_contract_detail +ADD COLUMN `service` VARCHAR(128) NOT NULL COMMENT '服务名称'; + +ALTER TABLE service_contract_detail +ADD COLUMN `protocol` VARCHAR(32) NOT NULL COMMENT '当前契约对应的协议信息 e.g. http/dubbo/grpc/thrift'; + +ALTER TABLE service_contract_detail +ADD COLUMN `version` VARCHAR(64) NOT NULL COMMENT '服务契约版本'; + +ALTER TABLE service_contract_detail +ADD COLUMN `type` VARCHAR(128) NOT NULL COMMENT '服务契约接口名称'; + +CREATE TABLE lane_group +( + id varchar(128) not null comment '泳道分组 ID', + name varchar(64) not null comment '泳道分组名称', + rule text not null comment '规则的 json 字符串', + description varchar(3000) comment '规则描述', + revision VARCHAR(40) NOT NULL comment '规则摘要', + flag tinyint default 0 comment '软删除标识位', + ctime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + mtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE = InnoDB; + +CREATE TABLE lane_rule +( + id varchar(128) not null comment '规则 id', + name varchar(64) not null comment '规则名称', + group_name varchar(64) not null comment '泳道分组名称', + rule text not null comment '规则的 json 字符串', + revision VARCHAR(40) NOT NULL comment '规则摘要', + description varchar(3000) comment '规则描述', + enable tinyint comment '是否启用', + flag tinyint default 0 comment '软删除标识位', + priority bigint NOT NULL DEFAULT 0 comment '泳道规则优先级', + ctime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + etime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + mtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`group_name`, `name`) +) ENGINE = InnoDB; diff --git a/store/mysql/scripts/polaris_server.sql b/store/mysql/scripts/polaris_server.sql index 174b6543a..6a076aa74 100644 --- a/store/mysql/scripts/polaris_server.sql +++ b/store/mysql/scripts/polaris_server.sql @@ -14,9 +14,11 @@ * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET + SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET time_zone = "+00:00"; +SET + time_zone = "+00:00"; -- -- Database: `polaris_server` @@ -29,211 +31,224 @@ USE `polaris_server`; -- -- Table structure `instance` -- -CREATE TABLE `instance` -( - `id` VARCHAR(128) NOT NULL COMMENT 'Unique ID', - `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', - `vpc_id` VARCHAR(64) DEFAULT NULL COMMENT 'VPC ID', - `host` VARCHAR(128) NOT NULL COMMENT 'instance Host Information', - `port` INT(11) NOT NULL COMMENT 'instance port information', - `protocol` VARCHAR(32) DEFAULT NULL COMMENT 'Listening protocols for corresponding ports, such as TPC, UDP, GRPC, DUBBO, etc.', - `version` VARCHAR(32) DEFAULT NULL COMMENT 'The version of the instance can be used for version routing', - `health_status` TINYINT(4) NOT NULL DEFAULT '1' COMMENT 'The health status of the instance, 1 is health, 0 is unhealthy', - `isolate` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Example isolation status flag, 0 is not isolated, 1 is isolated', - `weight` SMALLINT(6) NOT NULL DEFAULT '100' COMMENT 'The weight of the instance is mainly used for LoadBalance, default is 100', - `enable_health_check` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether to open a heartbeat on an instance, check the logic, 0 is not open, 1 is open', - `logic_set` VARCHAR(128) DEFAULT NULL COMMENT 'Example logic packet information', - `cmdb_region` VARCHAR(128) DEFAULT NULL COMMENT 'The region information of the instance is mainly used to close the route', - `cmdb_zone` VARCHAR(128) DEFAULT NULL COMMENT 'The ZONE information of the instance is mainly used to close the route.', - `cmdb_idc` VARCHAR(128) DEFAULT NULL COMMENT 'The IDC information of the instance is mainly used to close the route', - `priority` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Example priority, currently useless', - `revision` VARCHAR(32) NOT NULL COMMENT 'Instance version information', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`), - KEY `service_id` (`service_id`), - KEY `mtime` (`mtime`), - KEY `host` (`host`) -) ENGINE = InnoDB; +CREATE TABLE + `instance` ( + `id` VARCHAR(128) NOT NULL COMMENT 'Unique ID', + `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', + `vpc_id` VARCHAR(64) DEFAULT NULL COMMENT 'VPC ID', + `host` VARCHAR(128) NOT NULL COMMENT 'instance Host Information', + `port` INT(11) NOT NULL COMMENT 'instance port information', + `protocol` VARCHAR(32) DEFAULT NULL COMMENT 'Listening protocols for corresponding ports, such as TPC, UDP, GRPC, DUBBO, etc.', + `version` VARCHAR(32) DEFAULT NULL COMMENT 'The version of the instance can be used for version routing', + `health_status` TINYINT(4) NOT NULL DEFAULT '1' COMMENT 'The health status of the instance, 1 is health, 0 is unhealthy', + `isolate` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Example isolation status flag, 0 is not isolated, 1 is isolated', + `weight` SMALLINT(6) NOT NULL DEFAULT '100' COMMENT 'The weight of the instance is mainly used for LoadBalance, default is 100', + `enable_health_check` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether to open a heartbeat on an instance, check the logic, 0 is not open, 1 is open', + `logic_set` VARCHAR(128) DEFAULT NULL COMMENT 'Example logic packet information', + `cmdb_region` VARCHAR(128) DEFAULT NULL COMMENT 'The region information of the instance is mainly used to close the route', + `cmdb_zone` VARCHAR(128) DEFAULT NULL COMMENT 'The ZONE information of the instance is mainly used to close the route.', + `cmdb_idc` VARCHAR(128) DEFAULT NULL COMMENT 'The IDC information of the instance is mainly used to close the route', + `priority` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Example priority, currently useless', + `revision` VARCHAR(32) NOT NULL COMMENT 'Instance version information', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`), + KEY `service_id` (`service_id`), + KEY `mtime` (`mtime`), + KEY `host` (`host`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `health_check` -- -CREATE TABLE `health_check` -( - `id` VARCHAR(128) NOT NULL COMMENT 'Instance ID', - `type` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Instance health check type', - `ttl` INT(11) NOT NULL COMMENT 'TTL time jumping', - PRIMARY KEY (`id`) - /* CONSTRAINT `health_check_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ -) ENGINE = InnoDB; +CREATE TABLE + `health_check` ( + `id` VARCHAR(128) NOT NULL COMMENT 'Instance ID', + `type` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Instance health check type', + `ttl` INT(11) NOT NULL COMMENT 'TTL time jumping', + PRIMARY KEY (`id`) + /* CONSTRAINT `health_check_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `instance_metadata` -- -CREATE TABLE `instance_metadata` -( - `id` VARCHAR(128) NOT NULL COMMENT 'Instance ID', - `mkey` VARCHAR(128) NOT NULL COMMENT 'instance label of Key', - `mvalue` VARCHAR(4096) NOT NULL COMMENT 'instance label Value', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`, `mkey`), - KEY `mkey` (`mkey`) - /* CONSTRAINT `instance_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ -) ENGINE = InnoDB; +CREATE TABLE + `instance_metadata` ( + `id` VARCHAR(128) NOT NULL COMMENT 'Instance ID', + `mkey` VARCHAR(128) NOT NULL COMMENT 'instance label of Key', + `mvalue` VARCHAR(4096) NOT NULL COMMENT 'instance label Value', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`, `mkey`), + KEY `mkey` (`mkey`) + /* CONSTRAINT `instance_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `namespace` -- -CREATE TABLE `namespace` -( - `name` VARCHAR(64) NOT NULL COMMENT 'Namespace name, unique', - `comment` VARCHAR(1024) DEFAULT NULL COMMENT 'Description of namespace', - `token` VARCHAR(64) NOT NULL COMMENT 'TOKEN named space for write operation check', - `owner` VARCHAR(1024) NOT NULL COMMENT 'Responsible for named space Owner', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - `service_export_to` TEXT COMMENT 'namespace metadata', - `metadata` TEXT COMMENT 'namespace metadata', - PRIMARY KEY (`name`) -) ENGINE = InnoDB; +CREATE TABLE + `namespace` ( + `name` VARCHAR(64) NOT NULL COMMENT 'Namespace name, unique', + `comment` VARCHAR(1024) DEFAULT NULL COMMENT 'Description of namespace', + `token` VARCHAR(64) NOT NULL COMMENT 'TOKEN named space for write operation check', + `owner` VARCHAR(1024) NOT NULL COMMENT 'Responsible for named space Owner', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + `service_export_to` TEXT COMMENT 'namespace metadata', + `metadata` TEXT COMMENT 'namespace metadata', + PRIMARY KEY (`name`) + ) ENGINE = InnoDB; -- -- Data in the conveyor `namespace` -- -INSERT INTO `namespace` (`name`, - `comment`, - `token`, - `owner`, - `flag`, - `ctime`, - `mtime`) -VALUES ('Polaris', +INSERT INTO + `namespace` ( + `name`, + `comment`, + `token`, + `owner`, + `flag`, + `ctime`, + `mtime` + ) +VALUES + ( + 'Polaris', 'Polaris-server', '2d1bfe5d12e04d54b8ee69e62494c7fd', 'polaris', 0, '2019-09-06 07:55:07', - '2019-09-06 07:55:07'), - ('default', + '2019-09-06 07:55:07' + ), + ( + 'default', 'Default Environment', 'e2e473081d3d4306b52264e49f7ce227', 'polaris', 0, '2021-07-27 19:37:37', - '2021-07-27 19:37:37'); + '2021-07-27 19:37:37' + ); -- -------------------------------------------------------- -- -- Table structure `routing_config` -- -CREATE TABLE `routing_config` -( - `id` VARCHAR(32) NOT NULL COMMENT 'Routing configuration ID', - `in_bounds` TEXT COMMENT 'Service is routing rules', - `out_bounds` TEXT COMMENT 'Service main routing rules', - `revision` VARCHAR(40) NOT NULL COMMENT 'Routing rule version', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; +CREATE TABLE + `routing_config` ( + `id` VARCHAR(32) NOT NULL COMMENT 'Routing configuration ID', + `in_bounds` TEXT COMMENT 'Service is routing rules', + `out_bounds` TEXT COMMENT 'Service main routing rules', + `revision` VARCHAR(40) NOT NULL COMMENT 'Routing rule version', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `ratelimit_config` -- -CREATE TABLE `ratelimit_config` -( - `id` VARCHAR(32) NOT NULL COMMENT 'ratelimit rule ID', - `name` VARCHAR(64) NOT NULL COMMENT 'ratelimt rule name', - `disable` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'ratelimit disable', - `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', - `method` VARCHAR(512) NOT NULL COMMENT 'ratelimit method', - `labels` TEXT NOT NULL COMMENT 'Conductive flow for a specific label', - `priority` SMALLINT(6) NOT NULL DEFAULT '0' COMMENT 'ratelimit rule priority', - `rule` TEXT NOT NULL COMMENT 'Current limiting rules', - `revision` VARCHAR(32) NOT NULL COMMENT 'Limiting version', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - `etime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'RateLimit rule enable time', - PRIMARY KEY (`id`), - KEY `mtime` (`mtime`), - KEY `service_id` (`service_id`) -) ENGINE = InnoDB; +CREATE TABLE + `ratelimit_config` ( + `id` VARCHAR(32) NOT NULL COMMENT 'ratelimit rule ID', + `name` VARCHAR(64) NOT NULL COMMENT 'ratelimt rule name', + `disable` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'ratelimit disable', + `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', + `method` VARCHAR(512) NOT NULL COMMENT 'ratelimit method', + `labels` TEXT NOT NULL COMMENT 'Conductive flow for a specific label', + `priority` SMALLINT(6) NOT NULL DEFAULT '0' COMMENT 'ratelimit rule priority', + `rule` TEXT NOT NULL COMMENT 'Current limiting rules', + `revision` VARCHAR(32) NOT NULL COMMENT 'Limiting version', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + `etime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'RateLimit rule enable time', + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`), + KEY `service_id` (`service_id`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `ratelimit_revision` -- -CREATE TABLE `ratelimit_revision` -( - `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', - `last_revision` VARCHAR(40) NOT NULL COMMENT 'The latest limited limiting rule version of the corresponding service', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`service_id`), - KEY `service_id` (`service_id`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; +CREATE TABLE + `ratelimit_revision` ( + `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', + `last_revision` VARCHAR(40) NOT NULL COMMENT 'The latest limited limiting rule version of the corresponding service', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`service_id`), + KEY `service_id` (`service_id`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `service` -- -CREATE TABLE `service` -( - `id` VARCHAR(32) NOT NULL COMMENT 'Service ID', - `name` VARCHAR(128) NOT NULL COMMENT 'Service name, only under the namespace', - `namespace` VARCHAR(64) NOT NULL COMMENT 'Namespace belongs to the service', - `ports` TEXT DEFAULT NULL COMMENT 'Service will have a list of all port information of the external exposure (single process exposing multiple protocols)', - `business` VARCHAR(64) DEFAULT NULL COMMENT 'Service business information', - `department` VARCHAR(1024) DEFAULT NULL COMMENT 'Service department information', - `cmdb_mod1` VARCHAR(1024) DEFAULT NULL COMMENT '', - `cmdb_mod2` VARCHAR(1024) DEFAULT NULL COMMENT '', - `cmdb_mod3` VARCHAR(1024) DEFAULT NULL COMMENT '', - `comment` VARCHAR(1024) DEFAULT NULL COMMENT 'Description information', - `token` VARCHAR(2048) NOT NULL COMMENT 'Service token, used to handle all the services involved in the service', - `revision` VARCHAR(32) NOT NULL COMMENT 'Service version information', - `owner` VARCHAR(1024) NOT NULL COMMENT 'Owner information belonging to the service', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `reference` VARCHAR(32) DEFAULT NULL COMMENT 'Service alias, what is the actual service name that the service is actually pointed out?', - `refer_filter` VARCHAR(1024) DEFAULT NULL COMMENT '', - `platform_id` VARCHAR(32) DEFAULT '' COMMENT 'The platform ID to which the service belongs', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - `export_to` TEXT COMMENT 'service export to some namespace', - PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`, `namespace`), - KEY `namespace` (`namespace`), - KEY `mtime` (`mtime`), - KEY `reference` (`reference`), - KEY `platform_id` (`platform_id`) -) ENGINE = InnoDB; +CREATE TABLE + `service` ( + `id` VARCHAR(32) NOT NULL COMMENT 'Service ID', + `name` VARCHAR(128) NOT NULL COMMENT 'Service name, only under the namespace', + `namespace` VARCHAR(64) NOT NULL COMMENT 'Namespace belongs to the service', + `ports` TEXT DEFAULT NULL COMMENT 'Service will have a list of all port information of the external exposure (single process exposing multiple protocols)', + `business` VARCHAR(64) DEFAULT NULL COMMENT 'Service business information', + `department` VARCHAR(1024) DEFAULT NULL COMMENT 'Service department information', + `cmdb_mod1` VARCHAR(1024) DEFAULT NULL COMMENT '', + `cmdb_mod2` VARCHAR(1024) DEFAULT NULL COMMENT '', + `cmdb_mod3` VARCHAR(1024) DEFAULT NULL COMMENT '', + `comment` VARCHAR(1024) DEFAULT NULL COMMENT 'Description information', + `token` VARCHAR(2048) NOT NULL COMMENT 'Service token, used to handle all the services involved in the service', + `revision` VARCHAR(32) NOT NULL COMMENT 'Service version information', + `owner` VARCHAR(1024) NOT NULL COMMENT 'Owner information belonging to the service', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `reference` VARCHAR(32) DEFAULT NULL COMMENT 'Service alias, what is the actual service name that the service is actually pointed out?', + `refer_filter` VARCHAR(1024) DEFAULT NULL COMMENT '', + `platform_id` VARCHAR(32) DEFAULT '' COMMENT 'The platform ID to which the service belongs', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + `export_to` TEXT COMMENT 'service export to some namespace', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`, `namespace`), + KEY `namespace` (`namespace`), + KEY `mtime` (`mtime`), + KEY `reference` (`reference`), + KEY `platform_id` (`platform_id`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Data in the conveyor `service` -- -INSERT INTO `service` (`id`, - `name`, - `namespace`, - `comment`, - `business`, - `token`, - `revision`, - `owner`, - `flag`, - `ctime`, - `mtime`) -VALUES ('fbca9bfa04ae4ead86e1ecf5811e32a9', +INSERT INTO + `service` ( + `id`, + `name`, + `namespace`, + `comment`, + `business`, + `token`, + `revision`, + `owner`, + `flag`, + `ctime`, + `mtime` + ) +VALUES + ( + 'fbca9bfa04ae4ead86e1ecf5811e32a9', 'polaris.checker', 'Polaris', 'polaris checker service', @@ -243,417 +258,421 @@ VALUES ('fbca9bfa04ae4ead86e1ecf5811e32a9', 'polaris', 0, '2021-09-06 07:55:07', - '2021-09-06 07:55:09'); + '2021-09-06 07:55:09' + ); -- -------------------------------------------------------- -- -- Table structure `service_metadata` -- -CREATE TABLE `service_metadata` -( - `id` VARCHAR(32) NOT NULL COMMENT 'Service ID', - `mkey` VARCHAR(128) NOT NULL COMMENT 'Service label key', - `mvalue` VARCHAR(4096) NOT NULL COMMENT 'Service label Value', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`, `mkey`), - KEY `mkey` (`mkey`) - /* CONSTRAINT `service_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ -) ENGINE = InnoDB; +CREATE TABLE + `service_metadata` ( + `id` VARCHAR(32) NOT NULL COMMENT 'Service ID', + `mkey` VARCHAR(128) NOT NULL COMMENT 'Service label key', + `mvalue` VARCHAR(4096) NOT NULL COMMENT 'Service label Value', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`, `mkey`), + KEY `mkey` (`mkey`) + /* CONSTRAINT `service_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `owner_service_map`Quickly query all services under an Owner -- CREATE TABLE - `owner_service_map` -( - `id` VARCHAR(32) NOT NULL COMMENT '', - `owner` VARCHAR(32) NOT NULL COMMENT 'Service Owner', - `service` VARCHAR(128) NOT NULL COMMENT 'service name', - `namespace` VARCHAR(64) NOT NULL COMMENT 'namespace name', - PRIMARY KEY (`id`), - KEY `owner` (`owner`), - KEY `name` (`service`, `namespace`) -) ENGINE = InnoDB; + `owner_service_map` ( + `id` VARCHAR(32) NOT NULL COMMENT '', + `owner` VARCHAR(32) NOT NULL COMMENT 'Service Owner', + `service` VARCHAR(128) NOT NULL COMMENT 'service name', + `namespace` VARCHAR(64) NOT NULL COMMENT 'namespace name', + PRIMARY KEY (`id`), + KEY `owner` (`owner`), + KEY `name` (`service`, `namespace`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `circuitbreaker_rule` -- -CREATE TABLE `circuitbreaker_rule` -( - `id` VARCHAR(97) NOT NULL COMMENT 'Melting rule ID', - `version` VARCHAR(32) NOT NULL DEFAULT 'master' COMMENT 'Melting rule version, default is MASTR', - `name` VARCHAR(128) NOT NULL COMMENT 'Melting rule name', - `namespace` VARCHAR(64) NOT NULL COMMENT 'Melting rule belongs to name space', - `business` VARCHAR(64) DEFAULT NULL COMMENT 'Business information of fuse regular', - `department` VARCHAR(1024) DEFAULT NULL COMMENT 'Department information to which the fuse regular belongs', - `comment` VARCHAR(1024) DEFAULT NULL COMMENT 'Description of the fuse rule', - `inbounds` TEXT NOT NULL COMMENT 'Service-tuned fuse rule', - `outbounds` TEXT NOT NULL COMMENT 'Service Motoring Fuse Rule', - `token` VARCHAR(32) NOT NULL COMMENT 'Token, which is fucking, mainly for writing operation check', - `owner` VARCHAR(1024) NOT NULL COMMENT 'Melting rule Owner information', - `revision` VARCHAR(32) NOT NULL COMMENT 'Melt rule version information', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`, `version`), - UNIQUE KEY `name` (`name`, `namespace`, `version`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; +CREATE TABLE + `circuitbreaker_rule` ( + `id` VARCHAR(97) NOT NULL COMMENT 'Melting rule ID', + `version` VARCHAR(32) NOT NULL DEFAULT 'master' COMMENT 'Melting rule version, default is MASTR', + `name` VARCHAR(128) NOT NULL COMMENT 'Melting rule name', + `namespace` VARCHAR(64) NOT NULL COMMENT 'Melting rule belongs to name space', + `business` VARCHAR(64) DEFAULT NULL COMMENT 'Business information of fuse regular', + `department` VARCHAR(1024) DEFAULT NULL COMMENT 'Department information to which the fuse regular belongs', + `comment` VARCHAR(1024) DEFAULT NULL COMMENT 'Description of the fuse rule', + `inbounds` TEXT NOT NULL COMMENT 'Service-tuned fuse rule', + `outbounds` TEXT NOT NULL COMMENT 'Service Motoring Fuse Rule', + `token` VARCHAR(32) NOT NULL COMMENT 'Token, which is fucking, mainly for writing operation check', + `owner` VARCHAR(1024) NOT NULL COMMENT 'Melting rule Owner information', + `revision` VARCHAR(32) NOT NULL COMMENT 'Melt rule version information', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`, `version`), + UNIQUE KEY `name` (`name`, `namespace`, `version`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `circuitbreaker_rule_relation` -- -CREATE TABLE `circuitbreaker_rule_relation` -( - `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', - `rule_id` VARCHAR(97) NOT NULL COMMENT 'Melting rule ID', - `rule_version` VARCHAR(32) NOT NULL COMMENT 'Melting rule version', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`service_id`), - KEY `mtime` (`mtime`), - KEY `rule_id` (`rule_id`) - /* CONSTRAINT `circuitbreaker_rule_relation_ibfk_1` FOREIGN KEY (`service_id`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ -) ENGINE = InnoDB; +CREATE TABLE + `circuitbreaker_rule_relation` ( + `service_id` VARCHAR(32) NOT NULL COMMENT 'Service ID', + `rule_id` VARCHAR(97) NOT NULL COMMENT 'Melting rule ID', + `rule_version` VARCHAR(32) NOT NULL COMMENT 'Melting rule version', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Logic delete flag, 0 means visible, 1 means that it has been logically deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`service_id`), + KEY `mtime` (`mtime`), + KEY `rule_id` (`rule_id`) + /* CONSTRAINT `circuitbreaker_rule_relation_ibfk_1` FOREIGN KEY (`service_id`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE */ + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `t_ip_config` -- -CREATE TABLE `t_ip_config` -( - `Fip` INT(10) UNSIGNED NOT NULL COMMENT 'Machine IP', - `FareaId` INT(10) UNSIGNED NOT NULL COMMENT 'Area number', - `FcityId` INT(10) UNSIGNED NOT NULL COMMENT 'City number', - `FidcId` INT(10) UNSIGNED NOT NULL COMMENT 'IDC number', - `Fflag` TINYINT(4) DEFAULT '0', - `Fstamp` DATETIME NOT NULL, - `Fflow` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`Fip`), - KEY `idx_Fflow` (`Fflow`) -) ENGINE = InnoDB; +CREATE TABLE + `t_ip_config` ( + `Fip` INT(10) UNSIGNED NOT NULL COMMENT 'Machine IP', + `FareaId` INT(10) UNSIGNED NOT NULL COMMENT 'Area number', + `FcityId` INT(10) UNSIGNED NOT NULL COMMENT 'City number', + `FidcId` INT(10) UNSIGNED NOT NULL COMMENT 'IDC number', + `Fflag` TINYINT(4) DEFAULT '0', + `Fstamp` DATETIME NOT NULL, + `Fflow` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY (`Fip`), + KEY `idx_Fflow` (`Fflow`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `t_policy` -- -CREATE TABLE `t_policy` -( - `FmodId` INT(10) UNSIGNED NOT NULL, - `Fdiv` INT(10) UNSIGNED NOT NULL, - `Fmod` INT(10) UNSIGNED NOT NULL, - `Fflag` TINYINT(4) DEFAULT '0', - `Fstamp` DATETIME NOT NULL, - `Fflow` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`FmodId`) -) ENGINE = InnoDB; +CREATE TABLE + `t_policy` ( + `FmodId` INT(10) UNSIGNED NOT NULL, + `Fdiv` INT(10) UNSIGNED NOT NULL, + `Fmod` INT(10) UNSIGNED NOT NULL, + `Fflag` TINYINT(4) DEFAULT '0', + `Fstamp` DATETIME NOT NULL, + `Fflow` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY (`FmodId`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `t_route` -- -CREATE TABLE `t_route` -( - `Fip` INT(10) UNSIGNED NOT NULL, - `FmodId` INT(10) UNSIGNED NOT NULL, - `FcmdId` INT(10) UNSIGNED NOT NULL, - `FsetId` VARCHAR(32) NOT NULL, - `Fflag` TINYINT(4) DEFAULT '0', - `Fstamp` DATETIME NOT NULL, - `Fflow` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`Fip`, `FmodId`, `FcmdId`), - KEY `Fflow` (`Fflow`), - KEY `idx1` (`FmodId`, `FcmdId`, `FsetId`) -) ENGINE = InnoDB; +CREATE TABLE + `t_route` ( + `Fip` INT(10) UNSIGNED NOT NULL, + `FmodId` INT(10) UNSIGNED NOT NULL, + `FcmdId` INT(10) UNSIGNED NOT NULL, + `FsetId` VARCHAR(32) NOT NULL, + `Fflag` TINYINT(4) DEFAULT '0', + `Fstamp` DATETIME NOT NULL, + `Fflow` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY (`Fip`, `FmodId`, `FcmdId`), + KEY `Fflow` (`Fflow`), + KEY `idx1` (`FmodId`, `FcmdId`, `FsetId`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `t_section` -- -CREATE TABLE `t_section` -( - `FmodId` INT(10) UNSIGNED NOT NULL, - `Ffrom` INT(10) UNSIGNED NOT NULL, - `Fto` INT(10) UNSIGNED NOT NULL, - `Fxid` INT(10) UNSIGNED NOT NULL, - `Fflag` TINYINT(4) DEFAULT '0', - `Fstamp` DATETIME NOT NULL, - `Fflow` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`FmodId`, `Ffrom`, `Fto`) -) ENGINE = InnoDB; +CREATE TABLE + `t_section` ( + `FmodId` INT(10) UNSIGNED NOT NULL, + `Ffrom` INT(10) UNSIGNED NOT NULL, + `Fto` INT(10) UNSIGNED NOT NULL, + `Fxid` INT(10) UNSIGNED NOT NULL, + `Fflag` TINYINT(4) DEFAULT '0', + `Fstamp` DATETIME NOT NULL, + `Fflow` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY (`FmodId`, `Ffrom`, `Fto`) + ) ENGINE = InnoDB; -- -------------------------------------------------------- -- -- Table structure `start_lock` -- -CREATE TABLE `start_lock` -( - `lock_id` INT(11) NOT NULL COMMENT '锁序号', - `lock_key` VARCHAR(32) NOT NULL COMMENT 'Lock name', - `server` VARCHAR(32) NOT NULL COMMENT 'SERVER holding launch lock', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time', - PRIMARY KEY (`lock_id`, `lock_key`) -) ENGINE = InnoDB; +CREATE TABLE + `start_lock` ( + `lock_id` INT(11) NOT NULL COMMENT '锁序号', + `lock_key` VARCHAR(32) NOT NULL COMMENT 'Lock name', + `server` VARCHAR(32) NOT NULL COMMENT 'SERVER holding launch lock', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time', + PRIMARY KEY (`lock_id`, `lock_key`) + ) ENGINE = InnoDB; -- -- Data in the conveyor `start_lock` -- -INSERT INTO `start_lock` (`lock_id`, `lock_key`, `server`, `mtime`) -VALUES (1, 'sz', 'aaa', '2019-12-05 08:35:49'); +INSERT INTO + `start_lock` (`lock_id`, `lock_key`, `server`, `mtime`) +VALUES + (1, 'sz', 'aaa', '2019-12-05 08:35:49'); -- -------------------------------------------------------- -- -- Table structure `cl5_module` -- -CREATE TABLE `cl5_module` -( - `module_id` INT(11) NOT NULL COMMENT 'Module ID', - `interface_id` INT(11) NOT NULL COMMENT 'Interface ID', - `range_num` INT(11) NOT NULL, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`module_id`) -) ENGINE = InnoDB COMMENT = 'To generate SID'; +CREATE TABLE + `cl5_module` ( + `module_id` INT(11) NOT NULL COMMENT 'Module ID', + `interface_id` INT(11) NOT NULL COMMENT 'Interface ID', + `range_num` INT(11) NOT NULL, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`module_id`) + ) ENGINE = InnoDB COMMENT = 'To generate SID'; -- -- Data in the conveyor `cl5_module` -- -INSERT INTO cl5_module (module_id, interface_id, range_num) -VALUES (3000001, 1, 0); +INSERT INTO + cl5_module (module_id, interface_id, range_num) +VALUES + (3000001, 1, 0); -- -------------------------------------------------------- -- -- Table structure `config_file` -- -CREATE TABLE `config_file` -( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', - `group` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属的文件组', - `name` VARCHAR(128) NOT NULL COMMENT '配置文件名', - `content` LONGTEXT NOT NULL COMMENT '文件内容', - `format` VARCHAR(16) DEFAULT 'text' COMMENT '文件格式,枚举值', - `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', - `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 '最后更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_file` (`namespace`, `group`, `name`) -) ENGINE = InnoDB - AUTO_INCREMENT = 1 COMMENT = '配置文件表'; +CREATE TABLE + `config_file` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', + `group` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属的文件组', + `name` VARCHAR(128) NOT NULL COMMENT '配置文件名', + `content` LONGTEXT NOT NULL COMMENT '文件内容', + `format` VARCHAR(16) DEFAULT 'text' COMMENT '文件格式,枚举值', + `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', + `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 '最后更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_file` (`namespace`, `group`, `name`) + ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '配置文件表'; -- -------------------------------------------------------- -- -- Table structure `config_file_group` -- -CREATE TABLE `config_file_group` -( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `name` VARCHAR(128) NOT NULL COMMENT '配置文件分组名', - `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', - `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', - `owner` VARCHAR(1024) DEFAULT NULL 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 '最后更新人', - `business` VARCHAR(64) DEFAULT NULL COMMENT 'Service business information', - `department` VARCHAR(1024) DEFAULT NULL COMMENT 'Service department information', - `metadata` TEXT COMMENT '配置分组标签', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否被删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_name` (`namespace`, `name`) -) ENGINE = InnoDB - AUTO_INCREMENT = 1 COMMENT = '配置文件组表'; +CREATE TABLE + `config_file_group` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(128) NOT NULL COMMENT '配置文件分组名', + `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', + `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', + `owner` VARCHAR(1024) DEFAULT NULL 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 '最后更新人', + `business` VARCHAR(64) DEFAULT NULL COMMENT 'Service business information', + `department` VARCHAR(1024) DEFAULT NULL COMMENT 'Service department information', + `metadata` TEXT COMMENT '配置分组标签', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否被删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_name` (`namespace`, `name`) + ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '配置文件组表'; -- -------------------------------------------------------- -- -- Table structure `config_file_release` -- -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 '发布描述', - `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 = '配置文件发布表'; - +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 '发布描述', + `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` -- -CREATE TABLE `config_file_release_history` -( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `name` VARCHAR(64) DEFAULT '' COMMENT '发布名称', - `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', - `group` VARCHAR(128) NOT NULL COMMENT '所属的文件组', - `file_name` VARCHAR(128) NOT NULL COMMENT '配置文件名', - `content` LONGTEXT NOT NULL COMMENT '文件内容', - `format` VARCHAR(16) DEFAULT 'text' COMMENT '文件格式', - `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', - `md5` VARCHAR(128) NOT NULL COMMENT 'content的md5值', - `type` VARCHAR(32) NOT NULL COMMENT '发布类型,例如全量发布、灰度发布', - `status` VARCHAR(16) NOT NULL DEFAULT 'success' COMMENT '发布状态,success表示成功,fail 表示失败', - `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 '文件标签', - `version` BIGINT(11) COMMENT '版本号,每次发布自增1', - `reason` VARCHAR(3000) DEFAULT '' COMMENT '原因', - `description` VARCHAR(512) DEFAULT NULL COMMENT '发布描述', - PRIMARY KEY (`id`), - KEY `idx_file` (`namespace`, `group`, `file_name`) -) ENGINE = InnoDB - AUTO_INCREMENT = 1 COMMENT = '配置文件发布历史表'; +CREATE TABLE + `config_file_release_history` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(64) DEFAULT '' COMMENT '发布名称', + `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', + `group` VARCHAR(128) NOT NULL COMMENT '所属的文件组', + `file_name` VARCHAR(128) NOT NULL COMMENT '配置文件名', + `content` LONGTEXT NOT NULL COMMENT '文件内容', + `format` VARCHAR(16) DEFAULT 'text' COMMENT '文件格式', + `comment` VARCHAR(512) DEFAULT NULL COMMENT '备注信息', + `md5` VARCHAR(128) NOT NULL COMMENT 'content的md5值', + `type` VARCHAR(32) NOT NULL COMMENT '发布类型,例如全量发布、灰度发布', + `status` VARCHAR(16) NOT NULL DEFAULT 'success' COMMENT '发布状态,success表示成功,fail 表示失败', + `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 '文件标签', + `version` BIGINT(11) COMMENT '版本号,每次发布自增1', + `reason` VARCHAR(3000) DEFAULT '' COMMENT '原因', + `description` VARCHAR(512) DEFAULT NULL COMMENT '发布描述', + PRIMARY KEY (`id`), + KEY `idx_file` (`namespace`, `group`, `file_name`) + ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '配置文件发布历史表'; -- -------------------------------------------------------- -- -- Table structure `config_file_tag` -- -CREATE TABLE `config_file_tag` -( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `key` VARCHAR(128) NOT NULL COMMENT 'tag 的键', - `Value` VARCHAR(128) NOT NULL COMMENT 'tag 的值', - `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', - `group` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属的文件组', - `file_name` VARCHAR(128) NOT NULL 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 '最后更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tag` (`key`, `Value`, `namespace`, `group`, `file_name`), - KEY `idx_file` (`namespace`, `group`, `file_name`) -) ENGINE = InnoDB COMMENT = '配置文件标签表'; - -CREATE TABLE `user` -( - `id` VARCHAR(128) NOT NULL COMMENT 'User ID', - `name` VARCHAR(100) NOT NULL COMMENT 'user name', - `password` VARCHAR(100) NOT NULL COMMENT 'user password', - `owner` VARCHAR(128) NOT NULL COMMENT 'Main account ID', - `source` VARCHAR(32) NOT NULL COMMENT 'Account source', - `mobile` VARCHAR(12) NOT NULL DEFAULT '' COMMENT 'Account mobile phone number', - `email` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'Account mailbox', - `token` VARCHAR(255) NOT NULL COMMENT 'The token information owned by the account can be used for SDK access authentication', - `token_enable` TINYINT(4) NOT NULL DEFAULT 1, - `user_type` INT NOT NULL DEFAULT 20 COMMENT 'Account type, 0 is the admin super account, 20 is the primary account, 50 for the child account', - `comment` VARCHAR(255) NOT NULL COMMENT 'describe', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether the rules are valid, 0 is valid, 1 is invalid, it is deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`), - UNIQUE KEY (`name`, `owner`), - KEY `owner` (`owner`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; - -CREATE TABLE `user_group` -( - `id` VARCHAR(128) NOT NULL COMMENT 'User group ID', - `name` VARCHAR(100) NOT NULL COMMENT 'User group name', - `owner` VARCHAR(128) NOT NULL COMMENT 'The main account ID of the user group', - `token` VARCHAR(255) NOT NULL COMMENT 'TOKEN information of this user group', - `comment` VARCHAR(255) NOT NULL COMMENT 'Description', - `token_enable` TINYINT(4) NOT NULL DEFAULT 1, - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether the rules are valid, 0 is valid, 1 is invalid, it is deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`), - UNIQUE KEY (`name`, `owner`), - KEY `owner` (`owner`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; - -CREATE TABLE `user_group_relation` -( - `user_id` VARCHAR(128) NOT NULL COMMENT 'User ID', - `group_id` VARCHAR(128) NOT NULL COMMENT 'User group ID', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`user_id`, `group_id`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; - -CREATE TABLE `auth_strategy` -( - `id` VARCHAR(128) NOT NULL COMMENT 'Strategy ID', - `name` VARCHAR(100) NOT NULL COMMENT 'Policy name', - `action` VARCHAR(32) NOT NULL COMMENT 'Read and write permission for this policy, only_read = 0, read_write = 1', - `owner` VARCHAR(128) NOT NULL COMMENT 'The account ID to which this policy is', - `comment` VARCHAR(255) NOT NULL COMMENT 'describe', - `default` TINYINT(4) NOT NULL DEFAULT '0', - `revision` VARCHAR(128) NOT NULL COMMENT 'Authentication rule version', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether the rules are valid, 0 is valid, 1 is invalid, it is deleted', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`id`), - UNIQUE KEY (`name`, `owner`), - KEY `owner` (`owner`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; - -CREATE TABLE `auth_principal` -( - `strategy_id` VARCHAR(128) NOT NULL COMMENT 'Strategy ID', - `principal_id` VARCHAR(128) NOT NULL COMMENT 'Principal ID', - `principal_role` INT NOT NULL COMMENT 'PRINCIPAL type, 1 is User, 2 is Group', - PRIMARY KEY (`strategy_id`, `principal_id`, `principal_role`) -) ENGINE = InnoDB; - -CREATE TABLE `auth_strategy_resource` -( - `strategy_id` VARCHAR(128) NOT NULL COMMENT 'Strategy ID', - `res_type` INT NOT NULL COMMENT 'Resource Type, Namespaces = 0, Service = 1, configgroups = 2', - `res_id` VARCHAR(128) NOT NULL COMMENT 'Resource ID', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', - PRIMARY KEY (`strategy_id`, `res_type`, `res_id`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; +CREATE TABLE + `config_file_tag` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `key` VARCHAR(128) NOT NULL COMMENT 'tag 的键', + `Value` VARCHAR(128) NOT NULL COMMENT 'tag 的值', + `namespace` VARCHAR(64) NOT NULL COMMENT '所属的namespace', + `group` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属的文件组', + `file_name` VARCHAR(128) NOT NULL 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 '最后更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tag` (`key`, `Value`, `namespace`, `group`, `file_name`), + KEY `idx_file` (`namespace`, `group`, `file_name`) + ) ENGINE = InnoDB COMMENT = '配置文件标签表'; + +CREATE TABLE + `user` ( + `id` VARCHAR(128) NOT NULL COMMENT 'User ID', + `name` VARCHAR(100) NOT NULL COMMENT 'user name', + `password` VARCHAR(100) NOT NULL COMMENT 'user password', + `owner` VARCHAR(128) NOT NULL COMMENT 'Main account ID', + `source` VARCHAR(32) NOT NULL COMMENT 'Account source', + `mobile` VARCHAR(12) NOT NULL DEFAULT '' COMMENT 'Account mobile phone number', + `email` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'Account mailbox', + `token` VARCHAR(255) NOT NULL COMMENT 'The token information owned by the account can be used for SDK access authentication', + `token_enable` TINYINT(4) NOT NULL DEFAULT 1, + `user_type` INT NOT NULL DEFAULT 20 COMMENT 'Account type, 0 is the admin super account, 20 is the primary account, 50 for the child account', + `comment` VARCHAR(255) NOT NULL COMMENT 'describe', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether the rules are valid, 0 is valid, 1 is invalid, it is deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`, `owner`), + KEY `owner` (`owner`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; + +CREATE TABLE + `user_group` ( + `id` VARCHAR(128) NOT NULL COMMENT 'User group ID', + `name` VARCHAR(100) NOT NULL COMMENT 'User group name', + `owner` VARCHAR(128) NOT NULL COMMENT 'The main account ID of the user group', + `token` VARCHAR(255) NOT NULL COMMENT 'TOKEN information of this user group', + `comment` VARCHAR(255) NOT NULL COMMENT 'Description', + `token_enable` TINYINT(4) NOT NULL DEFAULT 1, + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether the rules are valid, 0 is valid, 1 is invalid, it is deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`, `owner`), + KEY `owner` (`owner`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; + +CREATE TABLE + `user_group_relation` ( + `user_id` VARCHAR(128) NOT NULL COMMENT 'User ID', + `group_id` VARCHAR(128) NOT NULL COMMENT 'User group ID', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`user_id`, `group_id`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; + +CREATE TABLE + `auth_strategy` ( + `id` VARCHAR(128) NOT NULL COMMENT 'Strategy ID', + `name` VARCHAR(100) NOT NULL COMMENT 'Policy name', + `action` VARCHAR(32) NOT NULL COMMENT 'Read and write permission for this policy, only_read = 0, read_write = 1', + `owner` VARCHAR(128) NOT NULL COMMENT 'The account ID to which this policy is', + `comment` VARCHAR(255) NOT NULL COMMENT 'describe', + `default` TINYINT(4) NOT NULL DEFAULT '0', + `revision` VARCHAR(128) NOT NULL COMMENT 'Authentication rule version', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Whether the rules are valid, 0 is valid, 1 is invalid, it is deleted', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`, `owner`), + KEY `owner` (`owner`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; + +CREATE TABLE + `auth_principal` ( + `strategy_id` VARCHAR(128) NOT NULL COMMENT 'Strategy ID', + `principal_id` VARCHAR(128) NOT NULL COMMENT 'Principal ID', + `principal_role` INT NOT NULL COMMENT 'PRINCIPAL type, 1 is User, 2 is Group', + PRIMARY KEY (`strategy_id`, `principal_id`, `principal_role`) + ) ENGINE = InnoDB; + +CREATE TABLE + `auth_strategy_resource` ( + `strategy_id` VARCHAR(128) NOT NULL COMMENT 'Strategy ID', + `res_type` INT NOT NULL COMMENT 'Resource Type, Namespaces = 0, Service = 1, configgroups = 2', + `res_id` VARCHAR(128) NOT NULL COMMENT 'Resource ID', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last updated time', + PRIMARY KEY (`strategy_id`, `res_type`, `res_id`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; -- Create a default master account, password is Polarismesh @ 2021 -INSERT INTO `user` (`id`, - `name`, - `password`, - `source`, - `token`, - `token_enable`, - `user_type`, - `comment`, - `mobile`, - `email`, - `owner`) -VALUES ('65e4789a6d5b49669adf1e9e8387549c', +INSERT INTO + `user` ( + `id`, + `name`, + `password`, + `source`, + `token`, + `token_enable`, + `user_type`, + `comment`, + `mobile`, + `email`, + `owner` + ) +VALUES + ( + '65e4789a6d5b49669adf1e9e8387549c', 'polaris', '$2a$10$3izWuZtE5SBdAtSZci.gs.iZ2pAn9I8hEqYrC6gwJp1dyjqQnrrum', 'Polaris', @@ -663,20 +682,26 @@ VALUES ('65e4789a6d5b49669adf1e9e8387549c', 'default polaris admin account', '12345678910', '12345678910', - ''); + '' + ); -- Permissions policy inserted into Polaris-Admin -INSERT INTO `auth_strategy` (`id`, - `name`, - `action`, - `owner`, - `comment`, - `default`, - `revision`, - `flag`, - `ctime`, - `mtime`) -VALUES ('fbca9bfa04ae4ead86e1ecf5811e32a9', +INSERT INTO + `auth_strategy` ( + `id`, + `name`, + `action`, + `owner`, + `comment`, + `default`, + `revision`, + `flag`, + `ctime`, + `mtime` + ) +VALUES + ( + 'fbca9bfa04ae4ead86e1ecf5811e32a9', '(用户) polaris的默认策略', 'READ_WRITE', '65e4789a6d5b49669adf1e9e8387549c', @@ -685,92 +710,106 @@ VALUES ('fbca9bfa04ae4ead86e1ecf5811e32a9', 'fbca9bfa04ae4ead86e1ecf5811e32a9', 0, SYSDATE(), - SYSDATE()); + SYSDATE() + ); -- Sport rules inserted into Polaris-Admin to access -INSERT INTO `auth_strategy_resource` (`strategy_id`, - `res_type`, - `res_id`, - `ctime`, - `mtime`) -VALUES ('fbca9bfa04ae4ead86e1ecf5811e32a9', +INSERT INTO + `auth_strategy_resource` ( + `strategy_id`, + `res_type`, + `res_id`, + `ctime`, + `mtime` + ) +VALUES + ( + 'fbca9bfa04ae4ead86e1ecf5811e32a9', 0, '*', SYSDATE(), - SYSDATE()), - ('fbca9bfa04ae4ead86e1ecf5811e32a9', + SYSDATE() + ), + ( + 'fbca9bfa04ae4ead86e1ecf5811e32a9', 1, '*', SYSDATE(), - SYSDATE()), - ('fbca9bfa04ae4ead86e1ecf5811e32a9', + SYSDATE() + ), + ( + 'fbca9bfa04ae4ead86e1ecf5811e32a9', 2, '*', SYSDATE(), - SYSDATE()); + SYSDATE() + ); -- Insert permission policies and association relationships for Polaris-Admin accounts -INSERT INTO auth_principal (`strategy_id`, `principal_id`, `principal_role`) VALUE ( - 'fbca9bfa04ae4ead86e1ecf5811e32a9', - '65e4789a6d5b49669adf1e9e8387549c', - 1 +INSERT INTO + auth_principal (`strategy_id`, `principal_id`, `principal_role`) VALUE ( + 'fbca9bfa04ae4ead86e1ecf5811e32a9', + '65e4789a6d5b49669adf1e9e8387549c', + 1 ); -- v1.8.0, support client info storage -CREATE TABLE `client` -( - `id` VARCHAR(128) NOT NULL COMMENT 'client id', - `host` VARCHAR(100) NOT NULL COMMENT 'client host IP', - `type` VARCHAR(100) NOT NULL COMMENT 'client type: polaris-java/polaris-go', - `version` VARCHAR(32) NOT NULL COMMENT 'client SDK version', - `region` VARCHAR(128) DEFAULT NULL COMMENT 'region info for client', - `zone` VARCHAR(128) DEFAULT NULL COMMENT 'zone info for client', - `campus` VARCHAR(128) DEFAULT NULL COMMENT 'campus info for client', - `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '0 is valid, 1 is invalid(deleted)', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time', - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last updated time', - PRIMARY KEY (`id`), - KEY `mtime` (`mtime`) -) ENGINE = InnoDB; - -CREATE TABLE `client_stat` -( - `client_id` VARCHAR(128) NOT NULL COMMENT 'client id', - `target` VARCHAR(100) NOT NULL COMMENT 'target stat platform', - `port` INT(11) NOT NULL COMMENT 'client port to get stat information', - `protocol` VARCHAR(100) NOT NULL COMMENT 'stat info transport protocol', - `path` VARCHAR(128) NOT NULL COMMENT 'stat metric path', - PRIMARY KEY (`client_id`, `target`, `port`) -) ENGINE = InnoDB; +CREATE TABLE + `client` ( + `id` VARCHAR(128) NOT NULL COMMENT 'client id', + `host` VARCHAR(100) NOT NULL COMMENT 'client host IP', + `type` VARCHAR(100) NOT NULL COMMENT 'client type: polaris-java/polaris-go', + `version` VARCHAR(32) NOT NULL COMMENT 'client SDK version', + `region` VARCHAR(128) DEFAULT NULL COMMENT 'region info for client', + `zone` VARCHAR(128) DEFAULT NULL COMMENT 'zone info for client', + `campus` VARCHAR(128) DEFAULT NULL COMMENT 'campus info for client', + `flag` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '0 is valid, 1 is invalid(deleted)', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time', + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last updated time', + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`) + ) ENGINE = InnoDB; + +CREATE TABLE + `client_stat` ( + `client_id` VARCHAR(128) NOT NULL COMMENT 'client id', + `target` VARCHAR(100) NOT NULL COMMENT 'target stat platform', + `port` INT(11) NOT NULL COMMENT 'client port to get stat information', + `protocol` VARCHAR(100) NOT NULL COMMENT 'stat info transport protocol', + `path` VARCHAR(128) NOT NULL COMMENT 'stat metric path', + PRIMARY KEY (`client_id`, `target`, `port`) + ) ENGINE = InnoDB; -- v1.9.0 -CREATE TABLE `config_file_template` -( - `id` BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', - `name` VARCHAR(128) COLLATE utf8_bin NOT NULL COMMENT '配置文件模板名称', - `content` LONGTEXT COLLATE utf8_bin NOT NULL COMMENT '配置文件模板内容', - `format` VARCHAR(16) COLLATE utf8_bin DEFAULT 'text' COMMENT '模板文件格式', - `comment` VARCHAR(512) COLLATE utf8_bin DEFAULT NULL COMMENT '模板描述信息', - `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `create_by` VARCHAR(32) COLLATE utf8_bin DEFAULT NULL COMMENT '创建人', - `modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', - `modify_by` VARCHAR(32) COLLATE utf8_bin DEFAULT NULL COMMENT '最后更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_name` (`name`) -) ENGINE = InnoDB - AUTO_INCREMENT = 1 - DEFAULT CHARSET = utf8 - COLLATE = utf8_bin COMMENT = '配置文件模板表'; - -INSERT INTO `config_file_template` (`name`, - `content`, - `format`, - `comment`, - `create_time`, - `create_by`, - `modify_time`, - `modify_by`) -VALUES ('spring-cloud-gateway-braining', +CREATE TABLE + `config_file_template` ( + `id` BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(128) COLLATE utf8_bin NOT NULL COMMENT '配置文件模板名称', + `content` LONGTEXT COLLATE utf8_bin NOT NULL COMMENT '配置文件模板内容', + `format` VARCHAR(16) COLLATE utf8_bin DEFAULT 'text' COMMENT '模板文件格式', + `comment` VARCHAR(512) COLLATE utf8_bin DEFAULT NULL COMMENT '模板描述信息', + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` VARCHAR(32) COLLATE utf8_bin DEFAULT NULL COMMENT '创建人', + `modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `modify_by` VARCHAR(32) COLLATE utf8_bin DEFAULT NULL COMMENT '最后更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_name` (`name`) + ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COLLATE = utf8_bin COMMENT = '配置文件模板表'; + +INSERT INTO + `config_file_template` ( + `name`, + `content`, + `format`, + `comment`, + `create_time`, + `create_by`, + `modify_time`, + `modify_by` + ) +VALUES + ( + 'spring-cloud-gateway-braining', '{ "rules":[ { @@ -795,139 +834,175 @@ VALUES ('spring-cloud-gateway-braining', NOW(), 'polaris', NOW(), - 'polaris'); + 'polaris' + ); -- v1.12.0 -CREATE TABLE `routing_config_v2` -( - `id` VARCHAR(128) NOT NULL, - `name` VARCHAR(64) NOT NULL DEFAULT '', - `namespace` VARCHAR(64) NOT NULL DEFAULT '', - `policy` VARCHAR(64) NOT NULL, - `config` TEXT, - `enable` INT NOT NULL DEFAULT 0, - `revision` VARCHAR(40) NOT NULL, - `description` VARCHAR(500) NOT NULL DEFAULT '', - `priority` SMALLINT(6) NOT NULL DEFAULT '0' COMMENT 'ratelimit rule priority', - `flag` TINYINT(4) NOT NULL DEFAULT '0', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `etime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `extend_info` VARCHAR(1024) DEFAULT '', - PRIMARY KEY (`id`), - KEY `mtime` (`mtime`) -) ENGINE = innodb; - -CREATE TABLE - `leader_election` -( - `elect_key` VARCHAR(128) NOT NULL, - `version` BIGINT NOT NULL DEFAULT 0, - `leader` VARCHAR(128) NOT NULL, - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`elect_key`), - KEY `version` (`version`) -) ENGINE = innodb; +CREATE TABLE + `routing_config_v2` ( + `id` VARCHAR(128) NOT NULL, + `name` VARCHAR(64) NOT NULL DEFAULT '', + `namespace` VARCHAR(64) NOT NULL DEFAULT '', + `policy` VARCHAR(64) NOT NULL, + `config` TEXT, + `enable` INT NOT NULL DEFAULT 0, + `revision` VARCHAR(40) NOT NULL, + `description` VARCHAR(500) NOT NULL DEFAULT '', + `priority` SMALLINT(6) NOT NULL DEFAULT '0' COMMENT 'ratelimit rule priority', + `flag` TINYINT(4) NOT NULL DEFAULT '0', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `etime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `extend_info` VARCHAR(1024) DEFAULT '', + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`) + ) ENGINE = innodb; + +CREATE TABLE + `leader_election` ( + `elect_key` VARCHAR(128) NOT NULL, + `version` BIGINT NOT NULL DEFAULT 0, + `leader` VARCHAR(128) NOT NULL, + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`elect_key`), + KEY `version` (`version`) + ) ENGINE = innodb; -- v1.14.0 -CREATE TABLE `circuitbreaker_rule_v2` -( - `id` VARCHAR(128) NOT NULL, - `name` VARCHAR(64) NOT NULL, - `namespace` VARCHAR(64) NOT NULL DEFAULT '', - `enable` INT NOT NULL DEFAULT 0, - `revision` VARCHAR(40) NOT NULL, - `description` VARCHAR(1024) NOT NULL DEFAULT '', - `level` INT NOT NULL, - `src_service` VARCHAR(128) NOT NULL, - `src_namespace` VARCHAR(64) NOT NULL, - `dst_service` VARCHAR(128) NOT NULL, - `dst_namespace` VARCHAR(64) NOT NULL, - `dst_method` VARCHAR(128) NOT NULL, - `config` TEXT, - `flag` TINYINT(4) NOT NULL DEFAULT '0', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `etime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - KEY `name` (`name`), - KEY `mtime` (`mtime`) -) ENGINE = innodb; - -CREATE TABLE `fault_detect_rule` -( - `id` VARCHAR(128) NOT NULL, - `name` VARCHAR(64) NOT NULL, - `namespace` VARCHAR(64) NOT NULL DEFAULT 'default', - `revision` VARCHAR(40) NOT NULL, - `description` VARCHAR(1024) NOT NULL DEFAULT '', - `dst_service` VARCHAR(128) NOT NULL, - `dst_namespace` VARCHAR(64) NOT NULL, - `dst_method` VARCHAR(128) NOT NULL, - `config` TEXT, - `flag` TINYINT(4) NOT NULL DEFAULT '0', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - KEY `name` (`name`), - KEY `mtime` (`mtime`) -) ENGINE = innodb; +CREATE TABLE + `circuitbreaker_rule_v2` ( + `id` VARCHAR(128) NOT NULL, + `name` VARCHAR(64) NOT NULL, + `namespace` VARCHAR(64) NOT NULL DEFAULT '', + `enable` INT NOT NULL DEFAULT 0, + `revision` VARCHAR(40) NOT NULL, + `description` VARCHAR(1024) NOT NULL DEFAULT '', + `level` INT NOT NULL, + `src_service` VARCHAR(128) NOT NULL, + `src_namespace` VARCHAR(64) NOT NULL, + `dst_service` VARCHAR(128) NOT NULL, + `dst_namespace` VARCHAR(64) NOT NULL, + `dst_method` VARCHAR(128) NOT NULL, + `config` TEXT, + `flag` TINYINT(4) NOT NULL DEFAULT '0', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `etime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `mtime` (`mtime`) + ) ENGINE = innodb; + +CREATE TABLE + `fault_detect_rule` ( + `id` VARCHAR(128) NOT NULL, + `name` VARCHAR(64) NOT NULL, + `namespace` VARCHAR(64) NOT NULL DEFAULT 'default', + `revision` VARCHAR(40) NOT NULL, + `description` VARCHAR(1024) NOT NULL DEFAULT '', + `dst_service` VARCHAR(128) NOT NULL, + `dst_namespace` VARCHAR(64) NOT NULL, + `dst_method` VARCHAR(128) NOT NULL, + `config` TEXT, + `flag` TINYINT(4) NOT NULL DEFAULT '0', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `mtime` (`mtime`) + ) ENGINE = innodb; /* 服务契约表 */ -CREATE TABLE service_contract -( - `id` VARCHAR(128) NOT NULL COMMENT '服务契约主键', - `name` VARCHAR(128) NOT NULL COMMENT '服务契约名称', - `namespace` VARCHAR(64) NOT NULL COMMENT '命名空间', - `service` VARCHAR(128) NOT NULL COMMENT '服务名称', - `protocol` VARCHAR(32) NOT NULL COMMENT '当前契约对应的协议信息 e.g. http/dubbo/grpc/thrift', - `version` VARCHAR(64) NOT NULL COMMENT '服务契约版本', - `revision` VARCHAR(128) NOT NULL COMMENT '当前服务契约的全部内容版本摘要', - `flag` TINYINT(4) DEFAULT 0 COMMENT '逻辑删除标志位 , 0 位有效 , 1 为逻辑删除', - `content` LONGTEXT COMMENT '描述信息', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - -- 通过 服务 + 协议信息 + 契约版本 + 名称 进行一次 hash 计算,作为主键 - PRIMARY KEY (`id`), - -- 服务 + 协议信息 + 契约版本 + 辅助标签 必须保证唯一 - KEY ( - `namespace`, - `service`, - `name`, - `version`, - `protocol` +CREATE TABLE + service_contract ( + `id` VARCHAR(128) NOT NULL COMMENT '服务契约主键', + `type` VARCHAR(128) NOT NULL COMMENT '服务契约名称', + `namespace` VARCHAR(64) NOT NULL COMMENT '命名空间', + `service` VARCHAR(128) NOT NULL COMMENT '服务名称', + `protocol` VARCHAR(32) NOT NULL COMMENT '当前契约对应的协议信息 e.g. http/dubbo/grpc/thrift', + `version` VARCHAR(64) NOT NULL COMMENT '服务契约版本', + `revision` VARCHAR(128) NOT NULL COMMENT '当前服务契约的全部内容版本摘要', + `flag` TINYINT(4) DEFAULT 0 COMMENT '逻辑删除标志位 , 0 位有效 , 1 为逻辑删除', + `content` LONGTEXT COMMENT '描述信息', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + -- 通过 服务 + 协议信息 + 契约版本 + 名称 进行一次 hash 计算,作为主键 + PRIMARY KEY (`id`), + -- 服务 + 协议信息 + 契约版本 + 辅助标签 必须保证唯一 + KEY ( + `namespace`, + `service`, + `name`, + `version`, + `protocol` ) -) ENGINE = InnoDB; + ) ENGINE = InnoDB; /* 服务契约中针对单个接口定义的详细信息描述表 */ -CREATE TABLE service_contract_detail -( - `id` VARCHAR(128) NOT NULL COMMENT '服务契约单个接口定义记录主键', - `contract_id` VARCHAR(128) NOT NULL COMMENT '服务契约 ID', - `name` VARCHAR(128) NOT NULL COMMENT '服务契约接口名称', - `method` VARCHAR(32) NOT NULL COMMENT 'http协议中的 method 字段, eg:POST/GET/PUT/DELETE, 其他 gRPC 可以用来标识 stream 类型', - `path` VARCHAR(128) NOT NULL COMMENT '接口具体全路径描述', - `source` INT COMMENT '该条记录来源, 0:SDK/1:MANUAL', - `content` LONGTEXT COMMENT '描述信息', - `revision` VARCHAR(128) NOT NULL COMMENT '当前接口定义的全部内容版本摘要', - `flag` TINYINT(4) DEFAULT 0 COMMENT '逻辑删除标志位, 0 位有效, 1 为逻辑删除', - `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - -- 服务契约id + method + path + source 需保证唯一 - KEY (`contract_id`, `path`, `method`, `source`) -) ENGINE = InnoDB; +CREATE TABLE + service_contract_detail ( + `id` VARCHAR(128) NOT NULL COMMENT '服务契约单个接口定义记录主键', + `contract_id` VARCHAR(128) NOT NULL COMMENT '服务契约 ID', + `type` VARCHAR(128) NOT NULL COMMENT '服务契约接口名称', + `namespace` VARCHAR(64) NOT NULL COMMENT '命名空间', + `service` VARCHAR(128) NOT NULL COMMENT '服务名称', + `protocol` VARCHAR(32) NOT NULL COMMENT '当前契约对应的协议信息 e.g. http/dubbo/grpc/thrift', + `version` VARCHAR(64) NOT NULL COMMENT '服务契约版本', + `method` VARCHAR(32) NOT NULL COMMENT 'http协议中的 method 字段, eg:POST/GET/PUT/DELETE, 其他 gRPC 可以用来标识 stream 类型', + `path` VARCHAR(128) NOT NULL COMMENT '接口具体全路径描述', + `source` INT COMMENT '该条记录来源, 0:SDK/1:MANUAL', + `content` LONGTEXT COMMENT '描述信息', + `revision` VARCHAR(128) NOT NULL COMMENT '当前接口定义的全部内容版本摘要', + `flag` TINYINT(4) DEFAULT 0 COMMENT '逻辑删除标志位, 0 位有效, 1 为逻辑删除', + `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + -- 服务契约id + method + path + source 需保证唯一 + KEY (`contract_id`, `path`, `method`, `source`) + ) 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 = '灰度资源表'; +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 = '灰度资源表'; + +CREATE TABLE + lane_group ( + id varchar(128) not null comment '泳道分组 ID', + name varchar(64) not null comment '泳道分组名称', + rule text not null comment '规则的 json 字符串', + description varchar(3000) comment '规则描述', + revision VARCHAR(40) NOT NULL comment '规则摘要', + flag tinyint default 0 comment '软删除标识位', + ctime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + mtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) + ) ENGINE = InnoDB; + +CREATE TABLE + lane_rule ( + id varchar(128) not null comment '规则 id', + name varchar(64) not null comment '规则名称', + group_name varchar(64) not null comment '泳道分组名称', + rule text not null comment '规则的 json 字符串', + revision VARCHAR(40) NOT NULL comment '规则摘要', + description varchar(3000) comment '规则描述', + enable tinyint comment '是否启用', + flag tinyint default 0 comment '软删除标识位', + priority bigint NOT NULL DEFAULT 0 comment '泳道规则优先级', + ctime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + etime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + mtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`group_name`, `name`) + ) ENGINE = InnoDB; \ No newline at end of file diff --git a/store/mysql/service_contract.go b/store/mysql/service_contract.go index 7166a61f1..efd903f7a 100644 --- a/store/mysql/service_contract.go +++ b/store/mysql/service_contract.go @@ -18,6 +18,10 @@ package sqldb import ( + "context" + "database/sql" + "fmt" + "strings" "time" "github.com/polarismesh/specification/source/go/api/v1/service_manage" @@ -27,17 +31,6 @@ import ( "github.com/polarismesh/polaris/store" ) -var ( - // contractAvaiableFilter 允许查询的字段 - contractAvaiableFilter = map[string]struct{}{ - "name": {}, - "service": {}, - "namespace": {}, - "version": {}, - "protocol": {}, - } -) - type serviceContractStore struct { master *BaseDB slave *BaseDB @@ -45,13 +38,13 @@ type serviceContractStore struct { // CreateServiceContract 创建服务契约 func (s *serviceContractStore) CreateServiceContract(contract *model.ServiceContract) error { - addSql := "INSERT INTO service_contract(`id`,`name`, `namespace`, `service`, `protocol`,`version`, " + + addSql := "INSERT INTO service_contract(`id`,`type`, `namespace`, `service`, `protocol`,`version`, " + " `revision`, `flag`, `content`, `ctime`, `mtime`" + ") VALUES (?,?,?,?,?,?,?,0,?,sysdate(),sysdate())" _, err := s.master.Exec(addSql, []interface{}{ contract.ID, - contract.Name, + contract.Type, contract.Namespace, contract.Service, contract.Protocol, @@ -83,7 +76,7 @@ func (s *serviceContractStore) DeleteServiceContract(contract *model.ServiceCont return err } - deleteDetailSql := "DELETE FROM service_contract_detail WHERE contract_id = ?" + deleteDetailSql := "UPDATE service_contract_detail SET flag = 1 WHERE contract_id = ?" if _, err := tx.Exec(deleteDetailSql, []interface{}{ contract.ID, }...); err != nil { @@ -96,49 +89,82 @@ func (s *serviceContractStore) DeleteServiceContract(contract *model.ServiceCont } // GetServiceContract 通过ID查询服务契约数据 -func (s *serviceContractStore) GetServiceContract(id string) (data *model.EnrichServiceContract, err error) { - querySql := "SELECT id, name, namespace, service, protocol, version, revision, flag,content, " + +func (s *serviceContractStore) GetServiceContract(id string) (*model.EnrichServiceContract, error) { + querySql := "SELECT id, type, namespace, service, protocol, version, revision, flag, content, " + " UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM service_contract WHERE flag = 0 AND id = ?" args := []interface{}{id} - rows, err := s.master.Query(querySql, args...) + + list := make([]*model.EnrichServiceContract, 0) + err := s.slave.processWithTransaction("GetServiceContract", func(tx *BaseTx) error { + rows, err := tx.Query(querySql, args...) + if err != nil { + log.Error("[Store][Contract] list contract ", zap.String("query sql", querySql), zap.Any("args", args)) + return err + } + err = transferEnrichServiceContract(rows, func(contract *model.EnrichServiceContract) { + list = append(list, contract) + }) + if err != nil { + log.Errorf("[Store][Contract] fetch contract rows scan err: %s", err.Error()) + return err + } + + for i := range list { + contract := list[i] + queryDetailSql := "SELECT id, contract_id, namespace, service, protocol, version, type, method, path, content, revision, " + + " UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime), IFNULL(source, 1) " + + " FROM service_contract_detail " + + " WHERE contract_id = ?" + + descriptors, err := s.loadContractInterfaces(tx, queryDetailSql, []interface{}{contract.ID}) + if err != nil { + log.Error("[Store][Contract] load service_contract link interfaces", + zap.String("contract_id", contract.ID), zap.Error(err)) + return err + } + contract.Interfaces = descriptors + } + return nil + }) if err != nil { - log.Error("[Store][Contract] list contract ", zap.String("query", querySql), zap.Any("args", args)) return nil, store.Error(err) } + + if len(list) == 0 { + return nil, nil + } + return list[0], nil +} + +func transferEnrichServiceContract(rows *sql.Rows, consumer func(contract *model.EnrichServiceContract)) error { defer func() { _ = rows.Close() }() - list := make([]*model.ServiceContract, 0) for rows.Next() { var flag, ctime, mtime int64 - contract := model.ServiceContract{} - if scanErr := rows.Scan(&contract.ID, &contract.Name, &contract.Namespace, &contract.Service, + contract := model.EnrichServiceContract{ + ServiceContract: &model.ServiceContract{}, + } + if scanErr := rows.Scan(&contract.ID, &contract.Type, &contract.Namespace, &contract.Service, &contract.Protocol, &contract.Version, &contract.Revision, &flag, &contract.Content, &ctime, &mtime); scanErr != nil { - log.Errorf("[Store][Contract] fetch contract rows scan err: %s", err.Error()) - return nil, store.Error(err) + log.Errorf("[Store][Contract] fetch contract rows scan err: %s", scanErr.Error()) + return scanErr } contract.Valid = flag == 0 - contract.CreateTime = time.Unix(ctime, 0) - contract.ModifyTime = time.Unix(mtime, 0) - - list = append(list, &contract) + contract.CreateTime = time.Unix(0, ctime) + contract.ModifyTime = time.Unix(0, mtime) + consumer(&contract) } - - if len(list) == 0 { - return nil, nil - } - return &model.EnrichServiceContract{ - ServiceContract: list[0], - }, nil + return nil } // AddServiceContractInterfaces 创建服务契约API接口 func (s *serviceContractStore) AddServiceContractInterfaces(contract *model.EnrichServiceContract) error { - return s.master.processWithTransaction("AddServiceContractDetail", func(tx *BaseTx) error { + return s.master.processWithTransaction("AddServiceContractInterfaces", func(tx *BaseTx) error { updateRevision := "UPDATE service_contract SET revision = ?, mtime = sysdate() WHERE id = ?" if _, err := tx.Exec(updateRevision, contract.Revision, contract.ID); err != nil { log.Errorf("[Store][database] update service contract revision err: %s", err.Error()) @@ -147,16 +173,19 @@ func (s *serviceContractStore) AddServiceContractInterfaces(contract *model.Enri // 新增批量数据 for _, item := range contract.Interfaces { - addSql := "REPLACE INTO service_contract_detail(`id`, `contract_id`, `name`, `method`, `path` " + - " ,`content`,`revision`" + - ",`flag`,`ctime`, `mtime`, `source`" + - ") VALUES (?,?,?,?,?,?,?,?,sysdate(),sysdate(),?)" + addSql := "REPLACE INTO service_contract_detail (id, contract_id, namespace, service, protocol, " + + " version, method, path, type, content, revision, flag, ctime, mtime, source) " + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, sysdate(), sysdate(), ?)" if _, err := tx.Exec(addSql, []interface{}{ item.ID, contract.ID, - item.Name, + item.Namespace, + item.Service, + item.Protocol, + item.Version, item.Method, item.Path, + item.Type, item.Content, item.Revision, 0, @@ -179,17 +208,21 @@ func (s *serviceContractStore) AppendServiceContractInterfaces(contract *model.E return err } for _, item := range contract.Interfaces { - addSql := "REPLACE INTO service_contract_detail(`id`,`contract_id`, `name`, `method`, " + - " `path` ,`content`,`revision`" + + addSql := "REPLACE INTO service_contract_detail(`id`,`contract_id`, `namespace`, `service`, " + + " `protocol`, `version`, `method`, `path`, `type` ,`content`,`revision`" + ",`flag`,`ctime`, `mtime`,`source`" + - ") VALUES (?,?,?,?,?,?,?,?,sysdate(),sysdate(),?)" + ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,sysdate(),sysdate(),?)" if _, err := tx.Exec(addSql, []interface{}{ item.ID, contract.ID, - item.Name, + item.Namespace, + item.Service, + item.Protocol, + item.Version, item.Method, item.Path, + item.Type, item.Content, item.Revision, 0, @@ -205,20 +238,20 @@ func (s *serviceContractStore) AppendServiceContractInterfaces(contract *model.E // DeleteServiceContractInterfaces 删除服务契约API接口 func (s *serviceContractStore) DeleteServiceContractInterfaces(contract *model.EnrichServiceContract) error { - return s.master.processWithTransaction("DeleteServiceContractDetail", func(tx *BaseTx) error { + return s.master.processWithTransaction("DeleteServiceContractInterfaces", func(tx *BaseTx) error { updateRevision := "UPDATE service_contract SET revision = ?, mtime = sysdate() WHERE id = ?" if _, err := tx.Exec(updateRevision, contract.Revision, contract.ID); err != nil { log.Errorf("[Store][database] update service contract revision err: %s", err.Error()) return err } for _, item := range contract.Interfaces { - addSql := "DELETE FROM service_contract_detail WHERE contract_id = ? AND method = ? AND path = ? AND name = ?" + addSql := "UPDATE service_contract_detail SET flag = 1 WHERE contract_id = ? AND method = ? AND path = ? AND type = ?" if _, err := tx.Exec(addSql, []interface{}{ item.ContractID, item.Method, item.Path, - item.Name, + item.Type, }...); err != nil { log.Errorf("[Store][database] delete service contract detail err: %s", err.Error()) return err @@ -228,100 +261,219 @@ 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, " + - " UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM service_contract WHERE mtime >= ? " - if firstUpdate { - mtime = time.Unix(0, 1) - querySql += " AND flag = 0 " +func (s *serviceContractStore) GetServiceContracts(ctx context.Context, filter map[string]string, + offset, limit uint32) (uint32, []*model.EnrichServiceContract, error) { + + querySql := ` + SELECT id, type, namespace, service, protocol + , version, revision, flag, content + , UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) + FROM service_contract + WHERE flag = 0 + ` + + countSql := "SELECT COUNT(*) FROM service_contract WHERE flag = 0 " + + brief := filter[briefSearch] == "true" + args := make([]interface{}, 0, len(filter)) + conditions := make([]string, 0, len(filter)) + for k, v := range filter { + if k == "order_field" || k == "order_type" { + continue + } + conditions = append(conditions, k+" = ? ") + args = append(args, v) + } + + if len(conditions) > 0 { + countSql += " AND " + strings.Join(conditions, " AND ") + querySql += " AND " + strings.Join(conditions, " AND ") } + querySql += fmt.Sprintf(" ORDER BY %s %s LIMIT ?, ? ", filter["order_field"], filter["order_type"]) + + var count int64 + var list = make([]*model.EnrichServiceContract, 0, limit) - tx, err := s.slave.Begin() + err := s.master.processWithTransaction("GetServiceContracts", func(tx *BaseTx) error { + row := tx.QueryRow(countSql, args...) + if err := row.Scan(&count); err != nil { + log.Error("[Store][Contract] count service_contracts", zap.String("count", countSql), zap.Any("args", args), zap.Error(err)) + return err + } + + args = append(args, offset, limit) + rows, err := tx.Query(querySql, args...) + if err != nil { + log.Error("[Store][Contract] list service_contracts", zap.String("query", querySql), zap.Any("args", args), zap.Error(err)) + return err + } + defer func() { + _ = rows.Close() + }() + + contractIds := make([]interface{}, 0, limit) + err = transferEnrichServiceContract(rows, func(contract *model.EnrichServiceContract) { + list = append(list, contract) + contractIds = append(contractIds, contract.ID) + }) + if err != nil { + log.Errorf("[Store][Contract] fetch contract rows scan err: %s", err.Error()) + return err + } + + if !brief && len(contractIds) > 0 { + // 加载 interfaces 列表 + queryDetailSql := fmt.Sprintf("SELECT id, contract_id, namespace, service, protocol, version, "+ + " type, method, path, content, revision, "+ + " UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime), IFNULL(source, 1) "+ + " FROM service_contract_detail "+ + " WHERE contract_id IN (%s)", placeholders(len(contractIds))) + + contractDetailMap := map[string][]*model.InterfaceDescriptor{} + interfaces, err := s.loadContractInterfaces(tx, queryDetailSql, contractIds) + if err != nil { + return err + } + for i := range interfaces { + descriptor := interfaces[i] + if _, ok := contractDetailMap[descriptor.ContractID]; !ok { + contractDetailMap[descriptor.ContractID] = make([]*model.InterfaceDescriptor, 0, 4) + } + contractDetailMap[descriptor.ContractID] = append(contractDetailMap[descriptor.ContractID], descriptor) + } + + for _, item := range list { + methods := contractDetailMap[item.ID] + item.Interfaces = methods + item.Format() + } + } + return nil + }) if err != nil { - log.Error("[Store][Contract] list contract for cache when begin tx", zap.Error(err)) - return nil, store.Error(err) + return 0, nil, store.Error(err) } - defer func() { - _ = tx.Commit() - }() + return uint32(count), list, nil +} + +func (s *serviceContractStore) GetInterfaceDescriptors(ctx context.Context, filter map[string]string, + offset, limit uint32) (uint32, []*model.InterfaceDescriptor, error) { + + countSql := "SELECT COUNT(*) FROM service_contract_detail sd WHERE flag = 0 " + + // 加载 interfaces 列表 + querySql := ` + SELECT id, contract_id, namespace, service, protocol, version + , type, method, path, content, revision + , UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) + , IFNULL(source, 1) + FROM service_contract_detail + WHERE flag = 0 + ` - rows, err := tx.Query(querySql, mtime) + args := make([]interface{}, 0, len(filter)) + conditions := make([]string, 0, len(filter)) + + for k, v := range filter { + if k == "order_field" || k == "order_type" { + continue + } + conditions = append(conditions, k+" = ? ") + args = append(args, v) + } + + var count uint32 + var list []*model.InterfaceDescriptor + + if len(conditions) > 0 { + countSql += " AND " + strings.Join(conditions, " AND ") + querySql += " AND " + strings.Join(conditions, " AND ") + } + querySql += fmt.Sprintf(" ORDER BY %s %s LIMIT ?, ? ", filter["order_field"], filter["order_type"]) + + err := s.slave.processWithTransaction("GetInterfaceDescriptors", func(tx *BaseTx) error { + row := tx.QueryRow(countSql, args...) + err := row.Scan(&count) + if err != nil { + log.Error("[Store][Contract] count service_interfaces", zap.String("countSql", countSql), zap.Any("args", args), zap.Error(err)) + return err + } + + args = append(args, offset, limit) + list, err = s.loadContractInterfaces(tx, querySql, args) + return err + }) if err != nil { - log.Error("[Store][Contract] list contract for cache when query", zap.Error(err)) - return nil, store.Error(err) + return 0, nil, store.Error(err) } + return count, list, nil +} + +func (s *serviceContractStore) loadContractInterfaces(tx *BaseTx, query string, args []interface{}) ([]*model.InterfaceDescriptor, error) { + rows, err := tx.Query(query, args...) + if err != nil { + log.Error("[Store][Contract] load service_contract interface list", zap.String("sql", query), zap.Error(err)) + return nil, err + } + defer func() { _ = rows.Close() }() - list := make([]*model.EnrichServiceContract, 0) + var list []*model.InterfaceDescriptor for rows.Next() { - var flag, ctime, mtime int64 - contract := &model.ServiceContract{} - if scanErr := rows.Scan(&contract.ID, &contract.Name, &contract.Namespace, &contract.Service, - &contract.Protocol, &contract.Version, &contract.Revision, &flag, - &contract.Content, &ctime, &mtime); scanErr != nil { - log.Error("[Store][Contract] fetch contract rows scan err: %s", zap.Error(err)) - return nil, store.Error(err) + var flag, ctime, mtime, source int64 + detailItem := &model.InterfaceDescriptor{} + if scanErr := rows.Scan( + &detailItem.ID, &detailItem.ContractID, &detailItem.Namespace, &detailItem.Service, &detailItem.Protocol, + &detailItem.Version, &detailItem.Type, &detailItem.Method, + &detailItem.Path, &detailItem.Content, &detailItem.Revision, + &ctime, &mtime, &source, + ); scanErr != nil { + log.Error("[Store][Contract] load service_contract interface rows scan", zap.Error(scanErr)) + return nil, err } - contract.Valid = flag == 0 - contract.CreateTime = time.Unix(ctime, 0) - contract.ModifyTime = time.Unix(mtime, 0) + detailItem.Valid = flag == 0 + detailItem.CreateTime = time.Unix(ctime, 0) + detailItem.ModifyTime = time.Unix(mtime, 0) + switch source { + case 2: + detailItem.Source = service_manage.InterfaceDescriptor_Client + default: + detailItem.Source = service_manage.InterfaceDescriptor_Manual + } - list = append(list, &model.EnrichServiceContract{ - ServiceContract: contract, - }) + list = append(list, detailItem) } + return list, nil +} - contractDetailMap := map[string][]*model.InterfaceDescriptor{} - if len(list) > 0 { - queryDetailSql := "SELECT sd.id, sd.contract_id, sd.name, sd.method, sd.path, sd.content, sd.revision, " + - " UNIX_TIMESTAMP(sd.ctime), UNIX_TIMESTAMP(sd.mtime), IFNULL(sd.source, 1) " + - " FROM service_contract_detail sd LEFT JOIN service_contract sc ON sd.contract_id = sc.id " + - " WHERE sc.mtime >= ?" - detailRows, err := tx.Query(queryDetailSql, mtime) - if err != nil { - log.Error("[Store][Contract] list contract detail", zap.String("query sql", queryDetailSql), zap.Error(err)) - return nil, store.Error(err) - } - defer func() { - _ = detailRows.Close() - }() - for detailRows.Next() { - var flag, ctime, mtime, source int64 - detailItem := &model.InterfaceDescriptor{} - if scanErr := detailRows.Scan( - &detailItem.ID, &detailItem.ContractID, &detailItem.Name, &detailItem.Method, - &detailItem.Path, &detailItem.Content, &detailItem.Revision, - &ctime, &mtime, &source, - ); scanErr != nil { - log.Error("[Store][Contract] fetch contract detail rows scan", zap.Error(scanErr)) - return nil, store.Error(scanErr) - } +// ListVersions . +func (s *serviceContractStore) ListVersions(ctx context.Context, service, namespace string) ([]*model.ServiceContract, error) { + list := make([]*model.ServiceContract, 0, 4) - detailItem.Valid = flag == 0 - detailItem.CreateTime = time.Unix(ctime, 0) - detailItem.ModifyTime = time.Unix(mtime, 0) - switch source { - case 2: - detailItem.Source = service_manage.InterfaceDescriptor_Client - default: - detailItem.Source = service_manage.InterfaceDescriptor_Manual - } + querySql := ` + SELECT id, type, namespace, service, protocol + , version, revision, flag + , UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) + FROM service_contract + WHERE flag = 0 AND namespace = ? AND service = ? + ` - if _, ok := contractDetailMap[detailItem.ContractID]; !ok { - contractDetailMap[detailItem.ContractID] = make([]*model.InterfaceDescriptor, 0, 4) - } - contractDetailMap[detailItem.ContractID] = append(contractDetailMap[detailItem.ContractID], detailItem) - } + rows, err := s.slave.Query(querySql, namespace, service) + if err != nil { + log.Error("[Store][Contract] list version service_contract", zap.String("namespace", namespace), + zap.String("service", service), zap.Error(err)) + return nil, store.Error(err) + } - for _, item := range list { - methods := contractDetailMap[item.ID] - item.Interfaces = methods - item.Format() - } + err = transferEnrichServiceContract(rows, func(contract *model.EnrichServiceContract) { + list = append(list, contract.ServiceContract) + }) + if err != nil { + log.Errorf("[Store][Contract] fetch contract rows scan err: %s", err.Error()) + return nil, err } return list, nil } From b40aa8c59f7b10581e4708443df35c6ceab14ed3 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Tue, 30 Apr 2024 10:38:08 +0800 Subject: [PATCH 07/23] feat:refactor auth code struct and fix config_center push bug --- apiserver/httpserver/utils/handler.go | 14 +- apiserver/nacosserver/server.go | 4 +- apiserver/nacosserver/v1/auth.go | 3 +- apiserver/nacosserver/v1/discover/server.go | 2 - apiserver/nacosserver/v1/options.go | 7 +- apiserver/nacosserver/v1/server.go | 2 +- apiserver/nacosserver/v2/discover/server.go | 1 - apiserver/nacosserver/v2/option.go | 5 +- auth/api.go | 127 +++-- auth/auth.go | 18 +- auth/authcheck/access.go | 57 +++ auth/authcheck/auth_checker.go | 222 +++++++++ .../auth_checker_test.go | 292 ++---------- .../{defaultauth => authcheck}/common_test.go | 260 +++++----- auth/{defaultauth => authcheck}/default.go | 5 +- auth/{defaultauth => authcheck}/log.go | 2 +- auth/{defaultauth => authcheck}/main_test.go | 2 +- auth/{defaultauth => authcheck}/server.go | 172 ++++--- auth/{defaultauth => authcheck}/strategy.go | 174 +++---- .../strategy_test.go | 38 +- auth/authcheck/utils.go | 69 +++ auth/defaultauth/auth_checker.go | 447 ------------------ auth/defaultauth/config.go | 69 --- auth/defaultauth/group_authability.go | 130 ----- auth/defaultauth/strategy_authability.go | 154 ------ auth/defaultauth/user_authability.go | 188 -------- auth/defaultauth/utils.go | 163 ------- auth/user/common_test.go | 406 ++++++++++++++++ .../test_export.go => user/default.go} | 26 +- auth/{defaultauth => user}/group.go | 22 +- auth/{defaultauth => user}/group_test.go | 20 +- auth/user/helper.go | 22 + auth/user/log.go | 24 + auth/user/main_test.go | 215 +++++++++ auth/user/server.go | 183 +++++++ auth/{defaultauth => user}/token.go | 108 +++-- auth/{defaultauth => user}/token_test.go | 23 +- auth/{defaultauth => user}/user.go | 124 ++++- auth/{defaultauth => user}/user_test.go | 17 +- auth/user/utils.go | 103 ++++ auth/{defaultauth => user}/utils_test.go | 9 +- cache/config/config_file.go | 101 ++-- common/model/acquire_context.go | 28 +- common/model/auth.go | 7 + config/common_test.go | 1 - config/config_file_release.go | 1 + plugin.go | 3 +- .../docker-compose/mysql/Dockerfile | 4 +- .../docker-compose/mysql/mysqld.cnf | 1 - service/common_test.go | 1 - store/mysql/service.go | 16 + test/suit/test_suit.go | 1 - 52 files changed, 2123 insertions(+), 1970 deletions(-) create mode 100644 auth/authcheck/access.go create mode 100644 auth/authcheck/auth_checker.go rename auth/{defaultauth => authcheck}/auth_checker_test.go (77%) rename auth/{defaultauth => authcheck}/common_test.go (95%) rename auth/{defaultauth => authcheck}/default.go (86%) rename auth/{defaultauth => authcheck}/log.go (97%) rename auth/{defaultauth => authcheck}/main_test.go (99%) rename auth/{defaultauth => authcheck}/server.go (66%) rename auth/{defaultauth => authcheck}/strategy.go (90%) rename auth/{defaultauth => authcheck}/strategy_test.go (97%) create mode 100644 auth/authcheck/utils.go delete mode 100644 auth/defaultauth/auth_checker.go delete mode 100644 auth/defaultauth/config.go delete mode 100644 auth/defaultauth/group_authability.go delete mode 100644 auth/defaultauth/strategy_authability.go delete mode 100644 auth/defaultauth/user_authability.go delete mode 100644 auth/defaultauth/utils.go create mode 100644 auth/user/common_test.go rename auth/{defaultauth/test_export.go => user/default.go} (54%) rename auth/{defaultauth => user}/group.go (97%) rename auth/{defaultauth => user}/group_test.go (98%) create mode 100644 auth/user/helper.go create mode 100644 auth/user/log.go create mode 100644 auth/user/main_test.go create mode 100644 auth/user/server.go rename auth/{defaultauth => user}/token.go (57%) rename auth/{defaultauth => user}/token_test.go (73%) rename auth/{defaultauth => user}/user.go (83%) rename auth/{defaultauth => user}/user_test.go (98%) create mode 100644 auth/user/utils.go rename auth/{defaultauth => user}/utils_test.go (92%) diff --git a/apiserver/httpserver/utils/handler.go b/apiserver/httpserver/utils/handler.go index f757a7d75..3dc8dba6f 100644 --- a/apiserver/httpserver/utils/handler.go +++ b/apiserver/httpserver/utils/handler.go @@ -78,7 +78,7 @@ func (h *Handler) parseArray(createMessage func() proto.Message, jsonDecoder *js } for jsonDecoder.More() { protoMessage := createMessage() - err := jsonpb.UnmarshalNext(jsonDecoder, protoMessage) + err := UnmarshalNext(jsonDecoder, protoMessage) if err != nil { accesslog.Error(err.Error(), utils.ZapRequestID(requestID)) return nil, err @@ -122,7 +122,7 @@ func (h *Handler) postParseMessage(requestID string) (context.Context, error) { // Parse 解析请求 func (h *Handler) Parse(message proto.Message) (context.Context, error) { requestID := h.Request.HeaderParameter("Request-Id") - if err := jsonpb.Unmarshal(h.Request.Request.Body, message); err != nil { + if err := Unmarshal(h.Request.Request.Body, message); err != nil { accesslog.Error(err.Error(), utils.ZapRequestID(requestID)) return nil, err } @@ -461,3 +461,13 @@ func InitProtoCache(option map[string]interface{}, cacheTypes []string, discover convert = discoverCacheConvert } } + +func UnmarshalNext(j *json.Decoder, m proto.Message) error { + var jsonpbMarshaler = jsonpb.Unmarshaler{AllowUnknownFields: true} + return jsonpbMarshaler.UnmarshalNext(j, m) +} + +func Unmarshal(j io.Reader, m proto.Message) error { + var jsonpbMarshaler = jsonpb.Unmarshaler{AllowUnknownFields: true} + return jsonpbMarshaler.Unmarshal(j, m) +} diff --git a/apiserver/nacosserver/server.go b/apiserver/nacosserver/server.go index f98460a36..913cc3281 100644 --- a/apiserver/nacosserver/server.go +++ b/apiserver/nacosserver/server.go @@ -199,7 +199,7 @@ func (n *NacosServer) prepareRun() error { nacosv1.WithNamespaceSvr(n.namespaceSvr), nacosv1.WithDiscoverSvr(n.discoverSvr, n.originDiscoverSvr, n.healthSvr), nacosv1.WithConfigSvr(n.configSvr, n.originConfigSvr), - nacosv1.WithAuthSvr(n.userSvr, n.strategySvr), + nacosv1.WithAuthSvr(n.userSvr), ) if err != nil { return err @@ -211,7 +211,7 @@ func (n *NacosServer) prepareRun() error { nacosv2.WithNamespaceSvr(n.namespaceSvr), nacosv2.WithDiscoverSvr(n.discoverSvr, n.originDiscoverSvr, n.healthSvr), nacosv2.WithConfigSvr(n.configSvr, n.originConfigSvr), - nacosv2.WithAuthSvr(n.userSvr, n.strategySvr), + nacosv2.WithAuthSvr(n.userSvr), ) if err != nil { return err diff --git a/apiserver/nacosserver/v1/auth.go b/apiserver/nacosserver/v1/auth.go index 2c1a114f0..6f6f1dd8b 100644 --- a/apiserver/nacosserver/v1/auth.go +++ b/apiserver/nacosserver/v1/auth.go @@ -30,7 +30,8 @@ func (n *NacosV1Server) handleLogin(ctx context.Context, params map[string]strin model.WithFromClient(), model.WithRequestContext(ctx), ) - if err := n.checker.GetAuthChecker().VerifyCredential(authCtx); err != nil { + + if err := n.discoverOpt.UserSvr.CheckCredential(authCtx); err != nil { return nil, err } diff --git a/apiserver/nacosserver/v1/discover/server.go b/apiserver/nacosserver/v1/discover/server.go index 6a4ba1738..b5e9ba1d1 100644 --- a/apiserver/nacosserver/v1/discover/server.go +++ b/apiserver/nacosserver/v1/discover/server.go @@ -33,7 +33,6 @@ type ServerOption struct { // polaris UserSvr auth.UserServer - CheckerSvr auth.StrategyServer NamespaceSvr namespace.NamespaceOperateServer DiscoverSvr service.DiscoverServer OriginDiscoverSvr service.DiscoverServer @@ -64,6 +63,5 @@ func (h *DiscoverServer) Initialize(opt *ServerOption) error { h.originDiscoverSvr = opt.OriginDiscoverSvr h.healthSvr = opt.HealthSvr h.userSvr = opt.UserSvr - h.checkerSvr = opt.CheckerSvr return nil } diff --git a/apiserver/nacosserver/v1/options.go b/apiserver/nacosserver/v1/options.go index 4d4cf6934..2009bb61f 100644 --- a/apiserver/nacosserver/v1/options.go +++ b/apiserver/nacosserver/v1/options.go @@ -64,14 +64,9 @@ func WithConfigSvr(configSvr config.ConfigCenterServer, originSvr config.ConfigC } } -func WithAuthSvr(userSvr auth.UserServer, checkerSvr auth.StrategyServer) option { +func WithAuthSvr(userSvr auth.UserServer) option { return func(svr *NacosV1Server) { - svr.checker = checkerSvr - svr.discoverOpt.UserSvr = userSvr - svr.discoverOpt.CheckerSvr = checkerSvr - svr.configOpt.UserSvr = userSvr - svr.configOpt.CheckerSvr = checkerSvr } } diff --git a/apiserver/nacosserver/v1/server.go b/apiserver/nacosserver/v1/server.go index 8be0f7bae..beb2c5d93 100644 --- a/apiserver/nacosserver/v1/server.go +++ b/apiserver/nacosserver/v1/server.go @@ -88,7 +88,7 @@ type NacosV1Server struct { pushCenter core.PushCenter store *core.NacosDataStorage - checker auth.StrategyServer + checker auth.UserServer discoverOpt *discover.ServerOption discoverSvr *discover.DiscoverServer diff --git a/apiserver/nacosserver/v2/discover/server.go b/apiserver/nacosserver/v2/discover/server.go index 263f9c714..e254ba16e 100644 --- a/apiserver/nacosserver/v2/discover/server.go +++ b/apiserver/nacosserver/v2/discover/server.go @@ -34,7 +34,6 @@ type ServerOption struct { // polaris UserSvr auth.UserServer - CheckerSvr auth.StrategyServer NamespaceSvr namespace.NamespaceOperateServer DiscoverSvr service.DiscoverServer OriginDiscoverSvr service.DiscoverServer diff --git a/apiserver/nacosserver/v2/option.go b/apiserver/nacosserver/v2/option.go index 567cbfada..d6958dba5 100644 --- a/apiserver/nacosserver/v2/option.go +++ b/apiserver/nacosserver/v2/option.go @@ -65,12 +65,9 @@ func WithConfigSvr(configSvr config.ConfigCenterServer, originSvr config.ConfigC } // WithAuthSvr 设置鉴权 Server -func WithAuthSvr(userSvr auth.UserServer, checkerSvr auth.StrategyServer) option { +func WithAuthSvr(userSvr auth.UserServer) option { return func(svr *NacosV2Server) { svr.discoverOpt.UserSvr = userSvr - svr.discoverOpt.CheckerSvr = checkerSvr - svr.configOpt.UserSvr = userSvr - svr.configOpt.CheckerSvr = checkerSvr } } diff --git a/auth/api.go b/auth/api.go index 25b2f4ba2..d7e58090d 100644 --- a/auth/api.go +++ b/auth/api.go @@ -19,6 +19,7 @@ package auth import ( "context" + "fmt" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" @@ -30,10 +31,6 @@ import ( // AuthChecker 权限管理通用接口定义 type AuthChecker interface { - // Initialize 执行初始化动作 - Initialize(options *Config, storage store.Store, cacheMgn cachetypes.CacheManager) error - // VerifyCredential 验证令牌 - VerifyCredential(preCtx *model.AcquireContext) error // CheckClientPermission 执行检查客户端动作判断是否有权限,并且对 RequestContext 注入操作者数据 CheckClientPermission(preCtx *model.AcquireContext) (bool, error) // CheckConsolePermission 执行检查控制台动作判断是否有权限,并且对 RequestContext 注入操作者数据 @@ -44,12 +41,51 @@ type AuthChecker interface { IsOpenClientAuth() bool } +// StrategyServer 策略相关操作 +type StrategyServer interface { + // Initialize 执行初始化动作 + Initialize(options *Config, storage store.Store, cacheMgr cachetypes.CacheManager, userSvr UserServer) error + // Name 策略管理server名称 + Name() string + // CreateStrategy 创建策略 + CreateStrategy(ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response + // UpdateStrategies 批量更新策略 + UpdateStrategies(ctx context.Context, reqs []*apisecurity.ModifyAuthStrategy) *apiservice.BatchWriteResponse + // DeleteStrategies 删除策略 + DeleteStrategies(ctx context.Context, reqs []*apisecurity.AuthStrategy) *apiservice.BatchWriteResponse + // GetStrategies 获取资源列表 + // support 1. 支持按照 principal-id + principal-role 进行查询 + // support 2. 支持普通的鉴权策略查询 + GetStrategies(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse + // GetStrategy 获取策略详细 + GetStrategy(ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response + // GetPrincipalResources 获取某个 principal 的所有可操作资源列表 + GetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response + // GetAuthChecker 获取鉴权检查器 + GetAuthChecker() AuthChecker + // AfterResourceOperation 操作完资源的后置处理逻辑 + AfterResourceOperation(afterCtx *model.AcquireContext) error +} + // UserServer 用户数据管理 server type UserServer interface { // Initialize 初始化 Initialize(authOpt *Config, storage store.Store, cacheMgn cachetypes.CacheManager) error // Name 用户数据管理server名称 Name() string + // Login 登录动作 + Login(req *apisecurity.LoginRequest) *apiservice.Response + // CheckCredential 检查当前操作用户凭证 + CheckCredential(authCtx *model.AcquireContext) error + // UserOperator + UserOperator + // GroupOperator + GroupOperator + // GetUserHelper + GetUserHelper() UserHelper +} + +type UserOperator interface { // CreateUsers 批量创建用户 CreateUsers(ctx context.Context, users []*apisecurity.User) *apiservice.BatchWriteResponse // UpdateUser 更新用户信息 @@ -66,12 +102,8 @@ type UserServer interface { UpdateUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response // ResetUserToken 重置用户的token ResetUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response - // Login 登录动作 - Login(req *apisecurity.LoginRequest) *apiservice.Response - GroupOperator } -// GroupOperator 用户组相关操作 type GroupOperator interface { // CreateGroup 创建用户组 CreateGroup(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response @@ -91,28 +123,59 @@ type GroupOperator interface { ResetGroupToken(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response } -// StrategyServer 策略相关操作 -type StrategyServer interface { - // Initialize 初始化 - Initialize(authOpt *Config, storage store.Store, cacheMgn cachetypes.CacheManager) error - // Name 策略管理server名称 - Name() string - // CreateStrategy 创建策略 - CreateStrategy(ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response - // UpdateStrategies 批量更新策略 - UpdateStrategies(ctx context.Context, reqs []*apisecurity.ModifyAuthStrategy) *apiservice.BatchWriteResponse - // DeleteStrategies 删除策略 - DeleteStrategies(ctx context.Context, reqs []*apisecurity.AuthStrategy) *apiservice.BatchWriteResponse - // GetStrategies 获取资源列表 - // support 1. 支持按照 principal-id + principal-role 进行查询 - // support 2. 支持普通的鉴权策略查询 - GetStrategies(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse - // GetStrategy 获取策略详细 - GetStrategy(ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response - // GetPrincipalResources 获取某个 principal 的所有可操作资源列表 - GetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response - // GetAuthChecker 获取鉴权检查器 - GetAuthChecker() AuthChecker - // AfterResourceOperation 操作完资源的后置处理逻辑 - AfterResourceOperation(afterCtx *model.AcquireContext) error +type UserHelper interface { + // CheckUserInGroup 检查用户是否在用户组中 + CheckUserInGroup(group *apisecurity.UserGroup, user *apisecurity.User) bool + // CheckGroupsExist 批量检查用户组是否存在 + CheckGroupsExist(groups []*apisecurity.UserGroup) error + // CheckUsersExist 批量检查用户是否存在 + CheckUsersExist(users []*apisecurity.User) error + // GetUserOwnGroup 查询某个用户所在的所有用户组 + GetUserOwnGroup(user *apisecurity.User) []*apisecurity.UserGroup + // GetUser 查询用户信息 + GetUser(user *apisecurity.User) *apisecurity.User + // GetGroup 查询用户组信息 + GetGroup(req *apisecurity.UserGroup) *apisecurity.UserGroup +} + +// OperatorInfo 根据 token 解析出来的具体额外信息 +type OperatorInfo struct { + // Origin 原始 token 字符串 + Origin string + // OperatorID 当前 token 绑定的 用户/用户组 ID + OperatorID string + // OwnerID 当前用户/用户组对应的 owner + OwnerID string + // Role 如果当前是 user token 的话,该值才能有信息 + Role model.UserRoleType + // IsUserToken 当前 token 是否是 user 的 token + IsUserToken bool + // Disable 标识用户 token 是否被禁用 + Disable bool + // 是否属于匿名操作者 + Anonymous bool +} + +func NewAnonymous() OperatorInfo { + return OperatorInfo{ + Origin: "", + OwnerID: "", + OperatorID: "__anonymous__", + Anonymous: true, + } +} + +// IsEmptyOperator token 是否是一个空类型 +func IsEmptyOperator(t OperatorInfo) bool { + return t.Origin == "" || t.Anonymous +} + +// IsSubAccount 当前 token 对应的账户类型 +func IsSubAccount(t OperatorInfo) bool { + return t.Role == model.SubAccountUserRole +} + +func (t *OperatorInfo) String() string { + return fmt.Sprintf("operator-id=%s, owner=%s, role=%d, is-user=%v, disable=%v", + t.OperatorID, t.OwnerID, t.Role, t.IsUserToken, t.Disable) } diff --git a/auth/auth.go b/auth/auth.go index 45e984ebf..4d5d6ad58 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -82,9 +82,9 @@ type StrategyConfig struct { var ( // userMgnSlots 保存用户管理manager slot - userMgnSlots = map[string]UserServer{} + userMgrSlots = map[string]UserServer{} // strategyMgnSlots 保存策略管理manager slot - strategyMgnSlots = map[string]StrategyServer{} + strategyMgrSlots = map[string]StrategyServer{} once sync.Once userMgn UserServer strategyMgn StrategyServer @@ -94,11 +94,11 @@ var ( // RegisterUserServer 注册一个新的 UserServer func RegisterUserServer(s UserServer) error { name := s.Name() - if _, ok := userMgnSlots[name]; ok { + if _, ok := userMgrSlots[name]; ok { return fmt.Errorf("UserServer=[%s] exist", name) } - userMgnSlots[name] = s + userMgrSlots[name] = s return nil } @@ -113,11 +113,11 @@ func GetUserServer() (UserServer, error) { // RegisterStrategyServer 注册一个新的 StrategyServer func RegisterStrategyServer(s StrategyServer) error { name := s.Name() - if _, ok := strategyMgnSlots[name]; ok { + if _, ok := strategyMgrSlots[name]; ok { return fmt.Errorf("StrategyServer=[%s] exist", name) } - strategyMgnSlots[name] = s + strategyMgrSlots[name] = s return nil } @@ -151,7 +151,7 @@ func initialize(_ context.Context, authOpt *Config, storage store.Store, return nil, nil, errors.New("UserServer Name is empty") } - namedUserMgn, ok := userMgnSlots[name] + namedUserMgn, ok := userMgrSlots[name] if !ok { return nil, nil, fmt.Errorf("no such UserServer plugin. name(%s)", name) } @@ -165,11 +165,11 @@ func initialize(_ context.Context, authOpt *Config, storage store.Store, return nil, nil, errors.New("StrategyServer Name is empty") } - namedStrategyMgn, ok := strategyMgnSlots[name] + namedStrategyMgn, ok := strategyMgrSlots[name] if !ok { return nil, nil, fmt.Errorf("no such StrategyServer plugin. name(%s)", name) } - if err := namedStrategyMgn.Initialize(authOpt, storage, cacheMgr); err != nil { + if err := namedStrategyMgn.Initialize(authOpt, storage, cacheMgr, namedUserMgn); err != nil { log.Printf("StrategyServer do initialize err: %s", err.Error()) return nil, nil, err } diff --git a/auth/authcheck/access.go b/auth/authcheck/access.go new file mode 100644 index 000000000..fda6cc8a2 --- /dev/null +++ b/auth/authcheck/access.go @@ -0,0 +1,57 @@ +/** + * 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 authcheck + +import ( + "context" + + apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" + apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" +) + +// CreateStrategy 创建鉴权策略 +func (svr *Server) CreateStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { + return svr.handleCreateStrategy(ctx, req) +} + +// UpdateStrategies 批量修改鉴权 +func (svr *Server) UpdateStrategies( + ctx context.Context, reqs []*apisecurity.ModifyAuthStrategy) *apiservice.BatchWriteResponse { + return svr.handleUpdateStrategies(ctx, reqs) +} + +// DeleteStrategies 批量删除鉴权策略 +func (svr *Server) DeleteStrategies( + ctx context.Context, reqs []*apisecurity.AuthStrategy) *apiservice.BatchWriteResponse { + return svr.handleDeleteStrategies(ctx, reqs) +} + +// GetStrategies 批量查询鉴权策略 +func (svr *Server) GetStrategies(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { + return svr.handleGetStrategies(ctx, query) +} + +// GetStrategy 查询单个鉴权策略 +func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { + return svr.handleGetStrategy(ctx, req) +} + +// GetPrincipalResources 查询鉴权策略所属资源 +func (svr *Server) GetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response { + return svr.handleGetPrincipalResources(ctx, query) +} diff --git a/auth/authcheck/auth_checker.go b/auth/authcheck/auth_checker.go new file mode 100644 index 000000000..29a55f0c8 --- /dev/null +++ b/auth/authcheck/auth_checker.go @@ -0,0 +1,222 @@ +/** + * 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 authcheck + +import ( + "github.com/pkg/errors" + apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" + "go.uber.org/zap" + + "github.com/polarismesh/polaris/auth" + 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" + "github.com/polarismesh/polaris/store" +) + +var ( + // ErrorNotAllowedAccess 鉴权失败 + ErrorNotAllowedAccess error = errors.New(api.Code2Info(api.NotAllowedAccess)) + // ErrorInvalidParameter 不合法的参数 + ErrorInvalidParameter error = errors.New(api.Code2Info(api.InvalidParameter)) + // ErrorNotPermission . + ErrorNotPermission = errors.New("no permission") +) + +// DefaultAuthChecker 北极星自带的默认鉴权中心 +type DefaultAuthChecker struct { + conf *AuthConfig + cacheMgr cachetypes.CacheManager + userSvr auth.UserServer +} + +func (d *DefaultAuthChecker) SetCacheMgr(mgr cachetypes.CacheManager) { + d.cacheMgr = mgr +} + +// Initialize 执行初始化动作 +func (d *DefaultAuthChecker) Initialize(conf *AuthConfig, s store.Store, cacheMgr cachetypes.CacheManager, userSvr auth.UserServer) error { + d.conf = conf + d.cacheMgr = cacheMgr + d.userSvr = userSvr + return nil +} + +// Cache 获取缓存统一管理 +func (d *DefaultAuthChecker) Cache() cachetypes.CacheManager { + return d.cacheMgr +} + +// IsOpenConsoleAuth 针对控制台是否开启了操作鉴权 +func (d *DefaultAuthChecker) IsOpenConsoleAuth() bool { + return d.conf.ConsoleOpen +} + +// IsOpenClientAuth 针对客户端是否开启了操作鉴权 +func (d *DefaultAuthChecker) IsOpenClientAuth() bool { + return d.conf.ClientOpen +} + +// IsOpenAuth 返回对于控制台/客户端任意其中的一个是否开启了操作鉴权 +func (d *DefaultAuthChecker) IsOpenAuth() bool { + return d.IsOpenConsoleAuth() || d.IsOpenClientAuth() +} + +// CheckClientPermission 执行检查客户端动作判断是否有权限,并且对 RequestContext 注入操作者数据 +func (d *DefaultAuthChecker) CheckClientPermission(preCtx *model.AcquireContext) (bool, error) { + preCtx.SetFromClient() + if !d.IsOpenClientAuth() { + return true, nil + } + if d.IsOpenClientAuth() && !d.conf.ClientStrict { + preCtx.SetAllowAnonymous(true) + } + return d.CheckPermission(preCtx) +} + +// CheckConsolePermission 执行检查控制台动作判断是否有权限,并且对 RequestContext 注入操作者数据 +func (d *DefaultAuthChecker) CheckConsolePermission(preCtx *model.AcquireContext) (bool, error) { + preCtx.SetFromConsole() + if !d.IsOpenConsoleAuth() { + return true, nil + } + if d.IsOpenConsoleAuth() && !d.conf.ConsoleStrict { + preCtx.SetAllowAnonymous(true) + } + if preCtx.GetModule() == model.MaintainModule { + return d.checkMaintainPermission(preCtx) + } + return d.CheckPermission(preCtx) +} + +// CheckMaintainPermission 执行检查运维动作判断是否有权限 +func (d *DefaultAuthChecker) checkMaintainPermission(preCtx *model.AcquireContext) (bool, error) { + if preCtx.GetOperation() == model.Read { + return true, nil + } + + attachVal, ok := preCtx.GetAttachment(model.TokenDetailInfoKey) + if !ok { + return false, model.ErrorTokenNotExist + } + tokenInfo, ok := attachVal.(auth.OperatorInfo) + if !ok { + return false, model.ErrorTokenNotExist + } + + if tokenInfo.Disable { + return false, model.ErrorTokenDisabled + } + if !tokenInfo.IsUserToken { + return false, errors.New("only user role can access maintain API") + } + if tokenInfo.Role != model.OwnerUserRole { + return false, errors.New("only owner account can access maintain API") + } + return true, nil +} + +// CheckPermission 执行检查动作判断是否有权限 +// +// step 1. 判断是否开启了鉴权 +// step 2. 对token进行检查判断 +// case 1. 如果 token 被禁用 +// a. 读操作,直接放通 +// b. 写操作,快速失败 +// step 3. 拉取token对应的操作者相关信息,注入到请求上下文中 +// step 4. 进行权限检查 +func (d *DefaultAuthChecker) CheckPermission(authCtx *model.AcquireContext) (bool, error) { + if err := d.userSvr.CheckCredential(authCtx); err != nil { + return false, err + } + + attachVal, ok := authCtx.GetAttachment(model.TokenDetailInfoKey) + if !ok { + return false, model.ErrorTokenNotExist + } + operatorInfo, ok := attachVal.(auth.OperatorInfo) + if !ok { + return false, model.ErrorTokenNotExist + } + // 这里需要检查当 token 被禁止的情况,如果 token 被禁止,无论是否可以操作目标资源,都无法进行写操作 + if operatorInfo.Disable { + return false, model.ErrorTokenDisabled + } + + log.Debug("[Auth][Checker] check permission args", utils.RequestID(authCtx.GetRequestContext()), + zap.String("method", authCtx.GetMethod()), zap.Any("resources", authCtx.GetAccessResources())) + + if pass, _ := d.doCheckPermission(authCtx); pass { + return ok, nil + } + + // 强制同步一次db中strategy数据到cache + if err := d.cacheMgr.AuthStrategy().ForceSync(); err != nil { + log.Error("[Auth][Checker] force sync strategy to cache failed", + utils.RequestID(authCtx.GetRequestContext()), zap.Error(err)) + return false, err + } + return d.doCheckPermission(authCtx) +} + +// doCheckPermission 执行权限检查 +func (d *DefaultAuthChecker) doCheckPermission(authCtx *model.AcquireContext) (bool, error) { + + var checkNamespace, checkSvc, checkCfgGroup bool + + reqRes := authCtx.GetAccessResources() + nsResEntries := reqRes[apisecurity.ResourceType_Namespaces] + svcResEntries := reqRes[apisecurity.ResourceType_Services] + cfgResEntries := reqRes[apisecurity.ResourceType_ConfigGroups] + + principleID, _ := authCtx.GetAttachments()[model.OperatorIDKey].(string) + principleType, _ := authCtx.GetAttachments()[model.OperatorPrincipalType].(model.PrincipalType) + p := model.Principal{ + PrincipalID: principleID, + PrincipalRole: principleType, + } + checkNamespace = d.checkAction(p, apisecurity.ResourceType_Namespaces, nsResEntries, authCtx) + checkSvc = d.checkAction(p, apisecurity.ResourceType_Services, svcResEntries, authCtx) + checkCfgGroup = d.checkAction(p, apisecurity.ResourceType_ConfigGroups, cfgResEntries, authCtx) + + checkAllResEntries := checkNamespace && checkSvc && checkCfgGroup + + var err error + if !checkAllResEntries { + err = ErrorNotPermission + } + return checkAllResEntries, err +} + +// checkAction 检查操作是否和策略匹配 +func (d *DefaultAuthChecker) checkAction(principal model.Principal, + resType apisecurity.ResourceType, resources []model.ResourceEntry, ctx *model.AcquireContext) bool { + // TODO 后续可针对读写操作进行鉴权, 并且可以针对具体的方法调用进行鉴权控制 + + switch ctx.GetOperation() { + case model.Read: + default: + for _, entry := range resources { + if !d.cacheMgr.AuthStrategy().IsResourceEditable(principal, resType, entry.ID) { + return false + } + } + } + return true +} diff --git a/auth/defaultauth/auth_checker_test.go b/auth/authcheck/auth_checker_test.go similarity index 77% rename from auth/defaultauth/auth_checker_test.go rename to auth/authcheck/auth_checker_test.go index 949ba90b7..50ae3202e 100644 --- a/auth/defaultauth/auth_checker_test.go +++ b/auth/authcheck/auth_checker_test.go @@ -15,199 +15,24 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package authcheck_test import ( "context" - "errors" "testing" - "time" "github.com/golang/mock/gomock" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" "github.com/stretchr/testify/assert" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/auth/defaultauth" + "github.com/polarismesh/polaris/auth/authcheck" "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" ) -func Test_defaultAuthManager_ParseToken(t *testing.T) { - defaultauth.AuthOption.Salt = "polaris@a7b068ce3235442b" - token := "orRm9Zt7sMqQaAM5b7yHLXnhWsr5dfPT0jpRlQ+C0tdy2UmuDa/X3uFG" - - authMgn := &defaultauth.DefaultAuthChecker{} - - tokenInfo, err := authMgn.DecodeToken(token) - - if err != nil { - t.Fatal(err) - } - - t.Logf("%#v", tokenInfo) -} - -func Test_DefaultAuthChecker_VerifyCredential(t *testing.T) { - reset(false) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - users := createMockUser(10) - - storage := storemock.NewMockStore(ctrl) - - storage.EXPECT().GetServicesCount().AnyTimes().Return(uint32(1), nil) - storage.EXPECT().GetUnixSecond(gomock.Any()).AnyTimes().Return(time.Now().Unix(), nil) - storage.EXPECT().GetUsersForCache(gomock.Any(), gomock.Any()).AnyTimes().Return(users, nil) - storage.EXPECT().GetGroupsForCache(gomock.Any(), gomock.Any()).AnyTimes().Return([]*model.UserGroupDetail{}, nil) - - ctx, cancel := context.WithCancel(context.Background()) - cacheMgn, err := cache.TestCacheInitialize(ctx, &cache.Config{}, storage) - if err != nil { - t.Fatal(err) - } - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ - { - Name: cachetypes.UsersName, - }, - }...) - - _ = cacheMgn.TestUpdate() - - t.Cleanup(func() { - cancel() - cacheMgn.Close() - }) - - checker := &defaultauth.DefaultAuthChecker{} - checker.Initialize(&auth.Config{ - User: &auth.UserConfig{ - Name: "", - Option: map[string]interface{}{}, - }, - Strategy: &auth.StrategyConfig{ - Name: "", - Option: map[string]interface{}{ - "": nil, - }, - }, - }, storage, cacheMgn) - checker.SetCacheMgr(cacheMgn) - - t.Run("主账户正常情况", func(t *testing.T) { - reset(false) - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[0].Token) - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.NoError(t, err, "Should be verify success") - assert.Equal(t, users[0].ID, utils.ParseUserID(authCtx.GetRequestContext()), "user-id should be equal") - assert.True(t, utils.ParseIsOwner(authCtx.GetRequestContext()), "should be owner") - }) - - t.Run("子账户在Token被禁用情况下", func(t *testing.T) { - reset(false) - users[1].TokenEnable = false - // 让 cache 可以刷新到 - time.Sleep(time.Second) - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[1].Token) - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.NoError(t, err, "Should be verify success") - assert.Equal(t, users[1].ID, utils.ParseUserID(authCtx.GetRequestContext()), "user-id should be equal") - assert.False(t, utils.ParseIsOwner(authCtx.GetRequestContext()), "should not be owner") - assert.True(t, authCtx.GetAttachments()[model.TokenDetailInfoKey].(defaultauth.OperatorInfo).Disable, "should be disable") - }) - - t.Run("权限检查非严格模式-错误的token字符串-降级为匿名用户", func(t *testing.T) { - reset(false) - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_VerifyCredential") - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.NoError(t, err, "Should be verify success") - assert.True(t, authCtx.GetAttachments()[model.TokenDetailInfoKey].(defaultauth.OperatorInfo).Anonymous, "should be anonymous") - }) - - t.Run("权限检查非严格模式-空token字符串-降级为匿名用户", func(t *testing.T) { - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.NoError(t, err, "Should be verify success") - assert.True(t, authCtx.GetAttachments()[model.TokenDetailInfoKey].(defaultauth.OperatorInfo).Anonymous, "should be anonymous") - }) - - t.Run("权限检查非严格模式-错误的token字符串-访问鉴权模块", func(t *testing.T) { - reset(false) - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_VerifyCredential") - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithModule(model.AuthModule), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.Error(t, err, "Should be verify fail") - }) - - t.Run("权限检查非严格模式-空token字符串-访问鉴权模块", func(t *testing.T) { - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithModule(model.AuthModule), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.Error(t, err, "Should be verify fail") - }) - - t.Run("权限检查严格模式-token非法-不允许降级为匿名用户", func(t *testing.T) { - reset(true) - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_VerifyCredential") - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.Error(t, err, "Should be verify fail") - assert.True(t, errors.Is(err, model.ErrorTokenInvalid), "should be token-invalid error") - }) - - t.Run("权限检查严格模式-token为空-不允许降级为匿名用户", func(t *testing.T) { - reset(true) - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - ) - err = checker.VerifyCredential(authCtx) - t.Logf("%+v", err) - assert.Error(t, err, "Should be verify fail") - assert.True(t, errors.Is(err, model.ErrorTokenInvalid), "should be token-invalid error") - }) -} - func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { reset(false) ctrl := gomock.NewController(t) @@ -230,11 +55,11 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { storage.EXPECT().GetMoreServices(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(serviceMap, nil) ctx, cancel := context.WithCancel(context.Background()) - cacheMgn, err := cache.TestCacheInitialize(ctx, cfg, storage) + cacheMgr, err := cache.TestCacheInitialize(ctx, cfg, storage) if err != nil { t.Fatal(err) } - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ { Name: cachetypes.UsersName, }, @@ -242,15 +67,15 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { Name: cachetypes.StrategyRuleName, }, }...) - _ = cacheMgn.TestUpdate() + _ = cacheMgr.TestUpdate() t.Cleanup(func() { cancel() - cacheMgn.Close() + cacheMgr.Close() }) - checker := &defaultauth.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgn) + checker := &authcheck.DefaultAuthChecker{} + checker.SetCacheMgr(cacheMgr) freeIndex := len(users) + len(groups) + 1 @@ -476,17 +301,17 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { storage.EXPECT().GetMoreServices(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(serviceMap, nil) ctx, cancel := context.WithCancel(context.Background()) - cacheMgn, err := cache.TestCacheInitialize(ctx, cfg, storage) + cacheMgr, err := cache.TestCacheInitialize(ctx, cfg, storage) if err != nil { t.Fatal(err) } t.Cleanup(func() { cancel() - cacheMgn.Close() + cacheMgr.Close() }) - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ { Name: cachetypes.UsersName, }, @@ -494,10 +319,10 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { Name: cachetypes.StrategyRuleName, }, }...) - _ = cacheMgn.TestUpdate() + _ = cacheMgr.TestUpdate() - checker := &defaultauth.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgn) + checker := &authcheck.DefaultAuthChecker{} + checker.SetCacheMgr(cacheMgr) freeIndex := len(users) + len(groups) + 1 @@ -505,7 +330,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[0].Token) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken(users[0].Token), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -528,7 +353,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[1].Token) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken(users[1].Token), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -550,7 +375,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[1].Token) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken(users[1].Token), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -569,10 +394,10 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }) t.Run("权限检查严格模式-token非法-匿名账户操作资源(资源有策略)", func(t *testing.T) { - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_VerifyCredential") + ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_CheckPermission_Write_Strict") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken("Test_DefaultAuthChecker_VerifyCredential"), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -594,7 +419,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken(""), model.WithModule(model.DiscoverModule), model.WithOperation(model.Create), @@ -613,10 +438,10 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }) t.Run("权限检查严格模式-token非法-匿名账户操作资源(资源没有策略)", func(t *testing.T) { - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_VerifyCredential") + ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_CheckPermission_Write_Strict") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken("Test_DefaultAuthChecker_VerifyCredential"), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -638,7 +463,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), + model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), // model.WithToken(""), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -679,16 +504,16 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { storage.EXPECT().GetMoreServices(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(serviceMap, nil) ctx, cancel := context.WithCancel(context.Background()) - cacheMgn, err := cache.TestCacheInitialize(ctx, cfg, storage) + cacheMgr, err := cache.TestCacheInitialize(ctx, cfg, storage) if err != nil { t.Fatal(err) } t.Cleanup(func() { cancel() - cacheMgn.Close() + cacheMgr.Close() }) - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ { Name: cachetypes.UsersName, }, @@ -696,10 +521,10 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { Name: cachetypes.StrategyRuleName, }, }...) - _ = cacheMgn.TestUpdate() + _ = cacheMgr.TestUpdate() - checker := &defaultauth.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgn) + checker := &authcheck.DefaultAuthChecker{} + checker.SetCacheMgr(cacheMgr) freeIndex := len(users) + len(groups) + 1 @@ -902,16 +727,16 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { storage.EXPECT().GetMoreServices(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(serviceMap, nil) ctx, cancel := context.WithCancel(context.Background()) - cacheMgn, err := cache.TestCacheInitialize(ctx, cfg, storage) + cacheMgr, err := cache.TestCacheInitialize(ctx, cfg, storage) if err != nil { t.Fatal(err) } t.Cleanup(func() { cancel() - cacheMgn.Close() + cacheMgr.Close() }) - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ { Name: cachetypes.UsersName, }, @@ -919,10 +744,10 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { Name: cachetypes.StrategyRuleName, }, }...) - _ = cacheMgn.TestUpdate() + _ = cacheMgr.TestUpdate() - checker := &defaultauth.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgn) + checker := &authcheck.DefaultAuthChecker{} + checker.SetCacheMgr(cacheMgr) freeIndex := len(users) + len(groups) + 1 @@ -1104,37 +929,12 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { } func Test_DefaultAuthChecker_Initialize(t *testing.T) { - reset(true) - ctrl := gomock.NewController(t) defer ctrl.Finish() - users := createMockUser(10) - - storage := storemock.NewMockStore(ctrl) - storage.EXPECT().GetUnixSecond(gomock.Any()).AnyTimes().Return(time.Now().Unix(), nil) - storage.EXPECT().GetUsersForCache(gomock.Any(), gomock.Any()).AnyTimes().Return(users, nil) - storage.EXPECT().GetGroupsForCache(gomock.Any(), gomock.Any()).AnyTimes().Return([]*model.UserGroupDetail{}, nil) - - ctx, cancel := context.WithCancel(context.Background()) - cacheMgn, err := cache.TestCacheInitialize(ctx, &cache.Config{}, storage) - if err != nil { - t.Fatal(err) - } - - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ - { - Name: cachetypes.UsersName, - }, - }...) - t.Cleanup(func() { - cancel() - cacheMgn.Close() - }) - t.Run("使用未迁移至auth.user.option及auth.strategy.option的配置", func(t *testing.T) { reset(true) - authChecker := &defaultauth.DefaultAuthChecker{} + authChecker := &authcheck.Server{} cfg := &auth.Config{} cfg.SetDefault() cfg.Name = "" @@ -1144,21 +944,20 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { "salt": "polarismesh@2021", "strict": false, } - err := authChecker.Initialize(cfg, storage, cacheMgn) + err := authChecker.ParseOptions(cfg) assert.NoError(t, err) - assert.Equal(t, &defaultauth.AuthConfig{ + assert.Equal(t, &authcheck.AuthConfig{ ConsoleOpen: true, ClientOpen: true, - Salt: "polarismesh@2021", Strict: false, ConsoleStrict: true, ClientStrict: false, - }, defaultauth.AuthOption) + }, authChecker.GetOptions()) }) t.Run("使用完全迁移至auth.user.option及auth.strategy.option的配置", func(t *testing.T) { reset(true) - authChecker := &defaultauth.DefaultAuthChecker{} + authChecker := &authcheck.Server{} cfg := &auth.Config{} cfg.SetDefault() @@ -1175,20 +974,19 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { }, } - err := authChecker.Initialize(cfg, storage, cacheMgn) + err := authChecker.ParseOptions(cfg) assert.NoError(t, err) - assert.Equal(t, &defaultauth.AuthConfig{ + assert.Equal(t, &authcheck.AuthConfig{ ConsoleOpen: true, ClientOpen: true, - Salt: "polarismesh@2021", Strict: false, ConsoleStrict: true, - }, defaultauth.AuthOption) + }, authChecker.GetOptions()) }) t.Run("使用部分迁移至auth.user.option及auth.strategy.option的配置(应当报错)", func(t *testing.T) { reset(true) - authChecker := &defaultauth.DefaultAuthChecker{} + authChecker := &authcheck.Server{} cfg := &auth.Config{} cfg.SetDefault() cfg.Name = "" @@ -1207,7 +1005,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { }, } - err := authChecker.Initialize(cfg, storage, cacheMgn) + err := authChecker.ParseOptions(cfg) assert.NoError(t, err) }) diff --git a/auth/defaultauth/common_test.go b/auth/authcheck/common_test.go similarity index 95% rename from auth/defaultauth/common_test.go rename to auth/authcheck/common_test.go index 5e6a067dc..015ce359f 100644 --- a/auth/defaultauth/common_test.go +++ b/auth/authcheck/common_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package authcheck_test import ( "fmt" @@ -28,7 +28,7 @@ import ( "golang.org/x/crypto/bcrypt" "google.golang.org/protobuf/types/known/wrapperspb" - "github.com/polarismesh/polaris/auth/defaultauth" + defaultuser "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/cache" "github.com/polarismesh/polaris/common/metrics" "github.com/polarismesh/polaris/common/model" @@ -37,12 +37,7 @@ import ( ) func reset(strict bool) { - defaultauth.AuthOption = defaultauth.DefaultAuthConfig() - defaultauth.AuthOption.ClientOpen = true - defaultauth.AuthOption.ConsoleOpen = true - defaultauth.AuthOption.Strict = strict - defaultauth.AuthOption.ConsoleStrict = strict - defaultauth.AuthOption.ClientStrict = strict + } func initCache(ctrl *gomock.Controller) (*cache.Config, *storemock.MockStore) { @@ -121,131 +116,6 @@ func createMockService(namespaces []*model.Namespace) []*model.Service { return services } -// createMockUser 默认 users[0] 为 owner 用户 -func createMockUser(total int, prefix ...string) []*model.User { - users := make([]*model.User, 0, total) - - ownerId := utils.NewUUID() - - nameTemp := "user-%d" - if len(prefix) != 0 { - nameTemp = prefix[0] + nameTemp - } - - for i := 0; i < total; i++ { - id := fmt.Sprintf("fake-user-id-%d-%s", i, utils.NewUUID()) - if i == 0 { - id = ownerId - } - pwd, _ := bcrypt.GenerateFromPassword([]byte("polaris"), bcrypt.DefaultCost) - token, _ := defaultauth.TestCreateToken(id, "") - users = append(users, &model.User{ - ID: id, - Name: fmt.Sprintf(nameTemp, i), - Password: string(pwd), - Owner: func() string { - if id == ownerId { - return "" - } - return ownerId - }(), - Source: "Polaris", - Mobile: "", - Email: "", - Type: func() model.UserRoleType { - if id == ownerId { - return model.OwnerUserRole - } - return model.SubAccountUserRole - }(), - Token: token, - TokenEnable: true, - Valid: true, - CreateTime: time.Time{}, - ModifyTime: time.Time{}, - }) - } - return users -} - -func createApiMockUser(total int, prefix ...string) []*apisecurity.User { - users := make([]*apisecurity.User, 0, total) - - models := createMockUser(total, prefix...) - - for i := range models { - users = append(users, &apisecurity.User{ - Name: utils.NewStringValue("test-" + models[i].Name), - Password: utils.NewStringValue("123456"), - Source: utils.NewStringValue("Polaris"), - Comment: utils.NewStringValue(models[i].Comment), - Mobile: utils.NewStringValue(models[i].Mobile), - Email: utils.NewStringValue(models[i].Email), - }) - } - - return users -} - -func createMockUserGroup(users []*model.User) []*model.UserGroupDetail { - groups := make([]*model.UserGroupDetail, 0, len(users)) - - for i := range users { - user := users[i] - id := utils.NewUUID() - - token, _ := defaultauth.TestCreateToken("", id) - - groups = append(groups, &model.UserGroupDetail{ - UserGroup: &model.UserGroup{ - ID: id, - Name: fmt.Sprintf("test-group-%d", i), - Owner: users[0].ID, - Token: token, - TokenEnable: true, - Valid: true, - Comment: "", - CreateTime: time.Time{}, - ModifyTime: time.Time{}, - }, - UserIds: map[string]struct{}{ - user.ID: {}, - }, - }) - } - - return groups -} - -// createMockApiUserGroup -func createMockApiUserGroup(users []*apisecurity.User) []*apisecurity.UserGroup { - musers := make([]*model.User, 0, len(users)) - for i := range users { - musers = append(musers, &model.User{ - ID: users[i].GetId().GetValue(), - }) - } - - models := createMockUserGroup(musers) - ret := make([]*apisecurity.UserGroup, 0, len(models)) - - for i := range models { - ret = append(ret, &apisecurity.UserGroup{ - Name: utils.NewStringValue(models[i].Name), - Comment: utils.NewStringValue(models[i].Comment), - Relation: &apisecurity.UserGroupRelation{ - Users: []*apisecurity.User{ - { - Id: utils.NewStringValue(users[i].GetId().GetValue()), - }, - }, - }, - }) - } - - return ret -} - func createMockStrategy(users []*model.User, groups []*model.UserGroupDetail, services []*model.Service) ([]*model.StrategyDetail, []*model.StrategyDetail) { strategies := make([]*model.StrategyDetail, 0, len(users)+len(groups)) defaultStrategies := make([]*model.StrategyDetail, 0, len(users)+len(groups)) @@ -406,3 +276,127 @@ func convertServiceSliceToMap(services []*model.Service) map[string]*model.Servi return ret } + +// createMockUser 默认 users[0] 为 owner 用户 +func createMockUser(total int, prefix ...string) []*model.User { + users := make([]*model.User, 0, total) + + ownerId := utils.NewUUID() + + nameTemp := "user-%d" + if len(prefix) != 0 { + nameTemp = prefix[0] + nameTemp + } + + for i := 0; i < total; i++ { + id := fmt.Sprintf("fake-user-id-%d-%s", i, utils.NewUUID()) + if i == 0 { + id = ownerId + } + pwd, _ := bcrypt.GenerateFromPassword([]byte("polaris"), bcrypt.DefaultCost) + token, _ := defaultuser.CreateToken(id, "", "polarismesh@2021") + users = append(users, &model.User{ + ID: id, + Name: fmt.Sprintf(nameTemp, i), + Password: string(pwd), + Owner: func() string { + if id == ownerId { + return "" + } + return ownerId + }(), + Source: "Polaris", + Mobile: "", + Email: "", + Type: func() model.UserRoleType { + if id == ownerId { + return model.OwnerUserRole + } + return model.SubAccountUserRole + }(), + Token: token, + TokenEnable: true, + Valid: true, + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }) + } + return users +} + +func createApiMockUser(total int, prefix ...string) []*apisecurity.User { + users := make([]*apisecurity.User, 0, total) + + models := createMockUser(total, prefix...) + + for i := range models { + users = append(users, &apisecurity.User{ + Name: utils.NewStringValue("test-" + models[i].Name), + Password: utils.NewStringValue("123456"), + Source: utils.NewStringValue("Polaris"), + Comment: utils.NewStringValue(models[i].Comment), + Mobile: utils.NewStringValue(models[i].Mobile), + Email: utils.NewStringValue(models[i].Email), + }) + } + + return users +} + +func createMockUserGroup(users []*model.User) []*model.UserGroupDetail { + groups := make([]*model.UserGroupDetail, 0, len(users)) + + for i := range users { + user := users[i] + id := utils.NewUUID() + + token, _ := defaultuser.CreateToken("", id, "polarismesh@2021") + groups = append(groups, &model.UserGroupDetail{ + UserGroup: &model.UserGroup{ + ID: id, + Name: fmt.Sprintf("test-group-%d", i), + Owner: users[0].ID, + Token: token, + TokenEnable: true, + Valid: true, + Comment: "", + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }, + UserIds: map[string]struct{}{ + user.ID: {}, + }, + }) + } + + return groups +} + +// createMockApiUserGroup +func createMockApiUserGroup(users []*apisecurity.User) []*apisecurity.UserGroup { + musers := make([]*model.User, 0, len(users)) + for i := range users { + musers = append(musers, &model.User{ + ID: users[i].GetId().GetValue(), + }) + } + + models := createMockUserGroup(musers) + ret := make([]*apisecurity.UserGroup, 0, len(models)) + + for i := range models { + ret = append(ret, &apisecurity.UserGroup{ + Name: utils.NewStringValue(models[i].Name), + Comment: utils.NewStringValue(models[i].Comment), + Relation: &apisecurity.UserGroupRelation{ + Users: []*apisecurity.User{ + { + Id: utils.NewStringValue(users[i].GetId().GetValue()), + }, + }, + }, + }) + } + + return ret +} diff --git a/auth/defaultauth/default.go b/auth/authcheck/default.go similarity index 86% rename from auth/defaultauth/default.go rename to auth/authcheck/default.go index 57e02248c..00802e4d3 100644 --- a/auth/defaultauth/default.go +++ b/auth/authcheck/default.go @@ -15,13 +15,12 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package authcheck import ( "github.com/polarismesh/polaris/auth" ) func init() { - _ = auth.RegisterUserServer(&UserAuthAbility{}) - _ = auth.RegisterStrategyServer(&StrategyAuthAbility{}) + _ = auth.RegisterStrategyServer(&Server{}) } diff --git a/auth/defaultauth/log.go b/auth/authcheck/log.go similarity index 97% rename from auth/defaultauth/log.go rename to auth/authcheck/log.go index 17443d8b0..ea38f3138 100644 --- a/auth/defaultauth/log.go +++ b/auth/authcheck/log.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package authcheck import commonlog "github.com/polarismesh/polaris/common/log" diff --git a/auth/defaultauth/main_test.go b/auth/authcheck/main_test.go similarity index 99% rename from auth/defaultauth/main_test.go rename to auth/authcheck/main_test.go index 467d4aea5..954abc910 100644 --- a/auth/defaultauth/main_test.go +++ b/auth/authcheck/main_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package authcheck_test import ( "errors" diff --git a/auth/defaultauth/server.go b/auth/authcheck/server.go similarity index 66% rename from auth/defaultauth/server.go rename to auth/authcheck/server.go index 48a1efe9f..ae5446e5a 100644 --- a/auth/defaultauth/server.go +++ b/auth/authcheck/server.go @@ -15,87 +15,135 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package authcheck import ( + "encoding/json" "errors" "fmt" "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" "go.uber.org/zap" - "golang.org/x/crypto/bcrypt" + "google.golang.org/protobuf/types/known/wrapperspb" - "github.com/polarismesh/polaris/cache" + "github.com/polarismesh/polaris/auth" 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" "github.com/polarismesh/polaris/plugin" "github.com/polarismesh/polaris/store" ) -func NewServer(storage store.Store, - history plugin.History, - cacheMgn *cache.CacheManager, - authMgn *DefaultAuthChecker) *Server { - return &Server{ - storage: storage, - history: history, - cacheMgn: cacheMgn, - authMgn: authMgn, +// AuthConfig 鉴权配置 +type AuthConfig struct { + // ConsoleOpen 控制台是否开启鉴权 + ConsoleOpen bool `json:"consoleOpen" xml:"consoleOpen"` + // ClientOpen 是否开启客户端接口鉴权 + ClientOpen bool `json:"clientOpen" xml:"clientOpen"` + // Strict 是否启用鉴权的严格模式,即对于没有任何鉴权策略的资源,也必须带上正确的token才能操作, 默认关闭 + // Deprecated + Strict bool `json:"strict"` + // ConsoleStrict 是否启用鉴权的严格模式,即对于没有任何鉴权策略的资源,也必须带上正确的token才能操作, 默认关闭 + ConsoleStrict bool `json:"consoleStrict"` + // ClientStrict 是否启用鉴权的严格模式,即对于没有任何鉴权策略的资源,也必须带上正确的token才能操作, 默认关闭 + ClientStrict bool `json:"clientStrict"` +} + +// DefaultAuthConfig 返回一个默认的鉴权配置 +func DefaultAuthConfig() *AuthConfig { + return &AuthConfig{ + // 针对控制台接口,默认开启鉴权操作 + ConsoleOpen: true, + // 针对客户端接口,默认不开启鉴权操作 + ClientOpen: false, + // 这里默认开启 OpenAPI 的强 Token 检查模式 + ConsoleStrict: true, + // 客户端接口默认不开启 token 强检查模式 + ClientStrict: false, } } type Server struct { + options *AuthConfig storage store.Store history plugin.History - cacheMgn cachetypes.CacheManager - authMgn *DefaultAuthChecker + cacheMgr cachetypes.CacheManager + checker *DefaultAuthChecker + userSvr auth.UserServer } // initialize -func (svr *Server) initialize() error { +func (svr *Server) Initialize(options *auth.Config, storage store.Store, cacheMgr cachetypes.CacheManager, userSvr auth.UserServer) error { + svr.cacheMgr = cacheMgr + svr.userSvr = userSvr + svr.storage = storage + if err := svr.ParseOptions(options); err != nil { + return err + } + + _ = cacheMgr.OpenResourceCache(cachetypes.ConfigEntry{ + Name: cachetypes.StrategyRuleName, + }) // 获取History插件,注意:插件的配置在bootstrap已经设置好 svr.history = plugin.GetHistory() if svr.history == nil { log.Warnf("Not Found History Log Plugin") } - return nil } -// Login 登录动作 -func (svr *Server) Login(req *apisecurity.LoginRequest) *apiservice.Response { - username := req.GetName().GetValue() - ownerName := req.GetOwner().GetValue() - if ownerName == "" { - ownerName = username - } - user := svr.cacheMgn.User().GetUserByName(username, ownerName) - if user == nil { - return api.NewAuthResponse(apimodel.Code_NotFoundUser) - } +func (svr *Server) GetOptions() *AuthConfig { + return svr.options +} - // TODO AES 解密操作,在进行密码比对计算 - err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.GetPassword().GetValue())) - if err != nil { - if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { - return api.NewAuthResponseWithMsg( - apimodel.Code_NotAllowedAccess, model.ErrorWrongUsernameOrPassword.Error()) +func (svr *Server) ParseOptions(options *auth.Config) error { + // 新版本鉴权策略配置均从auth.Option中迁移至auth.user.option及auth.strategy.option中 + var ( + strategyContentBytes []byte + authContentBytes []byte + err error + ) + + cfg := DefaultAuthConfig() + + // 一旦设置了auth.user.option或auth.strategy.option,将不会继续读取auth.option + if len(options.Strategy.Option) > 0 || len(options.User.Option) > 0 { + // 判断auth.option是否还有值,有则不兼容 + if len(options.Option) > 0 { + log.Warn("auth.user.option or auth.strategy.option has set, auth.option will ignore") + } + strategyContentBytes, err = json.Marshal(options.Strategy.Option) + if err != nil { + return err + } + if err := json.Unmarshal(strategyContentBytes, cfg); err != nil { + return err + } + } else { + log.Warn("[Auth][Checker] auth.option has deprecated, use auth.user.option and auth.strategy.option instead.") + authContentBytes, err = json.Marshal(options.Option) + if err != nil { + return err + } + if err := json.Unmarshal(authContentBytes, cfg); err != nil { + return err } - return api.NewAuthResponseWithMsg(apimodel.Code_ExecuteException, model.ErrorWrongUsernameOrPassword.Error()) } + // 兼容原本老的配置逻辑 + if cfg.Strict { + cfg.ConsoleOpen = cfg.Strict + } + svr.options = cfg + return nil +} - return api.NewLoginResponse(apimodel.Code_ExecuteSuccess, &apisecurity.LoginResponse{ - UserId: utils.NewStringValue(user.ID), - OwnerId: utils.NewStringValue(user.Owner), - Token: utils.NewStringValue(user.Token), - Name: utils.NewStringValue(user.Name), - Role: utils.NewStringValue(model.UserRoleNames[user.Type]), - }) +func (svr *Server) Name() string { + return auth.DefaultStrategyMgnPluginName +} + +func (svr *Server) GetAuthChecker() auth.AuthChecker { + return svr.checker } // RecordHistory Server对外提供history插件的简单封装 @@ -116,16 +164,16 @@ func (svr *Server) RecordHistory(entry *model.RecordEntry) { // AfterResourceOperation 对于资源的添加删除操作,需要执行后置逻辑 // 所有子用户或者用户分组,都默认获得对所创建的资源的写权限 func (svr *Server) AfterResourceOperation(afterCtx *model.AcquireContext) error { - if !svr.authMgn.IsOpenAuth() || afterCtx.GetOperation() == model.Read { + if !svr.checker.IsOpenAuth() || afterCtx.GetOperation() == model.Read { return nil } // 如果客户端鉴权没有开启,且请求来自客户端,忽略 - if afterCtx.IsFromClient() && !svr.authMgn.IsOpenClientAuth() { + if afterCtx.IsFromClient() && !svr.checker.IsOpenClientAuth() { return nil } // 如果控制台鉴权没有开启,且请求来自控制台,忽略 - if afterCtx.IsFromConsole() && !svr.authMgn.IsOpenConsoleAuth() { + if afterCtx.IsFromConsole() && !svr.checker.IsOpenConsoleAuth() { return nil } @@ -133,13 +181,13 @@ func (svr *Server) AfterResourceOperation(afterCtx *model.AcquireContext) error if !ok { return nil } - tokenInfo, ok := attachVal.(OperatorInfo) + tokenInfo, ok := attachVal.(auth.OperatorInfo) if !ok { return nil } // 如果 token 信息为空,则代表当前创建的资源,任何人都可以进行操作,不做资源的后置逻辑处理 - if IsEmptyOperator(tokenInfo) { + if auth.IsEmptyOperator(tokenInfo) { return nil } @@ -191,21 +239,22 @@ func (svr *Server) AfterResourceOperation(afterCtx *model.AcquireContext) error func (svr *Server) handleUserStrategy(userIds []string, afterCtx *model.AcquireContext, isRemove bool) error { for index := range utils.StringSliceDeDuplication(userIds) { userId := userIds[index] - user := svr.cacheMgn.User().GetUserByID(userId) + user := svr.userSvr.GetUserHelper().GetUser(&apisecurity.User{ + Id: wrapperspb.String(userId), + }) if user == nil { return errors.New("not found target user") } - ownerId := user.Owner + ownerId := user.GetOwner().GetValue() if ownerId == "" { - ownerId = user.ID + ownerId = user.GetId().GetValue() } if err := svr.handlerModifyDefaultStrategy(userId, ownerId, model.PrincipalUser, afterCtx, isRemove); err != nil { return err } } - return nil } @@ -213,12 +262,13 @@ func (svr *Server) handleUserStrategy(userIds []string, afterCtx *model.AcquireC func (svr *Server) handleGroupStrategy(groupIds []string, afterCtx *model.AcquireContext, isRemove bool) error { for index := range utils.StringSliceDeDuplication(groupIds) { groupId := groupIds[index] - group := svr.cacheMgn.User().GetGroup(groupId) + group := svr.userSvr.GetUserHelper().GetGroup(&apisecurity.UserGroup{ + Id: wrapperspb.String(groupId), + }) if group == nil { return errors.New("not found target group") } - - ownerId := group.Owner + ownerId := group.GetOwner().GetValue() if err := svr.handlerModifyDefaultStrategy(groupId, ownerId, model.PrincipalGroup, afterCtx, isRemove); err != nil { return err @@ -255,7 +305,6 @@ func (svr *Server) handlerModifyDefaultStrategy(id, ownerId string, uType model. if !ok { return nil } - // 资源删除时,清理该资源与所有策略的关联关系 if afterCtx.GetOperation() == model.Delete { strategyId = "" @@ -302,12 +351,3 @@ func (svr *Server) handlerModifyDefaultStrategy(id, ownerId string, uType model. plugin.GetHistory().Record(entry) return nil } - -func checkHasPassAll(rule *model.StrategyDetail) bool { - for i := range rule.Resources { - if rule.Resources[i].ResID == "*" { - return true - } - } - return false -} diff --git a/auth/defaultauth/strategy.go b/auth/authcheck/strategy.go similarity index 90% rename from auth/defaultauth/strategy.go rename to auth/authcheck/strategy.go index c0337133e..68f24bd78 100644 --- a/auth/defaultauth/strategy.go +++ b/auth/authcheck/strategy.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package authcheck import ( "context" @@ -30,6 +30,7 @@ import ( apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" "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" @@ -61,8 +62,8 @@ var ( } ) -// CreateStrategy 创建鉴权策略 -func (svr *Server) CreateStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { +// handleCreateStrategy 创建鉴权策略 +func (svr *Server) handleCreateStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { requestID := utils.ParseRequestID(ctx) ownerId := utils.ParseOwnerID(ctx) req.Owner = utils.NewStringValue(ownerId) @@ -87,8 +88,8 @@ func (svr *Server) CreateStrategy(ctx context.Context, req *apisecurity.AuthStra return api.NewAuthStrategyResponse(apimodel.Code_ExecuteSuccess, req) } -// UpdateStrategies 批量修改鉴权 -func (svr *Server) UpdateStrategies( +// handleUpdateStrategies 批量修改鉴权 +func (svr *Server) handleUpdateStrategies( ctx context.Context, reqs []*apisecurity.ModifyAuthStrategy) *apiservice.BatchWriteResponse { resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) @@ -140,8 +141,8 @@ func (svr *Server) UpdateStrategy(ctx context.Context, req *apisecurity.ModifyAu return api.NewModifyAuthStrategyResponse(apimodel.Code_ExecuteSuccess, req) } -// DeleteStrategies 批量删除鉴权策略 -func (svr *Server) DeleteStrategies( +// handleDeleteStrategies 批量删除鉴权策略 +func (svr *Server) handleDeleteStrategies( ctx context.Context, reqs []*apisecurity.AuthStrategy) *apiservice.BatchWriteResponse { resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) for index := range reqs { @@ -203,7 +204,7 @@ func (svr *Server) DeleteStrategy(ctx context.Context, req *apisecurity.AuthStra // a. 如果当前是超级管理账户,则按照传入的 query 进行查询即可 // b. 如果当前是主账户,则自动注入 owner 字段,即只能查看策略的 owner 是自己的策略 // c. 如果当前是子账户,则自动注入 principal_id 以及 principal_type 字段,即稚嫩查询与自己有关的策略 -func (svr *Server) GetStrategies(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { +func (svr *Server) handleGetStrategies(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { requestID := utils.ParseRequestID(ctx) platformID := utils.ParsePlatformID(ctx) @@ -221,7 +222,7 @@ func (svr *Server) GetStrategies(ctx context.Context, query map[string]string) * searchFilters[key] = value } - searchFilters = parseStrategySearchArgs(ctx, searchFilters) + searchFilters = ParseStrategySearchArgs(ctx, searchFilters) offset, limit, err := utils.ParseOffsetAndLimit(searchFilters) if err != nil { @@ -249,20 +250,22 @@ func (svr *Server) GetStrategies(ctx context.Context, query map[string]string) * return resp } -var resTypeFilter = map[string]string{ - "namespace": "0", - "service": "1", - "config_group": "2", -} +var ( + resTypeFilter = map[string]string{ + "namespace": "0", + "service": "1", + "config_group": "2", + } -var principalTypeFilter = map[string]string{ - "user": "1", - "group": "2", - "groups": "2", -} + principalTypeFilter = map[string]string{ + "user": "1", + "group": "2", + "groups": "2", + } +) -// parseStrategySearchArgs 处理鉴权策略的搜索参数 -func parseStrategySearchArgs(ctx context.Context, searchFilters map[string]string) map[string]string { +// ParseStrategySearchArgs 处理鉴权策略的搜索参数 +func ParseStrategySearchArgs(ctx context.Context, searchFilters map[string]string) map[string]string { if val, ok := searchFilters["res_type"]; ok { if v, exist := resTypeFilter[val]; exist { searchFilters["res_type"] = v @@ -297,12 +300,11 @@ func parseStrategySearchArgs(ctx context.Context, searchFilters map[string]strin return searchFilters } -// GetStrategy 根据策略ID获取详细的鉴权策略 +// handleGetStrategy 根据策略ID获取详细的鉴权策略 // Case 1 如果当前操作者是该策略 principal 中的一员,则可以查看 // Case 2 如果当前操作者是该策略的 owner,则可以查看 // Case 3 如果当前操作者是admin角色,直接查看 -func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) +func (svr *Server) handleGetStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { userId := utils.ParseUserID(ctx) isOwner := utils.ParseIsOwner(ctx) @@ -313,7 +315,7 @@ func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrateg ret, err := svr.storage.GetStrategyDetail(req.GetId().GetValue()) if err != nil { log.Error("[Auth][Strategy] get strategt from store", - utils.ZapRequestID(requestID), zap.Error(err)) + utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponse(commonstore.StoreCode2APICode(err)) } if ret == nil { @@ -328,6 +330,9 @@ func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrateg // 判断是否在该策略所属的成员列表中,如果自己在某个用户组,而该用户组又在这个策略的成员中,则也是可以查看的 if !canView { + curUser := &apisecurity.User{ + Id: wrapperspb.String(userId), + } for index := range ret.Principals { principal := ret.Principals[index] if principal.PrincipalRole == model.PrincipalUser && principal.PrincipalID == userId { @@ -335,7 +340,10 @@ func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrateg break } if principal.PrincipalRole == model.PrincipalGroup { - if svr.cacheMgn.User().IsUserInGroup(userId, principal.PrincipalID) { + group := &apisecurity.UserGroup{ + Id: wrapperspb.String(principal.PrincipalID), + } + if svr.userSvr.GetUserHelper().CheckUserInGroup(group, curUser) { canView = true break } @@ -345,9 +353,7 @@ func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrateg if !canView { log.Error("[Auth][Strategy] get strategy detail denied", - utils.ZapRequestID(requestID), - zap.String("user", userId), - zap.String("strategy", req.Id.Value), + utils.RequestID(ctx), zap.String("user", userId), zap.String("strategy", req.Id.Value), zap.Bool("is-owner", isOwner), ) return api.NewAuthStrategyResponse(apimodel.Code_NotAllowedAccess, req) @@ -356,8 +362,8 @@ func (svr *Server) GetStrategy(ctx context.Context, req *apisecurity.AuthStrateg return api.NewAuthStrategyResponse(apimodel.Code_ExecuteSuccess, svr.authStrategyFull2Api(ret)) } -// GetPrincipalResources 获取某个principal可以获取到的所有资源ID数据信息 -func (svr *Server) GetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response { +// handleGetPrincipalResources 获取某个principal可以获取到的所有资源ID数据信息 +func (svr *Server) handleGetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response { requestID := utils.ParseRequestID(ctx) if len(query) == 0 { return api.NewAuthResponse(apimodel.Code_EmptyRequest) @@ -387,9 +393,12 @@ func (svr *Server) GetPrincipalResources(ctx context.Context, query map[string]s // 找这个用户所关联的用户组 if model.PrincipalType(principalRole) == model.PrincipalUser { - groupIds := svr.cacheMgn.User().GetUserLinkGroupIds(principalId) - for i := range groupIds { - res, err := svr.storage.GetStrategyResources(groupIds[i], model.PrincipalGroup) + groups := svr.userSvr.GetUserHelper().GetUserOwnGroup(&apisecurity.User{ + Id: wrapperspb.String(principalId), + }) + for i := range groups { + item := groups[i] + res, err := svr.storage.GetStrategyResources(item.GetId().GetValue(), model.PrincipalGroup) if err != nil { log.Error("[Auth][Strategy] get principal link resource", utils.ZapRequestID(requestID), zap.String("principal-id", principalId), zap.Any("principal-role", principalRole), zap.Error(err)) @@ -675,30 +684,21 @@ func collectPrincipalEntry(ruleID string, uType model.PrincipalType, res []*apis // checkCreateStrategy 检查创建鉴权策略的请求 func (svr *Server) checkCreateStrategy(req *apisecurity.AuthStrategy) *apiservice.Response { // 检查名称信息 - if err := checkName(req.GetName()); err != nil { + if err := CheckName(req.GetName()); err != nil { return api.NewAuthStrategyResponse(apimodel.Code_InvalidUserName, req) } - - // 检查 owner 信息 - if err := checkOwner(req.GetOwner()); err != nil { - return api.NewAuthStrategyResponse(apimodel.Code_InvalidAuthStrategyOwners, req) - } - // 检查用户是否存在 if err := svr.checkUserExist(convertPrincipalsToUsers(req.GetPrincipals())); err != nil { return api.NewAuthStrategyResponse(apimodel.Code_NotFoundUser, req) } - // 检查用户组是否存在 if err := svr.checkGroupExist(convertPrincipalsToGroups(req.GetPrincipals())); err != nil { return api.NewAuthStrategyResponse(apimodel.Code_NotFoundUserGroup, req) } - // 检查资源是否存在 if errResp := svr.checkResourceExist(req.GetResources()); errResp != nil { return errResp } - return nil } @@ -829,16 +829,7 @@ func (svr *Server) checkUserExist(users []*apisecurity.User) error { if len(users) == 0 { return nil } - - userCache := svr.cacheMgn.User() - - for index := range users { - if val := userCache.GetUserByID(users[index].GetId().GetValue()); val == nil { - return model.ErrorNoUser - } - } - - return nil + return svr.userSvr.GetUserHelper().CheckUsersExist(users) } // checkUserGroupExist 检查用户组是否存在 @@ -846,22 +837,14 @@ func (svr *Server) checkGroupExist(groups []*apisecurity.UserGroup) error { if len(groups) == 0 { return nil } - userCache := svr.cacheMgn.User() - - for index := range groups { - if val := userCache.GetGroup(groups[index].GetId().GetValue()); val == nil { - return model.ErrorNoUserGroup - } - } - - return nil + return svr.userSvr.GetUserHelper().CheckGroupsExist(groups) } // checkResourceExist 检查资源是否存在 func (svr *Server) checkResourceExist(resources *apisecurity.StrategyResources) *apiservice.Response { namespaces := resources.GetNamespaces() - nsCache := svr.cacheMgn.Namespace() + nsCache := svr.cacheMgr.Namespace() for index := range namespaces { val := namespaces[index] if val.GetId().GetValue() == "*" { @@ -874,7 +857,7 @@ func (svr *Server) checkResourceExist(resources *apisecurity.StrategyResources) } services := resources.GetServices() - svcCache := svr.cacheMgn.Service() + svcCache := svr.cacheMgr.Service() for index := range services { val := services[index] if val.GetId().GetValue() == "*" { @@ -889,9 +872,7 @@ func (svr *Server) checkResourceExist(resources *apisecurity.StrategyResources) return nil } -// normalizeResource 对于资源进行归一化处理 -// -// 如果出现 * 的话,则该资源访问策略就是 * +// normalizeResource 对于资源进行归一化处理, 如果出现 * 的话,则该资源访问策略就是 * func (svr *Server) normalizeResource(resources *apisecurity.StrategyResources) *apisecurity.StrategyResources { namespaces := resources.GetNamespaces() for index := range namespaces { @@ -925,22 +906,26 @@ func (svr *Server) fillPrincipalInfo(resp *apisecurity.AuthStrategy, data *model for index := range data.Principals { principal := data.Principals[index] if principal.PrincipalRole == model.PrincipalUser { - user := svr.cacheMgn.User().GetUserByID(principal.PrincipalID) + user := svr.userSvr.GetUserHelper().GetUser(&apisecurity.User{ + Id: wrapperspb.String(principal.PrincipalID), + }) if user == nil { continue } users = append(users, &apisecurity.Principal{ - Id: utils.NewStringValue(user.ID), - Name: utils.NewStringValue(user.Name), + Id: utils.NewStringValue(user.GetId().GetValue()), + Name: utils.NewStringValue(user.GetName().GetValue()), }) } else { - group := svr.cacheMgn.User().GetGroup(principal.PrincipalID) + group := svr.userSvr.GetUserHelper().GetGroup(&apisecurity.UserGroup{ + Id: wrapperspb.String(principal.PrincipalID), + }) if group == nil { continue } groups = append(groups, &apisecurity.Principal{ - Id: utils.NewStringValue(group.ID), - Name: utils.NewStringValue(group.Name), + Id: utils.NewStringValue(group.GetId().GetValue()), + Name: utils.NewStringValue(group.GetName().GetValue()), }) } } @@ -980,7 +965,7 @@ func (svr *Server) fillResourceInfo(resp *apisecurity.AuthStrategy, data *model. } if !autoAllNs { - ns := svr.cacheMgn.Namespace().GetNamespace(res.ResID) + ns := svr.cacheMgr.Namespace().GetNamespace(res.ResID) if ns == nil { log.Warn("[Auth][Strategy] not found namespace in fill-info", zap.String("id", data.ID), zap.String("namespace", res.ResID)) @@ -1006,7 +991,7 @@ func (svr *Server) fillResourceInfo(resp *apisecurity.AuthStrategy, data *model. } if !autoAllSvc { - svc := svr.cacheMgn.Service().GetServiceByID(res.ResID) + svc := svr.cacheMgr.Service().GetServiceByID(res.ResID) if svc == nil { log.Warn("[Auth][Strategy] not found service in fill-info", zap.String("id", data.ID), zap.String("service", res.ResID)) @@ -1037,7 +1022,7 @@ func (svr *Server) fillResourceInfo(resp *apisecurity.AuthStrategy, data *model. zap.String("id", data.ID), zap.String("config_file_group", res.ResID)) continue } - group := svr.cacheMgn.ConfigGroup().GetGroupByID(groupId) + group := svr.cacheMgr.ConfigGroup().GetGroupByID(groupId) if group == nil { log.Warn("[Auth][Strategy] not found config_file_group in fill-info", zap.String("id", data.ID), zap.String("config_file_group", res.ResID)) @@ -1065,6 +1050,18 @@ type resourceFilter struct { conf map[string]struct{} } +func (f *resourceFilter) GetFilter(t apisecurity.ResourceType) (map[string]struct{}, bool) { + switch t { + case apisecurity.ResourceType_Namespaces: + return f.ns, true + case apisecurity.ResourceType_Services: + return f.svc, true + case apisecurity.ResourceType_ConfigGroups: + return f.conf, true + } + return nil, false +} + // filter different types of Strategy resources func resourceDeduplication(resources []model.StrategyResource) []model.StrategyResource { rLen := len(resources) @@ -1078,29 +1075,14 @@ func resourceDeduplication(resources []model.StrategyResource) []model.StrategyR est := struct{}{} for i := range resources { res := resources[i] - if res.ResType == int32(apisecurity.ResourceType_Namespaces) { - if _, exist := rf.ns[res.ResID]; !exist { - rf.ns[res.ResID] = est - ret = append(ret, res) - } - continue - } - - if res.ResType == int32(apisecurity.ResourceType_Services) { - if _, exist := rf.svc[res.ResID]; !exist { - rf.svc[res.ResID] = est - ret = append(ret, res) - } - + filter, ok := rf.GetFilter(apisecurity.ResourceType(res.ResType)) + if !ok { continue } - - // other type conf - if _, exist := rf.conf[res.ResID]; !exist { - rf.conf[res.ResID] = est + if _, exist := filter[res.ResID]; !exist { + rf.ns[res.ResID] = est ret = append(ret, res) } } - return ret } diff --git a/auth/defaultauth/strategy_test.go b/auth/authcheck/strategy_test.go similarity index 97% rename from auth/defaultauth/strategy_test.go rename to auth/authcheck/strategy_test.go index 510630daa..85944c9fe 100644 --- a/auth/defaultauth/strategy_test.go +++ b/auth/authcheck/strategy_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package authcheck_test import ( "context" @@ -30,7 +30,7 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/auth/defaultauth" + "github.com/polarismesh/polaris/auth/authcheck" "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" @@ -57,7 +57,7 @@ type StrategyTest struct { cacheMgn *cache.CacheManager checker auth.AuthChecker - svr *defaultauth.StrategyAuthAbility + svr *authcheck.Server cancel context.CancelFunc @@ -122,34 +122,19 @@ func newStrategyTest(t *testing.T) *StrategyTest { _ = cacheMgn.TestUpdate() - checker := &defaultauth.DefaultAuthChecker{} - checker.Initialize(&auth.Config{ - User: &auth.UserConfig{ - Name: "", - Option: map[string]interface{}{ - "salt": "polarismesh@2021", - }, - }, - Strategy: &auth.StrategyConfig{ - Name: "", - Option: map[string]interface{}{ - "consoleOpen": true, - "clientOpen": true, - "strict": false, - }, - }, - }, storage, cacheMgn) + checker := &authcheck.DefaultAuthChecker{} + checker.Initialize(&authcheck.AuthConfig{ + ConsoleOpen: true, + ClientOpen: true, + ConsoleStrict: false, + ClientStrict: false, + }, storage, cacheMgn, nil) checker.SetCacheMgr(cacheMgn) t.Cleanup(func() { cacheMgn.Close() }) - svr := defaultauth.NewStrategyAuthAbility( - checker, - defaultauth.NewServer(storage, nil, cacheMgn, checker), - ) - return &StrategyTest{ ownerOne: users[0], @@ -165,7 +150,6 @@ func newStrategyTest(t *testing.T) *StrategyTest { storage: storage, cacheMgn: cacheMgn, checker: checker, - svr: svr, cancel: cancel, @@ -850,7 +834,7 @@ func Test_parseStrategySearchArgs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := defaultauth.TestParseStrategySearchArgs(tt.args.ctx, tt.args.searchFilters); !reflect.DeepEqual(got, tt.want) { + if got := authcheck.ParseStrategySearchArgs(tt.args.ctx, tt.args.searchFilters); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseStrategySearchArgs() = %v, want %v", got, tt.want) } }) diff --git a/auth/authcheck/utils.go b/auth/authcheck/utils.go new file mode 100644 index 000000000..a4d1e3a17 --- /dev/null +++ b/auth/authcheck/utils.go @@ -0,0 +1,69 @@ +/** + * 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 authcheck + +import ( + "errors" + "regexp" + "unicode/utf8" + + "github.com/golang/protobuf/ptypes/wrappers" + + "github.com/polarismesh/polaris/common/utils" +) + +var ( + // MustOwner 必须超级账户 or 主账户 + MustOwner = true + // NotOwner 任意账户 + NotOwner = false + // WriteOp 写操作 + WriteOp = true + // ReadOp 读操作 + ReadOp = false +) + +var ( + regNameStr = regexp.MustCompile("^[\u4E00-\u9FA5A-Za-z0-9_\\-.]+$") + regEmail = regexp.MustCompile(`^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`) +) + +// CheckName 名称检查 +func CheckName(name *wrappers.StringValue) error { + if name == nil { + return errors.New(utils.NilErrString) + } + + if name.GetValue() == "" { + return errors.New(utils.EmptyErrString) + } + + if name.GetValue() == "polariadmin" { + return errors.New("illegal username") + } + + if utf8.RuneCountInString(name.GetValue()) > utils.MaxNameLength { + return errors.New("name too long") + } + + if ok := regNameStr.MatchString(name.GetValue()); !ok { + return errors.New("name contains invalid character") + } + + return nil +} diff --git a/auth/defaultauth/auth_checker.go b/auth/defaultauth/auth_checker.go deleted file mode 100644 index 8af012864..000000000 --- a/auth/defaultauth/auth_checker.go +++ /dev/null @@ -1,447 +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 defaultauth - -import ( - "context" - "encoding/json" - "strings" - - "github.com/pkg/errors" - apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" - "go.uber.org/zap" - - "github.com/polarismesh/polaris/auth" - 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" - "github.com/polarismesh/polaris/store" -) - -var ( - // ErrorNotAllowedAccess 鉴权失败 - ErrorNotAllowedAccess error = errors.New(api.Code2Info(api.NotAllowedAccess)) - // ErrorInvalidParameter 不合法的参数 - ErrorInvalidParameter error = errors.New(api.Code2Info(api.InvalidParameter)) - // ErrorNotPermission . - ErrorNotPermission = errors.New("no permission") -) - -// DefaultAuthChecker 北极星自带的默认鉴权中心 -type DefaultAuthChecker struct { - cacheMgn cachetypes.CacheManager -} - -func (d *DefaultAuthChecker) SetCacheMgr(mgr cachetypes.CacheManager) { - d.cacheMgn = mgr -} - -// Initialize 执行初始化动作 -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 - userContentBytes []byte - authContentBytes []byte - err error - ) - - cfg := DefaultAuthConfig() - - // 一旦设置了auth.user.option或auth.strategy.option,将不会继续读取auth.option - if len(options.Strategy.Option) > 0 || len(options.User.Option) > 0 { - // 判断auth.option是否还有值,有则不兼容 - if len(options.Option) > 0 { - log.Warn("auth.user.option or auth.strategy.option has set, auth.option will ignore") - } - strategyContentBytes, err = json.Marshal(options.Strategy.Option) - if err != nil { - return err - } - if err := json.Unmarshal(strategyContentBytes, cfg); err != nil { - return err - } - userContentBytes, err = json.Marshal(options.User.Option) - if err != nil { - return err - } - if err := json.Unmarshal(userContentBytes, cfg); err != nil { - return err - } - } else { - log.Warn("[Auth][Checker] auth.option has deprecated, use auth.user.option and auth.strategy.option instead.") - authContentBytes, err = json.Marshal(options.Option) - if err != nil { - return err - } - if err := json.Unmarshal(authContentBytes, cfg); err != nil { - return err - } - } - - if err := cfg.Verify(); err != nil { - return err - } - // 兼容原本老的配置逻辑 - if cfg.Strict { - cfg.ConsoleOpen = cfg.Strict - } - AuthOption = cfg - d.cacheMgn = cacheMgr - return nil -} - -// Cache 获取缓存统一管理 -func (d *DefaultAuthChecker) Cache() cachetypes.CacheManager { - return d.cacheMgn -} - -// IsOpenConsoleAuth 针对控制台是否开启了操作鉴权 -func (d *DefaultAuthChecker) IsOpenConsoleAuth() bool { - return AuthOption.ConsoleOpen -} - -// IsOpenClientAuth 针对客户端是否开启了操作鉴权 -func (d *DefaultAuthChecker) IsOpenClientAuth() bool { - return AuthOption.ClientOpen -} - -// IsOpenAuth 返回对于控制台/客户端任意其中的一个是否开启了操作鉴权 -func (d *DefaultAuthChecker) IsOpenAuth() bool { - return d.IsOpenConsoleAuth() || d.IsOpenClientAuth() -} - -// CheckClientPermission 执行检查客户端动作判断是否有权限,并且对 RequestContext 注入操作者数据 -func (d *DefaultAuthChecker) CheckClientPermission(preCtx *model.AcquireContext) (bool, error) { - preCtx.SetFromClient() - if !d.IsOpenClientAuth() { - return true, nil - } - return d.CheckPermission(preCtx) -} - -// CheckConsolePermission 执行检查控制台动作判断是否有权限,并且对 RequestContext 注入操作者数据 -func (d *DefaultAuthChecker) CheckConsolePermission(preCtx *model.AcquireContext) (bool, error) { - preCtx.SetFromConsole() - if !d.IsOpenConsoleAuth() { - return true, nil - } - if preCtx.GetModule() == model.MaintainModule { - return d.checkMaintainPermission(preCtx) - } - return d.CheckPermission(preCtx) -} - -// CheckMaintainPermission 执行检查运维动作判断是否有权限 -func (d *DefaultAuthChecker) checkMaintainPermission(preCtx *model.AcquireContext) (bool, error) { - if err := d.VerifyCredential(preCtx); err != nil { - return false, err - } - if preCtx.GetOperation() == model.Read { - return true, nil - } - - attachVal, ok := preCtx.GetAttachment(model.TokenDetailInfoKey) - if !ok { - return false, model.ErrorTokenNotExist - } - tokenInfo, ok := attachVal.(OperatorInfo) - if !ok { - return false, model.ErrorTokenNotExist - } - - if tokenInfo.Disable { - return false, model.ErrorTokenDisabled - } - if !tokenInfo.IsUserToken { - return false, errors.New("only user role can access maintain API") - } - if tokenInfo.Role != model.OwnerUserRole { - return false, errors.New("only owner account can access maintain API") - } - return true, nil -} - -// CheckPermission 执行检查动作判断是否有权限 -// -// step 1. 判断是否开启了鉴权 -// step 2. 对token进行检查判断 -// case 1. 如果 token 被禁用 -// a. 读操作,直接放通 -// b. 写操作,快速失败 -// step 3. 拉取token对应的操作者相关信息,注入到请求上下文中 -// step 4. 进行权限检查 -func (d *DefaultAuthChecker) CheckPermission(authCtx *model.AcquireContext) (bool, error) { - if err := d.VerifyCredential(authCtx); err != nil { - return false, err - } - - if authCtx.GetOperation() == model.Read { - return true, nil - } - - attachVal, ok := authCtx.GetAttachment(model.TokenDetailInfoKey) - if !ok { - return false, model.ErrorTokenNotExist - } - operatorInfo, ok := attachVal.(OperatorInfo) - if !ok { - return false, model.ErrorTokenNotExist - } - // 这里需要检查当 token 被禁止的情况,如果 token 被禁止,无论是否可以操作目标资源,都无法进行写操作 - if operatorInfo.Disable { - return false, model.ErrorTokenDisabled - } - - log.Debug("[Auth][Checker] check permission args", utils.RequestID(authCtx.GetRequestContext()), - zap.String("method", authCtx.GetMethod()), zap.Any("resources", authCtx.GetAccessResources())) - - ok, err := d.doCheckPermission(authCtx) - if ok { - return ok, nil - } - - // 强制同步一次db中strategy数据到cache - if err = d.cacheMgn.AuthStrategy().ForceSync(); err != nil { - log.Error("[Auth][Checker] force sync strategy to cache failed", - utils.RequestID(authCtx.GetRequestContext()), zap.Error(err)) - return false, err - } - - return d.doCheckPermission(authCtx) -} - -func canDowngradeAnonymous(authCtx *model.AcquireContext, err error) bool { - if authCtx.GetModule() == model.AuthModule { - return false - } - if authCtx.IsFromClient() && AuthOption.ClientStrict { - return false - } - if authCtx.IsFromConsole() && AuthOption.ConsoleStrict { - return false - } - if errors.Is(err, model.ErrorTokenInvalid) { - return true - } - if errors.Is(err, model.ErrorTokenNotExist) { - return true - } - return false -} - -// VerifyCredential 对 token 进行检查验证,并将 verify 过程中解析出的数据注入到 model.AcquireContext 中 -// step 1. 首先对 token 进行解析,获取相关的数据信息,注入到整个的 AcquireContext 中 -// step 2. 最后对 token 进行一些验证步骤的执行 -// step 3. 兜底措施:如果开启了鉴权的非严格模式,则根据错误的类型,判断是否转为匿名用户进行访问 -// - 如果是访问权限控制相关模块(用户、用户组、权限策略),不得转为匿名用户 -func (d *DefaultAuthChecker) VerifyCredential(authCtx *model.AcquireContext) error { - reqId := utils.ParseRequestID(authCtx.GetRequestContext()) - - checkErr := func() error { - authToken := utils.ParseAuthToken(authCtx.GetRequestContext()) - operator, err := d.decodeToken(authToken) - if err != nil { - log.Error("[Auth][Checker] decode token", zap.Error(err)) - return model.ErrorTokenInvalid - } - - ownerId, isOwner, err := d.checkToken(&operator) - if err != nil { - log.Errorf("[Auth][Checker] check token err : %s", errors.WithStack(err).Error()) - return err - } - - operator.OwnerID = ownerId - ctx := authCtx.GetRequestContext() - ctx = context.WithValue(ctx, utils.ContextIsOwnerKey, isOwner) - ctx = context.WithValue(ctx, utils.ContextUserIDKey, operator.OperatorID) - ctx = context.WithValue(ctx, utils.ContextOwnerIDKey, ownerId) - authCtx.SetRequestContext(ctx) - d.parseOperatorInfo(operator, authCtx) - if operator.Disable { - log.Warn("[Auth][Checker] token already disabled", utils.ZapRequestID(reqId), - zap.Any("token", operator.String())) - } - return nil - }() - - if checkErr != nil { - if !canDowngradeAnonymous(authCtx, checkErr) { - return checkErr - } - log.Warn("[Auth][Checker] parse operator info, downgrade to anonymous", utils.ZapRequestID(reqId), - zap.Error(checkErr)) - // 操作者信息解析失败,降级为匿名用户 - authCtx.SetAttachment(model.TokenDetailInfoKey, newAnonymous()) - } - - return nil -} - -func (d *DefaultAuthChecker) parseOperatorInfo(operator OperatorInfo, authCtx *model.AcquireContext) { - ctx := authCtx.GetRequestContext() - if operator.IsUserToken { - user := d.Cache().User().GetUserByID(operator.OperatorID) - if user != nil { - operator.Role = user.Type - ctx = context.WithValue(ctx, utils.ContextOperator, user.Name) - ctx = context.WithValue(ctx, utils.ContextUserNameKey, user.Name) - ctx = context.WithValue(ctx, utils.ContextUserRoleIDKey, user.Type) - } - } else { - userGroup := d.Cache().User().GetGroup(operator.OperatorID) - if userGroup != nil { - ctx = context.WithValue(ctx, utils.ContextOperator, userGroup.Name) - ctx = context.WithValue(ctx, utils.ContextUserNameKey, userGroup.Name) - } - } - - authCtx.SetAttachment(model.OperatorRoleKey, operator.Role) - authCtx.SetAttachment(model.OperatorPrincipalType, func() model.PrincipalType { - if operator.IsUserToken { - return model.PrincipalUser - } - return model.PrincipalGroup - }()) - authCtx.SetAttachment(model.OperatorIDKey, operator.OperatorID) - authCtx.SetAttachment(model.OperatorOwnerKey, operator) - authCtx.SetAttachment(model.TokenDetailInfoKey, operator) - - authCtx.SetRequestContext(ctx) -} - -// DecodeToken -func (d *DefaultAuthChecker) DecodeToken(t string) (OperatorInfo, error) { - return d.decodeToken(t) -} - -// decodeToken 解析 token 信息,如果 t == "",直接返回一个空对象 -func (d *DefaultAuthChecker) decodeToken(t string) (OperatorInfo, error) { - if t == "" { - return OperatorInfo{}, model.ErrorTokenInvalid - } - - ret, err := decryptMessage([]byte(AuthOption.Salt), t) - if err != nil { - return OperatorInfo{}, err - } - tokenDetails := strings.Split(ret, TokenSplit) - if len(tokenDetails) != 2 { - return OperatorInfo{}, model.ErrorTokenInvalid - } - - detail := strings.Split(tokenDetails[1], "/") - if len(detail) != 2 { - return OperatorInfo{}, model.ErrorTokenInvalid - } - - tokenInfo := OperatorInfo{ - Origin: t, - IsUserToken: detail[0] == model.TokenForUser, - OperatorID: detail[1], - Role: model.UnknownUserRole, - } - return tokenInfo, nil -} - -// checkToken 对 token 进行检查,如果 token 是一个空,直接返回默认值,但是不返回错误 -// return {owner-id} {is-owner} {error} -func (d *DefaultAuthChecker) checkToken(tokenInfo *OperatorInfo) (string, bool, error) { - if IsEmptyOperator(*tokenInfo) { - return "", false, nil - } - - id := tokenInfo.OperatorID - if tokenInfo.IsUserToken { - user := d.Cache().User().GetUserByID(id) - if user == nil { - return "", false, model.ErrorNoUser - } - - if tokenInfo.Origin != user.Token { - return "", false, model.ErrorTokenNotExist - } - - tokenInfo.Disable = !user.TokenEnable - if user.Owner == "" { - return user.ID, true, nil - } - - return user.Owner, false, nil - } - group := d.Cache().User().GetGroup(id) - if group == nil { - return "", false, model.ErrorNoUserGroup - } - - if tokenInfo.Origin != group.Token { - return "", false, model.ErrorTokenNotExist - } - - tokenInfo.Disable = !group.TokenEnable - return group.Owner, false, nil -} - -func (d *DefaultAuthChecker) isResourceEditable( - principal model.Principal, - resourceType apisecurity.ResourceType, - resEntries []model.ResourceEntry) bool { - for _, entry := range resEntries { - if !d.cacheMgn.AuthStrategy().IsResourceEditable(principal, resourceType, entry.ID) { - return false - } - } - return true -} - -// doCheckPermission 执行权限检查 -func (d *DefaultAuthChecker) doCheckPermission(authCtx *model.AcquireContext) (bool, error) { - - var checkNamespace, checkSvc, checkCfgGroup bool - - reqRes := authCtx.GetAccessResources() - nsResEntries := reqRes[apisecurity.ResourceType_Namespaces] - svcResEntries := reqRes[apisecurity.ResourceType_Services] - cfgResEntries := reqRes[apisecurity.ResourceType_ConfigGroups] - - principleID, _ := authCtx.GetAttachments()[model.OperatorIDKey].(string) - principleType, _ := authCtx.GetAttachments()[model.OperatorPrincipalType].(model.PrincipalType) - p := model.Principal{ - PrincipalID: principleID, - PrincipalRole: principleType, - } - checkNamespace = d.isResourceEditable(p, apisecurity.ResourceType_Namespaces, nsResEntries) - checkSvc = d.isResourceEditable(p, apisecurity.ResourceType_Services, svcResEntries) - checkCfgGroup = d.isResourceEditable(p, apisecurity.ResourceType_ConfigGroups, cfgResEntries) - - checkAllResEntries := checkNamespace && checkSvc && checkCfgGroup - - var err error - if !checkAllResEntries { - err = ErrorNotPermission - } - return checkAllResEntries, err -} - -// checkAction 检查操作是否和策略匹配 -func (d *DefaultAuthChecker) checkAction(expect string, actual model.ResourceOperation, method string) bool { - // TODO 后续可针对读写操作进行鉴权, 并且可以针对具体的方法调用进行鉴权控制 - return true -} diff --git a/auth/defaultauth/config.go b/auth/defaultauth/config.go deleted file mode 100644 index 380068b89..000000000 --- a/auth/defaultauth/config.go +++ /dev/null @@ -1,69 +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 defaultauth - -import "errors" - -// AuthOption 鉴权的配置信息 -var AuthOption = DefaultAuthConfig() - -// AuthConfig 鉴权配置 -type AuthConfig struct { - // ConsoleOpen 控制台是否开启鉴权 - ConsoleOpen bool `json:"consoleOpen" xml:"consoleOpen"` - // ClientOpen 是否开启客户端接口鉴权 - ClientOpen bool `json:"clientOpen" xml:"clientOpen"` - // Salt 相关密码、token加密的salt - Salt string `json:"salt" xml:"salt"` - // Strict 是否启用鉴权的严格模式,即对于没有任何鉴权策略的资源,也必须带上正确的token才能操作, 默认关闭 - // Deprecated - Strict bool `json:"strict"` - // ConsoleStrict 是否启用鉴权的严格模式,即对于没有任何鉴权策略的资源,也必须带上正确的token才能操作, 默认关闭 - ConsoleStrict bool `json:"consoleStrict"` - // ClientStrict 是否启用鉴权的严格模式,即对于没有任何鉴权策略的资源,也必须带上正确的token才能操作, 默认关闭 - ClientStrict bool `json:"clientStrict"` -} - -// Verify 检查配置是否合法 -func (cfg *AuthConfig) Verify() error { - k := len(cfg.Salt) - switch k { - case 16, 24, 32: - break - default: - return errors.New("[Auth][Config] salt len must 16 | 24 | 32") - } - - return nil -} - -// DefaultAuthConfig 返回一个默认的鉴权配置 -func DefaultAuthConfig() *AuthConfig { - return &AuthConfig{ - // 针对控制台接口,默认开启鉴权操作 - ConsoleOpen: true, - // 针对客户端接口,默认不开启鉴权操作 - ClientOpen: false, - // Salt token 加密 key - Salt: "polarismesh@2021", - // 这里默认开启 OpenAPI 的强 Token 检查模式 - ConsoleStrict: true, - // 客户端接口默认不开启 token 强检查模式 - ClientStrict: false, - } -} diff --git a/auth/defaultauth/group_authability.go b/auth/defaultauth/group_authability.go deleted file mode 100644 index ef61fb130..000000000 --- a/auth/defaultauth/group_authability.go +++ /dev/null @@ -1,130 +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 defaultauth - -import ( - "context" - - 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" - - api "github.com/polarismesh/polaris/common/api/v1" -) - -func NewGroupAuthAbility(authMgn *DefaultAuthChecker, target *Server) *GroupAuthAbility { - return &GroupAuthAbility{ - authMgn: authMgn, - target: target, - } -} - -type GroupAuthAbility struct { - authMgn *DefaultAuthChecker - target *Server -} - -// CreateGroup creates a group. -func (svr *GroupAuthAbility) CreateGroup(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - rsp.UserGroup = group - return rsp - } - - return svr.target.CreateGroup(ctx, group) -} - -// UpdateGroups updates groups. -func (svr *GroupAuthAbility) UpdateGroups(ctx context.Context, - reqs []*apisecurity.ModifyUserGroup) *apiservice.BatchWriteResponse { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) - api.Collect(resp, rsp) - return resp - } - - return svr.target.UpdateGroups(ctx, reqs) -} - -// DeleteGroups deletes groups. -func (svr *GroupAuthAbility) DeleteGroups(ctx context.Context, - reqs []*apisecurity.UserGroup) *apiservice.BatchWriteResponse { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) - api.Collect(resp, rsp) - return resp - } - - return svr.target.DeleteGroups(ctx, reqs) -} - -// GetGroups 查看用户组列表 -func (svr *GroupAuthAbility) GetGroups(ctx context.Context, - query map[string]string) *apiservice.BatchQueryResponse { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return api.NewAuthBatchQueryResponseWithMsg(apimodel.Code(rsp.GetCode().Value), rsp.Info.Value) - } - - return svr.target.GetGroups(ctx, query) -} - -// GetGroup 查看用户组 -func (svr *GroupAuthAbility) GetGroup(ctx context.Context, req *apisecurity.UserGroup) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, NotOwner, svr.authMgn) - if rsp != nil { - return rsp - } - - return svr.target.GetGroup(ctx, req) -} - -// GetGroupToken 获取用户组token -func (svr *GroupAuthAbility) GetGroupToken(ctx context.Context, req *apisecurity.UserGroup) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return rsp - } - - return svr.target.GetGroupToken(ctx, req) -} - -// UpdateGroupToken 更新用户组token -func (svr *GroupAuthAbility) UpdateGroupToken(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - rsp.UserGroup = group - return rsp - } - - return svr.target.UpdateGroupToken(ctx, group) -} - -// ResetGroupToken 重置用户组token -func (svr *GroupAuthAbility) ResetGroupToken(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - rsp.UserGroup = group - return rsp - } - - return svr.target.ResetGroupToken(ctx, group) -} diff --git a/auth/defaultauth/strategy_authability.go b/auth/defaultauth/strategy_authability.go deleted file mode 100644 index b5bb26466..000000000 --- a/auth/defaultauth/strategy_authability.go +++ /dev/null @@ -1,154 +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 defaultauth - -import ( - "context" - - 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" - - "github.com/polarismesh/polaris/auth" - 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/plugin" - "github.com/polarismesh/polaris/store" -) - -func NewStrategyAuthAbility(authMgn *DefaultAuthChecker, target *Server) *StrategyAuthAbility { - return &StrategyAuthAbility{ - authMgn: authMgn, - target: target, - } -} - -type StrategyAuthAbility struct { - authMgn *DefaultAuthChecker - target *Server -} - -// Initialize 执行初始化动作 -func (svr *StrategyAuthAbility) Initialize(authOpt *auth.Config, storage store.Store, - cacheMgn cachetypes.CacheManager) error { - _ = cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ - Name: cachetypes.StrategyRuleName, - }) - var ( - history = plugin.GetHistory() - authMgn = &DefaultAuthChecker{} - ) - if err := authMgn.Initialize(authOpt, storage, cacheMgn); err != nil { - return err - } - - svr.authMgn = authMgn - svr.target = &Server{ - storage: storage, - history: history, - cacheMgn: cacheMgn, - authMgn: authMgn, - } - return nil -} - -// Name of the user operator plugin -func (svr *StrategyAuthAbility) Name() string { - return auth.DefaultStrategyMgnPluginName -} - -// CreateStrategy creates a new strategy. -func (svr *StrategyAuthAbility) CreateStrategy( - ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - rsp.AuthStrategy = strategy - return rsp - } - - return svr.target.CreateStrategy(ctx, strategy) -} - -// UpdateStrategies update a strategy. -func (svr *StrategyAuthAbility) UpdateStrategies(ctx context.Context, - reqs []*apisecurity.ModifyAuthStrategy) *apiservice.BatchWriteResponse { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) - api.Collect(resp, rsp) - return resp - } - - return svr.target.UpdateStrategies(ctx, reqs) -} - -// DeleteStrategies delete strategy. -func (svr *StrategyAuthAbility) DeleteStrategies(ctx context.Context, - reqs []*apisecurity.AuthStrategy) *apiservice.BatchWriteResponse { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) - api.Collect(resp, rsp) - return resp - } - - return svr.target.DeleteStrategies(ctx, reqs) -} - -// GetStrategies get strategy list . -func (svr *StrategyAuthAbility) GetStrategies(ctx context.Context, - query map[string]string) *apiservice.BatchQueryResponse { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return api.NewAuthBatchQueryResponseWithMsg(apimodel.Code(rsp.GetCode().Value), rsp.Info.Value) - } - - return svr.target.GetStrategies(ctx, query) -} - -// GetStrategy get strategy. -func (svr *StrategyAuthAbility) GetStrategy( - ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return rsp - } - - return svr.target.GetStrategy(ctx, strategy) -} - -// GetPrincipalResources get principal resources. -func (svr *StrategyAuthAbility) GetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return rsp - } - - return svr.target.GetPrincipalResources(ctx, query) -} - -// GetAuthChecker 获取鉴权管理器 -func (svr *StrategyAuthAbility) GetAuthChecker() auth.AuthChecker { - return svr.authMgn -} - -// AfterResourceOperation is called after resource operation -func (svr *StrategyAuthAbility) AfterResourceOperation(afterCtx *model.AcquireContext) error { - return svr.target.AfterResourceOperation(afterCtx) -} diff --git a/auth/defaultauth/user_authability.go b/auth/defaultauth/user_authability.go deleted file mode 100644 index e668a7391..000000000 --- a/auth/defaultauth/user_authability.go +++ /dev/null @@ -1,188 +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 defaultauth - -import ( - "context" - - 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" - - "github.com/polarismesh/polaris/auth" - cachetypes "github.com/polarismesh/polaris/cache/api" - api "github.com/polarismesh/polaris/common/api/v1" - "github.com/polarismesh/polaris/plugin" - "github.com/polarismesh/polaris/store" -) - -func NewUserAuthAbility(authMgn *DefaultAuthChecker, target *Server) *UserAuthAbility { - return &UserAuthAbility{ - authMgn: authMgn, - target: target, - } -} - -type UserAuthAbility struct { - *GroupAuthAbility - authMgn *DefaultAuthChecker - target *Server -} - -// Initialize 执行初始化动作 -func (svr *UserAuthAbility) Initialize(authOpt *auth.Config, storage store.Store, - cacheMgn cachetypes.CacheManager) error { - _ = cacheMgn.OpenResourceCache(cachetypes.ConfigEntry{ - Name: cachetypes.UsersName, - }) - var ( - history = plugin.GetHistory() - authMgn = &DefaultAuthChecker{} - ) - if err := authMgn.Initialize(authOpt, storage, cacheMgn); err != nil { - return err - } - - svr.authMgn = authMgn - svr.target = &Server{ - storage: storage, - history: history, - cacheMgn: cacheMgn, - authMgn: authMgn, - } - svr.GroupAuthAbility = &GroupAuthAbility{ - authMgn: svr.authMgn, - target: svr.target, - } - - return nil -} - -// Name of the user operator plugin -func (svr *UserAuthAbility) Name() string { - return auth.DefaultUserMgnPluginName -} - -// CreateUsers 创建用户,只能由超级账户 or 主账户调用 -// -// case 1. 超级账户调用:创建的是主账户 -// case 2. 主账户调用:创建的是子账户 -func (svr *UserAuthAbility) CreateUsers(ctx context.Context, req []*apisecurity.User) *apiservice.BatchWriteResponse { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) - api.Collect(resp, rsp) - return resp - } - - return svr.target.CreateUsers(ctx, req) -} - -// UpdateUser 更新用户,任意账户均可以操作 -// 用户token被禁止也只是表示不能对北极星资源执行写操作,但是改用户信息还是可以执行的 -func (svr *UserAuthAbility) UpdateUser(ctx context.Context, user *apisecurity.User) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - rsp.User = user - return rsp - } - - return svr.target.UpdateUser(ctx, user) -} - -// UpdateUserPassword 更新用户信息 -func (svr *UserAuthAbility) UpdateUserPassword( - ctx context.Context, req *apisecurity.ModifyUserPassword) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return rsp - } - - return svr.target.UpdateUserPassword(ctx, req) -} - -// DeleteUsers 批量删除用户,只能由超级账户 or 主账户操作 -func (svr *UserAuthAbility) DeleteUsers( - ctx context.Context, reqs []*apisecurity.User) *apiservice.BatchWriteResponse { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) - api.Collect(resp, rsp) - return resp - } - - return svr.target.DeleteUsers(ctx, reqs) -} - -// DeleteUser 删除用户,只能由超级账户 or 主账户操作 -func (svr *UserAuthAbility) DeleteUser(ctx context.Context, user *apisecurity.User) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - rsp.User = user - return rsp - } - - return svr.target.DeleteUser(ctx, user) -} - -// GetUsers 获取用户列表,任意账户均可以操作 -func (svr *UserAuthAbility) GetUsers(ctx context.Context, filter map[string]string) *apiservice.BatchQueryResponse { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return api.NewAuthBatchQueryResponseWithMsg(apimodel.Code(rsp.GetCode().Value), rsp.Info.Value) - } - - return svr.target.GetUsers(ctx, filter) -} - -// GetUserToken 获取用户token,任意账户均可以操作 -func (svr *UserAuthAbility) GetUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, ReadOp, NotOwner, svr.authMgn) - if rsp != nil { - return rsp - } - - return svr.target.GetUserToken(ctx, user) -} - -// UpdateUserToken 更新用户的 token 状态,只允许超级、主账户进行操作 -func (svr *UserAuthAbility) UpdateUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, MustOwner, svr.authMgn) - if rsp != nil { - rsp.User = user - return rsp - } - - return svr.target.UpdateUserToken(ctx, user) -} - -// ResetUserToken 重置用户token,允许子账户进行操作 -func (svr *UserAuthAbility) ResetUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response { - ctx, rsp := verifyAuth(ctx, WriteOp, NotOwner, svr.authMgn) - if rsp != nil { - rsp.User = user - return rsp - } - - return svr.target.ResetUserToken(ctx, user) -} - -// Login login Servers -func (svr *UserAuthAbility) Login(req *apisecurity.LoginRequest) *apiservice.Response { - return svr.target.Login(req) -} diff --git a/auth/defaultauth/utils.go b/auth/defaultauth/utils.go deleted file mode 100644 index b686d96aa..000000000 --- a/auth/defaultauth/utils.go +++ /dev/null @@ -1,163 +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 defaultauth - -import ( - "context" - "errors" - "regexp" - "unicode/utf8" - - "github.com/golang/protobuf/ptypes/wrappers" - apimodel "github.com/polarismesh/specification/source/go/api/v1/model" - apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" - "go.uber.org/zap" - - api "github.com/polarismesh/polaris/common/api/v1" - "github.com/polarismesh/polaris/common/model" - "github.com/polarismesh/polaris/common/utils" -) - -var ( - // MustOwner 必须超级账户 or 主账户 - MustOwner = true - // NotOwner 任意账户 - NotOwner = false - // WriteOp 写操作 - WriteOp = true - // ReadOp 读操作 - ReadOp = false -) - -var ( - regNameStr = regexp.MustCompile("^[\u4E00-\u9FA5A-Za-z0-9_\\-.]+$") - regEmail = regexp.MustCompile(`^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`) -) - -// checkName 名称检查 -func checkName(name *wrappers.StringValue) error { - if name == nil { - return errors.New(utils.NilErrString) - } - - if name.GetValue() == "" { - return errors.New(utils.EmptyErrString) - } - - if name.GetValue() == "polariadmin" { - return errors.New("illegal username") - } - - if utf8.RuneCountInString(name.GetValue()) > utils.MaxNameLength { - return errors.New("name too long") - } - - if ok := regNameStr.MatchString(name.GetValue()); !ok { - return errors.New("name contains invalid character") - } - - return nil -} - -// checkPassword 密码检查 -func checkPassword(password *wrappers.StringValue) error { - if password == nil { - return errors.New(utils.NilErrString) - } - - if password.GetValue() == "" { - return errors.New(utils.EmptyErrString) - } - - if pLen := len(password.GetValue()); pLen < 6 || pLen > 17 { - return errors.New("password len need 6 ~ 17") - } - - return nil -} - -// checkOwner 检查用户的 owner 信息 -func checkOwner(owner *wrappers.StringValue) error { - if owner == nil { - return errors.New(utils.NilErrString) - } - - if owner.GetValue() == "" { - return errors.New(utils.EmptyErrString) - } - - if utf8.RuneCountInString(owner.GetValue()) > utils.MaxOwnersLength { - return errors.New("owners too long") - } - - return nil -} - -// verifyAuth 用于 user、group 以及 strategy 模块的鉴权工作检查 -func verifyAuth(ctx context.Context, isWrite bool, - needOwner bool, authMgn *DefaultAuthChecker) (context.Context, *apiservice.Response) { - reqId := utils.ParseRequestID(ctx) - authToken := utils.ParseAuthToken(ctx) - - if authToken == "" { - log.Error("[Auth][Server] auth token is empty", utils.ZapRequestID(reqId)) - return nil, api.NewAuthResponse(apimodel.Code_EmptyAutToken) - } - - authCtx := model.NewAcquireContext( - model.WithRequestContext(ctx), - model.WithModule(model.AuthModule), - ) - - // case 1. 如果 error 不是 token 被禁止的 error,直接返回 - // case 2. 如果 error 是 token 被禁止,按下面情况判断 - // i. 如果当前只是一个数据的读取操作,则放通 - // ii. 如果当前是一个数据的写操作,则只能允许处于正常的 token 进行操作 - if err := authMgn.VerifyCredential(authCtx); err != nil { - log.Error("[Auth][Server] verify auth token", utils.ZapRequestID(reqId), - zap.Error(err)) - return nil, api.NewAuthResponse(apimodel.Code_AuthTokenForbidden) - } - - attachVal, ok := authCtx.GetAttachment(model.TokenDetailInfoKey) - if !ok { - return nil, api.NewAuthResponse(apimodel.Code_TokenNotExisted) - } - tokenInfo, ok := attachVal.(OperatorInfo) - if !ok { - return nil, api.NewAuthResponse(apimodel.Code_TokenNotExisted) - } - - if isWrite && tokenInfo.Disable { - log.Error("[Auth][Server] token is disabled", utils.ZapRequestID(reqId), - zap.String("operation", authCtx.GetMethod())) - return nil, api.NewAuthResponse(apimodel.Code_TokenDisabled) - } - - if !tokenInfo.IsUserToken { - log.Error("[Auth][Server] only user role can access this API", utils.ZapRequestID(reqId)) - return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) - } - - if needOwner && IsSubAccount(tokenInfo) { - log.Error("[Auth][Server] only admin/owner account can access this API", utils.ZapRequestID(reqId)) - return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) - } - - return authCtx.GetRequestContext(), nil -} diff --git a/auth/user/common_test.go b/auth/user/common_test.go new file mode 100644 index 000000000..7a9432383 --- /dev/null +++ b/auth/user/common_test.go @@ -0,0 +1,406 @@ +/** + * 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 defaultuser_test + +import ( + "fmt" + "time" + + "github.com/golang/mock/gomock" + "github.com/google/uuid" + apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" + "github.com/polarismesh/specification/source/go/api/v1/service_manage" + "golang.org/x/crypto/bcrypt" + "google.golang.org/protobuf/types/known/wrapperspb" + + defaultuser "github.com/polarismesh/polaris/auth/user" + "github.com/polarismesh/polaris/cache" + "github.com/polarismesh/polaris/common/metrics" + "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" + storemock "github.com/polarismesh/polaris/store/mock" +) + +var ( + _defaultSalt = "polarismesh@2021" +) + +func reset(strict bool) { +} + +func initCache(ctrl *gomock.Controller) (*cache.Config, *storemock.MockStore) { + metrics.InitMetrics() + /* + - name: service # 加载服务数据 + option: + disableBusiness: false # 不加载业务服务 + needMeta: true # 加载服务元数据 + - name: instance # 加载实例数据 + option: + disableBusiness: false # 不加载业务服务实例 + needMeta: true # 加载实例元数据 + - name: routingConfig # 加载路由数据 + - name: rateLimitConfig # 加载限流数据 + - name: circuitBreakerConfig # 加载熔断数据 + - name: l5 # 加载l5数据 + - name: users + - name: strategyRule + - name: namespace + */ + cfg := &cache.Config{} + storage := storemock.NewMockStore(ctrl) + + mockTx := storemock.NewMockTx(ctrl) + mockTx.EXPECT().Commit().Return(nil).AnyTimes() + mockTx.EXPECT().Rollback().Return(nil).AnyTimes() + mockTx.EXPECT().CreateReadView().Return(nil).AnyTimes() + + storage.EXPECT().StartReadTx().Return(mockTx, nil).AnyTimes() + storage.EXPECT().GetServicesCount().AnyTimes().Return(uint32(1), nil) + storage.EXPECT().GetInstancesCountTx(gomock.Any()).AnyTimes().Return(uint32(1), nil) + storage.EXPECT().GetMoreInstances(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(map[string]*model.Instance{ + "123": { + Proto: &service_manage.Instance{ + Id: wrapperspb.String(uuid.NewString()), + Host: wrapperspb.String("127.0.0.1"), + Port: wrapperspb.UInt32(8080), + }, + Valid: true, + }, + }, nil).AnyTimes() + storage.EXPECT().GetUnixSecond(gomock.Any()).AnyTimes().Return(time.Now().Unix(), nil) + + return cfg, storage +} + +func createMockNamespace(total int, owner string) []*model.Namespace { + namespaces := make([]*model.Namespace, 0, total) + + for i := 0; i < total; i++ { + namespaces = append(namespaces, &model.Namespace{ + Name: fmt.Sprintf("namespace_%d", i), + Owner: owner, + Valid: true, + }) + } + + return namespaces +} + +func createMockService(namespaces []*model.Namespace) []*model.Service { + services := make([]*model.Service, 0, len(namespaces)) + + for i := 0; i < len(namespaces); i++ { + ns := namespaces[i] + services = append(services, &model.Service{ + ID: utils.NewUUID(), + Namespace: ns.Name, + Owner: ns.Owner, + Name: fmt.Sprintf("service_%d", i), + Valid: true, + }) + } + + return services +} + +// createMockUser 默认 users[0] 为 owner 用户 +func createMockUser(total int, prefix ...string) []*model.User { + users := make([]*model.User, 0, total) + + ownerId := utils.NewUUID() + + nameTemp := "user-%d" + if len(prefix) != 0 { + nameTemp = prefix[0] + nameTemp + } + + for i := 0; i < total; i++ { + id := fmt.Sprintf("fake-user-id-%d-%s", i, utils.NewUUID()) + if i == 0 { + id = ownerId + } + pwd, _ := bcrypt.GenerateFromPassword([]byte("polaris"), bcrypt.DefaultCost) + token, _ := defaultuser.CreateToken(id, "", "") + users = append(users, &model.User{ + ID: id, + Name: fmt.Sprintf(nameTemp, i), + Password: string(pwd), + Owner: func() string { + if id == ownerId { + return "" + } + return ownerId + }(), + Source: "Polaris", + Mobile: "", + Email: "", + Type: func() model.UserRoleType { + if id == ownerId { + return model.OwnerUserRole + } + return model.SubAccountUserRole + }(), + Token: token, + TokenEnable: true, + Valid: true, + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }) + } + return users +} + +func createApiMockUser(total int, prefix ...string) []*apisecurity.User { + users := make([]*apisecurity.User, 0, total) + + models := createMockUser(total, prefix...) + + for i := range models { + users = append(users, &apisecurity.User{ + Name: utils.NewStringValue("test-" + models[i].Name), + Password: utils.NewStringValue("123456"), + Source: utils.NewStringValue("Polaris"), + Comment: utils.NewStringValue(models[i].Comment), + Mobile: utils.NewStringValue(models[i].Mobile), + Email: utils.NewStringValue(models[i].Email), + }) + } + + return users +} + +func createMockUserGroup(users []*model.User) []*model.UserGroupDetail { + groups := make([]*model.UserGroupDetail, 0, len(users)) + + for i := range users { + user := users[i] + id := utils.NewUUID() + + token, _ := defaultuser.CreateToken("", id, _defaultSalt) + + groups = append(groups, &model.UserGroupDetail{ + UserGroup: &model.UserGroup{ + ID: id, + Name: fmt.Sprintf("test-group-%d", i), + Owner: users[0].ID, + Token: token, + TokenEnable: true, + Valid: true, + Comment: "", + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }, + UserIds: map[string]struct{}{ + user.ID: {}, + }, + }) + } + + return groups +} + +// createMockApiUserGroup +func createMockApiUserGroup(users []*apisecurity.User) []*apisecurity.UserGroup { + musers := make([]*model.User, 0, len(users)) + for i := range users { + musers = append(musers, &model.User{ + ID: users[i].GetId().GetValue(), + }) + } + + models := createMockUserGroup(musers) + ret := make([]*apisecurity.UserGroup, 0, len(models)) + + for i := range models { + ret = append(ret, &apisecurity.UserGroup{ + Name: utils.NewStringValue(models[i].Name), + Comment: utils.NewStringValue(models[i].Comment), + Relation: &apisecurity.UserGroupRelation{ + Users: []*apisecurity.User{ + { + Id: utils.NewStringValue(users[i].GetId().GetValue()), + }, + }, + }, + }) + } + + return ret +} + +func createMockStrategy(users []*model.User, groups []*model.UserGroupDetail, services []*model.Service) ([]*model.StrategyDetail, []*model.StrategyDetail) { + strategies := make([]*model.StrategyDetail, 0, len(users)+len(groups)) + defaultStrategies := make([]*model.StrategyDetail, 0, len(users)+len(groups)) + + owner := "" + for i := 0; i < len(users); i++ { + user := users[i] + if user.Owner == "" { + owner = user.ID + break + } + } + + for i := 0; i < len(users); i++ { + user := users[i] + service := services[i] + id := utils.NewUUID() + strategies = append(strategies, &model.StrategyDetail{ + ID: id, + Name: fmt.Sprintf("strategy_user_%s_%d", user.Name, i), + Action: apisecurity.AuthAction_READ_WRITE.String(), + Comment: "", + Principals: []model.Principal{ + { + PrincipalID: user.ID, + PrincipalRole: model.PrincipalUser, + }, + }, + Default: false, + Owner: owner, + Resources: []model.StrategyResource{ + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Namespaces), + ResID: service.Namespace, + }, + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Services), + ResID: service.ID, + }, + }, + Valid: true, + Revision: utils.NewUUID(), + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }) + + defaultStrategies = append(defaultStrategies, &model.StrategyDetail{ + ID: id, + Name: fmt.Sprintf("strategy_default_user_%s_%d", user.Name, i), + Action: apisecurity.AuthAction_READ_WRITE.String(), + Comment: "", + Principals: []model.Principal{ + { + PrincipalID: user.ID, + PrincipalRole: model.PrincipalUser, + }, + }, + Default: true, + Owner: owner, + Resources: []model.StrategyResource{ + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Namespaces), + ResID: service.Namespace, + }, + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Services), + ResID: service.ID, + }, + }, + Valid: true, + Revision: utils.NewUUID(), + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }) + } + + for i := 0; i < len(groups); i++ { + group := groups[i] + service := services[len(users)+i] + id := utils.NewUUID() + strategies = append(strategies, &model.StrategyDetail{ + ID: id, + Name: fmt.Sprintf("strategy_group_%s_%d", group.Name, i), + Action: apisecurity.AuthAction_READ_WRITE.String(), + Comment: "", + Principals: []model.Principal{ + { + PrincipalID: group.ID, + PrincipalRole: model.PrincipalGroup, + }, + }, + Default: false, + Owner: owner, + Resources: []model.StrategyResource{ + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Namespaces), + ResID: service.Namespace, + }, + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Services), + ResID: service.ID, + }, + }, + Valid: true, + Revision: utils.NewUUID(), + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }) + + defaultStrategies = append(defaultStrategies, &model.StrategyDetail{ + ID: id, + Name: fmt.Sprintf("strategy_default_group_%s_%d", group.Name, i), + Action: apisecurity.AuthAction_READ_WRITE.String(), + Comment: "", + Principals: []model.Principal{ + { + PrincipalID: group.ID, + PrincipalRole: model.PrincipalGroup, + }, + }, + Default: true, + Owner: owner, + Resources: []model.StrategyResource{ + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Namespaces), + ResID: service.Namespace, + }, + { + StrategyID: id, + ResType: int32(apisecurity.ResourceType_Services), + ResID: service.ID, + }, + }, + Valid: true, + Revision: utils.NewUUID(), + CreateTime: time.Time{}, + ModifyTime: time.Time{}, + }) + } + + return defaultStrategies, strategies +} + +func convertServiceSliceToMap(services []*model.Service) map[string]*model.Service { + ret := make(map[string]*model.Service) + + for i := range services { + service := services[i] + ret[service.ID] = service + } + + return ret +} diff --git a/auth/defaultauth/test_export.go b/auth/user/default.go similarity index 54% rename from auth/defaultauth/test_export.go rename to auth/user/default.go index f15eae235..cf8527556 100644 --- a/auth/defaultauth/test_export.go +++ b/auth/user/default.go @@ -15,30 +15,12 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package defaultuser import ( - "context" - - "github.com/golang/protobuf/ptypes/wrappers" + "github.com/polarismesh/polaris/auth" ) -func TestCheckPassword(password *wrappers.StringValue) error { - return checkPassword(password) -} - -func TestCheckName(password *wrappers.StringValue) error { - return checkName(password) -} - -func TestCreateToken(uid, gid string) (string, error) { - return createToken(uid, gid) -} - -func TestDecryptMessage(key []byte, message string) (string, error) { - return decryptMessage(key, message) -} - -func TestParseStrategySearchArgs(ctx context.Context, searchFilters map[string]string) map[string]string { - return parseStrategySearchArgs(ctx, searchFilters) +func init() { + _ = auth.RegisterUserServer(&Server{}) } diff --git a/auth/defaultauth/group.go b/auth/user/group.go similarity index 97% rename from auth/defaultauth/group.go rename to auth/user/group.go index 710c3a29b..9a3c95894 100644 --- a/auth/defaultauth/group.go +++ b/auth/user/group.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package defaultuser import ( "context" @@ -79,7 +79,7 @@ func (svr *Server) CreateGroup(ctx context.Context, req *apisecurity.UserGroup) return api.NewGroupResponse(apimodel.Code_UserGroupExisted, req) } - data, err := createGroupModel(req) + data, err := svr.createGroupModel(req) if err != nil { log.Error("create group model", utils.ZapRequestID(requestID), utils.ZapPlatformID(platformID), zap.Error(err)) @@ -368,7 +368,7 @@ func (svr *Server) ResetGroupToken(ctx context.Context, req *apisecurity.UserGro return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) } - newToken, err := createGroupToken(group.ID) + newToken, err := createGroupToken(group.ID, svr.authOpt.Salt) if err != nil { log.Error("reset group token", utils.ZapRequestID(requestID), utils.ZapPlatformID(platformID), zap.Error(err)) @@ -414,7 +414,7 @@ func (svr *Server) getGroupFromDB(id string) (*model.UserGroupDetail, *apiservic // getGroupFromCache 从缓存中获取用户组信息数据 func (svr *Server) getGroupFromCache(req *apisecurity.UserGroup) (*model.UserGroupDetail, *apiservice.Response) { - group := svr.cacheMgn.User().GetGroup(req.Id.GetValue()) + group := svr.cacheMgr.User().GetGroup(req.Id.GetValue()) if group == nil { return nil, api.NewGroupResponse(apimodel.Code_NotFoundUserGroup, req) } @@ -425,7 +425,7 @@ func (svr *Server) getGroupFromCache(req *apisecurity.UserGroup) (*model.UserGro // preCheckGroupRelation 检查用户-用户组关联关系中,对应的用户信息是否存在,即不能添加一个不存在的用户到用户组 func (svr *Server) preCheckGroupRelation(groupID string, req *apisecurity.UserGroupRelation) (*model.UserGroupDetail, *apiservice.Response) { - group := svr.cacheMgn.User().GetGroup(groupID) + group := svr.cacheMgr.User().GetGroup(groupID) if group == nil { return nil, api.NewAuthResponse(apimodel.Code_NotFoundUserGroup) } @@ -438,7 +438,7 @@ func (svr *Server) preCheckGroupRelation(groupID string, req *apisecurity.UserGr uIDs = utils.StringSliceDeDuplication(uIDs) for i := range uIDs { - user := svr.cacheMgn.User().GetUserByID(uIDs[i]) + user := svr.cacheMgr.User().GetUserByID(uIDs[i]) if user == nil { return group, api.NewGroupRelationResponse(apimodel.Code_NotFoundUser, req) } @@ -455,7 +455,7 @@ func (svr *Server) checkCreateGroup(_ context.Context, req *apisecurity.UserGrou users := req.GetRelation().GetUsers() for i := range users { - user := svr.cacheMgn.User().GetUserByID(users[i].GetId().GetValue()) + user := svr.cacheMgr.User().GetUserByID(users[i].GetId().GetValue()) if user == nil { return api.NewGroupRelationResponse(apimodel.Code_NotFoundUser, req.GetRelation()) } @@ -502,7 +502,7 @@ func (svr *Server) checkUpdateGroup(ctx context.Context, req *apisecurity.Modify } func (svr *Server) fillGroupUserCount(groups []*apisecurity.UserGroup) { - groupCache := svr.cacheMgn.User() + groupCache := svr.cacheMgr.User() for index := range groups { group := groups[index] @@ -569,7 +569,7 @@ func enhancedGroups2Api(groups []*model.UserGroup, handler UserGroup2Api) []*api } // createGroupModel 创建用户组的存储模型 -func createGroupModel(req *apisecurity.UserGroup) (group *model.UserGroupDetail, err error) { +func (svr *Server) createGroupModel(req *apisecurity.UserGroup) (group *model.UserGroupDetail, err error) { ids := make(map[string]struct{}, len(req.GetRelation().GetUsers())) for index := range req.GetRelation().GetUsers() { ids[req.GetRelation().GetUsers()[index].GetId().GetValue()] = struct{}{} @@ -589,7 +589,7 @@ func createGroupModel(req *apisecurity.UserGroup) (group *model.UserGroupDetail, UserIds: ids, } - if group.Token, err = createGroupToken(group.ID); err != nil { + if group.Token, err = createGroupToken(group.ID, svr.authOpt.Salt); err != nil { return nil, err } return group, nil @@ -623,7 +623,7 @@ func (svr *Server) userGroupDetail2Api(group *model.UserGroupDetail) *apisecurit users := make([]*apisecurity.User, 0, len(group.UserIds)) for id := range group.UserIds { - user := svr.cacheMgn.User().GetUserByID(id) + user := svr.cacheMgr.User().GetUserByID(id) users = append(users, &apisecurity.User{ Id: utils.NewStringValue(user.ID), Name: utils.NewStringValue(user.Name), diff --git a/auth/defaultauth/group_test.go b/auth/user/group_test.go similarity index 98% rename from auth/defaultauth/group_test.go rename to auth/user/group_test.go index 8757ee946..950415e91 100644 --- a/auth/defaultauth/group_test.go +++ b/auth/user/group_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package defaultuser_test import ( "context" @@ -28,7 +28,7 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/auth/defaultauth" + defaultauth "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" v1 "github.com/polarismesh/polaris/common/api/v1" @@ -51,10 +51,9 @@ type GroupTest struct { storage *storemock.MockStore cacheMgn *cache.CacheManager checker auth.AuthChecker + cancel context.CancelFunc - svr *defaultauth.GroupAuthAbility - - cancel context.CancelFunc + svr *defaultauth.Server } func newGroupTest(t *testing.T) *GroupTest { @@ -95,15 +94,6 @@ func newGroupTest(t *testing.T) *GroupTest { }) _ = cacheMgn.TestUpdate() - - checker := &defaultauth.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgn) - - svr := defaultauth.NewGroupAuthAbility( - checker, - defaultauth.NewServer(storage, nil, cacheMgn, checker), - ) - return &GroupTest{ ctrl: ctrl, @@ -117,8 +107,6 @@ func newGroupTest(t *testing.T) *GroupTest { storage: storage, cacheMgn: cacheMgn, - checker: checker, - svr: svr, cancel: cancel, } diff --git a/auth/user/helper.go b/auth/user/helper.go new file mode 100644 index 000000000..54137f30e --- /dev/null +++ b/auth/user/helper.go @@ -0,0 +1,22 @@ +/** + * 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 defaultuser + +type DefaultUserHelper struct { + +} diff --git a/auth/user/log.go b/auth/user/log.go new file mode 100644 index 000000000..6c2ab6b3c --- /dev/null +++ b/auth/user/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 defaultuser + +import commonlog "github.com/polarismesh/polaris/common/log" + +var ( + log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) +) diff --git a/auth/user/main_test.go b/auth/user/main_test.go new file mode 100644 index 000000000..6ccb1a835 --- /dev/null +++ b/auth/user/main_test.go @@ -0,0 +1,215 @@ +/** + * 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 defaultuser_test + +import ( + "errors" + + _ "github.com/go-sql-driver/mysql" + bolt "go.etcd.io/bbolt" + + "github.com/polarismesh/polaris/auth" + "github.com/polarismesh/polaris/cache" + _ "github.com/polarismesh/polaris/cache" + api "github.com/polarismesh/polaris/common/api/v1" + commonlog "github.com/polarismesh/polaris/common/log" + "github.com/polarismesh/polaris/namespace" + "github.com/polarismesh/polaris/plugin" + _ "github.com/polarismesh/polaris/plugin/cmdb/memory" + _ "github.com/polarismesh/polaris/plugin/discoverevent/local" + _ "github.com/polarismesh/polaris/plugin/healthchecker/memory" + _ "github.com/polarismesh/polaris/plugin/healthchecker/redis" + _ "github.com/polarismesh/polaris/plugin/history/logger" + _ "github.com/polarismesh/polaris/plugin/password" + _ "github.com/polarismesh/polaris/plugin/ratelimit/token" + _ "github.com/polarismesh/polaris/plugin/statis/logger" + _ "github.com/polarismesh/polaris/plugin/statis/prometheus" + "github.com/polarismesh/polaris/service/healthcheck" + "github.com/polarismesh/polaris/store" + "github.com/polarismesh/polaris/store/boltdb" + _ "github.com/polarismesh/polaris/store/boltdb" + _ "github.com/polarismesh/polaris/store/mysql" + sqldb "github.com/polarismesh/polaris/store/mysql" + testsuit "github.com/polarismesh/polaris/test/suit" +) + +const ( + tblUser string = "user" + tblStrategy string = "strategy" + tblGroup string = "group" +) + +type Bootstrap struct { + Logger map[string]*commonlog.Options +} + +type TestConfig struct { + Bootstrap Bootstrap `yaml:"bootstrap"` + Cache cache.Config `yaml:"cache"` + Namespace namespace.Config `yaml:"namespace"` + HealthChecks healthcheck.Config `yaml:"healthcheck"` + Store store.Config `yaml:"store"` + Auth auth.Config `yaml:"auth"` + Plugin plugin.Config `yaml:"plugin"` +} + +type AuthTestSuit struct { + testsuit.DiscoverTestSuit +} + +// 判断一个resp是否执行成功 +func respSuccess(resp api.ResponseMessage) bool { + + ret := api.CalcCode(resp) == 200 + + return ret +} + +type options func(cfg *TestConfig) + +func (d *AuthTestSuit) cleanAllUser() { + if d.Storage.Name() == sqldb.STORENAME { + func() { + tx, err := d.Storage.StartTx() + if err != nil { + panic(err) + } + + dbTx := tx.GetDelegateTx().(*sqldb.BaseTx) + + defer dbTx.Rollback() + + if _, err := dbTx.Exec("delete from user where name like 'test%'"); err != nil { + dbTx.Rollback() + panic(err) + } + + dbTx.Commit() + }() + } else if d.Storage.Name() == boltdb.STORENAME { + func() { + tx, err := d.Storage.StartTx() + if err != nil { + panic(err) + } + + dbTx := tx.GetDelegateTx().(*bolt.Tx) + defer dbTx.Rollback() + + if err := dbTx.DeleteBucket([]byte(tblUser)); err != nil { + if !errors.Is(err, bolt.ErrBucketNotFound) { + panic(err) + } + } + + dbTx.Commit() + }() + } +} + +func (d *AuthTestSuit) cleanAllUserGroup() { + if d.Storage.Name() == sqldb.STORENAME { + func() { + tx, err := d.Storage.StartTx() + if err != nil { + panic(err) + } + + dbTx := tx.GetDelegateTx().(*sqldb.BaseTx) + + defer dbTx.Rollback() + + if _, err := dbTx.Exec("delete from user_group where name like 'test%'"); err != nil { + dbTx.Rollback() + panic(err) + } + if _, err := dbTx.Exec("delete from user_group_relation"); err != nil { + dbTx.Rollback() + panic(err) + } + + dbTx.Commit() + }() + } else if d.Storage.Name() == boltdb.STORENAME { + func() { + tx, err := d.Storage.StartTx() + if err != nil { + panic(err) + } + + dbTx := tx.GetDelegateTx().(*bolt.Tx) + defer dbTx.Rollback() + + if err := dbTx.DeleteBucket([]byte(tblGroup)); err != nil { + if !errors.Is(err, bolt.ErrBucketNotFound) { + panic(err) + } + } + + dbTx.Commit() + }() + } +} + +func (d *AuthTestSuit) cleanAllAuthStrategy() { + if d.Storage.Name() == sqldb.STORENAME { + func() { + tx, err := d.Storage.StartTx() + if err != nil { + panic(err) + } + + dbTx := tx.GetDelegateTx().(*sqldb.BaseTx) + + defer dbTx.Rollback() + + if _, err := dbTx.Exec("delete from auth_strategy where id != 'fbca9bfa04ae4ead86e1ecf5811e32a9'"); err != nil { + dbTx.Rollback() + panic(err) + } + if _, err := dbTx.Exec("delete from auth_principal where strategy_id != 'fbca9bfa04ae4ead86e1ecf5811e32a9'"); err != nil { + dbTx.Rollback() + panic(err) + } + if _, err := dbTx.Exec("delete from auth_strategy_resource where strategy_id != 'fbca9bfa04ae4ead86e1ecf5811e32a9'"); err != nil { + dbTx.Rollback() + panic(err) + } + + dbTx.Commit() + }() + } else if d.Storage.Name() == boltdb.STORENAME { + func() { + tx, err := d.Storage.StartTx() + if err != nil { + panic(err) + } + + dbTx := tx.GetDelegateTx().(*bolt.Tx) + defer dbTx.Rollback() + + if err := dbTx.DeleteBucket([]byte(tblStrategy)); err != nil { + if !errors.Is(err, bolt.ErrBucketNotFound) { + panic(err) + } + } + + dbTx.Commit() + }() + } +} diff --git a/auth/user/server.go b/auth/user/server.go new file mode 100644 index 000000000..f767030bd --- /dev/null +++ b/auth/user/server.go @@ -0,0 +1,183 @@ +/** + * 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 defaultuser + +import ( + "encoding/json" + "errors" + + 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" + "golang.org/x/crypto/bcrypt" + + "github.com/polarismesh/polaris/auth" + 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" + "github.com/polarismesh/polaris/plugin" + "github.com/polarismesh/polaris/store" +) + +// AuthConfig 鉴权配置 +type AuthConfig struct { + // Salt 相关密码、token加密的salt + Salt string `json:"salt" xml:"salt"` +} + +// Verify 检查配置是否合法 +func (cfg *AuthConfig) Verify() error { + k := len(cfg.Salt) + switch k { + case 16, 24, 32: + break + default: + return errors.New("[Auth][Config] salt len must 16 | 24 | 32") + } + + return nil +} + +// DefaultUserConfig 返回一个默认的鉴权配置 +func DefaultUserConfig() *AuthConfig { + return &AuthConfig{ + // Salt token 加密 key + Salt: "polarismesh@2021", + } +} + +type Server struct { + authOpt *AuthConfig + storage store.Store + history plugin.History + cacheMgr cachetypes.CacheManager + helper auth.UserHelper +} + +// Name of the user operator plugin +func (svr *Server) Name() string { + return auth.DefaultUserMgnPluginName +} + +func (svr *Server) Initialize(authOpt *auth.Config, storage store.Store, cacheMgr cachetypes.CacheManager) error { + svr.cacheMgr = cacheMgr + svr.storage = storage + if err := svr.parseOptions(authOpt); err != nil { + return err + } + + _ = cacheMgr.OpenResourceCache(cachetypes.ConfigEntry{ + Name: cachetypes.UsersName, + }) + // 获取History插件,注意:插件的配置在bootstrap已经设置好 + svr.history = plugin.GetHistory() + if svr.history == nil { + log.Warnf("Not Found History Log Plugin") + } + return nil +} + +func (svr *Server) parseOptions(options *auth.Config) error { + // 新版本鉴权策略配置均从auth.Option中迁移至auth.user.option及auth.strategy.option中 + var ( + userContentBytes []byte + authContentBytes []byte + err error + ) + + cfg := DefaultUserConfig() + + // 一旦设置了auth.user.option或auth.strategy.option,将不会继续读取auth.option + if len(options.User.Option) > 0 { + // 判断auth.option是否还有值,有则不兼容 + if len(options.Option) > 0 { + log.Warn("auth.user.option or auth.strategy.option has set, auth.option will ignore") + } + userContentBytes, err = json.Marshal(options.User.Option) + if err != nil { + return err + } + if err := json.Unmarshal(userContentBytes, cfg); err != nil { + return err + } + } else { + log.Warn("[Auth][Checker] auth.option has deprecated, use auth.user.option and auth.strategy.option instead.") + authContentBytes, err = json.Marshal(options.Option) + if err != nil { + return err + } + if err := json.Unmarshal(authContentBytes, cfg); err != nil { + return err + } + } + if err := cfg.Verify(); err != nil { + return err + } + svr.authOpt = cfg + return nil +} + +// Login 登录动作 +func (svr *Server) Login(req *apisecurity.LoginRequest) *apiservice.Response { + username := req.GetName().GetValue() + ownerName := req.GetOwner().GetValue() + if ownerName == "" { + ownerName = username + } + user := svr.cacheMgr.User().GetUserByName(username, ownerName) + if user == nil { + return api.NewAuthResponse(apimodel.Code_NotFoundUser) + } + + err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.GetPassword().GetValue())) + if err != nil { + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { + return api.NewAuthResponseWithMsg( + apimodel.Code_NotAllowedAccess, model.ErrorWrongUsernameOrPassword.Error()) + } + return api.NewAuthResponseWithMsg(apimodel.Code_ExecuteException, model.ErrorWrongUsernameOrPassword.Error()) + } + + return api.NewLoginResponse(apimodel.Code_ExecuteSuccess, &apisecurity.LoginResponse{ + UserId: utils.NewStringValue(user.ID), + OwnerId: utils.NewStringValue(user.Owner), + Token: utils.NewStringValue(user.Token), + Name: utils.NewStringValue(user.Name), + Role: utils.NewStringValue(model.UserRoleNames[user.Type]), + }) +} + +// RecordHistory Server对外提供history插件的简单封装 +func (svr *Server) RecordHistory(entry *model.RecordEntry) { + // 如果插件没有初始化,那么不记录history + if svr.history == nil { + return + } + // 如果数据为空,则不需要打印了 + if entry == nil { + return + } + + // 调用插件记录history + svr.history.Record(entry) +} + +func (svr *Server) GetUserHelper() auth.UserHelper { + return svr.helper +} diff --git a/auth/defaultauth/token.go b/auth/user/token.go similarity index 57% rename from auth/defaultauth/token.go rename to auth/user/token.go index ea95476d6..3942257ef 100644 --- a/auth/defaultauth/token.go +++ b/auth/user/token.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package defaultuser import ( "crypto/aes" @@ -25,59 +25,79 @@ import ( "errors" "fmt" "io" + "strings" "github.com/google/uuid" + "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/common/model" ) -// OperatorInfo 根据 token 解析出来的具体额外信息 -type OperatorInfo struct { +// decodeToken 解析 token 信息,如果 t == "",直接返回一个空对象 +func (svr *Server) decodeToken(t string) (auth.OperatorInfo, error) { + if t == "" { + return auth.OperatorInfo{}, model.ErrorTokenInvalid + } - // Origin 原始 token 字符串 - Origin string + ret, err := DecryptMessage([]byte(svr.authOpt.Salt), t) + if err != nil { + return auth.OperatorInfo{}, err + } + tokenDetails := strings.Split(ret, TokenSplit) + if len(tokenDetails) != 2 { + return auth.OperatorInfo{}, model.ErrorTokenInvalid + } - // OperatorID 当前 token 绑定的 用户/用户组 ID - OperatorID string + detail := strings.Split(tokenDetails[1], "/") + if len(detail) != 2 { + return auth.OperatorInfo{}, model.ErrorTokenInvalid + } - // OwnerID 当前用户/用户组对应的 owner - OwnerID string + tokenInfo := auth.OperatorInfo{ + Origin: t, + IsUserToken: detail[0] == model.TokenForUser, + OperatorID: detail[1], + Role: model.UnknownUserRole, + } + return tokenInfo, nil +} - // Role 如果当前是 user token 的话,该值才能有信息 - Role model.UserRoleType +// checkToken 对 token 进行检查,如果 token 是一个空,直接返回默认值,但是不返回错误 +// return {owner-id} {is-owner} {error} +func (svr *Server) checkToken(tokenInfo *auth.OperatorInfo) (string, bool, error) { + if auth.IsEmptyOperator(*tokenInfo) { + return "", false, nil + } - // IsUserToken 当前 token 是否是 user 的 token - IsUserToken bool + id := tokenInfo.OperatorID + if tokenInfo.IsUserToken { + user := svr.cacheMgr.User().GetUserByID(id) + if user == nil { + return "", false, model.ErrorNoUser + } - // Disable 标识用户 token 是否被禁用 - Disable bool + if tokenInfo.Origin != user.Token { + return "", false, model.ErrorTokenNotExist + } - // 是否属于匿名操作者 - Anonymous bool -} + tokenInfo.Disable = !user.TokenEnable + if user.Owner == "" { + return user.ID, true, nil + } -func newAnonymous() OperatorInfo { - return OperatorInfo{ - Origin: "", - OwnerID: "", - OperatorID: "__anonymous__", - Anonymous: true, + return user.Owner, false, nil + } + group := svr.cacheMgr.User().GetGroup(id) + if group == nil { + return "", false, model.ErrorNoUserGroup } -} - -// IsEmptyOperator token 是否是一个空类型 -func IsEmptyOperator(t OperatorInfo) bool { - return t.Origin == "" || t.Anonymous -} -// IsSubAccount 当前 token 对应的账户类型 -func IsSubAccount(t OperatorInfo) bool { - return t.Role == model.SubAccountUserRole -} + if tokenInfo.Origin != group.Token { + return "", false, model.ErrorTokenNotExist + } -func (t *OperatorInfo) String() string { - return fmt.Sprintf("operator-id=%s, owner=%s, role=%d, is-user=%v, disable=%v", - t.OperatorID, t.OwnerID, t.Role, t.IsUserToken, t.Disable) + tokenInfo.Disable = !group.TokenEnable + return group.Owner, false, nil } const ( @@ -88,17 +108,17 @@ const ( ) // createUserToken Create a user token -func createUserToken(uid string) (string, error) { - return createToken(uid, "") +func createUserToken(uid string, salt string) (string, error) { + return CreateToken(uid, "", salt) } // createGroupToken Create user group token -func createGroupToken(gid string) (string, error) { - return createToken("", gid) +func createGroupToken(gid string, salt string) (string, error) { + return CreateToken("", gid, salt) } // createToken Determine what type of Token created according to the incoming parameters -func createToken(uid, gid string) (string, error) { +func CreateToken(uid, gid string, salt string) (string, error) { if uid == "" && gid == "" { return "", errors.New("uid and groupid not be empty at the same time") } @@ -111,7 +131,7 @@ func createToken(uid, gid string) (string, error) { } token := fmt.Sprintf(TokenPattern, uuid.NewString()[8:16], val) - return encryptMessage([]byte(AuthOption.Salt), token) + return encryptMessage([]byte(salt), token) } // encryptMessage 对消息进行加密 @@ -135,7 +155,7 @@ func encryptMessage(key []byte, message string) (string, error) { } // decryptMessage 对消息进行解密 -func decryptMessage(key []byte, message string) (string, error) { +func DecryptMessage(key []byte, message string) (string, error) { cipherText, err := base64.StdEncoding.DecodeString(message) if err != nil { return "", fmt.Errorf("could not base64 decode: %v", err) diff --git a/auth/defaultauth/token_test.go b/auth/user/token_test.go similarity index 73% rename from auth/defaultauth/token_test.go rename to auth/user/token_test.go index f7f9ed478..ebfa35b5b 100644 --- a/auth/defaultauth/token_test.go +++ b/auth/user/token_test.go @@ -15,29 +15,25 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package defaultuser_test import ( "fmt" "testing" "time" + defaultuser "github.com/polarismesh/polaris/auth/user" "golang.org/x/crypto/bcrypt" - - "github.com/polarismesh/polaris/auth/defaultauth" ) // Test_CustomDesignSalt 主要用于有自定义salt需求的用户 func Test_CustomDesignSalt(t *testing.T) { - defaultauth.AuthOption = defaultauth.DefaultAuthConfig() - // 设置自定义的 salt 值,长度需要满足在 [16, 24, 32] 中的任意一个 - defaultauth.AuthOption.Salt = "polarismesh@2021" - + salt := "polarismesh@2021" // polaris 用户的ID uid := "polaris" fmt.Printf("uid=%s\n", uid) - token, err := defaultauth.TestCreateToken(uid, "") + token, err := defaultuser.CreateToken(uid, "", salt) if err != nil { t.Fatal(err) } @@ -58,13 +54,12 @@ func Test_CustomDesignSalt(t *testing.T) { } func TestCreateToken(t *testing.T) { - defaultauth.AuthOption = defaultauth.DefaultAuthConfig() - defaultauth.AuthOption.Salt = "polarismesh@2021" + salt := "polarismesh@2021" uid := "65e4789a6d5b49669adf1e9e8387549c" fmt.Printf("uid=%s\n", uid) - token, err := defaultauth.TestCreateToken(uid, "") + token, err := defaultuser.CreateToken(uid, "", salt) if err != nil { t.Fatal(err) } @@ -82,12 +77,10 @@ func TestCreateToken(t *testing.T) { } func TestDecodeToken(t *testing.T) { - token := "bRJ76j5/mXQ1RFS0fs1vYIlcmGljGJ2W/CYKBKNVAdLGlW1otecX2qVQF0khlISq0q1r2v4fkI0o8OrhWcE=" - v, err := defaultauth.TestDecryptMessage([]byte("polaris@acb04d93c6e14bc1"), token) - + token := "o+Ad+6m9X2WOxxNrC1hgOhyiZ1L/ZHuvU6iqYRpyQAFRXHgIIH2ghUHTcaFsekUL1dT3IvDyap4R2WcSAL8=" + v, err := defaultuser.DecryptMessage([]byte("polaris@1ce410a2101348e9"), token) if err != nil { t.Fatal(err) } - t.Log(v) } diff --git a/auth/defaultauth/user.go b/auth/user/user.go similarity index 83% rename from auth/defaultauth/user.go rename to auth/user/user.go index 6e332c6f5..c64f0e81e 100644 --- a/auth/defaultauth/user.go +++ b/auth/user/user.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth +package defaultuser import ( "context" @@ -31,6 +31,7 @@ import ( "go.uber.org/zap" "golang.org/x/crypto/bcrypt" + "github.com/polarismesh/polaris/auth" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" authcommon "github.com/polarismesh/polaris/common/model/auth" @@ -118,7 +119,7 @@ func (svr *Server) CreateUser(ctx context.Context, req *apisecurity.User) *apise func (svr *Server) createUser(ctx context.Context, req *apisecurity.User) *apiservice.Response { requestID := utils.ParseRequestID(ctx) - data, err := createUserModel(req, authcommon.ParseUserRole(ctx)) + data, err := svr.createUserModel(req, authcommon.ParseUserRole(ctx)) if err != nil { log.Error("[Auth][User] create user model", utils.ZapRequestID(requestID), zap.Error(err)) @@ -350,12 +351,12 @@ func (svr *Server) GetUsers(ctx context.Context, query map[string]string) *apise func (svr *Server) GetUserToken(ctx context.Context, req *apisecurity.User) *apiservice.Response { var user *model.User if req.GetId().GetValue() != "" { - user = svr.cacheMgn.User().GetUserByID(req.GetId().GetValue()) + user = svr.cacheMgr.User().GetUserByID(req.GetId().GetValue()) } else if req.GetName().GetValue() != "" { ownerName := req.GetOwner().GetValue() ownerID := utils.ParseOwnerID(ctx) if ownerName == "" { - owner := svr.cacheMgn.User().GetUserByID(ownerID) + owner := svr.cacheMgr.User().GetUserByID(ownerID) if owner == nil { log.Error("[Auth][User] get user's owner not found", zap.String("name", req.GetName().GetValue()), zap.String("owner", ownerID)) @@ -363,7 +364,7 @@ func (svr *Server) GetUserToken(ctx context.Context, req *apisecurity.User) *api } ownerName = owner.Name } - user = svr.cacheMgn.User().GetUserByName(req.GetName().GetValue(), ownerName) + user = svr.cacheMgr.User().GetUserByName(req.GetName().GetValue(), ownerName) } else { return api.NewAuthResponse(apimodel.Code_InvalidParameter) } @@ -447,7 +448,7 @@ func (svr *Server) ResetUserToken(ctx context.Context, req *apisecurity.User) *a return api.NewUserResponse(apimodel.Code_NotAllowedAccess, req) } - newToken, err := createUserToken(user.ID) + newToken, err := createUserToken(user.ID, svr.authOpt.Salt) if err != nil { log.Error("[Auth][User] update user token", utils.ZapRequestID(requestID), zap.Error(err)) return api.NewUserResponse(apimodel.Code_ExecuteException, req) @@ -469,6 +470,103 @@ func (svr *Server) ResetUserToken(ctx context.Context, req *apisecurity.User) *a return api.NewUserResponse(apimodel.Code_ExecuteSuccess, req) } +// VerifyCredential 对 token 进行检查验证,并将 verify 过程中解析出的数据注入到 model.AcquireContext 中 +// step 1. 首先对 token 进行解析,获取相关的数据信息,注入到整个的 AcquireContext 中 +// step 2. 最后对 token 进行一些验证步骤的执行 +// step 3. 兜底措施:如果开启了鉴权的非严格模式,则根据错误的类型,判断是否转为匿名用户进行访问 +// - 如果是访问权限控制相关模块(用户、用户组、权限策略),不得转为匿名用户 +func (svr *Server) CheckCredential(authCtx *model.AcquireContext) error { + reqId := utils.ParseRequestID(authCtx.GetRequestContext()) + + checkErr := func() error { + authToken := utils.ParseAuthToken(authCtx.GetRequestContext()) + operator, err := svr.decodeToken(authToken) + if err != nil { + log.Error("[Auth][Checker] decode token", zap.Error(err)) + return model.ErrorTokenInvalid + } + + ownerId, isOwner, err := svr.checkToken(&operator) + if err != nil { + log.Errorf("[Auth][Checker] check token err : %s", err.Error()) + return err + } + + operator.OwnerID = ownerId + ctx := authCtx.GetRequestContext() + ctx = context.WithValue(ctx, utils.ContextIsOwnerKey, isOwner) + ctx = context.WithValue(ctx, utils.ContextUserIDKey, operator.OperatorID) + ctx = context.WithValue(ctx, utils.ContextOwnerIDKey, ownerId) + authCtx.SetRequestContext(ctx) + svr.parseOperatorInfo(operator, authCtx) + if operator.Disable { + log.Warn("[Auth][Checker] token already disabled", utils.ZapRequestID(reqId), + zap.Any("token", operator.String())) + } + return nil + }() + + if checkErr != nil { + if !canDowngradeAnonymous(authCtx, checkErr) { + return checkErr + } + log.Warn("[Auth][Checker] parse operator info, downgrade to anonymous", utils.ZapRequestID(reqId), + zap.Error(checkErr)) + // 操作者信息解析失败,降级为匿名用户 + authCtx.SetAttachment(model.TokenDetailInfoKey, auth.NewAnonymous()) + } + + return nil +} + +func (svr *Server) parseOperatorInfo(operator auth.OperatorInfo, authCtx *model.AcquireContext) { + ctx := authCtx.GetRequestContext() + if operator.IsUserToken { + user := svr.cacheMgr.User().GetUserByID(operator.OperatorID) + if user != nil { + operator.Role = user.Type + ctx = context.WithValue(ctx, utils.ContextOperator, user.Name) + ctx = context.WithValue(ctx, utils.ContextUserNameKey, user.Name) + ctx = context.WithValue(ctx, utils.ContextUserRoleIDKey, user.Type) + } + } else { + userGroup := svr.cacheMgr.User().GetGroup(operator.OperatorID) + if userGroup != nil { + ctx = context.WithValue(ctx, utils.ContextOperator, userGroup.Name) + ctx = context.WithValue(ctx, utils.ContextUserNameKey, userGroup.Name) + } + } + + authCtx.SetAttachment(model.OperatorRoleKey, operator.Role) + authCtx.SetAttachment(model.OperatorPrincipalType, func() model.PrincipalType { + if operator.IsUserToken { + return model.PrincipalUser + } + return model.PrincipalGroup + }()) + authCtx.SetAttachment(model.OperatorIDKey, operator.OperatorID) + authCtx.SetAttachment(model.OperatorOwnerKey, operator) + authCtx.SetAttachment(model.TokenDetailInfoKey, operator) + + authCtx.SetRequestContext(ctx) +} + +func canDowngradeAnonymous(authCtx *model.AcquireContext, err error) bool { + if authCtx.GetModule() == model.AuthModule { + return false + } + if !authCtx.IsAllowAnonymous() { + return false + } + if errors.Is(err, model.ErrorTokenInvalid) { + return true + } + if errors.Is(err, model.ErrorTokenNotExist) { + return true + } + return false +} + // checkUserViewPermission 检查是否可以操作该用户 // Case 1: 如果是自己操作自己,通过 // Case 2: 如果是主账户操作自己的子账户,通过 @@ -552,15 +650,15 @@ func checkCreateUser(req *apisecurity.User) *apiservice.Response { return api.NewUserResponse(apimodel.Code_EmptyRequest, req) } - if err := checkName(req.Name); err != nil { + if err := CheckName(req.Name); err != nil { return api.NewUserResponse(apimodel.Code_InvalidUserName, req) } - if err := checkPassword(req.Password); err != nil { + if err := CheckPassword(req.Password); err != nil { return api.NewUserResponse(apimodel.Code_InvalidUserPassword, req) } - if err := checkOwner(req.Owner); err != nil { + if err := CheckOwner(req.Owner); err != nil { return api.NewUserResponse(apimodel.Code_InvalidUserOwners, req) } return nil @@ -574,7 +672,7 @@ func checkUpdateUser(req *apisecurity.User) *apiservice.Response { // 如果本次请求需要修改密码的话 if req.GetPassword() != nil { - if err := checkPassword(req.Password); err != nil { + if err := CheckPassword(req.Password); err != nil { return api.NewUserResponseWithMsg(apimodel.Code_InvalidUserPassword, err.Error(), req) } } @@ -601,7 +699,7 @@ func updateUserPasswordAttribute( isAdmin bool, user *model.User, req *apisecurity.ModifyUserPassword) (*model.User, bool, error) { needUpdate := false - if err := checkPassword(req.NewPassword); err != nil { + if err := CheckPassword(req.NewPassword); err != nil { return nil, false, err } @@ -634,7 +732,7 @@ func updateUserPasswordAttribute( } // createUserModel 创建用户模型 -func createUserModel(req *apisecurity.User, role model.UserRoleType) (*model.User, error) { +func (svr *Server) createUserModel(req *apisecurity.User, role model.UserRoleType) (*model.User, error) { pwd, err := bcrypt.GenerateFromPassword([]byte(req.GetPassword().GetValue()), bcrypt.DefaultCost) if err != nil { return nil, err @@ -664,7 +762,7 @@ func createUserModel(req *apisecurity.User, role model.UserRoleType) (*model.Use user.Owner = "" } - newToken, err := createUserToken(user.ID) + newToken, err := createUserToken(user.ID, svr.authOpt.Salt) if err != nil { return nil, err } diff --git a/auth/defaultauth/user_test.go b/auth/user/user_test.go similarity index 98% rename from auth/defaultauth/user_test.go rename to auth/user/user_test.go index 2bd902bec..9de4f08b6 100644 --- a/auth/defaultauth/user_test.go +++ b/auth/user/user_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package defaultuser_test import ( "context" @@ -27,8 +27,7 @@ import ( apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" "github.com/stretchr/testify/assert" - "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/auth/defaultauth" + defaultuser "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" @@ -51,9 +50,8 @@ type UserTest struct { storage *storemock.MockStore cacheMgn *cache.CacheManager - checker auth.AuthChecker - svr *defaultauth.UserAuthAbility + svr *defaultuser.Server cancel context.CancelFunc ctrl *gomock.Controller @@ -105,15 +103,9 @@ func newUserTest(t *testing.T) *UserTest { ) time.Sleep(5 * time.Second) - checker := &defaultauth.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgn) - _ = cache.TestRun(ctx, cacheMgn) - svr := defaultauth.NewUserAuthAbility( - checker, - defaultauth.NewServer(storage, nil, cacheMgn, checker), - ) + svr := &defaultuser.Server{} return &UserTest{ admin: admin, ownerOne: users[0], @@ -125,7 +117,6 @@ func newUserTest(t *testing.T) *UserTest { storage: storage, cacheMgn: cacheMgn, - checker: checker, svr: svr, cancel: cancel, diff --git a/auth/user/utils.go b/auth/user/utils.go new file mode 100644 index 000000000..68e7828d1 --- /dev/null +++ b/auth/user/utils.go @@ -0,0 +1,103 @@ +/** + * 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 defaultuser + +import ( + "errors" + "regexp" + "unicode/utf8" + + "github.com/golang/protobuf/ptypes/wrappers" + + "github.com/polarismesh/polaris/common/utils" +) + +var ( + // MustOwner 必须超级账户 or 主账户 + MustOwner = true + // NotOwner 任意账户 + NotOwner = false + // WriteOp 写操作 + WriteOp = true + // ReadOp 读操作 + ReadOp = false +) + +var ( + regNameStr = regexp.MustCompile("^[\u4E00-\u9FA5A-Za-z0-9_\\-.]+$") + regEmail = regexp.MustCompile(`^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`) +) + +// CheckName 名称检查 +func CheckName(name *wrappers.StringValue) error { + if name == nil { + return errors.New(utils.NilErrString) + } + + if name.GetValue() == "" { + return errors.New(utils.EmptyErrString) + } + + if name.GetValue() == "polariadmin" { + return errors.New("illegal username") + } + + if utf8.RuneCountInString(name.GetValue()) > utils.MaxNameLength { + return errors.New("name too long") + } + + if ok := regNameStr.MatchString(name.GetValue()); !ok { + return errors.New("name contains invalid character") + } + + return nil +} + +// CheckPassword 密码检查 +func CheckPassword(password *wrappers.StringValue) error { + if password == nil { + return errors.New(utils.NilErrString) + } + + if password.GetValue() == "" { + return errors.New(utils.EmptyErrString) + } + + if pLen := len(password.GetValue()); pLen < 6 || pLen > 17 { + return errors.New("password len need 6 ~ 17") + } + + return nil +} + +// CheckOwner 检查用户的 owner 信息 +func CheckOwner(owner *wrappers.StringValue) error { + if owner == nil { + return errors.New(utils.NilErrString) + } + + if owner.GetValue() == "" { + return errors.New(utils.EmptyErrString) + } + + if utf8.RuneCountInString(owner.GetValue()) > utils.MaxOwnersLength { + return errors.New("owners too long") + } + + return nil +} diff --git a/auth/defaultauth/utils_test.go b/auth/user/utils_test.go similarity index 92% rename from auth/defaultauth/utils_test.go rename to auth/user/utils_test.go index 50cf8defb..952949451 100644 --- a/auth/defaultauth/utils_test.go +++ b/auth/user/utils_test.go @@ -15,14 +15,13 @@ * specific language governing permissions and limitations under the License. */ -package defaultauth_test +package defaultuser_test import ( "testing" "github.com/golang/protobuf/ptypes/wrappers" - - "github.com/polarismesh/polaris/auth/defaultauth" + defaultuser "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/common/utils" ) @@ -98,7 +97,7 @@ func Test_checkPassword(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := defaultauth.TestCheckPassword(tt.args.password); (err != nil) != tt.wantErr { + if err := defaultuser.CheckPassword(tt.args.password); (err != nil) != tt.wantErr { t.Errorf("checkPassword() error = %v, wantErr %v, args = %#v", err, tt.wantErr, tt.args.password.GetValue()) } }) @@ -147,7 +146,7 @@ func Test_checkName(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := defaultauth.TestCheckName(tt.args.name); (err != nil) != tt.wantErr { + if err := defaultuser.CheckName(tt.args.name); (err != nil) != tt.wantErr { t.Errorf("checkName() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/cache/config/config_file.go b/cache/config/config_file.go index 956d1b519..37651fab8 100644 --- a/cache/config/config_file.go +++ b/cache/config/config_file.go @@ -138,6 +138,9 @@ func (fc *fileCache) realUpdate() (map[string]time.Time, int64, error) { if len(releases) == 0 { return nil, 0, nil } + if err := fc.setActiveReleases(releases); err != nil { + return nil, 0, err + } lastMimes, update, del, err := fc.setReleases(releases) if err != nil { @@ -149,6 +152,55 @@ func (fc *fileCache) realUpdate() (map[string]time.Time, int64, error) { return lastMimes, int64(len(releases)), err } +func (fc *fileCache) setActiveReleases(releases []*model.ConfigFileRelease) error { + // 按照 namespace->group->file 分类,保存最后一个 version 版本的 release 发布信息 + effectRelease := map[string]map[string]map[string]*model.ConfigFileRelease{} + for i := range releases { + item := releases[i] + oldVal := fc.GetRelease(*item.ConfigFileReleaseKey) + // 判断是否取消发布了 + if oldVal != nil && (oldVal.Active && !item.Active) { + // 如果配置文件发布被取消了,那么这里等同认为 valid == false 的情况,同时记录 version 为最新的 version + item = &model.ConfigFileRelease{ + SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ + ConfigFileReleaseKey: item.ConfigFileReleaseKey, + Valid: false, + Version: item.Version, + }, + } + } + + if item.Active { + if _, ok := effectRelease[item.Namespace]; !ok { + effectRelease[item.Namespace] = make(map[string]map[string]*model.ConfigFileRelease) + } + if _, ok := effectRelease[item.Namespace][item.Group]; !ok { + effectRelease[item.Namespace][item.Group] = make(map[string]*model.ConfigFileRelease) + } + saveVal, ok := effectRelease[item.Namespace][item.Group][item.FileName] + // 只保存最新的 version 的 active 状态的配置发布记录 + // 该逻辑主要是为了避免配置回滚的情况下,客户端有概率性读取到中间态数据,导致程序执行不符合用户预期 + if !ok || saveVal.Version < item.Version { + effectRelease[item.Namespace][item.Group][item.FileName] = item + } + } + } + + for _, groups := range effectRelease { + for _, files := range groups { + for _, releaseFile := range files { + if !releaseFile.Valid { + fc.cleanActiveRelease(releaseFile.SimpleConfigFileRelease) + } else { + fc.saveActiveRelease(releaseFile) + } + fc.sendEvent(releaseFile) + } + } + } + return nil +} + func (fc *fileCache) setReleases(releases []*model.ConfigFileRelease) (map[string]time.Time, int, int, error) { lastMtime := fc.LastMtime().Unix() update := 0 @@ -227,15 +279,7 @@ func (fc *fileCache) handleUpdateRelease(oldVal *model.SimpleConfigFileRelease, files, _ := group.Load(item.FileName) files.Store(item.Name, item.SimpleConfigFileRelease) }() - - if !item.Active { - if oldVal != nil && oldVal.Active { - return fc.cleanActiveRelease(oldVal) - } - return nil - } - - return fc.saveActiveRelease(item) + return nil } // handleDeleteRelease @@ -244,32 +288,27 @@ func (fc *fileCache) handleDeleteRelease(release *model.SimpleConfigFileRelease) return nil } fc.releases.Del(release.Id) - func() { - // 记录 namespace -> group -> file_name -> []SimpleRelease 信息 - if _, ok := fc.name2release.Load(release.Namespace); !ok { - return - } - namespace, _ := fc.name2release.Load(release.Namespace) - if _, ok := namespace.Load(release.Group); !ok { - return - } - group, _ := namespace.Load(release.Group) - if _, ok := group.Load(release.FileName); !ok { - return - } - files, _ := group.Load(release.FileName) - files.Delete(release.Name) + // 记录 namespace -> group -> file_name -> []SimpleRelease 信息 + if _, ok := fc.name2release.Load(release.Namespace); !ok { + return nil + } + namespace, _ := fc.name2release.Load(release.Namespace) + if _, ok := namespace.Load(release.Group); !ok { + return nil + } + group, _ := namespace.Load(release.Group) + if _, ok := group.Load(release.FileName); !ok { + return nil + } - if files.Len() == 0 { - group.Delete(release.FileName) - } - }() + files, _ := group.Load(release.FileName) + files.Delete(release.Name) - if !release.Active { - return nil + if files.Len() == 0 { + group.Delete(release.FileName) } - return fc.cleanActiveRelease(release) + return nil } func (fc *fileCache) saveActiveRelease(item *model.ConfigFileRelease) error { diff --git a/common/model/acquire_context.go b/common/model/acquire_context.go index 8c211840b..23511ac4d 100644 --- a/common/model/acquire_context.go +++ b/common/model/acquire_context.go @@ -23,6 +23,14 @@ import ( apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" ) +type acquireContextOption func(authCtx *AcquireContext) + +var ( + _defaultAuthContextOptions []acquireContextOption = []acquireContextOption{ + WithFromConsole(), + } +) + // AcquireContext 每次鉴权请求上下文信息 type AcquireContext struct { // RequestContext 请求上下文 @@ -39,16 +47,10 @@ type AcquireContext struct { attachment map[string]interface{} // fromClient 是否来自客户端的请求 fromClient bool + // allowAnonymous 是否允许匿名用户 + allowAnonymous bool } -type acquireContextOption func(authCtx *AcquireContext) - -var ( - _defaultAuthContextOptions []acquireContextOption = []acquireContextOption{ - WithFromConsole(), - } -) - // NewAcquireContext 创建一个请求响应 // // @param options @@ -243,3 +245,13 @@ func (authCtx *AcquireContext) IsAccessResourceEmpty() bool { return nsEmpty && svcEmpty && cfgEmpty } + +// AllowAnonymous 本次请求是否允许匿名访问 +func (authCtx *AcquireContext) IsAllowAnonymous() bool { + return authCtx.allowAnonymous +} + +// SetAllowAnonymous 本次请求是否允许匿名访问 +func (authCtx *AcquireContext) SetAllowAnonymous(a bool) { + authCtx.allowAnonymous = a +} diff --git a/common/model/auth.go b/common/model/auth.go index 6dc8007e6..0ad70f25f 100644 --- a/common/model/auth.go +++ b/common/model/auth.go @@ -24,6 +24,7 @@ import ( "time" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" ) var ( @@ -333,3 +334,9 @@ type Principal struct { PrincipalID string PrincipalRole PrincipalType } + +type OperateResource struct { + ResOwner string + ResType apisecurity.ResourceType + ResID string +} diff --git a/config/common_test.go b/config/common_test.go index 9ca2ab406..a496c1757 100644 --- a/config/common_test.go +++ b/config/common_test.go @@ -25,7 +25,6 @@ import ( apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage" "github.com/polarismesh/polaris/auth" - _ "github.com/polarismesh/polaris/auth/defaultauth" "github.com/polarismesh/polaris/cache" _ "github.com/polarismesh/polaris/cache" commonlog "github.com/polarismesh/polaris/common/log" diff --git a/config/config_file_release.go b/config/config_file_release.go index e1efa3d03..a2d23ca69 100644 --- a/config/config_file_release.go +++ b/config/config_file_release.go @@ -400,6 +400,7 @@ func (s *Server) handleDescribeConfigFileReleases(ctx context.Context, return resp } +// RollbackConfigFileReleases 批量回滚配置 func (s *Server) RollbackConfigFileReleases(ctx context.Context, reqs []*apiconfig.ConfigFileRelease) *apiconfig.ConfigBatchWriteResponse { diff --git a/plugin.go b/plugin.go index 08a5dab47..419bf06a2 100644 --- a/plugin.go +++ b/plugin.go @@ -25,7 +25,8 @@ import ( _ "github.com/polarismesh/polaris/apiserver/l5pbserver" _ "github.com/polarismesh/polaris/apiserver/nacosserver" _ "github.com/polarismesh/polaris/apiserver/xdsserverv3" - _ "github.com/polarismesh/polaris/auth/defaultauth" + _ "github.com/polarismesh/polaris/auth/authcheck" + _ "github.com/polarismesh/polaris/auth/user" _ "github.com/polarismesh/polaris/cache" _ "github.com/polarismesh/polaris/cache/auth" _ "github.com/polarismesh/polaris/cache/client" diff --git a/release/standalone/docker-compose/mysql/Dockerfile b/release/standalone/docker-compose/mysql/Dockerfile index 2f7db459a..6fba8294a 100644 --- a/release/standalone/docker-compose/mysql/Dockerfile +++ b/release/standalone/docker-compose/mysql/Dockerfile @@ -1,4 +1,4 @@ -FROM mysql:8 +FROM mysql:8.0.23 LABEL maintainer="polaris" @@ -6,7 +6,7 @@ LABEL maintainer="polaris" ARG TZ=Asia/Shanghai ENV TZ ${TZ} RUN pwd \ - && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ + && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ && echo $TZ > /etc/timezone \ && chown -R mysql:root /var/lib/mysql/ \ && true diff --git a/release/standalone/docker-compose/mysql/mysqld.cnf b/release/standalone/docker-compose/mysql/mysqld.cnf index 965b8e8cb..61da16b80 100644 --- a/release/standalone/docker-compose/mysql/mysqld.cnf +++ b/release/standalone/docker-compose/mysql/mysqld.cnf @@ -1,4 +1,3 @@ [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci -skip-character-set-client-handshake diff --git a/service/common_test.go b/service/common_test.go index b6cedbcc1..56262c522 100644 --- a/service/common_test.go +++ b/service/common_test.go @@ -34,7 +34,6 @@ import ( apitraffic "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" bolt "go.etcd.io/bbolt" - _ "github.com/polarismesh/polaris/auth/defaultauth" _ "github.com/polarismesh/polaris/cache" api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/log" diff --git a/store/mysql/service.go b/store/mysql/service.go index fe0f1aed6..bcae68a6d 100644 --- a/store/mysql/service.go +++ b/store/mysql/service.go @@ -119,6 +119,11 @@ func (ss *serviceStore) deleteService(id, serviceName, namespaceName string) err return err } + if err := deleteServiceMetadata(tx, serviceName, namespaceName); err != nil { + log.Errorf("[Store][database] delete service_metadata(%s) err : %s", id, err.Error()) + return err + } + if err := tx.Commit(); err != nil { log.Errorf("[Store][database] add service tx commit err: %s", err.Error()) return err @@ -1218,6 +1223,17 @@ func deleteOwnerServiceMap(tx *BaseTx, service, namespace string) error { return nil } +// deleteServiceMetadata 删除 service_metadata 中的残留数据 +func deleteServiceMetadata(tx *BaseTx, service, namespace string) error { + log.Infof("[Store][database] delete service(%s) namespace(%s)", service, namespace) + delSql := "delete from service_metadata where id IN (select id from service where service=? and namespace=?)" + if _, err := tx.Exec(delSql, service, namespace); err != nil { + return err + } + + return nil +} + // updateOwnerServiceMap owner_service_map表,先删除,后填充 func updateOwnerServiceMap(tx *BaseTx, service, namespace, owner string) error { // 删除 diff --git a/test/suit/test_suit.go b/test/suit/test_suit.go index a840ce2b1..e1c54b0b9 100644 --- a/test/suit/test_suit.go +++ b/test/suit/test_suit.go @@ -35,7 +35,6 @@ import ( "gopkg.in/yaml.v2" "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" From cbb248c591d07b971227ac9292a0d2c5db9ed722 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Tue, 21 May 2024 23:15:24 +0800 Subject: [PATCH 08/23] feat:support nacos-address server endpoints --- apiserver/httpserver/admin_access.go | 15 + apiserver/httpserver/docs/admin_apidoc.go | 9 + apiserver/httpserver/server.go | 34 +- .../{swagger_access.go => third_access.go} | 56 ++- apiserver/nacosserver/config.go | 2 + apiserver/nacosserver/v1/access.go | 52 -- apiserver/nacosserver/v1/auth.go | 24 + apiserver/nacosserver/v1/endpoints.go | 72 +++ apiserver/nacosserver/v1/server.go | 5 +- auth/api.go | 14 +- auth/auth.go | 42 +- auth/{authcheck => policy}/access.go | 2 +- auth/{authcheck => policy}/auth_checker.go | 3 +- .../auth_checker_test.go | 250 ++++++---- auth/{authcheck => policy}/common_test.go | 2 +- auth/policy/default.go | 78 +++ .../inteceptor/auth/log.go} | 12 +- auth/policy/inteceptor/auth/server.go | 189 +++++++ auth/{authcheck => policy}/log.go | 2 +- auth/{authcheck => policy}/main_test.go | 2 +- auth/{authcheck => policy}/server.go | 16 +- auth/{authcheck => policy}/strategy.go | 14 +- auth/{authcheck => policy}/strategy_test.go | 12 +- auth/{authcheck => policy}/utils.go | 2 +- auth/user/common_test.go | 2 +- auth/user/default.go | 54 +- auth/user/group.go | 208 ++------ auth/user/group_test.go | 41 +- auth/user/helper.go | 99 ++++ auth/user/inteceptor/auth/log.go | 24 + auth/user/inteceptor/auth/server.go | 469 ++++++++++++++++++ auth/user/server.go | 1 + auth/user/token_test.go | 3 +- auth/user/user.go | 58 --- auth/user/user_test.go | 60 ++- auth/user/utils_test.go | 1 + cache/config/config_file.go | 1 + cache/service/instance_query.go | 19 +- cache/service/service_contract.go | 132 +++-- common/api/v1/naming_response.go | 18 + common/model/auth.go | 52 +- common/model/naming.go | 31 ++ plugin.go | 2 +- release/tool/check.sh | 39 ++ release/tool/include | 41 ++ release/tool/start.sh | 12 + release/tool/stop.sh | 1 + service/client_test.go | 44 ++ service/instance.go | 14 +- service/interceptor/paramcheck/check.go | 5 +- store/boltdb/service_contract.go | 29 ++ store/discover_api.go | 2 + store/mock/api_mock.go | 15 + store/mysql/service_contract.go | 100 +++- test/suit/plugin.go | 37 ++ test/suit/test_suit.go | 14 - 56 files changed, 1952 insertions(+), 585 deletions(-) rename apiserver/httpserver/{swagger_access.go => third_access.go} (62%) delete mode 100644 apiserver/nacosserver/v1/access.go create mode 100644 apiserver/nacosserver/v1/endpoints.go rename auth/{authcheck => policy}/access.go (99%) rename auth/{authcheck => policy}/auth_checker.go (99%) rename auth/{authcheck => policy}/auth_checker_test.go (85%) rename auth/{authcheck => policy}/common_test.go (99%) create mode 100644 auth/policy/default.go rename auth/{authcheck/default.go => policy/inteceptor/auth/log.go} (82%) create mode 100644 auth/policy/inteceptor/auth/server.go rename auth/{authcheck => policy}/log.go (98%) rename auth/{authcheck => policy}/main_test.go (99%) rename auth/{authcheck => policy}/server.go (96%) rename auth/{authcheck => policy}/strategy.go (98%) rename auth/{authcheck => policy}/strategy_test.go (99%) rename auth/{authcheck => policy}/utils.go (98%) create mode 100644 auth/user/inteceptor/auth/log.go create mode 100644 auth/user/inteceptor/auth/server.go create mode 100644 release/tool/check.sh create mode 100644 test/suit/plugin.go diff --git a/apiserver/httpserver/admin_access.go b/apiserver/httpserver/admin_access.go index 60b3987ed..de388929d 100644 --- a/apiserver/httpserver/admin_access.go +++ b/apiserver/httpserver/admin_access.go @@ -67,6 +67,7 @@ func (h *HTTPServer) GetAdminAccessServer() *restful.WebService { ws.Route(docs.EnrichReleaseLeaderElectionApiDocs(ws.POST("/leaders/release").To(h.ReleaseLeaderElection))) ws.Route(docs.EnrichGetCMDBInfoApiDocs(ws.GET("/cmdb/info").To(h.GetCMDBInfo))) ws.Route(docs.EnrichGetReportClientsApiDocs(ws.GET("/report/clients").To(h.GetReportClients))) + ws.Route(docs.EnrichEnablePprofApiDocs(ws.POST("/pprof/enable").To(h.EnablePprof))) return ws } @@ -294,6 +295,20 @@ func (h *HTTPServer) GetCMDBInfo(req *restful.Request, rsp *restful.Response) { _ = rsp.WriteAsJson(ret) } +func (h *HTTPServer) EnablePprof(req *restful.Request, rsp *restful.Response) { + var pprofEnable struct { + Enable bool `json:"enable"` + } + + if err := httpcommon.ParseJsonBody(req, &pprofEnable); err != nil { + _ = rsp.WriteErrorString(http.StatusBadRequest, err.Error()) + return + } + + h.enablePprof.Store(pprofEnable.Enable) + _ = rsp.WriteEntity("ok") +} + func initContext(req *restful.Request) context.Context { ctx := context.Background() diff --git a/apiserver/httpserver/docs/admin_apidoc.go b/apiserver/httpserver/docs/admin_apidoc.go index f72155874..6c387aeae 100644 --- a/apiserver/httpserver/docs/admin_apidoc.go +++ b/apiserver/httpserver/docs/admin_apidoc.go @@ -140,3 +140,12 @@ func EnrichGetReportClientsApiDocs(r *restful.RouteBuilder) *restful.RouteBuilde Metadata(restfulspec.KeyOpenAPITags, maintainApiTags). Returns(0, "", model.PrometheusDiscoveryResponse{}) } + +func EnrichEnablePprofApiDocs(r *restful.RouteBuilder) *restful.RouteBuilder { + return r. + Doc("开启/关闭 golang 的 pprof 能力"). + Metadata(restfulspec.KeyOpenAPITags, maintainApiTags). + Reads(struct { + Enable bool `json:"enable"` + }{}) +} diff --git a/apiserver/httpserver/server.go b/apiserver/httpserver/server.go index 611dbb77a..529ec7525 100644 --- a/apiserver/httpserver/server.go +++ b/apiserver/httpserver/server.go @@ -22,8 +22,8 @@ import ( "fmt" "net" "net/http" - "net/http/pprof" "strings" + "sync/atomic" "time" restful "github.com/emicklei/go-restful/v3" @@ -78,7 +78,7 @@ type HTTPServer struct { restart bool exitCh chan struct{} - enablePprof bool + enablePprof *atomic.Bool enableSwagger bool server *http.Server @@ -125,7 +125,10 @@ func (h *HTTPServer) Initialize(ctx context.Context, option map[string]interface h.openAPI = apiConf h.listenIP = option["listenIP"].(string) h.listenPort = uint32(option["listenPort"].(int)) - h.enablePprof, _ = option["enablePprof"].(bool) + h.enablePprof = &atomic.Bool{} + if val, _ := option["enablePprof"].(bool); val { + h.enablePprof.Store(true) + } h.enableSwagger, _ = option["enableSwagger"].(bool) h.apiserverSlots, _ = ctx.Value(utils.ContextAPIServerSlot{}).(map[string]apiserver.Apiserver) // 连接数限制的配置 @@ -436,35 +439,14 @@ func (h *HTTPServer) createRestfulContainer() (*restful.Container, error) { } } - if h.enablePprof { - h.enablePprofAccess(wsContainer) - } - - if h.enableSwagger { - h.enableSwaggerAPI(wsContainer) - } + h.enablePprofAccess(wsContainer) + h.enableSwaggerAPI(wsContainer) // 收集插件的 endpoint 数据 h.enablePluginDebugAccess(wsContainer) h.enablePrometheusAccess(wsContainer) return wsContainer, nil } -// enablePprofAccess 开启pprof接口 -func (h *HTTPServer) enablePprofAccess(wsContainer *restful.Container) { - log.Infof("open http access for pprof") - wsContainer.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) - wsContainer.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) - wsContainer.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) - wsContainer.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) -} - -// enablePrometheusAccess 开启 Prometheus 接口 -func (h *HTTPServer) enablePrometheusAccess(wsContainer *restful.Container) { - log.Infof("open http access for prometheus") - - wsContainer.Handle("/metrics", metrics.GetHttpHandler()) -} - // enablePluginDebugAccess . func (h *HTTPServer) enablePluginDebugAccess(wsContainer *restful.Container) { log.Infof("open http access for plugin") diff --git a/apiserver/httpserver/swagger_access.go b/apiserver/httpserver/third_access.go similarity index 62% rename from apiserver/httpserver/swagger_access.go rename to apiserver/httpserver/third_access.go index 1b1c93461..03bfa413b 100644 --- a/apiserver/httpserver/swagger_access.go +++ b/apiserver/httpserver/third_access.go @@ -18,11 +18,63 @@ package httpserver import ( + "net/http" + "net/http/pprof" + "github.com/emicklei/go-restful/v3" "github.com/go-openapi/spec" restfulspec "github.com/polarismesh/go-restful-openapi/v2" + + "github.com/polarismesh/polaris/common/metrics" ) +// enablePprofAccess 开启pprof接口 +func (h *HTTPServer) enablePprofAccess(wsContainer *restful.Container) { + log.Infof("open http access for pprof") + wsContainer.Handle("/debug/pprof/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if h.enablePprof.Load() { + pprof.Index(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + })) + wsContainer.Handle("/debug/pprof/cmdline", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if h.enablePprof.Load() { + pprof.Cmdline(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + })) + wsContainer.Handle("/debug/pprof/profile", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if h.enablePprof.Load() { + pprof.Profile(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + })) + wsContainer.Handle("/debug/pprof/symbol", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if h.enablePprof.Load() { + pprof.Symbol(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + })) + wsContainer.Handle("/debug/pprof/trace", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if h.enablePprof.Load() { + pprof.Trace(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + })) +} + +// enablePrometheusAccess 开启 Prometheus 接口 +func (h *HTTPServer) enablePrometheusAccess(wsContainer *restful.Container) { + log.Infof("open http access for prometheus") + + wsContainer.Handle("/metrics", metrics.GetHttpHandler()) +} + func (h *HTTPServer) enableSwaggerAPI(wsContainer *restful.Container) { log.Infof("[HTTPServer] open http access for swagger API") config := restfulspec.Config{ @@ -31,7 +83,9 @@ func (h *HTTPServer) enableSwaggerAPI(wsContainer *restful.Container) { PostBuildSwaggerObjectHandler: enrichSwaggerObject, } - wsContainer.Add(restfulspec.NewOpenAPIService(config)) + if h.enableSwagger { + wsContainer.Add(restfulspec.NewOpenAPIService(config)) + } } func enrichSwaggerObject(swo *spec.Swagger) { diff --git a/apiserver/nacosserver/config.go b/apiserver/nacosserver/config.go index 345d22ff3..cc754904f 100644 --- a/apiserver/nacosserver/config.go +++ b/apiserver/nacosserver/config.go @@ -30,6 +30,8 @@ type NacosConfig struct { ConnLimit *connlimit.Config `mapstructure:"connLimit"` TLS *secure.TLSConfig `mapstructure:tls` DefaultNamespace string `mapstructure:defaultNamespace` + ServerService string `mapstructure:"serverService"` + ServerNamespace string `mapstructure:"serverNamespace"` } func loadNacosConfig(raw map[string]interface{}) (*NacosConfig, error) { diff --git a/apiserver/nacosserver/v1/access.go b/apiserver/nacosserver/v1/access.go deleted file mode 100644 index 52364a3bc..000000000 --- a/apiserver/nacosserver/v1/access.go +++ /dev/null @@ -1,52 +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 v1 - -import ( - "github.com/emicklei/go-restful/v3" - - nacoshttp "github.com/polarismesh/polaris/apiserver/nacosserver/v1/http" -) - -func (n *NacosV1Server) GetAuthServer() (*restful.WebService, error) { - ws := new(restful.WebService) - ws.Route(ws.POST("/nacos/v1/auth/login").To(n.Login)) - ws.Route(ws.POST("/nacos/v1/auth/users/login").To(n.Login)) - return ws, nil -} - -func (n *NacosV1Server) Login(req *restful.Request, rsp *restful.Response) { - handler := nacoshttp.Handler{ - Request: req, - Response: rsp, - } - - ctx := handler.ParseHeaderContext() - data, err := n.handleLogin(ctx, nacoshttp.ParseQueryParams(req)) - if err != nil { - nacoshttp.WrirteNacosErrorResponse(err, rsp) - return - } - nacoshttp.WrirteNacosResponse(data, rsp) -} - -func (n *NacosV1Server) ServerHealthStatus(req *restful.Request, rsp *restful.Response) { - nacoshttp.WrirteNacosResponse(map[string]interface{}{ - "status": "UP", - }, rsp) -} diff --git a/apiserver/nacosserver/v1/auth.go b/apiserver/nacosserver/v1/auth.go index 6f6f1dd8b..2b00222bc 100644 --- a/apiserver/nacosserver/v1/auth.go +++ b/apiserver/nacosserver/v1/auth.go @@ -20,9 +20,33 @@ package v1 import ( "context" + "github.com/emicklei/go-restful/v3" + nacoshttp "github.com/polarismesh/polaris/apiserver/nacosserver/v1/http" "github.com/polarismesh/polaris/common/model" ) +func (n *NacosV1Server) GetAuthServer() (*restful.WebService, error) { + ws := new(restful.WebService) + ws.Route(ws.POST("/nacos/v1/auth/login").To(n.Login)) + ws.Route(ws.POST("/nacos/v1/auth/users/login").To(n.Login)) + return ws, nil +} + +func (n *NacosV1Server) Login(req *restful.Request, rsp *restful.Response) { + handler := nacoshttp.Handler{ + Request: req, + Response: rsp, + } + + ctx := handler.ParseHeaderContext() + data, err := n.handleLogin(ctx, nacoshttp.ParseQueryParams(req)) + if err != nil { + nacoshttp.WrirteNacosErrorResponse(err, rsp) + return + } + nacoshttp.WrirteNacosResponse(data, rsp) +} + func (n *NacosV1Server) handleLogin(ctx context.Context, params map[string]string) (map[string]interface{}, error) { username := params["username"] token := params["password"] diff --git a/apiserver/nacosserver/v1/endpoints.go b/apiserver/nacosserver/v1/endpoints.go new file mode 100644 index 000000000..5343d8722 --- /dev/null +++ b/apiserver/nacosserver/v1/endpoints.go @@ -0,0 +1,72 @@ +/** + * 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 v1 + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/emicklei/go-restful/v3" + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/specification/source/go/api/v1/service_manage" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func (n *NacosV1Server) GetAddressServer() (*restful.WebService, error) { + ws := new(restful.WebService) + ws.Route(ws.GET("/nacos/serverlist").To(n.FetchNacosEndpoints)) + return ws, nil +} + +// FetchNacosEndpoints 处理 nacos 地址服务器 +func (n *NacosV1Server) FetchNacosEndpoints(req *restful.Request, rsp *restful.Response) { + serverSvcName, _ := n.option["serverService"].(string) + serverSvcNamespace, _ := n.option["serverNamespace"].(string) + + if serverSvcName == "" || serverSvcNamespace == "" { + rsp.WriteHeader(http.StatusNotFound) + return + } + + insResp := n.discoverOpt.OriginDiscoverSvr.ServiceInstancesCache(context.Background(), &service_manage.DiscoverFilter{ + OnlyHealthyInstance: true, + }, &service_manage.Service{ + Namespace: wrapperspb.String(serverSvcNamespace), + Name: wrapperspb.String(serverSvcName), + }) + + if !api.IsSuccess(insResp) { + rsp.WriteHeader(api.CalcCode(insResp)) + return + } + + ips := []string{} + for i := range insResp.GetInstances() { + item := insResp.GetInstances()[i] + ips = append(ips, fmt.Sprintf("%s:%d", item.GetHost().GetValue(), item.GetPort().GetValue())) + } + if len(ips) == 0 { + rsp.WriteHeader(http.StatusNotFound) + return + } + + rsp.WriteHeader(http.StatusOK) + rsp.Write([]byte(strings.Join(ips, "\n"))) +} diff --git a/apiserver/nacosserver/v1/server.go b/apiserver/nacosserver/v1/server.go index beb2c5d93..9cb140ce1 100644 --- a/apiserver/nacosserver/v1/server.go +++ b/apiserver/nacosserver/v1/server.go @@ -73,8 +73,9 @@ func NewNacosV1Server(store *core.NacosDataStorage, options ...option) (*NacosV1 // NacosV1Server HTTP API服务器 type NacosV1Server struct { - listenIP string - listenPort uint32 + listenIP string + listenPort uint32 + connLimitConfig *connlimit.Config tlsInfo *secure.TLSInfo option map[string]interface{} diff --git a/auth/api.go b/auth/api.go index d7e58090d..bc4ea1a61 100644 --- a/auth/api.go +++ b/auth/api.go @@ -125,17 +125,19 @@ type GroupOperator interface { type UserHelper interface { // CheckUserInGroup 检查用户是否在用户组中 - CheckUserInGroup(group *apisecurity.UserGroup, user *apisecurity.User) bool + CheckUserInGroup(ctx context.Context, group *apisecurity.UserGroup, user *apisecurity.User) bool // CheckGroupsExist 批量检查用户组是否存在 - CheckGroupsExist(groups []*apisecurity.UserGroup) error + CheckGroupsExist(ctx context.Context, groups []*apisecurity.UserGroup) error // CheckUsersExist 批量检查用户是否存在 - CheckUsersExist(users []*apisecurity.User) error + CheckUsersExist(ctx context.Context, users []*apisecurity.User) error // GetUserOwnGroup 查询某个用户所在的所有用户组 - GetUserOwnGroup(user *apisecurity.User) []*apisecurity.UserGroup + GetUserOwnGroup(ctx context.Context, user *apisecurity.User) []*apisecurity.UserGroup // GetUser 查询用户信息 - GetUser(user *apisecurity.User) *apisecurity.User + GetUser(ctx context.Context, user *apisecurity.User) *apisecurity.User + // GetUserByID 查询用户信息 + GetUserByID(ctx context.Context, id string) *apisecurity.User // GetGroup 查询用户组信息 - GetGroup(req *apisecurity.UserGroup) *apisecurity.UserGroup + GetGroup(ctx context.Context, req *apisecurity.UserGroup) *apisecurity.UserGroup } // OperatorInfo 根据 token 解析出来的具体额外信息 diff --git a/auth/auth.go b/auth/auth.go index 4d5d6ad58..8cbb006ed 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -32,8 +32,8 @@ import ( const ( // DefaultUserMgnPluginName default user server name DefaultUserMgnPluginName = "defaultUser" - // DefaultStrategyMgnPluginName default strategy server name - DefaultStrategyMgnPluginName = "defaultStrategy" + // DefaultPolicyPluginName default strategy server name + DefaultPolicyPluginName = "defaultStrategy" ) // Config 鉴权能力的相关配置参数 @@ -47,6 +47,8 @@ type Config struct { User *UserConfig `yaml:"user"` // Strategy StrategyOperator的相关配置 Strategy *StrategyConfig `yaml:"strategy"` + // Interceptors . + Interceptors []string `yaml:"-"` } func (c *Config) SetDefault() { @@ -58,7 +60,7 @@ func (c *Config) SetDefault() { } if c.Strategy == nil { c.Strategy = &StrategyConfig{ - Name: DefaultStrategyMgnPluginName, + Name: DefaultPolicyPluginName, Option: map[string]interface{}{}, } } @@ -146,33 +148,33 @@ func Initialize(ctx context.Context, authOpt *Config, storage store.Store, cache func initialize(_ context.Context, authOpt *Config, storage store.Store, cacheMgr cachetypes.CacheManager) (UserServer, StrategyServer, error) { authOpt.SetDefault() - name := authOpt.User.Name - if name == "" { + + userMgrName := authOpt.User.Name + if userMgrName == "" { return nil, nil, errors.New("UserServer Name is empty") } + policyMgrName := authOpt.Strategy.Name + if policyMgrName == "" { + return nil, nil, errors.New("StrategyServer Name is empty") + } - namedUserMgn, ok := userMgrSlots[name] + userMgr, ok := userMgrSlots[userMgrName] if !ok { - return nil, nil, fmt.Errorf("no such UserServer plugin. name(%s)", name) - } - if err := namedUserMgn.Initialize(authOpt, storage, cacheMgr); err != nil { - log.Printf("UserServer do initialize err: %s", err.Error()) - return nil, nil, err + return nil, nil, fmt.Errorf("no such UserServer plugin. name(%s)", userMgrName) } - - name = authOpt.Strategy.Name - if name == "" { - return nil, nil, errors.New("StrategyServer Name is empty") + policyMgr, ok := strategyMgrSlots[policyMgrName] + if !ok { + return nil, nil, fmt.Errorf("no such StrategyServer plugin. name(%s)", policyMgrName) } - namedStrategyMgn, ok := strategyMgrSlots[name] - if !ok { - return nil, nil, fmt.Errorf("no such StrategyServer plugin. name(%s)", name) + if err := userMgr.Initialize(authOpt, storage, cacheMgr); err != nil { + log.Printf("UserServer do initialize err: %s", err.Error()) + return nil, nil, err } - if err := namedStrategyMgn.Initialize(authOpt, storage, cacheMgr, namedUserMgn); err != nil { + if err := policyMgr.Initialize(authOpt, storage, cacheMgr, userMgr); err != nil { log.Printf("StrategyServer do initialize err: %s", err.Error()) return nil, nil, err } finishInit = true - return namedUserMgn, namedStrategyMgn, nil + return userMgr, policyMgr, nil } diff --git a/auth/authcheck/access.go b/auth/policy/access.go similarity index 99% rename from auth/authcheck/access.go rename to auth/policy/access.go index fda6cc8a2..752bfaac9 100644 --- a/auth/authcheck/access.go +++ b/auth/policy/access.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package policy import ( "context" diff --git a/auth/authcheck/auth_checker.go b/auth/policy/auth_checker.go similarity index 99% rename from auth/authcheck/auth_checker.go rename to auth/policy/auth_checker.go index 29a55f0c8..12a5a9a60 100644 --- a/auth/authcheck/auth_checker.go +++ b/auth/policy/auth_checker.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package policy import ( "github.com/pkg/errors" @@ -211,6 +211,7 @@ func (d *DefaultAuthChecker) checkAction(principal model.Principal, switch ctx.GetOperation() { case model.Read: + return true default: for _, entry := range resources { if !d.cacheMgr.AuthStrategy().IsResourceEditable(principal, resType, entry.ID) { diff --git a/auth/authcheck/auth_checker_test.go b/auth/policy/auth_checker_test.go similarity index 85% rename from auth/authcheck/auth_checker_test.go rename to auth/policy/auth_checker_test.go index 50ae3202e..f56144c8b 100644 --- a/auth/authcheck/auth_checker_test.go +++ b/auth/policy/auth_checker_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck_test +package policy_test import ( "context" @@ -26,14 +26,18 @@ import ( "github.com/stretchr/testify/assert" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/auth/authcheck" + "github.com/polarismesh/polaris/auth/policy" + defaultuser "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/cache" - cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" ) -func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { +func newPolicyServer() (*policy.Server, auth.StrategyServer, error) { + return policy.BuildServer() +} + +func Test_DefaultAuthChecker_CheckConsolePermission_Write_NoStrict(t *testing.T) { reset(false) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -59,23 +63,38 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { if err != nil { t.Fatal(err) } - _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ - { - Name: cachetypes.UsersName, - }, - { - Name: cachetypes.StrategyRuleName, - }, - }...) - _ = cacheMgr.TestUpdate() - t.Cleanup(func() { cancel() cacheMgr.Close() }) - checker := &authcheck.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgr) + _, proxySvr, err := defaultuser.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, + }, + }, storage, cacheMgr) + + _, svr, err := newPolicyServer() + if err != nil { + t.Fatal(err) + } + if err := svr.Initialize(&auth.Config{ + Strategy: &auth.StrategyConfig{ + Name: auth.DefaultPolicyPluginName, + }, + }, storage, cacheMgr, proxySvr); err != nil { + t.Fatal(err) + } + checker := svr.GetAuthChecker() + + _ = cacheMgr.TestUpdate() freeIndex := len(users) + len(groups) + 1 @@ -96,7 +115,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -118,7 +137,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -140,7 +159,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -162,7 +181,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -184,7 +203,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -206,7 +225,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -229,7 +248,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -251,7 +270,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -273,13 +292,13 @@ func Test_DefaultAuthChecker_CheckPermission_Write_NoStrict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) } -func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { +func Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict(t *testing.T) { reset(true) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -311,18 +330,33 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { cacheMgr.Close() }) - _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ - { - Name: cachetypes.UsersName, + _, proxySvr, err := defaultuser.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, }, - { - Name: cachetypes.StrategyRuleName, + }, storage, cacheMgr) + + _, svr, err := newPolicyServer() + if err != nil { + t.Fatal(err) + } + if err := svr.Initialize(&auth.Config{ + Strategy: &auth.StrategyConfig{ + Name: auth.DefaultPolicyPluginName, }, - }...) - _ = cacheMgr.TestUpdate() + }, storage, cacheMgr, proxySvr); err != nil { + t.Fatal(err) + } + checker := svr.GetAuthChecker() - checker := &authcheck.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgr) + _ = cacheMgr.TestUpdate() freeIndex := len(users) + len(groups) + 1 @@ -330,7 +364,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[0].Token) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken(users[0].Token), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -344,7 +378,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -353,7 +387,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[1].Token) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken(users[1].Token), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -366,7 +400,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -375,7 +409,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, users[1].Token) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken(users[1].Token), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -388,16 +422,16 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) t.Run("权限检查严格模式-token非法-匿名账户操作资源(资源有策略)", func(t *testing.T) { - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_CheckPermission_Write_Strict") + ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken("Test_DefaultAuthChecker_VerifyCredential"), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -410,7 +444,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -419,7 +453,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken(""), model.WithModule(model.DiscoverModule), model.WithOperation(model.Create), @@ -432,16 +466,16 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) t.Run("权限检查严格模式-token非法-匿名账户操作资源(资源没有策略)", func(t *testing.T) { - ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_CheckPermission_Write_Strict") + ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken("Test_DefaultAuthChecker_VerifyCredential"), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -454,7 +488,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -463,7 +497,7 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, "") authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), - model.WithMethod("Test_DefaultAuthChecker_CheckPermission_Write_Strict"), + model.WithMethod("Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict"), // model.WithToken(""), model.WithOperation(model.Create), model.WithModule(model.DiscoverModule), @@ -476,13 +510,13 @@ func Test_DefaultAuthChecker_CheckPermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) } -func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { +func Test_DefaultAuthChecker_CheckConsolePermission_Read_NoStrict(t *testing.T) { reset(false) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -513,18 +547,34 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { cancel() cacheMgr.Close() }) - _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ - { - Name: cachetypes.UsersName, + + _, proxySvr, err := defaultuser.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, }, - { - Name: cachetypes.StrategyRuleName, + }, storage, cacheMgr) + + _, svr, err := newPolicyServer() + if err != nil { + t.Fatal(err) + } + if err := svr.Initialize(&auth.Config{ + Strategy: &auth.StrategyConfig{ + Name: auth.DefaultPolicyPluginName, }, - }...) - _ = cacheMgr.TestUpdate() + }, storage, cacheMgr, proxySvr); err != nil { + t.Fatal(err) + } + checker := svr.GetAuthChecker() - checker := &authcheck.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgr) + _ = cacheMgr.TestUpdate() freeIndex := len(users) + len(groups) + 1 @@ -545,7 +595,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -567,7 +617,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -589,7 +639,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -611,7 +661,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -633,7 +683,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -655,7 +705,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -677,7 +727,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -699,13 +749,13 @@ func Test_DefaultAuthChecker_CheckPermission_Read_NoStrict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) } -func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { +func Test_DefaultAuthChecker_CheckConsolePermission_Read_Strict(t *testing.T) { reset(true) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -736,18 +786,34 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { cancel() cacheMgr.Close() }) - _ = cacheMgr.OpenResourceCache([]cachetypes.ConfigEntry{ - { - Name: cachetypes.UsersName, + + _, proxySvr, err := defaultuser.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, }, - { - Name: cachetypes.StrategyRuleName, + }, storage, cacheMgr) + + _, svr, err := newPolicyServer() + if err != nil { + t.Fatal(err) + } + if err := svr.Initialize(&auth.Config{ + Strategy: &auth.StrategyConfig{ + Name: auth.DefaultPolicyPluginName, }, - }...) - _ = cacheMgr.TestUpdate() + }, storage, cacheMgr, proxySvr); err != nil { + t.Fatal(err) + } + checker := svr.GetAuthChecker() - checker := &authcheck.DefaultAuthChecker{} - checker.SetCacheMgr(cacheMgr) + _ = cacheMgr.TestUpdate() freeIndex := len(users) + len(groups) + 1 @@ -768,7 +834,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -790,7 +856,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -812,7 +878,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -834,7 +900,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.NoError(t, err, "Should be verify success") }) @@ -856,7 +922,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -878,7 +944,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -900,7 +966,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -922,7 +988,7 @@ func Test_DefaultAuthChecker_CheckPermission_Read_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckPermission(authCtx) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -934,7 +1000,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { t.Run("使用未迁移至auth.user.option及auth.strategy.option的配置", func(t *testing.T) { reset(true) - authChecker := &authcheck.Server{} + authChecker := &policy.Server{} cfg := &auth.Config{} cfg.SetDefault() cfg.Name = "" @@ -946,7 +1012,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { } err := authChecker.ParseOptions(cfg) assert.NoError(t, err) - assert.Equal(t, &authcheck.AuthConfig{ + assert.Equal(t, &policy.AuthConfig{ ConsoleOpen: true, ClientOpen: true, Strict: false, @@ -957,7 +1023,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { t.Run("使用完全迁移至auth.user.option及auth.strategy.option的配置", func(t *testing.T) { reset(true) - authChecker := &authcheck.Server{} + authChecker := &policy.Server{} cfg := &auth.Config{} cfg.SetDefault() @@ -976,7 +1042,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { err := authChecker.ParseOptions(cfg) assert.NoError(t, err) - assert.Equal(t, &authcheck.AuthConfig{ + assert.Equal(t, &policy.AuthConfig{ ConsoleOpen: true, ClientOpen: true, Strict: false, @@ -986,7 +1052,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { t.Run("使用部分迁移至auth.user.option及auth.strategy.option的配置(应当报错)", func(t *testing.T) { reset(true) - authChecker := &authcheck.Server{} + authChecker := &policy.Server{} cfg := &auth.Config{} cfg.SetDefault() cfg.Name = "" diff --git a/auth/authcheck/common_test.go b/auth/policy/common_test.go similarity index 99% rename from auth/authcheck/common_test.go rename to auth/policy/common_test.go index 015ce359f..c64e4718c 100644 --- a/auth/authcheck/common_test.go +++ b/auth/policy/common_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck_test +package policy_test import ( "fmt" diff --git a/auth/policy/default.go b/auth/policy/default.go new file mode 100644 index 000000000..337feff77 --- /dev/null +++ b/auth/policy/default.go @@ -0,0 +1,78 @@ +/** + * 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 policy + +import ( + "fmt" + golog "log" + + "github.com/polarismesh/polaris/auth" + policy_auth "github.com/polarismesh/polaris/auth/policy/inteceptor/auth" +) + +type ServerProxyFactory func(svr *Server, pre auth.StrategyServer) (auth.StrategyServer, error) + +var ( + // serverProxyFactories auth.UserServer API 代理工厂 + serverProxyFactories = map[string]ServerProxyFactory{} +) + +// RegisterServerProxy . +func RegisterServerProxy(name string, factor ServerProxyFactory) { + if _, ok := serverProxyFactories[name]; ok { + golog.Printf("duplicate ServerProxyFactory, name(%s)", name) + return + } + serverProxyFactories[name] = factor +} + +func init() { + _, nextSvr, err := BuildServer() + if err != nil { + panic(err) + } + _ = auth.RegisterStrategyServer(nextSvr) +} + +func loadInteceptors() { + RegisterServerProxy("auth", func(svr *Server, pre auth.StrategyServer) (auth.StrategyServer, error) { + return policy_auth.NewServer(pre), nil + }) +} + +func BuildServer() (*Server, auth.StrategyServer, error) { + loadInteceptors() + svr := &Server{} + var nextSvr auth.StrategyServer + nextSvr = svr + // 需要返回包装代理的 DiscoverServer + order := []string{"auth"} + 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(svr, nextSvr) + if err != nil { + panic(err) + } + nextSvr = proxySvr + } + return svr, nextSvr, nil +} diff --git a/auth/authcheck/default.go b/auth/policy/inteceptor/auth/log.go similarity index 82% rename from auth/authcheck/default.go rename to auth/policy/inteceptor/auth/log.go index 00802e4d3..7707503ce 100644 --- a/auth/authcheck/default.go +++ b/auth/policy/inteceptor/auth/log.go @@ -15,12 +15,10 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package auth -import ( - "github.com/polarismesh/polaris/auth" -) +import commonlog "github.com/polarismesh/polaris/common/log" -func init() { - _ = auth.RegisterStrategyServer(&Server{}) -} +var ( + log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) +) diff --git a/auth/policy/inteceptor/auth/server.go b/auth/policy/inteceptor/auth/server.go new file mode 100644 index 000000000..676ee3d2d --- /dev/null +++ b/auth/policy/inteceptor/auth/server.go @@ -0,0 +1,189 @@ +/** + * 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 auth + +import ( + "context" + + 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" + "go.uber.org/zap" + + "github.com/polarismesh/polaris/auth" + 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" + "github.com/polarismesh/polaris/store" +) + +var ( + // MustOwner 必须超级账户 or 主账户 + MustOwner = true + // NotOwner 任意账户 + NotOwner = false + // WriteOp 写操作 + WriteOp = true + // ReadOp 读操作 + ReadOp = false +) + +func NewServer(nextSvr auth.StrategyServer) auth.StrategyServer { + return &Server{ + nextSvr: nextSvr, + } +} + +type Server struct { + nextSvr auth.StrategyServer + userSvr auth.UserServer +} + +// Initialize 执行初始化动作 +func (svr *Server) Initialize(options *auth.Config, storage store.Store, cacheMgr cachetypes.CacheManager, userSvr auth.UserServer) error { + svr.userSvr = userSvr + return svr.nextSvr.Initialize(options, storage, cacheMgr, userSvr) +} + +// Name 策略管理server名称 +func (svr *Server) Name() string { + return svr.nextSvr.Name() +} + +// CreateStrategy 创建策略 +func (svr *Server) CreateStrategy(ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + return rsp + } + return svr.nextSvr.CreateStrategy(ctx, strategy) +} + +// UpdateStrategies 批量更新策略 +func (svr *Server) UpdateStrategies(ctx context.Context, reqs []*apisecurity.ModifyAuthStrategy) *apiservice.BatchWriteResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + api.Collect(resp, rsp) + return resp + } + return svr.nextSvr.UpdateStrategies(ctx, reqs) +} + +// DeleteStrategies 删除策略 +func (svr *Server) DeleteStrategies(ctx context.Context, reqs []*apisecurity.AuthStrategy) *apiservice.BatchWriteResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + api.Collect(resp, rsp) + return resp + } + return svr.nextSvr.DeleteStrategies(ctx, reqs) +} + +// GetStrategies 获取资源列表 +// support 1. 支持按照 principal-id + principal-role 进行查询 +// support 2. 支持普通的鉴权策略查询 +func (svr *Server) GetStrategies(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return api.NewAuthBatchQueryResponseWithMsg(apimodel.Code(rsp.GetCode().Value), rsp.Info.Value) + } + return svr.nextSvr.GetStrategies(ctx, query) +} + +// GetStrategy 获取策略详细 +func (svr *Server) GetStrategy(ctx context.Context, strategy *apisecurity.AuthStrategy) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return rsp + } + return svr.nextSvr.GetStrategy(ctx, strategy) +} + +// GetPrincipalResources 获取某个 principal 的所有可操作资源列表 +func (svr *Server) GetPrincipalResources(ctx context.Context, query map[string]string) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return rsp + } + return svr.nextSvr.GetPrincipalResources(ctx, query) +} + +// GetAuthChecker 获取鉴权检查器 +func (svr *Server) GetAuthChecker() auth.AuthChecker { + return svr.nextSvr.GetAuthChecker() +} + +// AfterResourceOperation 操作完资源的后置处理逻辑 +func (svr *Server) AfterResourceOperation(afterCtx *model.AcquireContext) error { + return svr.nextSvr.AfterResourceOperation(afterCtx) +} + +// verifyAuth 用于 user、group 以及 strategy 模块的鉴权工作检查 +func (svr *Server) verifyAuth(ctx context.Context, isWrite bool, + needOwner bool) (context.Context, *apiservice.Response) { + reqId := utils.ParseRequestID(ctx) + authToken := utils.ParseAuthToken(ctx) + + if authToken == "" { + log.Error("[Auth][Server] auth token is empty", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_EmptyAutToken) + } + + authCtx := model.NewAcquireContext( + model.WithRequestContext(ctx), + model.WithModule(model.AuthModule), + ) + + // case 1. 如果 error 不是 token 被禁止的 error,直接返回 + // case 2. 如果 error 是 token 被禁止,按下面情况判断 + // i. 如果当前只是一个数据的读取操作,则放通 + // ii. 如果当前是一个数据的写操作,则只能允许处于正常的 token 进行操作 + if err := svr.userSvr.CheckCredential(authCtx); err != nil { + log.Error("[Auth][Server] verify auth token", utils.ZapRequestID(reqId), + zap.Error(err)) + return nil, api.NewAuthResponse(apimodel.Code_AuthTokenForbidden) + } + + attachVal, exist := authCtx.GetAttachment(model.TokenDetailInfoKey) + if !exist { + log.Error("[Auth][Server] token detail info not exist", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_TokenNotExisted) + } + + operateInfo := attachVal.(auth.OperatorInfo) + if isWrite && operateInfo.Disable { + log.Error("[Auth][Server] token is disabled", utils.ZapRequestID(reqId), + zap.String("operation", authCtx.GetMethod())) + return nil, api.NewAuthResponse(apimodel.Code_TokenDisabled) + } + + if !operateInfo.IsUserToken { + log.Error("[Auth][Server] only user role can access this API", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) + } + + if needOwner && auth.IsSubAccount(operateInfo) { + log.Error("[Auth][Server] only admin/owner account can access this API", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) + } + + return authCtx.GetRequestContext(), nil +} diff --git a/auth/authcheck/log.go b/auth/policy/log.go similarity index 98% rename from auth/authcheck/log.go rename to auth/policy/log.go index ea38f3138..3d249b9ac 100644 --- a/auth/authcheck/log.go +++ b/auth/policy/log.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package policy import commonlog "github.com/polarismesh/polaris/common/log" diff --git a/auth/authcheck/main_test.go b/auth/policy/main_test.go similarity index 99% rename from auth/authcheck/main_test.go rename to auth/policy/main_test.go index 954abc910..4d5302ba7 100644 --- a/auth/authcheck/main_test.go +++ b/auth/policy/main_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck_test +package policy_test import ( "errors" diff --git a/auth/authcheck/server.go b/auth/policy/server.go similarity index 96% rename from auth/authcheck/server.go rename to auth/policy/server.go index ae5446e5a..020c74e4a 100644 --- a/auth/authcheck/server.go +++ b/auth/policy/server.go @@ -15,9 +15,10 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package policy import ( + "context" "encoding/json" "errors" "fmt" @@ -90,6 +91,9 @@ func (svr *Server) Initialize(options *auth.Config, storage store.Store, cacheMg if svr.history == nil { log.Warnf("Not Found History Log Plugin") } + + svr.checker = &DefaultAuthChecker{} + svr.checker.Initialize(svr.options, svr.storage, cacheMgr, userSvr) return nil } @@ -107,8 +111,8 @@ func (svr *Server) ParseOptions(options *auth.Config) error { cfg := DefaultAuthConfig() - // 一旦设置了auth.user.option或auth.strategy.option,将不会继续读取auth.option - if len(options.Strategy.Option) > 0 || len(options.User.Option) > 0 { + // 设置了 auth.strategy.option,将不会继续读取 auth.option + if len(options.Strategy.Option) > 0 { // 判断auth.option是否还有值,有则不兼容 if len(options.Option) > 0 { log.Warn("auth.user.option or auth.strategy.option has set, auth.option will ignore") @@ -139,7 +143,7 @@ func (svr *Server) ParseOptions(options *auth.Config) error { } func (svr *Server) Name() string { - return auth.DefaultStrategyMgnPluginName + return auth.DefaultPolicyPluginName } func (svr *Server) GetAuthChecker() auth.AuthChecker { @@ -239,7 +243,7 @@ func (svr *Server) AfterResourceOperation(afterCtx *model.AcquireContext) error func (svr *Server) handleUserStrategy(userIds []string, afterCtx *model.AcquireContext, isRemove bool) error { for index := range utils.StringSliceDeDuplication(userIds) { userId := userIds[index] - user := svr.userSvr.GetUserHelper().GetUser(&apisecurity.User{ + user := svr.userSvr.GetUserHelper().GetUser(context.TODO(), &apisecurity.User{ Id: wrapperspb.String(userId), }) if user == nil { @@ -262,7 +266,7 @@ func (svr *Server) handleUserStrategy(userIds []string, afterCtx *model.AcquireC func (svr *Server) handleGroupStrategy(groupIds []string, afterCtx *model.AcquireContext, isRemove bool) error { for index := range utils.StringSliceDeDuplication(groupIds) { groupId := groupIds[index] - group := svr.userSvr.GetUserHelper().GetGroup(&apisecurity.UserGroup{ + group := svr.userSvr.GetUserHelper().GetGroup(context.TODO(), &apisecurity.UserGroup{ Id: wrapperspb.String(groupId), }) if group == nil { diff --git a/auth/authcheck/strategy.go b/auth/policy/strategy.go similarity index 98% rename from auth/authcheck/strategy.go rename to auth/policy/strategy.go index 68f24bd78..faa42e6b0 100644 --- a/auth/authcheck/strategy.go +++ b/auth/policy/strategy.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package policy import ( "context" @@ -343,7 +343,7 @@ func (svr *Server) handleGetStrategy(ctx context.Context, req *apisecurity.AuthS group := &apisecurity.UserGroup{ Id: wrapperspb.String(principal.PrincipalID), } - if svr.userSvr.GetUserHelper().CheckUserInGroup(group, curUser) { + if svr.userSvr.GetUserHelper().CheckUserInGroup(context.TODO(), group, curUser) { canView = true break } @@ -393,7 +393,7 @@ func (svr *Server) handleGetPrincipalResources(ctx context.Context, query map[st // 找这个用户所关联的用户组 if model.PrincipalType(principalRole) == model.PrincipalUser { - groups := svr.userSvr.GetUserHelper().GetUserOwnGroup(&apisecurity.User{ + groups := svr.userSvr.GetUserHelper().GetUserOwnGroup(context.TODO(), &apisecurity.User{ Id: wrapperspb.String(principalId), }) for i := range groups { @@ -829,7 +829,7 @@ func (svr *Server) checkUserExist(users []*apisecurity.User) error { if len(users) == 0 { return nil } - return svr.userSvr.GetUserHelper().CheckUsersExist(users) + return svr.userSvr.GetUserHelper().CheckUsersExist(context.TODO(), users) } // checkUserGroupExist 检查用户组是否存在 @@ -837,7 +837,7 @@ func (svr *Server) checkGroupExist(groups []*apisecurity.UserGroup) error { if len(groups) == 0 { return nil } - return svr.userSvr.GetUserHelper().CheckGroupsExist(groups) + return svr.userSvr.GetUserHelper().CheckGroupsExist(context.TODO(), groups) } // checkResourceExist 检查资源是否存在 @@ -906,7 +906,7 @@ func (svr *Server) fillPrincipalInfo(resp *apisecurity.AuthStrategy, data *model for index := range data.Principals { principal := data.Principals[index] if principal.PrincipalRole == model.PrincipalUser { - user := svr.userSvr.GetUserHelper().GetUser(&apisecurity.User{ + user := svr.userSvr.GetUserHelper().GetUser(context.TODO(), &apisecurity.User{ Id: wrapperspb.String(principal.PrincipalID), }) if user == nil { @@ -917,7 +917,7 @@ func (svr *Server) fillPrincipalInfo(resp *apisecurity.AuthStrategy, data *model Name: utils.NewStringValue(user.GetName().GetValue()), }) } else { - group := svr.userSvr.GetUserHelper().GetGroup(&apisecurity.UserGroup{ + group := svr.userSvr.GetUserHelper().GetGroup(context.TODO(), &apisecurity.UserGroup{ Id: wrapperspb.String(principal.PrincipalID), }) if group == nil { diff --git a/auth/authcheck/strategy_test.go b/auth/policy/strategy_test.go similarity index 99% rename from auth/authcheck/strategy_test.go rename to auth/policy/strategy_test.go index 85944c9fe..68daba4da 100644 --- a/auth/authcheck/strategy_test.go +++ b/auth/policy/strategy_test.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck_test +package policy_test import ( "context" @@ -30,7 +30,7 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" "github.com/polarismesh/polaris/auth" - "github.com/polarismesh/polaris/auth/authcheck" + "github.com/polarismesh/polaris/auth/policy" "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" api "github.com/polarismesh/polaris/common/api/v1" @@ -57,7 +57,7 @@ type StrategyTest struct { cacheMgn *cache.CacheManager checker auth.AuthChecker - svr *authcheck.Server + svr *policy.Server cancel context.CancelFunc @@ -122,8 +122,8 @@ func newStrategyTest(t *testing.T) *StrategyTest { _ = cacheMgn.TestUpdate() - checker := &authcheck.DefaultAuthChecker{} - checker.Initialize(&authcheck.AuthConfig{ + checker := &policy.DefaultAuthChecker{} + checker.Initialize(&policy.AuthConfig{ ConsoleOpen: true, ClientOpen: true, ConsoleStrict: false, @@ -834,7 +834,7 @@ func Test_parseStrategySearchArgs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := authcheck.ParseStrategySearchArgs(tt.args.ctx, tt.args.searchFilters); !reflect.DeepEqual(got, tt.want) { + if got := policy.ParseStrategySearchArgs(tt.args.ctx, tt.args.searchFilters); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseStrategySearchArgs() = %v, want %v", got, tt.want) } }) diff --git a/auth/authcheck/utils.go b/auth/policy/utils.go similarity index 98% rename from auth/authcheck/utils.go rename to auth/policy/utils.go index a4d1e3a17..f198c91ed 100644 --- a/auth/authcheck/utils.go +++ b/auth/policy/utils.go @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package authcheck +package policy import ( "errors" diff --git a/auth/user/common_test.go b/auth/user/common_test.go index 7a9432383..e50624246 100644 --- a/auth/user/common_test.go +++ b/auth/user/common_test.go @@ -136,7 +136,7 @@ func createMockUser(total int, prefix ...string) []*model.User { id = ownerId } pwd, _ := bcrypt.GenerateFromPassword([]byte("polaris"), bcrypt.DefaultCost) - token, _ := defaultuser.CreateToken(id, "", "") + token, _ := defaultuser.CreateToken(id, "", "polarismesh@2021") users = append(users, &model.User{ ID: id, Name: fmt.Sprintf(nameTemp, i), diff --git a/auth/user/default.go b/auth/user/default.go index cf8527556..a92a536b6 100644 --- a/auth/user/default.go +++ b/auth/user/default.go @@ -18,9 +18,61 @@ package defaultuser import ( + "fmt" + golog "log" + "github.com/polarismesh/polaris/auth" + user_auth "github.com/polarismesh/polaris/auth/user/inteceptor/auth" +) + +type ServerProxyFactory func(svr *Server, pre auth.UserServer) (auth.UserServer, error) + +var ( + // serverProxyFactories auth.UserServer API 代理工厂 + serverProxyFactories = map[string]ServerProxyFactory{} ) +// RegisterServerProxy . +func RegisterServerProxy(name string, factor ServerProxyFactory) { + if _, ok := serverProxyFactories[name]; ok { + golog.Printf("duplicate ServerProxyFactory, name(%s)", name) + return + } + serverProxyFactories[name] = factor +} + func init() { - _ = auth.RegisterUserServer(&Server{}) + _, nextSvr, err := BuildServer() + if err != nil { + panic(err) + } + _ = auth.RegisterUserServer(nextSvr) +} + +func loadInteceptors() { + RegisterServerProxy("auth", func(svr *Server, pre auth.UserServer) (auth.UserServer, error) { + return user_auth.NewServer(pre), nil + }) +} + +func BuildServer() (*Server, auth.UserServer, error) { + loadInteceptors() + svr := &Server{} + var nextSvr auth.UserServer + nextSvr = svr + // 需要返回包装代理的 DiscoverServer + order := []string{"auth"} + 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(svr, nextSvr) + if err != nil { + panic(err) + } + nextSvr = proxySvr + } + return svr, nextSvr, nil } diff --git a/auth/user/group.go b/auth/user/group.go index 9a3c95894..4dcdc301c 100644 --- a/auth/user/group.go +++ b/auth/user/group.go @@ -30,7 +30,6 @@ import ( api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/polaris/common/model" - authcommon "github.com/polarismesh/polaris/common/model/auth" commonstore "github.com/polarismesh/polaris/common/store" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" @@ -43,14 +42,15 @@ type ( var ( // UserLinkGroupAttributes is the user link group attributes - UserLinkGroupAttributes = map[string]bool{ - "id": true, - "user_id": true, - "user_name": true, - "group_id": true, - "name": true, - "offset": true, - "limit": true, + UserLinkGroupAttributes = map[string]struct{}{ + "id": {}, + "user_id": {}, + "user_name": {}, + "group_id": {}, + "name": {}, + "offset": {}, + "limit": {}, + "owner": {}, } ) @@ -63,12 +63,12 @@ func (svr *Server) CreateGroup(ctx context.Context, req *apisecurity.UserGroup) ) req.Owner = utils.NewStringValue(ownerID) - if checkErrResp := svr.checkCreateGroup(ctx, req); checkErrResp != nil { - return checkErrResp + if rsp := svr.preCheckGroupRelation(req.GetRelation()); rsp != nil { + return rsp } // 根据 owner + groupname 确定唯一的用户组信息 - group, err := svr.storage.GetGroupByName(req.Name.GetValue(), ownerID) + group, err := svr.storage.GetGroupByName(req.GetName().GetValue(), ownerID) if err != nil { log.Error("get group when create", utils.ZapRequestID(requestID), utils.ZapPlatformID(platformID), zap.Error(err)) @@ -115,11 +115,6 @@ func (svr *Server) UpdateGroups( // UpdateGroup 更新用户组 func (svr *Server) UpdateGroup(ctx context.Context, req *apisecurity.ModifyUserGroup) *apiservice.Response { - var ( - requestID = utils.ParseRequestID(ctx) - platformID = utils.ParsePlatformID(ctx) - ) - if checkErrResp := svr.checkUpdateGroup(ctx, req); checkErrResp != nil { return checkErrResp } @@ -132,17 +127,16 @@ func (svr *Server) UpdateGroup(ctx context.Context, req *apisecurity.ModifyUserG modifyReq, needUpdate := updateGroupAttribute(ctx, data.UserGroup, req) if !needUpdate { log.Info("update group data no change, no need update", - utils.ZapRequestID(requestID), utils.ZapPlatformID(platformID), zap.String("group", req.String())) + utils.RequestID(ctx), zap.String("group", req.String())) return api.NewModifyGroupResponse(apimodel.Code_NoNeedUpdate, req) } if err := svr.storage.UpdateGroup(modifyReq); err != nil { - log.Error(err.Error(), utils.ZapRequestID(requestID), utils.ZapPlatformID(platformID)) + log.Error("update group", zap.Error(err), utils.RequestID(ctx)) return api.NewAuthResponseWithMsg(commonstore.StoreCode2APICode(err), err.Error()) } - log.Info("update group", zap.String("name", data.Name), utils.ZapRequestID(requestID), - utils.ZapPlatformID(platformID)) + log.Info("update group", zap.String("name", data.Name), utils.RequestID(ctx)) svr.RecordHistory(modifyUserGroupRecordEntry(ctx, req, data.UserGroup, model.OUpdateGroup)) return api.NewModifyGroupResponse(apimodel.Code_ExecuteSuccess, req) @@ -161,32 +155,21 @@ func (svr *Server) DeleteGroups(ctx context.Context, reqs []*apisecurity.UserGro // DeleteGroup 删除用户组 func (svr *Server) DeleteGroup(ctx context.Context, req *apisecurity.UserGroup) *apiservice.Response { - var ( - requestID = utils.ParseRequestID(ctx) - userID = utils.ParseUserID(ctx) - ) - group, err := svr.storage.GetGroup(req.GetId().GetValue()) if err != nil { - log.Error("get group from store", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("get group from store", utils.RequestID(ctx), zap.Error(err)) return api.NewGroupResponse(commonstore.StoreCode2APICode(err), req) } if group == nil { return api.NewGroupResponse(apimodel.Code_ExecuteSuccess, req) } - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - if group.Owner != userID { - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - } - if err := svr.storage.DeleteGroup(group); err != nil { - log.Error("delete group from store", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("delete group from store", utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponseWithMsg(commonstore.StoreCode2APICode(err), err.Error()) } - log.Info("delete group", utils.ZapRequestID(requestID), zap.String("name", req.Name.GetValue())) + log.Info("delete group", utils.RequestID(ctx), zap.String("name", req.Name.GetValue())) svr.RecordHistory(userGroupRecordEntry(ctx, req, group.UserGroup, model.ODelete)) return api.NewGroupResponse(apimodel.Code_ExecuteSuccess, req) @@ -194,24 +177,21 @@ func (svr *Server) DeleteGroup(ctx context.Context, req *apisecurity.UserGroup) // GetGroups 查看用户组 func (svr *Server) GetGroups(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { - requestID := utils.ParseRequestID(ctx) - log.Info("[Auth][Group] origin get groups query params", - utils.ZapRequestID(requestID), zap.Any("query", query)) + utils.RequestID(ctx), zap.Any("query", query)) - var ( - offset, limit uint32 - err error - ) - - offset, limit, err = utils.ParseOffsetAndLimit(query) + offset, limit, err := utils.ParseOffsetAndLimit(query) if err != nil { return api.NewAuthBatchQueryResponse(apimodel.Code_InvalidParameter) } - searchFilters, errResp := parseGroupSearchArgs(ctx, query) - if errResp != nil { - return errResp + searchFilters := make(map[string]string, len(query)) + for key, value := range query { + if _, ok := UserLinkGroupAttributes[key]; !ok { + log.Errorf("[Auth][Group] get groups attribute(%s) it not allowed", key) + return api.NewAuthBatchQueryResponseWithMsg(apimodel.Code_InvalidParameter, key+" is not allowed") + } + searchFilters[key] = value } total, groups, err := svr.storage.GetGroups(searchFilters, offset, limit) @@ -225,35 +205,16 @@ func (svr *Server) GetGroups(ctx context.Context, query map[string]string) *apis resp.Size = utils.NewUInt32Value(uint32(len(groups))) resp.UserGroups = enhancedGroups2Api(groups, userGroup2Api) - svr.fillGroupUserCount(resp.UserGroups) - - return resp -} - -func parseGroupSearchArgs( - ctx context.Context, query map[string]string) (map[string]string, *apiservice.BatchQueryResponse) { - searchFilters := make(map[string]string, len(query)) - for key, value := range query { - if _, ok := UserLinkGroupAttributes[key]; !ok { - log.Errorf("[Auth][Group] get groups attribute(%s) it not allowed", key) - return nil, api.NewAuthBatchQueryResponseWithMsg(apimodel.Code_InvalidParameter, key+" is not allowed") - } - - searchFilters[key] = value - } - - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - // step 1: 设置 owner 信息,只能查看归属主帐户下的用户组 - searchFilters["owner"] = utils.ParseOwnerID(ctx) - if authcommon.ParseUserRole(ctx) != model.OwnerUserRole { - // step 2: 非主帐户,只能查看自己所在的用户组 - if _, ok := searchFilters["user_id"]; !ok { - searchFilters["user_id"] = utils.ParseUserID(ctx) - } + for index := range resp.UserGroups { + group := resp.UserGroups[index] + cacheVal := svr.cacheMgr.User().GetGroup(group.Id.Value) + if cacheVal == nil { + group.UserCount = utils.NewUInt32Value(0) + } else { + group.UserCount = utils.NewUInt32Value(uint32(len(cacheVal.UserIds))) } } - - return searchFilters, nil + return resp } // GetGroup 查看对应用户组下的用户信息 @@ -267,18 +228,6 @@ func (svr *Server) GetGroup(ctx context.Context, req *apisecurity.UserGroup) *ap return errResp } - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - userID := utils.ParseUserID(ctx) - isGroupOwner := group.Owner == userID - _, find := group.UserIds[userID] - if !isGroupOwner && !find { - log.Error("can't see group info", zap.String("user", userID), - zap.String("group", req.GetId().GetValue()), zap.Bool("group-owner", isGroupOwner), - zap.Bool("in-group", find)) - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - } - return api.NewGroupResponse(apimodel.Code_ExecuteSuccess, svr.userGroupDetail2Api(group)) } @@ -293,18 +242,6 @@ func (svr *Server) GetGroupToken(ctx context.Context, req *apisecurity.UserGroup return errResp } - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - userID := utils.ParseUserID(ctx) - isGroupOwner := groupCache.Owner == userID - _, find := groupCache.UserIds[userID] - if !isGroupOwner && !find { - log.Error("can't see group token", zap.String("user", userID), - zap.String("group", req.GetId().GetValue()), zap.Bool("group-owner", isGroupOwner), - zap.Bool("in-group", find)) - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - } - req.AuthToken = utils.NewStringValue(groupCache.Token) req.TokenEnable = utils.NewBoolValue(groupCache.TokenEnable) @@ -323,13 +260,6 @@ func (svr *Server) UpdateGroupToken(ctx context.Context, req *apisecurity.UserGr return errResp } - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - userID := utils.ParseUserID(ctx) - if group.Owner != userID { - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - } - group.TokenEnable = req.TokenEnable.GetValue() modifyReq := &model.ModifyUserGroup{ @@ -423,13 +353,7 @@ func (svr *Server) getGroupFromCache(req *apisecurity.UserGroup) (*model.UserGro } // preCheckGroupRelation 检查用户-用户组关联关系中,对应的用户信息是否存在,即不能添加一个不存在的用户到用户组 -func (svr *Server) preCheckGroupRelation(groupID string, req *apisecurity.UserGroupRelation) (*model.UserGroupDetail, - *apiservice.Response) { - group := svr.cacheMgr.User().GetGroup(groupID) - if group == nil { - return nil, api.NewAuthResponse(apimodel.Code_NotFoundUserGroup) - } - +func (svr *Server) preCheckGroupRelation(req *apisecurity.UserGroupRelation) *apiservice.Response { // 检查该关系中所有的用户是否存在 uIDs := make([]string, len(req.GetUsers())) for i := range req.GetUsers() { @@ -440,24 +364,7 @@ func (svr *Server) preCheckGroupRelation(groupID string, req *apisecurity.UserGr for i := range uIDs { user := svr.cacheMgr.User().GetUserByID(uIDs[i]) if user == nil { - return group, api.NewGroupRelationResponse(apimodel.Code_NotFoundUser, req) - } - } - - return group, nil -} - -// checkCreateGroup 检查创建用户组的请求 -func (svr *Server) checkCreateGroup(_ context.Context, req *apisecurity.UserGroup) *apiservice.Response { - if req == nil { - return api.NewGroupResponse(apimodel.Code_EmptyRequest, req) - } - - users := req.GetRelation().GetUsers() - for i := range users { - user := svr.cacheMgr.User().GetUserByID(users[i].GetId().GetValue()) - if user == nil { - return api.NewGroupRelationResponse(apimodel.Code_NotFoundUser, req.GetRelation()) + return api.NewGroupRelationResponse(apimodel.Code_NotFoundUser, req) } } @@ -466,55 +373,18 @@ func (svr *Server) checkCreateGroup(_ context.Context, req *apisecurity.UserGrou // checkUpdateGroup 检查用户组的更新请求 func (svr *Server) checkUpdateGroup(ctx context.Context, req *apisecurity.ModifyUserGroup) *apiservice.Response { - userID := utils.ParseUserID(ctx) - isOwner := utils.ParseIsOwner(ctx) - if req == nil { return api.NewModifyGroupResponse(apimodel.Code_EmptyRequest, req) } - if req.Id == nil || req.Id.GetValue() == "" { return api.NewModifyGroupResponse(apimodel.Code_InvalidUserGroupID, req) } - - group, checkErrResp := svr.preCheckGroupRelation(req.GetId().GetValue(), req.GetAddRelations()) - if checkErrResp != nil { - return checkErrResp - } - - // 满足以下情况才可以进行操作 - // 1.管理员 - // 2.自己在这个用户组里面 - // 3.自己是这个用户组的owner角色 - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - _, inGroup := group.UserIds[userID] - if !inGroup && group.Owner != userID { - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - - // 如果当前用户只是在这个组里面,但不是该用户组的owner,那只能添加用户,不能删除用户 - if inGroup && !isOwner && len(req.GetRemoveRelations().GetUsers()) != 0 { - return api.NewAuthResponseWithMsg( - apimodel.Code_NotAllowedAccess, "only main account can remove user from usergroup") - } + if rsp := svr.preCheckGroupRelation(req.GetAddRelations()); rsp != nil { + return rsp } return nil } -func (svr *Server) fillGroupUserCount(groups []*apisecurity.UserGroup) { - groupCache := svr.cacheMgr.User() - - for index := range groups { - group := groups[index] - cacheVal := groupCache.GetGroup(group.Id.Value) - if cacheVal == nil { - group.UserCount = utils.NewUInt32Value(0) - } else { - group.UserCount = utils.NewUInt32Value(uint32(len(cacheVal.UserIds))) - } - } -} - // updateGroupAttribute 更新计算用户组更新时的结构体数据,并判断是否需要执行更新操作 func updateGroupAttribute(ctx context.Context, old *model.UserGroup, newUser *apisecurity.ModifyUserGroup) ( *model.ModifyUserGroup, bool) { diff --git a/auth/user/group_test.go b/auth/user/group_test.go index 950415e91..e36229dc9 100644 --- a/auth/user/group_test.go +++ b/auth/user/group_test.go @@ -53,7 +53,7 @@ type GroupTest struct { checker auth.AuthChecker cancel context.CancelFunc - svr *defaultauth.Server + svr auth.UserServer } func newGroupTest(t *testing.T) *GroupTest { @@ -93,22 +93,31 @@ func newGroupTest(t *testing.T) *GroupTest { _ = cacheMgn.Close() }) + _, proxySvr, err := defaultauth.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, + }, + }, storage, cacheMgn) _ = cacheMgn.TestUpdate() return &GroupTest{ - ctrl: ctrl, - - ownerOne: users[0], - ownerTwo: newUsers[0], - + ctrl: ctrl, + ownerOne: users[0], + ownerTwo: newUsers[0], users: users, groups: groups, newGroups: newGroups, allGroups: allGroups, - - storage: storage, - cacheMgn: cacheMgn, - - cancel: cancel, + storage: storage, + cacheMgn: cacheMgn, + cancel: cancel, + svr: proxySvr, } } @@ -196,7 +205,7 @@ func Test_server_CreateGroup(t *testing.T) { t.Run("子账户去查询自己所在的用户组", func(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, groupTest.users[1].Token) - groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[1], nil) + groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[1], nil).AnyTimes() resp := groupTest.svr.GetGroup(reqCtx, &apisecurity.UserGroup{ Id: utils.NewStringValue(groupTest.groups[1].ID), @@ -208,7 +217,7 @@ func Test_server_CreateGroup(t *testing.T) { t.Run("子账户去查询自己不在的用户组", func(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, groupTest.users[1].Token) - groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[2], nil) + groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[2], nil).AnyTimes() resp := groupTest.svr.GetGroup(reqCtx, &apisecurity.UserGroup{ Id: utils.NewStringValue(groupTest.groups[2].ID), @@ -249,7 +258,7 @@ func Test_server_GetGroup(t *testing.T) { t.Run("子账户去查询自己所在的用户组", func(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, groupTest.users[1].Token) - groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[1], nil) + groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[1], nil).AnyTimes() resp := groupTest.svr.GetGroup(reqCtx, &apisecurity.UserGroup{ Id: utils.NewStringValue(groupTest.groups[1].ID), @@ -261,7 +270,7 @@ func Test_server_GetGroup(t *testing.T) { t.Run("子账户去查询自己不在的用户组", func(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, groupTest.users[1].Token) - groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[2], nil) + groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[2], nil).AnyTimes() resp := groupTest.svr.GetGroup(reqCtx, &apisecurity.UserGroup{ Id: utils.NewStringValue(groupTest.groups[2].ID), @@ -652,7 +661,7 @@ func Test_server_RefreshGroupToken(t *testing.T) { }) reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, groupTest.ownerTwo.Token) - groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[0], nil) + groupTest.storage.EXPECT().GetGroup(gomock.Any()).Return(groupTest.groups[0], nil).AnyTimes() batchResp := groupTest.svr.ResetGroupToken(reqCtx, &apisecurity.UserGroup{ Id: utils.NewStringValue(groupTest.groups[2].ID), diff --git a/auth/user/helper.go b/auth/user/helper.go index 54137f30e..7fd203b9c 100644 --- a/auth/user/helper.go +++ b/auth/user/helper.go @@ -17,6 +17,105 @@ package defaultuser +import ( + "context" + "errors" + + apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" + "go.uber.org/zap" +) + +var ( + ErrGroupNotExist = errors.New("group not exist") + ErrUserNotExist = errors.New("user not exist") +) + type DefaultUserHelper struct { + svr *Server +} + +// CheckUserInGroup 检查用户是否在用户组中 +func (helper *DefaultUserHelper) CheckUserInGroup(ctx context.Context, + group *apisecurity.UserGroup, user *apisecurity.User) bool { + + cacheMgr := helper.svr.cacheMgr + return cacheMgr.User().IsUserInGroup(user.GetId().GetValue(), group.GetId().GetValue()) +} + +// CheckGroupsExist 批量检查用户组是否存在 +func (helper *DefaultUserHelper) CheckGroupsExist(ctx context.Context, groups []*apisecurity.UserGroup) error { + cacheMgr := helper.svr.cacheMgr + for i := range groups { + item := groups[i] + if ret := cacheMgr.User().GetGroup(item.GetId().GetValue()); ret == nil { + return ErrGroupNotExist + } + } + return nil +} + +// CheckUsersExist 批量检查用户是否存在 +func (helper *DefaultUserHelper) CheckUsersExist(ctx context.Context, users []*apisecurity.User) error { + cacheMgr := helper.svr.cacheMgr + for i := range users { + item := users[i] + if ret := cacheMgr.User().GetUserByID(item.GetId().GetValue()); ret == nil { + return ErrUserNotExist + } + } + return nil +} + +// GetUserOwnGroup 查询某个用户所在的所有用户组 +func (helper *DefaultUserHelper) GetUserOwnGroup(ctx context.Context, user *apisecurity.User) []*apisecurity.UserGroup { + cacheMgr := helper.svr.cacheMgr + + ids := cacheMgr.User().GetUserLinkGroupIds(user.GetId().GetValue()) + groups := make([]*apisecurity.UserGroup, 0, len(ids)) + for i := range ids { + item := ids[i] + group := cacheMgr.User().GetGroup(item) + if group != nil { + groups = append(groups, group.ToSpec()) + } + } + return groups +} + +// GetUser 查询用户信息 +func (helper *DefaultUserHelper) GetUser(ctx context.Context, user *apisecurity.User) *apisecurity.User { + cacheMgr := helper.svr.cacheMgr + if user.GetName().GetValue() == "" { + return cacheMgr.User().GetUserByID(user.GetId().GetValue()).ToSpec() + } + owner := "" + if user.GetOwner().GetValue() != "" { + ownerUser := cacheMgr.User().GetUserByID(user.GetOwner().GetValue()) + if ownerUser != nil { + owner = ownerUser.ID + } + } + return cacheMgr.User().GetUserByName(user.GetName().GetValue(), owner).ToSpec() +} + +func (helper *DefaultUserHelper) GetUserByID(ctx context.Context, id string) *apisecurity.User { + cacheMgr := helper.svr.cacheMgr + return cacheMgr.User().GetUserByID(id).ToSpec() +} +// GetGroup 查询用户组信息 +func (helper *DefaultUserHelper) GetGroup(ctx context.Context, req *apisecurity.UserGroup) *apisecurity.UserGroup { + cacheMgr := helper.svr.cacheMgr + saveVal := cacheMgr.User().GetGroup(req.GetId().GetValue()) + if saveVal != nil { + return saveVal.ToSpec() + } + // 从数据库在获取一次 + saveVal, err := helper.svr.storage.GetGroup(req.GetId().GetValue()) + if err != nil { + log.Error("[Auth][UserHelper] get user_group from store", zap.String("id", req.GetId().GetValue()), + zap.Error(err)) + return nil + } + return saveVal.ToSpec() } diff --git a/auth/user/inteceptor/auth/log.go b/auth/user/inteceptor/auth/log.go new file mode 100644 index 000000000..7707503ce --- /dev/null +++ b/auth/user/inteceptor/auth/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 auth + +import commonlog "github.com/polarismesh/polaris/common/log" + +var ( + log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) +) diff --git a/auth/user/inteceptor/auth/server.go b/auth/user/inteceptor/auth/server.go new file mode 100644 index 000000000..d84c48b8a --- /dev/null +++ b/auth/user/inteceptor/auth/server.go @@ -0,0 +1,469 @@ +/** + * 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 auth + +import ( + "context" + "strconv" + + "github.com/polarismesh/polaris/auth" + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/model" + authcommon "github.com/polarismesh/polaris/common/model/auth" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/store" + 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" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/wrapperspb" + + cachetypes "github.com/polarismesh/polaris/cache/api" +) + +var ( + // MustOwner 必须超级账户 or 主账户 + MustOwner = true + // NotOwner 任意账户 + NotOwner = false + // WriteOp 写操作 + WriteOp = true + // ReadOp 读操作 + ReadOp = false +) + +func NewServer(nextSvr auth.UserServer) auth.UserServer { + return &Server{ + nextSvr: nextSvr, + } +} + +type Server struct { + nextSvr auth.UserServer +} + +// Initialize 初始化 +func (svr *Server) Initialize(authOpt *auth.Config, storage store.Store, cacheMgr cachetypes.CacheManager) error { + return svr.nextSvr.Initialize(authOpt, storage, cacheMgr) +} + +// Name 用户数据管理server名称 +func (svr *Server) Name() string { + return svr.nextSvr.Name() +} + +// Login 登录动作 +func (svr *Server) Login(req *apisecurity.LoginRequest) *apiservice.Response { + return svr.nextSvr.Login(req) +} + +// CheckCredential 检查当前操作用户凭证 +func (svr *Server) CheckCredential(authCtx *model.AcquireContext) error { + return svr.nextSvr.CheckCredential(authCtx) +} + +// GetUserHelper +func (svr *Server) GetUserHelper() auth.UserHelper { + return svr.nextSvr.GetUserHelper() +} + +// CreateUsers 批量创建用户 +func (svr *Server) CreateUsers(ctx context.Context, users []*apisecurity.User) *apiservice.BatchWriteResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + api.Collect(resp, rsp) + return resp + } + return svr.nextSvr.CreateUsers(ctx, users) +} + +// UpdateUser 更新用户信息 +func (svr *Server) UpdateUser(ctx context.Context, user *apisecurity.User) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + rsp.User = user + return rsp + } + helper := svr.GetUserHelper() + targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + if !checkUserViewPermission(ctx, targetUser) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.UpdateUser(ctx, user) +} + +// UpdateUserPassword 更新用户密码 +func (svr *Server) UpdateUserPassword(ctx context.Context, req *apisecurity.ModifyUserPassword) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return rsp + } + helper := svr.GetUserHelper() + targetUser := helper.GetUserByID(ctx, req.GetId().GetValue()) + if !checkUserViewPermission(ctx, targetUser) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.UpdateUserPassword(ctx, req) +} + +// DeleteUsers 批量删除用户 +func (svr *Server) DeleteUsers(ctx context.Context, users []*apisecurity.User) *apiservice.BatchWriteResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + api.Collect(resp, rsp) + return resp + } + return svr.nextSvr.DeleteUsers(ctx, users) +} + +// GetUsers 查询用户列表 +func (svr *Server) GetUsers(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return api.NewAuthBatchQueryResponseWithMsg(apimodel.Code(rsp.GetCode().Value), rsp.Info.Value) + } + query["hide_admin"] = strconv.FormatBool(true) + // 如果不是超级管理员,查看数据有限制 + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + // 设置 owner 参数,只能查看对应 owner 下的用户 + query["owner"] = utils.ParseOwnerID(ctx) + } + return svr.nextSvr.GetUsers(ctx, query) +} + +// GetUserToken 获取用户的 token +func (svr *Server) GetUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return rsp + } + helper := svr.GetUserHelper() + targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + if !checkUserViewPermission(ctx, targetUser) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.GetUserToken(ctx, user) +} + +// UpdateUserToken 禁止用户的token使用 +func (svr *Server) UpdateUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, NotOwner) + if rsp != nil { + return rsp + } + helper := svr.GetUserHelper() + targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + if !checkUserViewPermission(ctx, targetUser) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.UpdateUserToken(ctx, user) +} + +// ResetUserToken 重置用户的token +func (svr *Server) ResetUserToken(ctx context.Context, user *apisecurity.User) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, NotOwner) + if rsp != nil { + return rsp + } + helper := svr.GetUserHelper() + targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + if !checkUserViewPermission(ctx, targetUser) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.ResetUserToken(ctx, user) +} + +// CreateGroup 创建用户组 +func (svr *Server) CreateGroup(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + return rsp + } + return svr.nextSvr.CreateGroup(ctx, group) +} + +// UpdateGroups 更新用户组 +func (svr *Server) UpdateGroups(ctx context.Context, groups []*apisecurity.ModifyUserGroup) *apiservice.BatchWriteResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + api.Collect(resp, rsp) + return resp + } + + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + for i := range groups { + item := groups[i] + rsp := svr.checkUpdateGroup(ctx, item) + api.Collect(resp, rsp) + } + if !api.IsSuccess(resp) { + return resp + } + + return svr.nextSvr.UpdateGroups(ctx, groups) +} + +// DeleteGroups 批量删除用户组 +func (svr *Server) DeleteGroups(ctx context.Context, groups []*apisecurity.UserGroup) *apiservice.BatchWriteResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + api.Collect(resp, rsp) + return resp + } + resp := api.NewAuthBatchWriteResponse(apimodel.Code_ExecuteSuccess) + for i := range groups { + item := groups[i] + if !svr.checkGroupViewAuth(ctx, item.GetId().GetValue()) { + api.Collect(resp, api.NewAuthResponse(apimodel.Code_NotAllowedAccess)) + } + } + if !api.IsSuccess(resp) { + return resp + } + return svr.nextSvr.DeleteGroups(ctx, groups) +} + +// GetGroups 查询用户组列表(不带用户详细信息) +func (svr *Server) GetGroups(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + resp := api.NewAuthBatchQueryResponse(apimodel.Code_ExecuteSuccess) + api.QueryCollect(resp, rsp) + return resp + } + + delete(query, "owner") + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + // step 1: 设置 owner 信息,只能查看归属主帐户下的用户组 + query["owner"] = utils.ParseOwnerID(ctx) + if authcommon.ParseUserRole(ctx) != model.OwnerUserRole { + // step 2: 非主帐户,只能查看自己所在的用户组 + if _, ok := query["user_id"]; !ok { + query["user_id"] = utils.ParseUserID(ctx) + } + } + } + + return svr.nextSvr.GetGroups(ctx, query) +} + +// GetGroup 根据用户组信息,查询该用户组下的用户相信 +func (svr *Server) GetGroup(ctx context.Context, req *apisecurity.UserGroup) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return rsp + } + if !svr.checkGroupViewAuth(ctx, req.GetId().GetValue()) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.GetGroup(ctx, req) +} + +// GetGroupToken 获取用户组的 token +func (svr *Server) GetGroupToken(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, ReadOp, NotOwner) + if rsp != nil { + return rsp + } + if !svr.checkGroupViewAuth(ctx, group.GetId().GetValue()) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + return svr.nextSvr.GetGroupToken(ctx, group) +} + +// UpdateGroupToken 取消用户组的 token 使用 +func (svr *Server) UpdateGroupToken(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + return rsp + } + saveGroup := svr.GetUserHelper().GetGroup(ctx, &apisecurity.UserGroup{ + Id: wrapperspb.String(group.GetId().GetValue()), + }) + if saveGroup == nil { + return api.NewAuthResponse(apimodel.Code_NotFoundUserGroup) + } + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + if saveGroup.GetOwner().GetValue() != utils.ParseUserID(ctx) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + } + return svr.nextSvr.UpdateGroupToken(ctx, group) +} + +// ResetGroupToken 重置用户组的 token +func (svr *Server) ResetGroupToken(ctx context.Context, group *apisecurity.UserGroup) *apiservice.Response { + ctx, rsp := svr.verifyAuth(ctx, WriteOp, MustOwner) + if rsp != nil { + return rsp + } + saveGroup := svr.GetUserHelper().GetGroup(ctx, &apisecurity.UserGroup{ + Id: wrapperspb.String(group.GetId().GetValue()), + }) + if saveGroup == nil { + return api.NewAuthResponse(apimodel.Code_NotFoundUserGroup) + } + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + if saveGroup.GetOwner().GetValue() != utils.ParseUserID(ctx) { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + } + return svr.nextSvr.ResetGroupToken(ctx, group) +} + +// verifyAuth 用于 user、group 以及 strategy 模块的鉴权工作检查 +func (svr *Server) verifyAuth(ctx context.Context, isWrite bool, + needOwner bool) (context.Context, *apiservice.Response) { + reqId := utils.ParseRequestID(ctx) + authToken := utils.ParseAuthToken(ctx) + + if authToken == "" { + log.Error("[Auth][Server] auth token is empty", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_EmptyAutToken) + } + + authCtx := model.NewAcquireContext( + model.WithRequestContext(ctx), + model.WithModule(model.AuthModule), + ) + + // case 1. 如果 error 不是 token 被禁止的 error,直接返回 + // case 2. 如果 error 是 token 被禁止,按下面情况判断 + // i. 如果当前只是一个数据的读取操作,则放通 + // ii. 如果当前是一个数据的写操作,则只能允许处于正常的 token 进行操作 + if err := svr.CheckCredential(authCtx); err != nil { + log.Error("[Auth][Server] verify auth token", utils.ZapRequestID(reqId), + zap.Error(err)) + return nil, api.NewAuthResponse(apimodel.Code_AuthTokenForbidden) + } + + attachVal, exist := authCtx.GetAttachment(model.TokenDetailInfoKey) + if !exist { + log.Error("[Auth][Server] token detail info not exist", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_TokenNotExisted) + } + + operateInfo := attachVal.(auth.OperatorInfo) + if isWrite && operateInfo.Disable { + log.Error("[Auth][Server] token is disabled", utils.ZapRequestID(reqId), + zap.String("operation", authCtx.GetMethod())) + return nil, api.NewAuthResponse(apimodel.Code_TokenDisabled) + } + + if !operateInfo.IsUserToken { + log.Error("[Auth][Server] only user role can access this API", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) + } + + if needOwner && auth.IsSubAccount(operateInfo) { + log.Error("[Auth][Server] only admin/owner account can access this API", utils.ZapRequestID(reqId)) + return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) + } + + return authCtx.GetRequestContext(), nil +} + +// checkUserViewPermission 检查是否可以操作该用户 +// Case 1: 如果是自己操作自己,通过 +// Case 2: 如果是主账户操作自己的子账户,通过 +// Case 3: 如果是超级账户,通过 +func checkUserViewPermission(ctx context.Context, user *apisecurity.User) bool { + role := authcommon.ParseUserRole(ctx) + if role == model.AdminUserRole { + log.Debug("check user view permission", utils.RequestID(ctx), zap.Bool("admin", true)) + return true + } + + userId := utils.ParseUserID(ctx) + if user.GetId().GetValue() == userId { + return true + } + + if user.GetOwner().GetValue() == userId { + log.Debug("check user view permission", utils.RequestID(ctx), + zap.Any("user", user), zap.String("owner", user.GetOwner().GetValue()), zap.String("operator", userId)) + return true + } + + return false +} + +// checkUpdateGroup 检查用户组的更新请求 +func (svr *Server) checkUpdateGroup(ctx context.Context, req *apisecurity.ModifyUserGroup) *apiservice.Response { + userId := utils.ParseUserID(ctx) + isOwner := utils.ParseIsOwner(ctx) + saveGroup := svr.GetUserHelper().GetGroup(ctx, &apisecurity.UserGroup{ + Id: wrapperspb.String(req.GetId().GetValue()), + }) + if saveGroup == nil { + return api.NewAuthResponse(apimodel.Code_NotFoundUserGroup) + } + + // 满足以下情况才可以进行操作 + // 1.管理员 + // 2.自己在这个用户组里面 + // 3.自己是这个用户组的owner角色 + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + inGroup := false + for i := range saveGroup.GetRelation().GetUsers() { + if userId == saveGroup.GetRelation().GetUsers()[i].GetId().GetValue() { + inGroup = true + break + } + } + if !inGroup && saveGroup.GetOwner().GetValue() != userId { + return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) + } + // 如果当前用户只是在这个组里面,但不是该用户组的owner,那只能添加用户,不能删除用户 + if inGroup && !isOwner && len(req.GetRemoveRelations().GetUsers()) != 0 { + return api.NewAuthResponseWithMsg( + apimodel.Code_NotAllowedAccess, "only main account can remove user from usergroup") + } + } + return nil +} + +func (svr *Server) checkGroupViewAuth(ctx context.Context, id string) bool { + saveGroup := svr.GetUserHelper().GetGroup(ctx, &apisecurity.UserGroup{ + Id: wrapperspb.String(id), + }) + if saveGroup == nil { + return false + } + + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + userID := utils.ParseUserID(ctx) + inGroup := svr.GetUserHelper().CheckUserInGroup(ctx, &apisecurity.UserGroup{ + Id: wrapperspb.String(id), + }, &apisecurity.User{ + Id: wrapperspb.String(userID), + }) + isGroupOwner := saveGroup.GetOwner().GetValue() == userID + if !isGroupOwner && !inGroup { + log.Error("can't see group info", zap.String("user", userID), + zap.String("group", id), zap.Bool("group-owner", isGroupOwner), + zap.Bool("in-group", inGroup)) + return false + } + } + return true +} diff --git a/auth/user/server.go b/auth/user/server.go index f767030bd..2c76d14fa 100644 --- a/auth/user/server.go +++ b/auth/user/server.go @@ -90,6 +90,7 @@ func (svr *Server) Initialize(authOpt *auth.Config, storage store.Store, cacheMg if svr.history == nil { log.Warnf("Not Found History Log Plugin") } + svr.helper = &DefaultUserHelper{svr: svr} return nil } diff --git a/auth/user/token_test.go b/auth/user/token_test.go index ebfa35b5b..dcd7921fb 100644 --- a/auth/user/token_test.go +++ b/auth/user/token_test.go @@ -22,8 +22,9 @@ import ( "testing" "time" - defaultuser "github.com/polarismesh/polaris/auth/user" "golang.org/x/crypto/bcrypt" + + defaultuser "github.com/polarismesh/polaris/auth/user" ) // Test_CustomDesignSalt 主要用于有自定义salt需求的用户 diff --git a/auth/user/user.go b/auth/user/user.go index c64f0e81e..e7d737113 100644 --- a/auth/user/user.go +++ b/auth/user/user.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "strconv" "time" "github.com/gogo/protobuf/jsonpb" @@ -159,10 +158,6 @@ func (svr *Server) UpdateUser(ctx context.Context, req *apisecurity.User) *apise return api.NewUserResponse(apimodel.Code_NotFoundUser, req) } - if !checkUserViewPermission(ctx, user) { - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - data, needUpdate, err := updateUserAttribute(user, req) if err != nil { return api.NewAuthResponseWithMsg(apimodel.Code_ExecuteException, err.Error()) @@ -201,10 +196,6 @@ func (svr *Server) UpdateUserPassword(ctx context.Context, req *apisecurity.Modi return api.NewAuthResponse(apimodel.Code_NotFoundUser) } - if !checkUserViewPermission(ctx, user) { - return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) - } - ignoreOrigin := authcommon.ParseUserRole(ctx) == model.AdminUserRole || authcommon.ParseUserRole(ctx) == model.OwnerUserRole data, needUpdate, err := updateUserPasswordAttribute(ignoreOrigin, user, req) @@ -260,11 +251,6 @@ func (svr *Server) DeleteUser(ctx context.Context, req *apisecurity.User) *apise return api.NewUserResponse(apimodel.Code_ExecuteSuccess, req) } - if !checkUserViewPermission(ctx, user) { - log.Error("[Auth][User] delete user forbidden", utils.ZapRequestID(requestID), - zap.String("name", req.GetName().GetValue())) - return api.NewUserResponse(apimodel.Code_NotAllowedAccess, req) - } if user.ID == utils.ParseOwnerID(ctx) { log.Error("[Auth][User] delete user forbidden, can't delete when self is owner", utils.ZapRequestID(requestID), zap.String("name", req.Name.GetValue())) @@ -316,13 +302,6 @@ func (svr *Server) GetUsers(ctx context.Context, query map[string]string) *apise searchFilters[key] = value } - searchFilters["hide_admin"] = strconv.FormatBool(true) - // 如果不是超级管理员,查看数据有限制 - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - // 设置 owner 参数,只能查看对应 owner 下的用户 - searchFilters["owner"] = utils.ParseOwnerID(ctx) - } - var ( total uint32 users []*model.User @@ -373,10 +352,6 @@ func (svr *Server) GetUserToken(ctx context.Context, req *apisecurity.User) *api return api.NewUserResponse(apimodel.Code_NotFoundUser, req) } - if !checkUserViewPermission(ctx, user) { - return api.NewUserResponse(apimodel.Code_NotAllowedAccess, req) - } - out := &apisecurity.User{ Id: utils.NewStringValue(user.ID), Name: utils.NewStringValue(user.Name), @@ -403,10 +378,6 @@ func (svr *Server) UpdateUserToken(ctx context.Context, req *apisecurity.User) * return api.NewUserResponse(apimodel.Code_NotFoundUser, req) } - if !checkUserViewPermission(ctx, user) { - return api.NewUserResponse(apimodel.Code_NotAllowedAccess, req) - } - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { if user.Type != model.SubAccountUserRole { return api.NewUserResponseWithMsg(apimodel.Code_NotAllowedAccess, "only disable sub-account token", req) @@ -444,10 +415,6 @@ func (svr *Server) ResetUserToken(ctx context.Context, req *apisecurity.User) *a return api.NewUserResponse(apimodel.Code_NotFoundUser, req) } - if !checkUserViewPermission(ctx, user) { - return api.NewUserResponse(apimodel.Code_NotAllowedAccess, req) - } - newToken, err := createUserToken(user.ID, svr.authOpt.Salt) if err != nil { log.Error("[Auth][User] update user token", utils.ZapRequestID(requestID), zap.Error(err)) @@ -567,31 +534,6 @@ func canDowngradeAnonymous(authCtx *model.AcquireContext, err error) bool { return false } -// checkUserViewPermission 检查是否可以操作该用户 -// Case 1: 如果是自己操作自己,通过 -// Case 2: 如果是主账户操作自己的子账户,通过 -// Case 3: 如果是超级账户,通过 -func checkUserViewPermission(ctx context.Context, user *model.User) bool { - role := authcommon.ParseUserRole(ctx) - if role == model.AdminUserRole { - log.Debug("check user view permission", utils.RequestID(ctx), zap.Bool("admin", true)) - return true - } - - userId := utils.ParseUserID(ctx) - if user.ID == userId { - return true - } - - if user.Owner == userId { - log.Debug("check user view permission", utils.RequestID(ctx), - zap.Any("user", user), zap.String("owner", user.Owner), zap.String("operator", userId)) - return true - } - - return false -} - // user 数组转为[]*apisecurity.User func enhancedUsers2Api(users []*model.User, handler User2Api) []*apisecurity.User { out := make([]*apisecurity.User, 0, len(users)) diff --git a/auth/user/user_test.go b/auth/user/user_test.go index 9de4f08b6..218ae6e1a 100644 --- a/auth/user/user_test.go +++ b/auth/user/user_test.go @@ -27,6 +27,7 @@ import ( apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" "github.com/stretchr/testify/assert" + "github.com/polarismesh/polaris/auth" defaultuser "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/cache" cachetypes "github.com/polarismesh/polaris/cache/api" @@ -51,7 +52,7 @@ type UserTest struct { storage *storemock.MockStore cacheMgn *cache.CacheManager - svr *defaultuser.Server + svr auth.UserServer cancel context.CancelFunc ctrl *gomock.Controller @@ -105,7 +106,18 @@ func newUserTest(t *testing.T) *UserTest { _ = cache.TestRun(ctx, cacheMgn) - svr := &defaultuser.Server{} + _, proxySvr, err := defaultuser.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, + }, + }, storage, cacheMgn) return &UserTest{ admin: admin, ownerOne: users[0], @@ -117,7 +129,7 @@ func newUserTest(t *testing.T) *UserTest { storage: storage, cacheMgn: cacheMgn, - svr: svr, + svr: proxySvr, cancel: cancel, ctrl: ctrl, @@ -569,8 +581,10 @@ func Test_server_DeleteUser(t *testing.T) { userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[0], nil) reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) - resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ - Id: utils.NewStringValue(userTest.users[0].ID), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(userTest.users[0].ID), + }, }) assert.True(t, resp.GetCode().Value == api.NotAllowedAccess, resp.Info.GetValue()) @@ -590,8 +604,10 @@ func Test_server_DeleteUser(t *testing.T) { }, nil) reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) - resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ - Id: utils.NewStringValue(uid), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(uid), + }, }) assert.True(t, resp.GetCode().Value == api.NotAllowedAccess, resp.Info.GetValue()) @@ -606,8 +622,10 @@ func Test_server_DeleteUser(t *testing.T) { 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{ - Id: utils.NewStringValue(userTest.users[1].ID), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(userTest.users[1].ID), + }, }) assert.True(t, resp.GetCode().Value == api.ExecuteSuccess, resp.Info.GetValue()) @@ -628,8 +646,10 @@ func Test_server_DeleteUser(t *testing.T) { }, nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) - resp := userTest.svr.DeleteUser(reqCtx, &apisecurity.User{ - Id: utils.NewStringValue(uid), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(uid), + }, }) assert.True(t, resp.GetCode().Value == api.NotAllowedAccess, resp.Info.GetValue()) @@ -645,8 +665,10 @@ func Test_server_DeleteUser(t *testing.T) { 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{ - Id: utils.NewStringValue(userTest.users[0].ID), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(userTest.users[0].ID), + }, }) assert.True(t, resp.GetCode().Value == api.ExecuteSuccess, resp.Info.GetValue()) @@ -662,8 +684,10 @@ func Test_server_DeleteUser(t *testing.T) { 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{ - Id: utils.NewStringValue(userTest.users[0].ID), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(userTest.users[0].ID), + }, }) assert.True(t, resp.GetCode().Value == api.SubAccountExisted, resp.Info.GetValue()) @@ -676,8 +700,10 @@ func Test_server_DeleteUser(t *testing.T) { }) 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), + resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ + &apisecurity.User{ + Id: utils.NewStringValue(userTest.users[0].ID), + }, }) assert.True(t, resp.GetCode().Value == api.OperationRoleException, resp.Info.GetValue()) diff --git a/auth/user/utils_test.go b/auth/user/utils_test.go index 952949451..069b2a963 100644 --- a/auth/user/utils_test.go +++ b/auth/user/utils_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/golang/protobuf/ptypes/wrappers" + defaultuser "github.com/polarismesh/polaris/auth/user" "github.com/polarismesh/polaris/common/utils" ) diff --git a/cache/config/config_file.go b/cache/config/config_file.go index 37651fab8..be23bf818 100644 --- a/cache/config/config_file.go +++ b/cache/config/config_file.go @@ -166,6 +166,7 @@ func (fc *fileCache) setActiveReleases(releases []*model.ConfigFileRelease) erro ConfigFileReleaseKey: item.ConfigFileReleaseKey, Valid: false, Version: item.Version, + Active: true, }, } } diff --git a/cache/service/instance_query.go b/cache/service/instance_query.go index fb70a3fe3..da5467ff9 100644 --- a/cache/service/instance_query.go +++ b/cache/service/instance_query.go @@ -212,14 +212,27 @@ func (ic *instanceCache) QueryInstances(filter, metaFilter map[string]string, return true, nil }) - sort.Slice(tempInstances, func(i, j int) bool { - return tempInstances[i].ModifyTime.After(tempInstances[j].ModifyTime) - }) + sortInstances(tempInstances) total, ret := ic.doPage(tempInstances, offset, limit) return total, ret, nil } +func sortInstances(tempInstances []*model.Instance) { + sort.Slice(tempInstances, func(i, j int) bool { + aTime := tempInstances[i].ModifyTime + bTime := tempInstances[j].ModifyTime + if aTime.After(bTime) { + return true + } + if aTime.Before(bTime) { + return false + } + // 按照实例 ID 进行排序,确保排序结果的稳定性 + return strings.Compare(tempInstances[i].ID(), tempInstances[j].ID()) == 1 + }) +} + func (ic *instanceCache) doPage(ins []*model.Instance, offset, limit uint32) (uint32, []*model.Instance) { total := uint32(len(ins)) if offset > total { diff --git a/cache/service/service_contract.go b/cache/service/service_contract.go index 7739524a2..829959b0c 100644 --- a/cache/service/service_contract.go +++ b/cache/service/service_contract.go @@ -19,102 +19,98 @@ package service import ( "context" - "sync/atomic" "time" "go.uber.org/zap" "golang.org/x/sync/singleflight" - types "github.com/polarismesh/polaris/cache/api" + cachetypes "github.com/polarismesh/polaris/cache/api" "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/store" ) -const ( - maxAliveDuration = time.Minute - faultAliveDuration = 5 * time.Second -) - -func NewServiceContractCache(storage store.Store, cacheMgr types.CacheManager) types.ServiceContractCache { - return &serviceContractCache{ - lastRunTime: time.Now(), - lastStoreErrTime: 0, - BaseCache: types.NewBaseCache(storage, cacheMgr), +func NewServiceContractCache(storage store.Store, cacheMgr cachetypes.CacheManager) cachetypes.ServiceContractCache { + return &ServiceContractCache{ + BaseCache: cachetypes.NewBaseCache(storage, cacheMgr), } } -type serviceContractCache struct { - *types.BaseCache +type ServiceContractCache struct { + *cachetypes.BaseCache + // data namespace/service/type/protocol/version -> *model.EnrichServiceContract + data *utils.SyncMap[string, *model.EnrichServiceContract] + singleGroup singleflight.Group +} - lastRunTime time.Time - lastStoreErrTime int64 - lastMtimeLogged int64 +// Initialize +func (sc *ServiceContractCache) Initialize(c map[string]interface{}) error { + sc.data = utils.NewSyncMap[string, *model.EnrichServiceContract]() + return nil +} - // data namespace/service/name/protocol/version -> *model.EnrichServiceContract - data *utils.SyncMap[string, *types.ExpireEntry[*model.EnrichServiceContract]] - singleGroup *singleflight.Group +// Update +func (sc *ServiceContractCache) Update() error { + err, _ := sc.singleUpdate() + return err } -// Initialize . -func (sc *serviceContractCache) Initialize(c map[string]interface{}) error { - sc.lastRunTime = time.Now() - sc.singleGroup = &singleflight.Group{} - sc.data = utils.NewSyncMap[string, *types.ExpireEntry[*model.EnrichServiceContract]]() - return nil +func (sc *ServiceContractCache) singleUpdate() (error, bool) { + // 多个线程竞争,只有一个线程进行更新 + _, err, shared := sc.singleGroup.Do(sc.Name(), func() (interface{}, error) { + return nil, sc.DoCacheUpdate(sc.Name(), sc.realUpdate) + }) + return err, shared } -// Update . -func (sc *serviceContractCache) Update() error { - if time.Since(sc.lastRunTime) < time.Minute { - return nil +func (sc *ServiceContractCache) realUpdate() (map[string]time.Time, int64, error) { + start := time.Now() + values, err := sc.Store().GetMoreServiceContracts(sc.IsFirstUpdate(), sc.LastFetchTime()) + if err != nil { + log.Error("[Cache][ServiceContract] update service_contract", zap.Error(err)) + return nil, 0, err } - sc.lastRunTime = time.Now() - lastStoreErrTime := atomic.LoadInt64(&sc.lastStoreErrTime) - // 如果存储层在近 1min 中内发生错误,则不会清理 expire 的数据 - showSkip := time.Now().Unix()-lastStoreErrTime < 60 - log.Info("[ServiceContract] cache expire entry clean start") - waitDel := make([]string, 0, 4) - sc.data.ReadRange(func(key string, val *types.ExpireEntry[*model.EnrichServiceContract]) { - if showSkip { - return - } - if val.IsExpire() { - waitDel = append(waitDel, key) + lastMtimes, update, del := sc.setContracts(values) + costTime := time.Since(start) + log.Info("[Cache][ServiceContract] get more service_contract", zap.Int("total", len(values)), + zap.Int("upsert", update), zap.Int("delete", del), zap.Time("last", sc.LastMtime(sc.Name())), + zap.Duration("used", costTime)) + return lastMtimes, int64(len(values)), err +} + +func (sc *ServiceContractCache) setContracts(values []*model.EnrichServiceContract) (map[string]time.Time, int, int) { + var ( + upsert, del int + lastMtime time.Time + ) + for i := range values { + item := values[i] + if !item.Valid { + del++ + sc.data.Delete(item.GetCacheKey()) + continue } - }) - for i := range waitDel { - sc.data.Delete(waitDel[i]) - log.Info("[ServiceContract] cache expire entry", zap.String("key", waitDel[i])) + upsert++ + sc.data.Store(item.GetCacheKey(), item) } - return nil + return map[string]time.Time{ + sc.Name(): lastMtime, + }, upsert, del } -// Clear . -func (sc *serviceContractCache) Clear() error { - sc.data = utils.NewSyncMap[string, *types.ExpireEntry[*model.EnrichServiceContract]]() +// Clear +func (sc *ServiceContractCache) Clear() error { + sc.data = utils.NewSyncMap[string, *model.EnrichServiceContract]() return nil } -// Name . -func (sc *serviceContractCache) Name() string { - return types.ServiceContractName +// Name +func (sc *ServiceContractCache) Name() string { + return cachetypes.ServiceContractName } -func (sc *serviceContractCache) Get(ctx context.Context, req *model.ServiceContract) *model.EnrichServiceContract { - ret, _ := sc.data.ComputeIfAbsent(req.GetCacheKey(), func(k string) *types.ExpireEntry[*model.EnrichServiceContract] { - id, err := utils.CalculateContractID(req.Namespace, req.Service, req.Type, req.Protocol, req.Version) - if err != nil { - return types.EmptyExpireEntry(&model.EnrichServiceContract{}, faultAliveDuration) - } - val, err := sc.Store().GetServiceContract(id) - if err != nil { - atomic.StoreInt64(&sc.lastStoreErrTime, time.Now().Unix()) - return types.EmptyExpireEntry(&model.EnrichServiceContract{}, faultAliveDuration) - } - return types.NewExpireEntry(val, maxAliveDuration) - }) - - return ret.Get() +func (sc *ServiceContractCache) Get(ctx context.Context, req *model.ServiceContract) *model.EnrichServiceContract { + ret, _ := sc.data.Load(req.GetCacheKey()) + return ret } diff --git a/common/api/v1/naming_response.go b/common/api/v1/naming_response.go index 94d76e4cd..c2f516b68 100644 --- a/common/api/v1/naming_response.go +++ b/common/api/v1/naming_response.go @@ -59,6 +59,11 @@ func CalcCodeV2(rm ResponseMessageV2) int { return int(rm.GetCode() / 1000) } +// IsSuccess . +func IsSuccess(rsp ResponseMessage) bool { + return rsp.GetCode().GetValue() == uint32(apimodel.Code_ExecuteSuccess) +} + /** * @brief BatchWriteResponse添加Response */ @@ -75,6 +80,19 @@ func Collect(batchWriteResponse *apiservice.BatchWriteResponse, response *apiser batchWriteResponse.Responses = append(batchWriteResponse.Responses, response) } +/** + * @brief BatchWriteResponse添加Response + */ +func QueryCollect(resp *apiservice.BatchQueryResponse, response *apiservice.Response) { + // 非200的code,都归为异常 + if CalcCode(response) != 200 { + if response.GetCode().GetValue() >= resp.GetCode().GetValue() { + resp.Code.Value = response.GetCode().GetValue() + resp.Info.Value = code2info[resp.GetCode().GetValue()] + } + } +} + // AddNamespace BatchQueryResponse添加命名空间 func AddNamespace(b *apiservice.BatchQueryResponse, namespace *apimodel.Namespace) { b.Namespaces = append(b.Namespaces, namespace) diff --git a/common/model/auth.go b/common/model/auth.go index 0ad70f25f..3e5f4e014 100644 --- a/common/model/auth.go +++ b/common/model/auth.go @@ -25,6 +25,9 @@ import ( apimodel "github.com/polarismesh/specification/source/go/api/v1/model" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" + "google.golang.org/protobuf/types/known/wrapperspb" + + commontime "github.com/polarismesh/polaris/common/time" ) var ( @@ -221,6 +224,22 @@ type User struct { ModifyTime time.Time } +func (u *User) ToSpec() *apisecurity.User { + return &apisecurity.User{ + Id: &wrapperspb.StringValue{}, + Name: &wrapperspb.StringValue{}, + Password: &wrapperspb.StringValue{}, + Owner: &wrapperspb.StringValue{}, + Source: &wrapperspb.StringValue{}, + AuthToken: &wrapperspb.StringValue{}, + TokenEnable: &wrapperspb.BoolValue{}, + Comment: &wrapperspb.StringValue{}, + Ctime: &wrapperspb.StringValue{}, + Mtime: &wrapperspb.StringValue{}, + UserType: &wrapperspb.StringValue{}, + } +} + // UserGroupDetail 用户组详细(带用户列表) type UserGroupDetail struct { *UserGroup @@ -235,10 +254,41 @@ func (ugd *UserGroupDetail) ToUserIdSlice() []string { for uid := range ugd.UserIds { uids = append(uids, uid) } - return uids } +func (ugd *UserGroupDetail) ListSpecUser() []*apisecurity.User { + users := make([]*apisecurity.User, 0, len(ugd.UserIds)) + for i := range ugd.UserIds { + users = append(users, &apisecurity.User{ + Id: wrapperspb.String(i), + }) + } + return users +} + +// ToSpec 将用户ID Map 专为 slice +func (ugd *UserGroupDetail) ToSpec() *apisecurity.UserGroup { + if ugd == nil { + return nil + } + return &apisecurity.UserGroup{ + Id: wrapperspb.String(ugd.ID), + Name: wrapperspb.String(ugd.Name), + Owner: wrapperspb.String(ugd.Owner), + AuthToken: wrapperspb.String(ugd.Token), + TokenEnable: wrapperspb.Bool(ugd.TokenEnable), + Comment: wrapperspb.String(ugd.Comment), + Ctime: wrapperspb.String(commontime.Time2String(ugd.CreateTime)), + Mtime: wrapperspb.String(commontime.Time2String(ugd.ModifyTime)), + Relation: &apisecurity.UserGroupRelation{ + GroupId: wrapperspb.String(ugd.ID), + Users: ugd.ListSpecUser(), + }, + UserCount: wrapperspb.UInt32(uint32(len(ugd.UserIds))), + } +} + // UserGroup 用户组 type UserGroup struct { ID string diff --git a/common/model/naming.go b/common/model/naming.go index bcda9c854..b949a6c4a 100644 --- a/common/model/naming.go +++ b/common/model/naming.go @@ -29,6 +29,7 @@ import ( 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" + "google.golang.org/protobuf/types/known/wrapperspb" commontime "github.com/polarismesh/polaris/common/time" "github.com/polarismesh/polaris/common/utils" @@ -98,6 +99,36 @@ type Service struct { OldExportTo map[string]struct{} } +func (s *Service) ToSpec() *apiservice.Service { + return &apiservice.Service{ + Name: wrapperspb.String(s.Name), + Namespace: wrapperspb.String(s.Namespace), + Metadata: s.CopyMeta(), + Ports: wrapperspb.String(s.Ports), + Business: wrapperspb.String(s.Business), + Department: wrapperspb.String(s.Department), + CmdbMod1: wrapperspb.String(s.CmdbMod1), + CmdbMod2: wrapperspb.String(s.CmdbMod2), + CmdbMod3: wrapperspb.String(s.CmdbMod3), + Comment: wrapperspb.String(s.Comment), + Owners: wrapperspb.String(s.Owner), + Token: wrapperspb.String(s.Token), + Ctime: wrapperspb.String(commontime.Time2String(s.CreateTime)), + Mtime: wrapperspb.String(commontime.Time2String(s.ModifyTime)), + Revision: wrapperspb.String(s.Revision), + Id: wrapperspb.String(s.ID), + ExportTo: s.ListExportTo(), + } +} + +func (s *Service) CopyMeta() map[string]string { + ret := make(map[string]string) + for k, v := range s.Meta { + ret[k] = v + } + return ret +} + func (s *Service) ProtectThreshold() float32 { if len(s.Meta) == 0 { return 0 diff --git a/plugin.go b/plugin.go index 419bf06a2..9494a723d 100644 --- a/plugin.go +++ b/plugin.go @@ -25,7 +25,7 @@ import ( _ "github.com/polarismesh/polaris/apiserver/l5pbserver" _ "github.com/polarismesh/polaris/apiserver/nacosserver" _ "github.com/polarismesh/polaris/apiserver/xdsserverv3" - _ "github.com/polarismesh/polaris/auth/authcheck" + _ "github.com/polarismesh/polaris/auth/policy" _ "github.com/polarismesh/polaris/auth/user" _ "github.com/polarismesh/polaris/cache" _ "github.com/polarismesh/polaris/cache/auth" diff --git a/release/tool/check.sh b/release/tool/check.sh new file mode 100644 index 000000000..feb7fe126 --- /dev/null +++ b/release/tool/check.sh @@ -0,0 +1,39 @@ +# 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. +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +pids=$(ps -ef | grep -w "$cmdline" | grep -v "grep" | awk '{print $2}') +array=($pids) +if [ "${#array[@]}" == "0" ]; then + start +fi + +#------------------------------------------------------ + +cd $curpath diff --git a/release/tool/include b/release/tool/include index 29832900b..efbfcacf0 100644 --- a/release/tool/include +++ b/release/tool/include @@ -15,6 +15,7 @@ server_name="polaris-server" cmdline="./polaris-server start" +mac_os=$(uname -a | grep -E "Darwin|darwin" | wc -l) function log_date() { echo $(date "+%Y-%m-%dT%H:%M:%S") @@ -48,6 +49,7 @@ function start() { fi set +e ulimit -n 409600 + ulimit -c unlimited set -e chmod +x $server_name nohup $cmdline >>/dev/null 2>&1 & @@ -62,3 +64,42 @@ function stop() { kill -15 $pid done } + +function add_cron() { + if [[ "${mac_os}" != "0" ]]; then + log_info "mac os not support crontab, skip add cron" + return + fi + set +e + item="bash $workdir/tool/check.sh >>$workdir/log/check.log 2>&1" + exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) + if [ "$exist" == "0" ]; then + log_info "add cron for $server_name" + + cron=$(mktemp) + crontab -l >$cron + echo "*/1 * * * * $item" >>$cron + crontab $cron + rm -f $cron + fi + set -e +} + +function del_cron() { + if [[ "${mac_os}" != "0" ]]; then + log_info "mac os not support crontab, skip del cron" + return + fi + set +e + item="bash $workdir/tool/check.sh >>$workdir/log/check.log 2>&1" + exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) + if [ "$exist" != "0" ]; then + log_info "del cron for $server_name" + + cron=$(mktemp) + crontab -l | grep -v "$item" >$cron + crontab $cron + rm -f $cron + fi + set -e +} diff --git a/release/tool/start.sh b/release/tool/start.sh index 224d8ed8d..d71b84130 100644 --- a/release/tool/start.sh +++ b/release/tool/start.sh @@ -14,6 +14,14 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. +enable_cron="false" + +for arg in "$@" +do + if [[ "${arg}" == "--enable-cron" ]]; then + enable_cron="true" + fi +done curpath=$(pwd) @@ -35,6 +43,10 @@ if [ "${#array[@]}" == "0" ]; then start fi +if [[ "${enable_cron}" == "true" ]]; then + add_cron +fi + #------------------------------------------------------ cd $curpath diff --git a/release/tool/stop.sh b/release/tool/stop.sh index 73fb5c934..2765d0b39 100644 --- a/release/tool/stop.sh +++ b/release/tool/stop.sh @@ -28,6 +28,7 @@ workdir=$(pwd) #------------------------------------------------------ source tool/include +del_cron stop #------------------------------------------------------ diff --git a/service/client_test.go b/service/client_test.go index 06b3fa84a..286c030c2 100644 --- a/service/client_test.go +++ b/service/client_test.go @@ -23,6 +23,8 @@ import ( "sync" "testing" + "github.com/golang/protobuf/proto" + apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "github.com/polarismesh/specification/source/go/api/v1/service_manage" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" @@ -445,3 +447,45 @@ func TestServer_GetReportClients(t *testing.T) { assert.Equal(t, qresp.GetSize().GetValue(), uint32(1)) }) } + +// TestServer_ReportServiceContract 测试上报服务合约 +func TestServer_ReportServiceContract(t *testing.T) { + discoverSuit := &DiscoverTestSuit{} + if err := discoverSuit.Initialize(); err != nil { + t.Fatal(err) + } + + req := &apiservice.ServiceContract{ + Namespace: "default", + Service: "test", + Protocol: "http", + Name: "test", + Version: "1.0.0", + Content: "test", + Interfaces: []*apiservice.InterfaceDescriptor{ + { + Name: "test", + Path: "/test", + Method: "GET", + }, + }, + } + + t.Run("multi_report_same_contract", func(t *testing.T) { + for i := 0; i < 5; i++ { + copyReq := proto.Clone(req).(*apiservice.ServiceContract) + copyReq.Content = fmt.Sprintf("test%d", i) + rsp := discoverSuit.DiscoverServer().ReportServiceContract(discoverSuit.DefaultCtx, copyReq) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), rsp.GetCode().GetValue(), rsp.GetInfo().GetValue()) + } + }) + + t.Run("get_service_contract", func(t *testing.T) { + err := discoverSuit.CacheMgr().TestUpdate() + assert.NoError(t, err) + rsp := discoverSuit.DiscoverServer().GetServiceContractWithCache(discoverSuit.DefaultCtx, req) + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), rsp.GetCode().GetValue(), rsp.GetInfo().GetValue()) + assert.Equal(t, 1, len(rsp.GetServiceContract().GetInterfaces())) + }) + +} diff --git a/service/instance.go b/service/instance.go index 693dbf468..22415d238 100644 --- a/service/instance.go +++ b/service/instance.go @@ -758,6 +758,8 @@ func updateHealthCheck(req *apiservice.Instance, instance *model.Instance) bool func (s *Server) GetInstances(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { showLastHeartbeat := query["show_last_heartbeat"] == "true" delete(query, "show_last_heartbeat") + showServiceRevision := query["show_service_revision"] == "true" + delete(query, "show_service_revision") // 对数据先进行提前处理一下 filters, metaFilter, batchErr := preGetInstances(query) if batchErr != nil { @@ -779,6 +781,7 @@ func (s *Server) GetInstances(ctx context.Context, query map[string]string) *api out.Amount = utils.NewUInt32Value(total) out.Size = utils.NewUInt32Value(uint32(len(instances))) + svcInfos := make(map[string]*model.Service, 4) apiInstances := make([]*apiservice.Instance, 0, len(instances)) for _, instance := range instances { svc, _ := s.loadServiceByID(instance.ServiceID) @@ -795,7 +798,16 @@ func (s *Server) GetInstances(ctx context.Context, query map[string]string) *api if showLastHeartbeat { s.fillLastHeartbeatTime(apiInstances) } - + if showServiceRevision { + // 额外显示每个服务的 revision 版本列表信息数据 + out.Services = make([]*apiservice.Service, 0, len(svcInfos)) + for i := range svcInfos { + svc := svcInfos[i].ToSpec() + revision := s.caches.Service().GetRevisionWorker().GetServiceInstanceRevision(svc.GetId().GetValue()) + svc.Revision = wrapperspb.String(revision) + out.Services = append(out.Services, svc) + } + } out.Instances = apiInstances return out } diff --git a/service/interceptor/paramcheck/check.go b/service/interceptor/paramcheck/check.go index 908767286..553bc8632 100644 --- a/service/interceptor/paramcheck/check.go +++ b/service/interceptor/paramcheck/check.go @@ -3,11 +3,12 @@ package paramcheck import ( "context" - "github.com/polarismesh/polaris/common/api/l5" - "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/specification/source/go/api/v1/fault_tolerance" "github.com/polarismesh/specification/source/go/api/v1/service_manage" "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" + + "github.com/polarismesh/polaris/common/api/l5" + "github.com/polarismesh/polaris/common/model" ) // AppendServiceContractInterfaces implements service.DiscoverServer. diff --git a/store/boltdb/service_contract.go b/store/boltdb/service_contract.go index e683ed1d8..5a4de9642 100644 --- a/store/boltdb/service_contract.go +++ b/store/boltdb/service_contract.go @@ -412,6 +412,35 @@ func (s *serviceContractStore) ListVersions(ctx context.Context, service, namesp return nil, nil } +// GetMoreServiceContracts . +func (s *serviceContractStore) GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) { + if firstUpdate { + mtime = time.Unix(0, 0) + } + + fields := []string{ContractFieldValid, ContractFieldModifyTime} + values, err := s.handler.LoadValuesByFilter(tblServiceContract, fields, &ServiceContract{}, + func(m map[string]interface{}) bool { + if firstUpdate { + valid, _ := m[ContractFieldValid].(bool) + if !valid { + return false + } + } + saveMtime, _ := m[ContractFieldModifyTime].(time.Time) + return !saveMtime.Before(mtime) + }) + if err != nil { + return nil, store.Error(err) + } + + ret := make([]*model.EnrichServiceContract, 0, len(values)) + for _, v := range values { + ret = append(ret, s.toModel(v.(*ServiceContract))) + } + return ret, nil +} + func (s *serviceContractStore) toModel(data *ServiceContract) *model.EnrichServiceContract { interfaces := make([]*model.InterfaceDescriptor, 0, 4) _ = json.Unmarshal([]byte(data.Interfaces), &interfaces) diff --git a/store/discover_api.go b/store/discover_api.go index 56555dbe4..5e16ed666 100644 --- a/store/discover_api.go +++ b/store/discover_api.go @@ -294,6 +294,8 @@ type ServiceContractStore interface { GetInterfaceDescriptors(ctx context.Context, filter map[string]string, offset, limit uint32) (uint32, []*model.InterfaceDescriptor, error) // ListVersions . ListVersions(ctx context.Context, service, namespace string) ([]*model.ServiceContract, error) + // GetMoreServiceContracts 查询服务契约公共属性列表 + GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) } // LaneStore 泳道资源存储操作 diff --git a/store/mock/api_mock.go b/store/mock/api_mock.go index d7f448876..a7fcda1c7 100644 --- a/store/mock/api_mock.go +++ b/store/mock/api_mock.go @@ -1624,6 +1624,21 @@ func (mr *MockStoreMockRecorder) GetMoreReleaseFile(firstUpdate, modifyTime inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreReleaseFile", reflect.TypeOf((*MockStore)(nil).GetMoreReleaseFile), firstUpdate, modifyTime) } +// GetMoreServiceContracts mocks base method. +func (m *MockStore) GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreServiceContracts", firstUpdate, mtime) + ret0, _ := ret[0].([]*model.EnrichServiceContract) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreServiceContracts indicates an expected call of GetMoreServiceContracts. +func (mr *MockStoreMockRecorder) GetMoreServiceContracts(firstUpdate, mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreServiceContracts", reflect.TypeOf((*MockStore)(nil).GetMoreServiceContracts), firstUpdate, mtime) +} + // GetMoreServices mocks base method. func (m *MockStore) GetMoreServices(mtime time.Time, firstUpdate, disableBusiness, needMeta bool) (map[string]*model.Service, error) { m.ctrl.T.Helper() diff --git a/store/mysql/service_contract.go b/store/mysql/service_contract.go index efd903f7a..617714bf7 100644 --- a/store/mysql/service_contract.go +++ b/store/mysql/service_contract.go @@ -455,7 +455,7 @@ func (s *serviceContractStore) ListVersions(ctx context.Context, service, namesp querySql := ` SELECT id, type, namespace, service, protocol - , version, revision, flag + , version, revision, flag, content , UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) FROM service_contract WHERE flag = 0 AND namespace = ? AND service = ? @@ -477,3 +477,101 @@ func (s *serviceContractStore) ListVersions(ctx context.Context, service, namesp } return list, nil } + +// GetMoreServiceContracts . +func (s *serviceContractStore) GetMoreServiceContracts(firstUpdate bool, mtime time.Time) ([]*model.EnrichServiceContract, error) { + querySql := "SELECT id, type, 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) + querySql += " AND flag = 0 " + } + + tx, err := s.slave.Begin() + if err != nil { + log.Error("[Store][Contract] list contract for cache when begin tx", zap.Error(err)) + return nil, store.Error(err) + } + defer func() { + _ = tx.Commit() + }() + + rows, err := tx.Query(querySql, mtime) + if err != nil { + log.Error("[Store][Contract] list contract for cache when query", zap.Error(err)) + return nil, store.Error(err) + } + defer func() { + _ = rows.Close() + }() + + list := make([]*model.EnrichServiceContract, 0) + for rows.Next() { + var flag, ctime, mtime int64 + contract := &model.ServiceContract{} + if scanErr := rows.Scan(&contract.ID, &contract.Type, &contract.Namespace, &contract.Service, + &contract.Protocol, &contract.Version, &contract.Revision, &flag, + &contract.Content, &ctime, &mtime); scanErr != nil { + log.Error("[Store][Contract] fetch contract rows scan err: %s", zap.Error(err)) + return nil, store.Error(err) + } + + contract.Valid = flag == 0 + contract.CreateTime = time.Unix(ctime, 0) + contract.ModifyTime = time.Unix(mtime, 0) + + list = append(list, &model.EnrichServiceContract{ + ServiceContract: contract, + }) + } + + contractDetailMap := map[string][]*model.InterfaceDescriptor{} + if len(list) > 0 { + queryDetailSql := "SELECT sd.id, sd.contract_id, sd.type, sd.method, sd.path, sd.content, sd.revision, " + + " UNIX_TIMESTAMP(sd.ctime), UNIX_TIMESTAMP(sd.mtime), IFNULL(sd.source, 1) " + + " FROM service_contract_detail sd LEFT JOIN service_contract sc ON sd.contract_id = sc.id " + + " WHERE sc.mtime >= ?" + detailRows, err := tx.Query(queryDetailSql, mtime) + if err != nil { + log.Error("[Store][Contract] list contract detail", zap.String("query sql", queryDetailSql), zap.Error(err)) + return nil, store.Error(err) + } + defer func() { + _ = detailRows.Close() + }() + for detailRows.Next() { + var flag, ctime, mtime, source int64 + detailItem := &model.InterfaceDescriptor{} + if scanErr := detailRows.Scan( + &detailItem.ID, &detailItem.ContractID, &detailItem.Type, &detailItem.Method, + &detailItem.Path, &detailItem.Content, &detailItem.Revision, + &ctime, &mtime, &source, + ); scanErr != nil { + log.Error("[Store][Contract] fetch contract detail rows scan", zap.Error(scanErr)) + return nil, store.Error(scanErr) + } + + detailItem.Valid = flag == 0 + detailItem.CreateTime = time.Unix(ctime, 0) + detailItem.ModifyTime = time.Unix(mtime, 0) + switch source { + case 2: + detailItem.Source = service_manage.InterfaceDescriptor_Client + default: + detailItem.Source = service_manage.InterfaceDescriptor_Manual + } + + if _, ok := contractDetailMap[detailItem.ContractID]; !ok { + contractDetailMap[detailItem.ContractID] = make([]*model.InterfaceDescriptor, 0, 4) + } + contractDetailMap[detailItem.ContractID] = append(contractDetailMap[detailItem.ContractID], detailItem) + } + + for _, item := range list { + methods := contractDetailMap[item.ID] + item.Interfaces = methods + item.Format() + } + } + return list, nil +} diff --git a/test/suit/plugin.go b/test/suit/plugin.go new file mode 100644 index 000000000..ba758a8e5 --- /dev/null +++ b/test/suit/plugin.go @@ -0,0 +1,37 @@ +/** + * 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 testsuit + +import ( + _ "github.com/polarismesh/polaris/auth/policy" + _ "github.com/polarismesh/polaris/auth/user" + _ "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" + _ "github.com/polarismesh/polaris/plugin/healthchecker/leader" + _ "github.com/polarismesh/polaris/plugin/healthchecker/memory" + _ "github.com/polarismesh/polaris/plugin/healthchecker/redis" + _ "github.com/polarismesh/polaris/plugin/history/logger" + _ "github.com/polarismesh/polaris/plugin/password" + _ "github.com/polarismesh/polaris/plugin/ratelimit/token" + _ "github.com/polarismesh/polaris/plugin/statis/logger" + _ "github.com/polarismesh/polaris/plugin/statis/prometheus" + _ "github.com/polarismesh/polaris/service/interceptor" + _ "github.com/polarismesh/polaris/store/boltdb" +) diff --git a/test/suit/test_suit.go b/test/suit/test_suit.go index e1c54b0b9..00b8d3ff1 100644 --- a/test/suit/test_suit.go +++ b/test/suit/test_suit.go @@ -44,27 +44,13 @@ 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" - _ "github.com/polarismesh/polaris/plugin/crypto/aes" - _ "github.com/polarismesh/polaris/plugin/discoverevent/local" - _ "github.com/polarismesh/polaris/plugin/healthchecker/leader" - _ "github.com/polarismesh/polaris/plugin/healthchecker/memory" - _ "github.com/polarismesh/polaris/plugin/healthchecker/redis" - _ "github.com/polarismesh/polaris/plugin/history/logger" - _ "github.com/polarismesh/polaris/plugin/password" - _ "github.com/polarismesh/polaris/plugin/ratelimit/token" - _ "github.com/polarismesh/polaris/plugin/statis/logger" - _ "github.com/polarismesh/polaris/plugin/statis/prometheus" "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" sqldb "github.com/polarismesh/polaris/store/mysql" testdata "github.com/polarismesh/polaris/test/data" ) From be1955b4b45ac0b99eca6b8969af644c4f2533f3 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Thu, 23 May 2024 17:50:03 +0800 Subject: [PATCH 09/23] feat:support nacos-address server endpoints --- cache/api/types.go | 4 ++++ cache/service/service_query.go | 36 +++++++++++++++++++++-------- service/default.go | 2 +- service/server.go | 5 +++- service/service.go | 42 ++++++++++++++++++++-------------- store/mysql/config_file.go | 1 + 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/cache/api/types.go b/cache/api/types.go index 301344adb..387e798bd 100644 --- a/cache/api/types.go +++ b/cache/api/types.go @@ -196,6 +196,10 @@ type ( Name string // EmptyCondition 是否是空条件,即只需要从所有服务或者某个命名空间下面的服务,进行不需要匹配的遍历,返回前面的服务即可 EmptyCondition bool + // OnlyExistHealthInstance 只展示存在健康实例的服务 + OnlyExistHealthInstance bool + // OnlyExistInstance 只展示存在实例的服务 + OnlyExistInstance bool } // ServiceCache 服务数据缓存接口 diff --git a/cache/service/service_query.go b/cache/service/service_query.go index da32d9012..5cd8f6ea5 100644 --- a/cache/service/service_query.go +++ b/cache/service/service_query.go @@ -49,14 +49,32 @@ func (sc *serviceCache) GetServicesByFilter(serviceFilters *types.ServiceArgs, var amount uint32 var err error - var services []*model.Service + var matchServices []*model.Service // 如果具有名字条件,并且不是模糊查询,直接获取对应命名空间下面的服务,并检查是否匹配所有条件 if serviceFilters.Name != "" && !serviceFilters.WildName && !serviceFilters.WildNamespace { - amount, services, err = sc.getServicesFromCacheByName(serviceFilters, instanceFilters, offset, limit) + matchServices, err = sc.getServicesFromCacheByName(serviceFilters, instanceFilters, offset, limit) } else { - amount, services, err = sc.getServicesByIteratingCache(serviceFilters, instanceFilters, offset, limit) + matchServices, err = sc.getServicesByIteratingCache(serviceFilters, instanceFilters, offset, limit) } + + if serviceFilters.OnlyExistHealthInstance || serviceFilters.OnlyExistInstance { + tmpSvcs := make([]*model.Service, 0, len(matchServices)) + for i := range matchServices { + count := sc.instCache.GetInstancesCountByServiceID(matchServices[i].ID) + if serviceFilters.OnlyExistInstance && count.TotalInstanceCount == 0 { + continue + } + if serviceFilters.OnlyExistHealthInstance && count.HealthyInstanceCount == 0 { + continue + } + tmpSvcs = append(tmpSvcs, matchServices[i]) + } + matchServices = tmpSvcs + } + + amount, services := sortBeforeTrim(matchServices, offset, limit) + var enhancedServices []*model.EnhancedService if amount > 0 { enhancedServices = make([]*model.EnhancedService, 0, len(services)) @@ -151,7 +169,7 @@ func (sc *serviceCache) GetAllNamespaces() []string { // 通过具体的名字来进行查询服务 func (sc *serviceCache) getServicesFromCacheByName(svcArgs *types.ServiceArgs, instArgs *store.InstanceArgs, - offset, limit uint32) (uint32, []*model.Service, error) { + offset, limit uint32) ([]*model.Service, error) { var res []*model.Service if svcArgs.Namespace != "" { svc := sc.GetServiceByName(svcArgs.Name, svcArgs.Namespace) @@ -168,8 +186,7 @@ func (sc *serviceCache) getServicesFromCacheByName(svcArgs *types.ServiceArgs, i } } } - amount, services := sortBeforeTrim(res, offset, limit) - return amount, services, nil + return res, nil } func sortBeforeTrim(services []*model.Service, offset, limit uint32) (uint32, []*model.Service) { @@ -275,7 +292,7 @@ func (sc *serviceCache) matchInstance(svc *model.Service, instArgs *store.Instan // getServicesByIteratingCache 通过遍历缓存中的服务 func (sc *serviceCache) getServicesByIteratingCache( - svcArgs *types.ServiceArgs, instArgs *store.InstanceArgs, offset, limit uint32) (uint32, []*model.Service, error) { + svcArgs *types.ServiceArgs, instArgs *store.InstanceArgs, offset, limit uint32) ([]*model.Service, error) { var res []*model.Service var process = func(svc *model.Service) { // 如果是别名,直接略过 @@ -296,7 +313,7 @@ func (sc *serviceCache) getServicesByIteratingCache( // 从命名空间来找 spaces, ok := sc.names.Load(svcArgs.Namespace) if !ok { - return 0, nil, nil + return nil, nil } spaces.ReadRange(func(key string, value *model.Service) { process(value) @@ -308,6 +325,5 @@ func (sc *serviceCache) getServicesByIteratingCache( return true, nil }) } - amount, services := sortBeforeTrim(res, offset, limit) - return amount, services, nil + return res, nil } diff --git a/service/default.go b/service/default.go index 882d015f0..44493e657 100644 --- a/service/default.go +++ b/service/default.go @@ -69,7 +69,7 @@ func RegisterServerProxy(name string, factor ServerProxyFactory) error { // Config 核心逻辑层配置 type Config struct { - L5Open bool `yaml:"l5Open"` + L5Open *bool `yaml:"l5Open"` AutoCreate *bool `yaml:"autoCreate"` Batch map[string]interface{} `yaml:"batch"` Interceptors []string `yaml:"-"` diff --git a/service/server.go b/service/server.go index cba356dab..e472e30b5 100644 --- a/service/server.go +++ b/service/server.go @@ -64,7 +64,10 @@ type Server struct { } func (s *Server) isSupportL5() bool { - return s.config.L5Open + if s.config.L5Open != nil { + return *s.config.L5Open + } + return true } func (s *Server) allowAutoCreate() bool { diff --git a/service/service.go b/service/service.go index e955abbbf..4f9195c2d 100644 --- a/service/service.go +++ b/service/service.go @@ -50,23 +50,25 @@ var ( serviceMetaFilter = 3 // 过滤service Metadata的 instanceMetaFilter = 4 // 过滤instance Metadata的 ServiceFilterAttributes = map[string]int{ - "name": serviceFilter, - "namespace": serviceFilter, - "business": serviceFilter, - "department": serviceFilter, - "cmdb_mod1": serviceFilter, - "cmdb_mod2": serviceFilter, - "cmdb_mod3": serviceFilter, - "owner": serviceFilter, - "offset": serviceFilter, - "limit": serviceFilter, - "platform_id": serviceFilter, - "host": instanceFilter, - "port": instanceFilter, - "keys": serviceMetaFilter, - "values": serviceMetaFilter, - "instance_keys": instanceMetaFilter, - "instance_values": instanceMetaFilter, + "name": serviceFilter, + "namespace": serviceFilter, + "business": serviceFilter, + "department": serviceFilter, + "cmdb_mod1": serviceFilter, + "cmdb_mod2": serviceFilter, + "cmdb_mod3": serviceFilter, + "owner": serviceFilter, + "offset": serviceFilter, + "limit": serviceFilter, + "platform_id": serviceFilter, + // 只返回存在健康实例的服务列表 + "only_exist_health_instance": serviceFilter, + "host": instanceFilter, + "port": instanceFilter, + "keys": serviceMetaFilter, + "values": serviceMetaFilter, + "instance_keys": instanceMetaFilter, + "instance_values": instanceMetaFilter, } ) @@ -476,6 +478,12 @@ func parseServiceArgs(filter map[string]string, metaFilter map[string]string, business, utils.ParseOperator(ctx)) res.WildBusiness = true } + if val, ok := filter["only_exist_health_instance"]; ok { + res.OnlyExistHealthInstance = val == "true" + } + if val, ok := filter["only_exist_instance"]; ok { + res.OnlyExistInstance = val == "true" + } // 如果元数据条件是空的话,判断是否是空条件匹配 if len(metaFilter) == 0 { // 如果没有匹配条件,那么就是空条件匹配 diff --git a/store/mysql/config_file.go b/store/mysql/config_file.go index 69a1580f0..bc8c35d3d 100644 --- a/store/mysql/config_file.go +++ b/store/mysql/config_file.go @@ -35,6 +35,7 @@ var ( "group": "`group`", "file_name": "name", "namespace": "namespace", + "content": "content", }, "config_file_release": { "group": "`group`", From 7a10f32a1d23574331bc27cc63d6e79956079668 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Thu, 23 May 2024 23:37:24 +0800 Subject: [PATCH 10/23] feat:support nacos-address server endpoints --- admin/job/clean_deleted_client.go | 83 ---------------- admin/job/clean_deleted_instance.go | 82 ---------------- admin/job/clean_deleted_resource.go | 141 ++++++++++++++++++++++++++++ admin/job/job.go | 9 +- auth/user/inteceptor/auth/server.go | 2 +- common/model/auth.go | 20 ++-- config/config_file_release_test.go | 59 ++++++++++++ release/conf/polaris-server.yaml | 30 +++--- store/boltdb/config_file_release.go | 12 ++- 9 files changed, 237 insertions(+), 201 deletions(-) delete mode 100644 admin/job/clean_deleted_client.go delete mode 100644 admin/job/clean_deleted_instance.go create mode 100644 admin/job/clean_deleted_resource.go diff --git a/admin/job/clean_deleted_client.go b/admin/job/clean_deleted_client.go deleted file mode 100644 index 1d992ba42..000000000 --- a/admin/job/clean_deleted_client.go +++ /dev/null @@ -1,83 +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 job - -import ( - "time" - - "github.com/mitchellh/mapstructure" - - "github.com/polarismesh/polaris/store" -) - -type CleanDeletedClientsJobConfig struct { - ClientCleanTimeout time.Duration `mapstructure:"clientCleanTimeout"` -} - -type cleanDeletedClientsJob struct { - cfg *CleanDeletedClientsJobConfig - storage store.Store -} - -func (job *cleanDeletedClientsJob) init(raw map[string]interface{}) error { - cfg := &CleanDeletedClientsJobConfig{ - ClientCleanTimeout: 10 * time.Minute, - } - decodeConfig := &mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - Result: cfg, - } - decoder, err := mapstructure.NewDecoder(decodeConfig) - if err != nil { - log.Errorf("[Maintain][Job][CleanDeletedClients] new config decoder err: %v", err) - return err - } - if err := decoder.Decode(raw); err != nil { - log.Errorf("[Maintain][Job][CleanDeletedClients] parse config err: %v", err) - return err - } - if cfg.ClientCleanTimeout < 2*time.Minute { - cfg.ClientCleanTimeout = 2 * time.Minute - } - job.cfg = cfg - return nil -} - -func (job *cleanDeletedClientsJob) execute() { - batchSize := uint32(100) - for { - count, err := job.storage.BatchCleanDeletedClients(job.cfg.ClientCleanTimeout, batchSize) - if err != nil { - log.Errorf("[Maintain][Job][CleanDeletedClients] batch clean deleted client, err: %v", err) - break - } - - log.Infof("[Maintain][Job][CleanDeletedClients] clean deleted client count %d", count) - - if count < batchSize { - break - } - } -} - -func (job *cleanDeletedClientsJob) clear() { -} - -func (job *cleanDeletedClientsJob) interval() time.Duration { - return job.cfg.ClientCleanTimeout -} diff --git a/admin/job/clean_deleted_instance.go b/admin/job/clean_deleted_instance.go deleted file mode 100644 index 8e6dcf51b..000000000 --- a/admin/job/clean_deleted_instance.go +++ /dev/null @@ -1,82 +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 job - -import ( - "time" - - "github.com/mitchellh/mapstructure" - - "github.com/polarismesh/polaris/store" -) - -type CleanDeletedInstancesJobConfig struct { - InstanceCleanTimeout time.Duration `mapstructure:"instanceCleanTimeout"` -} - -type cleanDeletedInstancesJob struct { - cfg *CleanDeletedInstancesJobConfig - storage store.Store -} - -func (job *cleanDeletedInstancesJob) init(raw map[string]interface{}) error { - cfg := &CleanDeletedInstancesJobConfig{ - InstanceCleanTimeout: 10 * time.Minute, - } - decodeConfig := &mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - Result: cfg, - } - decoder, err := mapstructure.NewDecoder(decodeConfig) - if err != nil { - log.Errorf("[Maintain][Job][CleanDeletedInstances] new config decoder err: %v", err) - return err - } - if err = decoder.Decode(raw); err != nil { - log.Errorf("[Maintain][Job][CleanDeletedInstances] parse config err: %v", err) - return err - } - if cfg.InstanceCleanTimeout < 2*time.Minute { - cfg.InstanceCleanTimeout = 2 * time.Minute - } - job.cfg = cfg - return nil -} - -func (job *cleanDeletedInstancesJob) execute() { - batchSize := uint32(100) - for { - count, err := job.storage.BatchCleanDeletedInstances(job.cfg.InstanceCleanTimeout, batchSize) - if err != nil { - log.Errorf("[Maintain][Job][CleanDeletedInstances] batch clean deleted instance, err: %v", err) - break - } - - log.Infof("[Maintain][Job][CleanDeletedInstances] clean deleted instance count %d", count) - if count < batchSize { - break - } - } -} - -func (job *cleanDeletedInstancesJob) interval() time.Duration { - return job.cfg.InstanceCleanTimeout -} - -func (job *cleanDeletedInstancesJob) clear() { -} diff --git a/admin/job/clean_deleted_resource.go b/admin/job/clean_deleted_resource.go new file mode 100644 index 000000000..9a077f449 --- /dev/null +++ b/admin/job/clean_deleted_resource.go @@ -0,0 +1,141 @@ +/** + * 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 job + +import ( + "sync" + "time" + + "github.com/mitchellh/mapstructure" + "github.com/polarismesh/polaris/store" +) + +var cleanFuncMapping = map[string]func(timeout time.Duration, job *cleanDeletedResourceJob){ + "instance": cleanDeletedInstances, + "service": nil, + "clients": cleanDeletedClients, + "circuitbreaker_rule": nil, + "ratelimit_rule": nil, + "router_rule": nil, + "faultdetect_rule": nil, + "config_file_release": nil, +} + +type CleanDeletedResource struct { + // Resource 记录需要清理的资源类型 + Resource string `mapstructure:"resource"` + // Timeout 记录资源的额外超时时间,用户可自定义 + Timeout *time.Duration `mapstructure:"timeout"` + // Enable 记录是否开启清理 + Enable bool `mapstructure:"enable"` +} + +type CleandeletedResourceConf struct { + // ResourceTimeout 记录资源的额外超时时间,用户可自定义 + Resources []CleanDeletedResource `json:"resourceTimeout"` + // Timeout 记录清理资源的超时时间,默认20分钟 + Timeout time.Duration `mapstructure:"timeout"` +} + +type cleanDeletedResourceJob struct { + cfg *CleandeletedResourceConf + storage store.Store +} + +func (job *cleanDeletedResourceJob) init(raw map[string]interface{}) error { + cfg := &CleandeletedResourceConf{ + Timeout: 20 * time.Minute, + } + decodeConfig := &mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + Result: cfg, + } + decoder, err := mapstructure.NewDecoder(decodeConfig) + if err != nil { + log.Errorf("[Maintain][Job][CleanDeletedClients] new config decoder err: %v", err) + return err + } + if err := decoder.Decode(raw); err != nil { + log.Errorf("[Maintain][Job][CleanDeletedClients] parse config err: %v", err) + return err + } + if cfg.Timeout < 2*time.Minute { + cfg.Timeout = 2 * time.Minute + } + job.cfg = cfg + return nil +} + +func (job *cleanDeletedResourceJob) execute() { + wait := &sync.WaitGroup{} + for _, resource := range job.cfg.Resources { + if !resource.Enable { + continue + } + timeout := job.cfg.Timeout + if resource.Timeout != nil { + timeout = *resource.Timeout + } + if cleanFunc, ok := cleanFuncMapping[resource.Resource]; ok { + wait.Add(1) + go func(timeout time.Duration, job *cleanDeletedResourceJob) { + defer wait.Done() + cleanFunc(timeout, job) + }(timeout, job) + } + } + wait.Wait() +} + +func (job *cleanDeletedResourceJob) clear() { +} + +func (job *cleanDeletedResourceJob) interval() time.Duration { + return time.Minute +} + +func cleanDeletedClients(timeout time.Duration, job *cleanDeletedResourceJob) { + batchSize := uint32(100) + for { + count, err := job.storage.BatchCleanDeletedClients(timeout, batchSize) + if err != nil { + log.Errorf("[Maintain][Job][CleanDeletedClients] batch clean deleted client, err: %v", err) + break + } + log.Infof("[Maintain][Job][CleanDeletedClients] clean deleted client count %d", count) + if count < batchSize { + break + } + } +} + +func cleanDeletedInstances(timeout time.Duration, job *cleanDeletedResourceJob) { + batchSize := uint32(100) + for { + count, err := job.storage.BatchCleanDeletedInstances(timeout, batchSize) + if err != nil { + log.Errorf("[Maintain][Job][CleanDeletedInstances] batch clean deleted instance, err: %v", err) + break + } + + log.Infof("[Maintain][Job][CleanDeletedInstances] clean deleted instance count %d", count) + if count < batchSize { + break + } + } +} diff --git a/admin/job/job.go b/admin/job/job.go index 487194702..0816e4301 100644 --- a/admin/job/job.go +++ b/admin/job/job.go @@ -49,12 +49,10 @@ func NewMaintainJobs(namingServer service.DiscoverServer, cacheMgn *cache.CacheM namingServer: namingServer, storage: storage}, "DeleteEmptyService": &deleteEmptyServiceJob{ namingServer: namingServer, cacheMgn: cacheMgn, storage: storage}, - "CleanDeletedInstances": &cleanDeletedInstancesJob{ - storage: storage}, - "CleanDeletedClients": &cleanDeletedClientsJob{ - storage: storage}, "CleanConfigReleaseHistory": &cleanConfigFileHistoryJob{ storage: storage}, + "CleanDeletedResources": &cleanDeletedResourceJob{ + storage: storage}, }, startedJobs: map[string]maintainJob{}, storage: storage, @@ -73,7 +71,8 @@ func (mj *MaintainJobs) StartMaintianJobs(configs []JobConfig) error { jobName := parseJobName(cfg.Name) job, ok := mj.findAdminJob(jobName) if !ok { - return fmt.Errorf("[Maintain][Job] job (%s) not exist", jobName) + log.Warnf("[Maintain][Job] job (%s) not exist", jobName) + continue } if _, ok := mj.startedJobs[jobName]; ok { return fmt.Errorf("[Maintain][Job] job (%s) duplicated", jobName) diff --git a/auth/user/inteceptor/auth/server.go b/auth/user/inteceptor/auth/server.go index d84c48b8a..e85d0a2fb 100644 --- a/auth/user/inteceptor/auth/server.go +++ b/auth/user/inteceptor/auth/server.go @@ -155,7 +155,7 @@ func (svr *Server) GetUserToken(ctx context.Context, user *apisecurity.User) *ap return rsp } helper := svr.GetUserHelper() - targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + targetUser := helper.GetUser(ctx, user) if !checkUserViewPermission(ctx, targetUser) { return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) } diff --git a/common/model/auth.go b/common/model/auth.go index 3e5f4e014..eef58fb8e 100644 --- a/common/model/auth.go +++ b/common/model/auth.go @@ -226,17 +226,15 @@ type User struct { func (u *User) ToSpec() *apisecurity.User { return &apisecurity.User{ - Id: &wrapperspb.StringValue{}, - Name: &wrapperspb.StringValue{}, - Password: &wrapperspb.StringValue{}, - Owner: &wrapperspb.StringValue{}, - Source: &wrapperspb.StringValue{}, - AuthToken: &wrapperspb.StringValue{}, - TokenEnable: &wrapperspb.BoolValue{}, - Comment: &wrapperspb.StringValue{}, - Ctime: &wrapperspb.StringValue{}, - Mtime: &wrapperspb.StringValue{}, - UserType: &wrapperspb.StringValue{}, + Id: wrapperspb.String(u.ID), + Name: wrapperspb.String(u.Name), + Password: wrapperspb.String(u.Password), + Owner: wrapperspb.String(u.Owner), + Source: wrapperspb.String(u.Source), + AuthToken: wrapperspb.String(u.Token), + TokenEnable: wrapperspb.Bool(u.TokenEnable), + Comment: wrapperspb.String(u.Comment), + UserType: wrapperspb.String(fmt.Sprintf("%d", u.Type)), } } diff --git a/config/config_file_release_test.go b/config/config_file_release_test.go index 67e9ce356..7d92da17e 100644 --- a/config/config_file_release_test.go +++ b/config/config_file_release_test.go @@ -176,6 +176,65 @@ func Test_PublishConfigFile(t *testing.T) { assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), pubResp.GetCode().GetValue(), pubResp.GetInfo().GetValue()) }) + // 创建一个 v1 的配置发布 + // 删除 v1 配置发布 + // 再创建一个 v1 的配置发布 + // 客户端可以正常读取到数据 + t.Run("create_delete_recreate_same", func(t *testing.T) { + pubResp := testSuit.ConfigServer().UpsertAndReleaseConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFilePublishInfo{ + ReleaseName: utils.NewStringValue(mockReleaseName + "same-v1"), + Namespace: utils.NewStringValue(mockNamespace + "same-v1"), + Group: utils.NewStringValue(mockGroup + "same-v1"), + FileName: utils.NewStringValue(mockFileName + "same-v1"), + Content: utils.NewStringValue(mockContent + "same-v1"), + Comment: utils.NewStringValue("mock_comment"), + Format: utils.NewStringValue("yaml"), + ReleaseDescription: utils.NewStringValue("mock_releaseDescription"), + Tags: []*config_manage.ConfigFileTag{ + { + Key: utils.NewStringValue("mock_key"), + Value: utils.NewStringValue("mock_value"), + }, + }, + }) + + // 正常发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), pubResp.GetCode().GetValue(), pubResp.GetInfo().GetValue()) + + delResp := testSuit.ConfigServer().DeleteConfigFileReleases(testSuit.DefaultCtx, []*config_manage.ConfigFileRelease{ + { + Name: utils.NewStringValue(mockReleaseName + "same-v1"), + Namespace: utils.NewStringValue(mockNamespace + "same-v1"), + Group: utils.NewStringValue(mockGroup + "same-v1"), + FileName: utils.NewStringValue(mockFileName + "same-v1"), + }, + }) + // 删除成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), delResp.GetCode().GetValue(), delResp.GetInfo().GetValue()) + + // 再次重新发布 + pubResp = testSuit.ConfigServer().PublishConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFileRelease{ + Name: utils.NewStringValue(mockReleaseName + "same-v1"), + Namespace: utils.NewStringValue(mockNamespace + "same-v1"), + Group: utils.NewStringValue(mockGroup + "same-v1"), + FileName: utils.NewStringValue(mockFileName + "same-v1"), + }) + + // 再次重新发布成功 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), pubResp.GetCode().GetValue(), pubResp.GetInfo().GetValue()) + + // 客户端读取数据正常 + _ = testSuit.CacheMgr().TestUpdate() + + clientRsp := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, &config_manage.ClientConfigFileInfo{ + Namespace: utils.NewStringValue(mockNamespace + "same-v1"), + Group: utils.NewStringValue(mockGroup + "same-v1"), + FileName: utils.NewStringValue(mockFileName + "same-v1"), + }) + // 正常读取到数据 + assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), clientRsp.GetCode().GetValue(), clientRsp.GetInfo().GetValue()) + }) + t.Run("list_release_version", func(t *testing.T) { t.Run("invalid_namespace", func(t *testing.T) { queryRsp := testSuit.ConfigServer().GetConfigFileReleaseVersions(testSuit.DefaultCtx, map[string]string{ diff --git a/release/conf/polaris-server.yaml b/release/conf/polaris-server.yaml index 26a523f3b..5aac81bfa 100644 --- a/release/conf/polaris-server.yaml +++ b/release/conf/polaris-server.yaml @@ -448,22 +448,22 @@ maintain: # Storage configuration store: # # Standalone file storage plugin - # name: boltdbStore - # option: - # path: ./polaris.bolt - ## Database storage plugin - name: defaultStore + name: boltdbStore option: - master: - dbType: mysql - dbName: polaris_server - dbUser: ${MYSQL_USER} ##DB_USER## - dbPwd: ${MYSQL_PWD} ##DB_PWD## - dbAddr: ${MYSQL_HOST} ##DB_ADDR## - maxOpenConns: 300 - maxIdleConns: 50 - connMaxLifetime: 300 # Unit second - txIsolationLevel: 2 #LevelReadCommitted + path: ./polaris.bolt + ## Database storage plugin + # name: defaultStore + # option: + # master: + # dbType: mysql + # dbName: polaris_server + # dbUser: ${MYSQL_USER} ##DB_USER## + # dbPwd: ${MYSQL_PWD} ##DB_PWD## + # dbAddr: ${MYSQL_HOST} ##DB_ADDR## + # maxOpenConns: 300 + # maxIdleConns: 50 + # connMaxLifetime: 300 # Unit second + # txIsolationLevel: 2 #LevelReadCommitted # polaris-server plugin settings plugin: crypto: diff --git a/store/boltdb/config_file_release.go b/store/boltdb/config_file_release.go index ba49cc6e6..610142f76 100644 --- a/store/boltdb/config_file_release.go +++ b/store/boltdb/config_file_release.go @@ -76,8 +76,10 @@ func (cfr *configFileReleaseStore) CreateConfigFileReleaseTx(proxyTx store.Tx, &ConfigFileRelease{}, values); err != nil { return err } - if len(values) != 0 { - return store.NewStatusError(store.DuplicateEntryErr, "exist record") + for i := range values { + if ret := cfr.toModelData(values[i].(*ConfigFileRelease)); ret != nil { + return store.NewStatusError(store.DuplicateEntryErr, "exist record") + } } table, err := tx.CreateBucketIfNotExists([]byte(tblConfigFileRelease)) @@ -361,9 +363,8 @@ func (cfr *configFileReleaseStore) inactiveConfigFileRelease(tx *bolt.Tx, // 查询这个 release 相关的所有 if err := loadValuesByFilter(tx, tblConfigFileRelease, fields, &ConfigFileRelease{}, func(m map[string]interface{}) bool { - flag, _ := m[FileReleaseFieldFlag].(int) // 已经删除的不管 - if flag == 1 { + if valid, _ := m[FileReleaseFieldValid].(bool); !valid { return false } isActive, _ := m[FileReleaseFieldActive].(bool) @@ -445,6 +446,9 @@ type ConfigFileRelease struct { } func (cfr *configFileReleaseStore) toModelData(data *ConfigFileRelease) *model.ConfigFileRelease { + if !data.Valid { + return nil + } return &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ From 3de6f56444e600f770bc96c7efae976e00c6f7a2 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Tue, 28 May 2024 10:52:22 +0800 Subject: [PATCH 11/23] feat:support nacos-address server endpoints --- admin/job/clean_deleted_resource.go | 1 + apiserver/nacosserver/v1/auth.go | 1 + apiserver/nacosserver/v1/endpoints.go | 3 +- auth/api.go | 2 + auth/policy/auth_checker.go | 30 +++++++++- auth/policy/inteceptor/auth/log.go | 2 +- auth/user/inteceptor/auth/log.go | 2 +- auth/user/inteceptor/auth/server.go | 12 ++-- cache/cache.go | 1 + cache/service/service_contract.go | 57 +++++++++++++++++-- common/model/acquire_context.go | 9 +++ common/model/contract.go | 3 + common/model/metadata.go | 2 + config/interceptor/auth/client_authibility.go | 20 +++---- .../auth/config_file_authibility.go | 16 +++--- .../auth/config_file_group_authibility.go | 28 ++++----- .../auth/config_file_release_authibility.go | 20 +++---- ...config_file_release_history_authibility.go | 2 +- .../auth/config_file_template_authibility.go | 6 +- config/interceptor/auth/resource_listener.go | 2 +- config/interceptor/auth/server_authability.go | 16 +++--- namespace/namespace_authability.go | 17 +++--- service/client_test.go | 1 - .../auth/circuitbreaker_rule_authability.go | 10 ++-- .../interceptor/auth/client_v1_authability.go | 24 ++++---- .../auth/faultdetect_config_authability.go | 8 +-- .../interceptor/auth/instance_authability.go | 16 +++--- .../auth/ratelimit_config_authability.go | 10 ++-- service/interceptor/auth/resource_listen.go | 2 +- .../auth/routing_config_v1_authability.go | 8 +-- .../auth/routing_config_v2_authability.go | 8 +-- .../interceptor/auth/server_authability.go | 14 ++--- .../auth/service_alias_authability.go | 26 ++++----- .../interceptor/auth/service_authability.go | 35 ++++++------ .../auth/service_contract_authability.go | 14 ++--- 35 files changed, 257 insertions(+), 171 deletions(-) diff --git a/admin/job/clean_deleted_resource.go b/admin/job/clean_deleted_resource.go index 9a077f449..e1e668d3e 100644 --- a/admin/job/clean_deleted_resource.go +++ b/admin/job/clean_deleted_resource.go @@ -22,6 +22,7 @@ import ( "time" "github.com/mitchellh/mapstructure" + "github.com/polarismesh/polaris/store" ) diff --git a/apiserver/nacosserver/v1/auth.go b/apiserver/nacosserver/v1/auth.go index 2b00222bc..2090b55dd 100644 --- a/apiserver/nacosserver/v1/auth.go +++ b/apiserver/nacosserver/v1/auth.go @@ -21,6 +21,7 @@ import ( "context" "github.com/emicklei/go-restful/v3" + nacoshttp "github.com/polarismesh/polaris/apiserver/nacosserver/v1/http" "github.com/polarismesh/polaris/common/model" ) diff --git a/apiserver/nacosserver/v1/endpoints.go b/apiserver/nacosserver/v1/endpoints.go index 5343d8722..321679d7b 100644 --- a/apiserver/nacosserver/v1/endpoints.go +++ b/apiserver/nacosserver/v1/endpoints.go @@ -24,9 +24,10 @@ import ( "strings" "github.com/emicklei/go-restful/v3" - api "github.com/polarismesh/polaris/common/api/v1" "github.com/polarismesh/specification/source/go/api/v1/service_manage" "google.golang.org/protobuf/types/known/wrapperspb" + + api "github.com/polarismesh/polaris/common/api/v1" ) func (n *NacosV1Server) GetAddressServer() (*restful.WebService, error) { diff --git a/auth/api.go b/auth/api.go index bc4ea1a61..e571b6e18 100644 --- a/auth/api.go +++ b/auth/api.go @@ -39,6 +39,8 @@ type AuthChecker interface { IsOpenConsoleAuth() bool // IsOpenClientAuth IsOpenClientAuth() bool + // AllowResourceOperate 是否允许资源的操作 + AllowResourceOperate(ctx *model.AcquireContext, opInfo *model.ResourceOpInfo) bool } // StrategyServer 策略相关操作 diff --git a/auth/policy/auth_checker.go b/auth/policy/auth_checker.go index 12a5a9a60..1b1198f62 100644 --- a/auth/policy/auth_checker.go +++ b/auth/policy/auth_checker.go @@ -51,7 +51,8 @@ func (d *DefaultAuthChecker) SetCacheMgr(mgr cachetypes.CacheManager) { } // Initialize 执行初始化动作 -func (d *DefaultAuthChecker) Initialize(conf *AuthConfig, s store.Store, cacheMgr cachetypes.CacheManager, userSvr auth.UserServer) error { +func (d *DefaultAuthChecker) Initialize(conf *AuthConfig, s store.Store, + cacheMgr cachetypes.CacheManager, userSvr auth.UserServer) error { d.conf = conf d.cacheMgr = cacheMgr d.userSvr = userSvr @@ -78,6 +79,33 @@ func (d *DefaultAuthChecker) IsOpenAuth() bool { return d.IsOpenConsoleAuth() || d.IsOpenClientAuth() } +// AllowResourceOperate 是否允许资源的操作 +func (d *DefaultAuthChecker) AllowResourceOperate(ctx *model.AcquireContext, opInfo *model.ResourceOpInfo) bool { + // 如果鉴权能力没有开启,那就默认都可以进行编辑 + if !d.IsOpenAuth() { + return true + } + attachVal, ok := ctx.GetAttachment(model.TokenDetailInfoKey) + if !ok { + // TODO need log + return false + } + tokenInfo, ok := attachVal.(auth.OperatorInfo) + + principal := model.Principal{ + PrincipalID: tokenInfo.OperatorID, + PrincipalRole: func() model.PrincipalType { + if tokenInfo.IsUserToken { + return model.PrincipalUser + } + return model.PrincipalGroup + }(), + } + + editable := d.cacheMgr.AuthStrategy().IsResourceEditable(principal, opInfo.ResourceType, opInfo.ResourceID) + return editable +} + // CheckClientPermission 执行检查客户端动作判断是否有权限,并且对 RequestContext 注入操作者数据 func (d *DefaultAuthChecker) CheckClientPermission(preCtx *model.AcquireContext) (bool, error) { preCtx.SetFromClient() diff --git a/auth/policy/inteceptor/auth/log.go b/auth/policy/inteceptor/auth/log.go index 7707503ce..7bce284da 100644 --- a/auth/policy/inteceptor/auth/log.go +++ b/auth/policy/inteceptor/auth/log.go @@ -20,5 +20,5 @@ package auth import commonlog "github.com/polarismesh/polaris/common/log" var ( - log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) + log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) ) diff --git a/auth/user/inteceptor/auth/log.go b/auth/user/inteceptor/auth/log.go index 7707503ce..7bce284da 100644 --- a/auth/user/inteceptor/auth/log.go +++ b/auth/user/inteceptor/auth/log.go @@ -20,5 +20,5 @@ package auth import commonlog "github.com/polarismesh/polaris/common/log" var ( - log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) + log = commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName) ) diff --git a/auth/user/inteceptor/auth/server.go b/auth/user/inteceptor/auth/server.go index e85d0a2fb..73d9ee3fe 100644 --- a/auth/user/inteceptor/auth/server.go +++ b/auth/user/inteceptor/auth/server.go @@ -21,19 +21,19 @@ import ( "context" "strconv" - "github.com/polarismesh/polaris/auth" - api "github.com/polarismesh/polaris/common/api/v1" - "github.com/polarismesh/polaris/common/model" - authcommon "github.com/polarismesh/polaris/common/model/auth" - "github.com/polarismesh/polaris/common/utils" - "github.com/polarismesh/polaris/store" 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" "go.uber.org/zap" "google.golang.org/protobuf/types/known/wrapperspb" + "github.com/polarismesh/polaris/auth" cachetypes "github.com/polarismesh/polaris/cache/api" + api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/model" + authcommon "github.com/polarismesh/polaris/common/model/auth" + "github.com/polarismesh/polaris/common/utils" + "github.com/polarismesh/polaris/store" ) var ( diff --git a/cache/cache.go b/cache/cache.go index c91e859d8..d1617738e 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -137,6 +137,7 @@ func (nc *CacheManager) Start(ctx context.Context) error { if !exist { return fmt.Errorf("cache resource %s not exists", name) } + // 每个缓存各自在自己的协程内部按照期望的缓存更新时间完成数据缓存刷新 go func(c types.Cache) { ticker := time.NewTicker(nc.GetUpdateCacheInterval()) for { diff --git a/cache/service/service_contract.go b/cache/service/service_contract.go index 829959b0c..66ae645a4 100644 --- a/cache/service/service_contract.go +++ b/cache/service/service_contract.go @@ -19,8 +19,12 @@ package service import ( "context" + "encoding/json" + "os" + "path/filepath" "time" + "go.etcd.io/bbolt" "go.uber.org/zap" "golang.org/x/sync/singleflight" @@ -39,7 +43,9 @@ func NewServiceContractCache(storage store.Store, cacheMgr cachetypes.CacheManag type ServiceContractCache struct { *cachetypes.BaseCache // data namespace/service/type/protocol/version -> *model.EnrichServiceContract - data *utils.SyncMap[string, *model.EnrichServiceContract] + data *utils.SyncMap[string, *model.EnrichServiceContract] + // valueCache save ConfigFileRelease.Content into local file to reduce memory use + valueCache *bbolt.DB singleGroup singleflight.Group } @@ -49,6 +55,23 @@ func (sc *ServiceContractCache) Initialize(c map[string]interface{}) error { return nil } +func (fc *ServiceContractCache) openBoltCache(opt map[string]interface{}) (*bbolt.DB, error) { + path, _ := opt["cachePath"].(string) + if path == "" { + path = "./data/cache/service_contract" + } + if err := os.MkdirAll(path, os.ModePerm); err != nil { + return nil, err + } + dbFile := filepath.Join(path, "service_contract.bolt") + _ = os.Remove(dbFile) + valueCache, err := bbolt.Open(dbFile, os.ModePerm, &bbolt.Options{}) + if err != nil { + return nil, err + } + return valueCache, nil +} + // Update func (sc *ServiceContractCache) Update() error { err, _ := sc.singleUpdate() @@ -88,11 +111,11 @@ func (sc *ServiceContractCache) setContracts(values []*model.EnrichServiceContra item := values[i] if !item.Valid { del++ - sc.data.Delete(item.GetCacheKey()) + sc.upsertValueCache(item, true) continue } upsert++ - sc.data.Store(item.GetCacheKey(), item) + sc.upsertValueCache(item, false) } return map[string]time.Time{ sc.Name(): lastMtime, @@ -111,6 +134,32 @@ func (sc *ServiceContractCache) Name() string { } func (sc *ServiceContractCache) Get(ctx context.Context, req *model.ServiceContract) *model.EnrichServiceContract { - ret, _ := sc.data.Load(req.GetCacheKey()) + ret, _ := sc.loadValueCache(req) return ret } + +func (fc *ServiceContractCache) upsertValueCache(item *model.EnrichServiceContract, del bool) error { + return fc.valueCache.Update(func(tx *bbolt.Tx) error { + if del { + return tx.DeleteBucket([]byte(item.GetCacheKey())) + } + bucket, err := tx.CreateBucketIfNotExists([]byte(item.GetCacheKey())) + if err != nil { + return err + } + return bucket.Put([]byte(item.GetCacheKey()), []byte(utils.MustJson(item))) + }) +} + +func (fc *ServiceContractCache) loadValueCache(release *model.ServiceContract) (*model.EnrichServiceContract, error) { + ret := &model.EnrichServiceContract{} + err := fc.valueCache.View(func(tx *bbolt.Tx) error { + bucket := tx.Bucket([]byte(release.GetCacheKey())) + if bucket == nil { + return nil + } + val := bucket.Get([]byte(release.GetCacheKey())) + return json.Unmarshal(val, ret) + }) + return ret, err +} diff --git a/common/model/acquire_context.go b/common/model/acquire_context.go index 23511ac4d..91bfae548 100644 --- a/common/model/acquire_context.go +++ b/common/model/acquire_context.go @@ -255,3 +255,12 @@ func (authCtx *AcquireContext) IsAllowAnonymous() bool { func (authCtx *AcquireContext) SetAllowAnonymous(a bool) { authCtx.allowAnonymous = a } + +// ResourceOpInfo 资源的数据操作信息 +type ResourceOpInfo struct { + ResourceType apisecurity.ResourceType + Namespace string + ResourceName string + ResourceID string + Operation ResourceOperation +} diff --git a/common/model/contract.go b/common/model/contract.go index cfb8ed1d6..e2feab3f5 100644 --- a/common/model/contract.go +++ b/common/model/contract.go @@ -89,6 +89,9 @@ func (e *EnrichServiceContract) Format() { } e.Interfaces = append(e.Interfaces, e.ClientInterfaces[k]) } + // 格式化完毕之后,清空暂存的 ClientInterface 以及 ManualInterface 数据 + e.ClientInterfaces = nil + e.ManualInterfaces = nil } func (e *EnrichServiceContract) ToSpec() *apiservice.ServiceContract { diff --git a/common/model/metadata.go b/common/model/metadata.go index 98577473a..d42367c43 100644 --- a/common/model/metadata.go +++ b/common/model/metadata.go @@ -39,4 +39,6 @@ const ( MetaKeyConfigFileSyncSourceKey = "internal-sync-source" // MetaKeyConfigFileSyncSourceClusterKey 配置同步来源所在集群 MetaKeyConfigFileSyncSourceClusterKey = "internal-sync-sourcecluster" + // MetaKey3RdPlatform 第三方平台标签 + MetaKey3RdPlatform = "internal-3rd-platform" ) diff --git a/config/interceptor/auth/client_authibility.go b/config/interceptor/auth/client_authibility.go index a3516a3cf..fdef9c8d2 100644 --- a/config/interceptor/auth/client_authibility.go +++ b/config/interceptor/auth/client_authibility.go @@ -33,7 +33,7 @@ func (s *ServerAuthability) UpsertAndReleaseConfigFileFromClient(ctx context.Con req *apiconfig.ConfigFilePublishInfo) *apiconfig.ConfigResponse { authCtx := s.collectConfigFilePublishAuthContext(ctx, []*apiconfig.ConfigFilePublishInfo{req}, model.Modify, "UpsertAndReleaseConfigFileFromClient") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigFileResponse(model.ConvertToErrCode(err), nil) } @@ -52,7 +52,7 @@ func (s *ServerAuthability) CreateConfigFileFromClient(ctx context.Context, Name: fileInfo.Name, Group: fileInfo.Group}, }, model.Create, "CreateConfigFileFromClient") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -67,7 +67,7 @@ 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -83,7 +83,7 @@ func (s *ServerAuthability) DeleteConfigFileFromClient(ctx context.Context, authCtx := s.collectConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{req}, model.Delete, "DeleteConfigFileFromClient") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -102,7 +102,7 @@ func (s *ServerAuthability) PublishConfigFileFromClient(ctx context.Context, Name: fileInfo.FileName, Group: fileInfo.Group}, }, model.Create, "PublishConfigFileFromClient") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -121,7 +121,7 @@ func (s *ServerAuthability) GetConfigFileWithCache(ctx context.Context, Name: fileInfo.FileName, Group: fileInfo.Group}, }, model.Read, "GetConfigFileForClient") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -134,7 +134,7 @@ func (s *ServerAuthability) GetConfigFileWithCache(ctx context.Context, 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return func() *apiconfig.ConfigClientResponse { return api.NewConfigClientResponseWithInfo(model.ConvertToErrCode(err), err.Error()) }, nil @@ -156,7 +156,7 @@ func (s *ServerAuthability) GetConfigFileNamesWithCache(ctx context.Context, Group: req.GetConfigFileGroup().GetName(), }, }, model.Read, "GetConfigFileNamesWithCache") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { out := api.NewConfigClientListResponse(model.ConvertToErrCode(err)) return out } @@ -174,7 +174,7 @@ func (s *ServerAuthability) GetConfigGroupsWithCache(ctx context.Context, Namespace: req.GetNamespace(), }, }, model.Read, "GetConfigGroupsWithCache") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { out := api.NewConfigDiscoverResponse(model.ConvertToErrCode(err)) return out } @@ -190,7 +190,7 @@ func (s *ServerAuthability) CasUpsertAndReleaseConfigFileFromClient(ctx context. authCtx := s.collectConfigFilePublishAuthContext(ctx, []*apiconfig.ConfigFilePublishInfo{req}, model.Modify, "CasUpsertAndReleaseConfigFileFromClient") - if _, err := s.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckClientPermission(authCtx); err != nil { return api.NewConfigFileResponse(model.ConvertToErrCode(err), nil) } diff --git a/config/interceptor/auth/config_file_authibility.go b/config/interceptor/auth/config_file_authibility.go index cdf3cc774..ed6a8f5df 100644 --- a/config/interceptor/auth/config_file_authibility.go +++ b/config/interceptor/auth/config_file_authibility.go @@ -32,7 +32,7 @@ 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -48,7 +48,7 @@ func (s *ServerAuthability) GetConfigFileRichInfo(ctx context.Context, authCtx := s.collectConfigFileAuthContext( ctx, []*apiconfig.ConfigFile{req}, model.Read, "GetConfigFileRichInfo") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -61,7 +61,7 @@ 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigFileBatchQueryResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -75,7 +75,7 @@ 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -91,7 +91,7 @@ func (s *ServerAuthability) DeleteConfigFile(ctx context.Context, authCtx := s.collectConfigFileAuthContext(ctx, []*apiconfig.ConfigFile{req}, model.Delete, "DeleteConfigFile") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -106,7 +106,7 @@ 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -127,7 +127,7 @@ func (s *ServerAuthability) ExportConfigFile(ctx context.Context, configFiles = append(configFiles, configFile) } authCtx := s.collectConfigFileAuthContext(ctx, configFiles, model.Read, "ExportConfigFile") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigFileExportResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -139,7 +139,7 @@ func (s *ServerAuthability) ExportConfigFile(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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigFileImportResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } diff --git a/config/interceptor/auth/config_file_group_authibility.go b/config/interceptor/auth/config_file_group_authibility.go index 27633868d..17c7c3c7f 100644 --- a/config/interceptor/auth/config_file_group_authibility.go +++ b/config/interceptor/auth/config_file_group_authibility.go @@ -36,7 +36,7 @@ func (s *ServerAuthability) CreateConfigFileGroup(ctx context.Context, model.Create, "CreateConfigFileGroup") // 验证 token 信息 - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -52,7 +52,7 @@ func (s *ServerAuthability) QueryConfigFileGroups(ctx context.Context, authCtx := s.collectConfigGroupAuthContext(ctx, nil, model.Read, "QueryConfigFileGroups") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchQueryResponse(model.ConvertToErrCode(err)) } @@ -61,22 +61,22 @@ func (s *ServerAuthability) QueryConfigFileGroups(ctx context.Context, resp := s.nextServer.QueryConfigFileGroups(ctx, filter) if len(resp.ConfigFileGroups) != 0 { - principal := model.Principal{ - PrincipalID: utils.ParseUserID(ctx), - PrincipalRole: model.PrincipalUser, - } for index := range resp.ConfigFileGroups { group := resp.ConfigFileGroups[index] - editable := true - // 如果鉴权能力没有开启,那就默认都可以进行编辑 - if s.strategyMgn.GetAuthChecker().IsOpenConsoleAuth() { - editable = s.cacheMgr.AuthStrategy().IsResourceEditable(principal, - apisecurity.ResourceType_ConfigGroups, fmt.Sprintf("%d", group.GetId().GetValue())) + editable := s.policyMgr.GetAuthChecker().AllowResourceOperate(authCtx, &model.ResourceOpInfo{ + ResourceType: apisecurity.ResourceType_ConfigGroups, + Namespace: group.GetNamespace().GetValue(), + ResourceName: group.GetName().GetValue(), + ResourceID: fmt.Sprintf("%d", group.GetId().GetValue()), + Operation: authCtx.GetOperation(), + }) + // 如果包含特殊标签,也不允许修改 + if _, ok := group.GetMetadata()[model.MetaKey3RdPlatform]; ok { + editable = false } group.Editable = utils.NewBoolValue(editable) } } - return resp } @@ -86,7 +86,7 @@ func (s *ServerAuthability) DeleteConfigFileGroup( 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -102,7 +102,7 @@ func (s *ServerAuthability) UpdateConfigFileGroup(ctx context.Context, authCtx := s.collectConfigGroupAuthContext(ctx, []*apiconfig.ConfigFileGroup{configFileGroup}, model.Modify, "UpdateConfigFileGroup") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } diff --git a/config/interceptor/auth/config_file_release_authibility.go b/config/interceptor/auth/config_file_release_authibility.go index 1af06424c..09711aff5 100644 --- a/config/interceptor/auth/config_file_release_authibility.go +++ b/config/interceptor/auth/config_file_release_authibility.go @@ -34,7 +34,7 @@ func (s *ServerAuthability) PublishConfigFile(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{configFileRelease}, model.Modify, "PublishConfigFile") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -51,7 +51,7 @@ func (s *ServerAuthability) GetConfigFileRelease(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, []*apiconfig.ConfigFileRelease{req}, model.Read, "GetConfigFileRelease") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -65,7 +65,7 @@ func (s *ServerAuthability) DeleteConfigFileReleases(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, reqs, model.Delete, "DeleteConfigFileReleases") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchWriteResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -79,7 +79,7 @@ func (s *ServerAuthability) DeleteConfigFileRelease(ctx context.Context, req *ap req, }, model.Delete, "DeleteConfigFileRelease") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -93,7 +93,7 @@ func (s *ServerAuthability) GetConfigFileReleaseVersions(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, nil, model.Read, "GetConfigFileReleaseVersions") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchQueryResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -107,7 +107,7 @@ func (s *ServerAuthability) GetConfigFileReleases(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, nil, model.Read, "GetConfigFileReleases") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchQueryResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -121,7 +121,7 @@ func (s *ServerAuthability) RollbackConfigFileReleases(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, reqs, model.Modify, "RollbackConfigFileReleases") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchWriteResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -136,7 +136,7 @@ func (s *ServerAuthability) RollbackConfigFileRelease(ctx context.Context, req, }, model.Modify, "RollbackConfigFileRelease") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() @@ -149,7 +149,7 @@ 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigFileResponse(model.ConvertToErrCode(err), nil) } @@ -164,7 +164,7 @@ func (s *ServerAuthability) StopGrayConfigFileReleases(ctx context.Context, authCtx := s.collectConfigFileReleaseAuthContext(ctx, reqs, model.Modify, "StopGrayConfigFileReleases") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchWriteResponse(model.ConvertToErrCode(err)) } diff --git a/config/interceptor/auth/config_file_release_history_authibility.go b/config/interceptor/auth/config_file_release_history_authibility.go index d8c985dd4..54860942c 100644 --- a/config/interceptor/auth/config_file_release_history_authibility.go +++ b/config/interceptor/auth/config_file_release_history_authibility.go @@ -33,7 +33,7 @@ func (s *ServerAuthability) GetConfigFileReleaseHistories(ctx context.Context, authCtx := s.collectConfigFileReleaseHistoryAuthContext(ctx, nil, model.Read, "GetConfigFileReleaseHistories") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigBatchQueryResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } ctx = authCtx.GetRequestContext() diff --git a/config/interceptor/auth/config_file_template_authibility.go b/config/interceptor/auth/config_file_template_authibility.go index 3aa76986a..c41cdf258 100644 --- a/config/interceptor/auth/config_file_template_authibility.go +++ b/config/interceptor/auth/config_file_template_authibility.go @@ -31,7 +31,7 @@ import ( 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigFileBatchQueryResponseWithMessage(model.ConvertToErrCode(err), err.Error()) } @@ -44,7 +44,7 @@ func (s *ServerAuthability) GetAllConfigFileTemplates(ctx context.Context) *apic 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 { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } @@ -59,7 +59,7 @@ func (s *ServerAuthability) CreateConfigFileTemplate(ctx context.Context, authCtx := s.collectConfigFileTemplateAuthContext(ctx, []*apiconfig.ConfigFileTemplate{template}, model.Create, "CreateConfigFileTemplate") - if _, err := s.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := s.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewConfigResponseWithInfo(model.ConvertToErrCode(err), err.Error()) } diff --git a/config/interceptor/auth/resource_listener.go b/config/interceptor/auth/resource_listener.go index 2b89f99a7..6e3e99858 100644 --- a/config/interceptor/auth/resource_listener.go +++ b/config/interceptor/auth/resource_listener.go @@ -68,5 +68,5 @@ func (s *ServerAuthability) onConfigGroupResource(ctx context.Context, res *conf authCtx.SetAttachment(model.LinkGroupsKey, utils.StringSliceDeDuplication(groups)) authCtx.SetAttachment(model.RemoveLinkGroupsKey, utils.StringSliceDeDuplication(removeGroups)) - return s.strategyMgn.AfterResourceOperation(authCtx) + return s.policyMgr.AfterResourceOperation(authCtx) } diff --git a/config/interceptor/auth/server_authability.go b/config/interceptor/auth/server_authability.go index c6e27cdfb..53f2e1324 100644 --- a/config/interceptor/auth/server_authability.go +++ b/config/interceptor/auth/server_authability.go @@ -36,19 +36,19 @@ var _ config.ConfigCenterServer = (*ServerAuthability)(nil) // Server 配置中心核心服务 type ServerAuthability struct { - cacheMgr cachetypes.CacheManager - nextServer config.ConfigCenterServer - userMgn auth.UserServer - strategyMgn auth.StrategyServer + cacheMgr cachetypes.CacheManager + nextServer config.ConfigCenterServer + userMgn auth.UserServer + policyMgr auth.StrategyServer } func New(nextServer config.ConfigCenterServer, cacheMgr cachetypes.CacheManager, userMgr auth.UserServer, strategyMgr auth.StrategyServer) config.ConfigCenterServer { proxy := &ServerAuthability{ - nextServer: nextServer, - cacheMgr: cacheMgr, - userMgn: userMgr, - strategyMgn: strategyMgr, + nextServer: nextServer, + cacheMgr: cacheMgr, + userMgn: userMgr, + policyMgr: strategyMgr, } if val, ok := nextServer.(*config.Server); ok { val.SetResourceHooks(proxy) diff --git a/namespace/namespace_authability.go b/namespace/namespace_authability.go index 1e9c48cd5..7e4b5cc21 100644 --- a/namespace/namespace_authability.go +++ b/namespace/namespace_authability.go @@ -152,18 +152,15 @@ func (svr *serverAuthAbility) GetNamespaces( resp := svr.targetServer.GetNamespaces(ctx, query) if len(resp.Namespaces) != 0 { - principal := model.Principal{ - PrincipalID: utils.ParseUserID(ctx), - PrincipalRole: model.PrincipalUser, - } for index := range resp.Namespaces { ns := resp.Namespaces[index] - editable := true - // 如果鉴权能力没有开启,那就默认都可以进行编辑 - if svr.strategyMgn.GetAuthChecker().IsOpenConsoleAuth() { - editable = svr.targetServer.caches.AuthStrategy().IsResourceEditable(principal, - apisecurity.ResourceType_Namespaces, ns.Id.GetValue()) - } + editable := svr.strategyMgn.GetAuthChecker().AllowResourceOperate(authCtx, &model.ResourceOpInfo{ + ResourceType: apisecurity.ResourceType_Namespaces, + Namespace: ns.GetName().GetValue(), + ResourceName: ns.GetName().GetValue(), + ResourceID: ns.GetId().GetValue(), + Operation: authCtx.GetOperation(), + }) ns.Editable = utils.NewBoolValue(editable) } } diff --git a/service/client_test.go b/service/client_test.go index 286c030c2..d0f428910 100644 --- a/service/client_test.go +++ b/service/client_test.go @@ -24,7 +24,6 @@ import ( "testing" "github.com/golang/protobuf/proto" - apimodel "github.com/polarismesh/specification/source/go/api/v1/model" "github.com/polarismesh/specification/source/go/api/v1/service_manage" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" diff --git a/service/interceptor/auth/circuitbreaker_rule_authability.go b/service/interceptor/auth/circuitbreaker_rule_authability.go index 2f1137fe3..37e5640d0 100644 --- a/service/interceptor/auth/circuitbreaker_rule_authability.go +++ b/service/interceptor/auth/circuitbreaker_rule_authability.go @@ -33,7 +33,7 @@ func (svr *ServerAuthAbility) CreateCircuitBreakerRules( // TODO not support CircuitBreaker resource auth, so we set op is read authCtx := svr.collectCircuitBreakerRuleV2AuthContext(ctx, request, model.Read, "CreateCircuitBreakerRules") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -46,7 +46,7 @@ func (svr *ServerAuthAbility) CreateCircuitBreakerRules( 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) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -59,7 +59,7 @@ func (svr *ServerAuthAbility) DeleteCircuitBreakerRules( 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) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -72,7 +72,7 @@ func (svr *ServerAuthAbility) EnableCircuitBreakerRules( 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) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -85,7 +85,7 @@ func (svr *ServerAuthAbility) UpdateCircuitBreakerRules( 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) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchQueryResponse(convertToErrCode(err)) } diff --git a/service/interceptor/auth/client_v1_authability.go b/service/interceptor/auth/client_v1_authability.go index b486000c4..fec85a0f6 100644 --- a/service/interceptor/auth/client_v1_authability.go +++ b/service/interceptor/auth/client_v1_authability.go @@ -33,7 +33,7 @@ func (svr *ServerAuthAbility) RegisterInstance(ctx context.Context, req *apiserv authCtx := svr.collectClientInstanceAuthContext( ctx, []*apiservice.Instance{req}, model.Create, "RegisterInstance") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error()) return resp @@ -50,7 +50,7 @@ func (svr *ServerAuthAbility) DeregisterInstance(ctx context.Context, req *apise authCtx := svr.collectClientInstanceAuthContext( ctx, []*apiservice.Instance{req}, model.Create, "DeregisterInstance") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error()) return resp @@ -75,7 +75,7 @@ func (svr *ServerAuthAbility) ReportServiceContract(ctx context.Context, req *ap Namespace: wrapperspb.String(req.GetNamespace()), }}, model.Create, "ReportServiceContract") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error()) return resp @@ -99,7 +99,7 @@ func (svr *ServerAuthAbility) GetServiceWithCache( authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverServices") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -117,7 +117,7 @@ func (svr *ServerAuthAbility) ServiceInstancesCache( authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverInstances") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -135,7 +135,7 @@ func (svr *ServerAuthAbility) GetRoutingConfigWithCache( authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverRouterRule") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -153,7 +153,7 @@ func (svr *ServerAuthAbility) GetRateLimitWithCache( authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverRateLimit") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -171,7 +171,7 @@ func (svr *ServerAuthAbility) GetCircuitBreakerWithCache( authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverCircuitBreaker") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -188,7 +188,7 @@ func (svr *ServerAuthAbility) GetFaultDetectWithCache( authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverFaultDetect") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -205,7 +205,7 @@ func (svr *ServerAuthAbility) UpdateInstance(ctx context.Context, req *apiservic authCtx := svr.collectClientInstanceAuthContext( ctx, []*apiservice.Instance{req}, model.Modify, "UpdateInstance") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error()) return resp @@ -225,7 +225,7 @@ func (svr *ServerAuthAbility) GetServiceContractWithCache(ctx context.Context, Name: wrapperspb.String(req.Service), }}, model.Read, "GetServiceContractWithCache") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) @@ -242,7 +242,7 @@ func (svr *ServerAuthAbility) GetServiceContractWithCache(ctx context.Context, func (svr *ServerAuthAbility) GetLaneRuleWithCache(ctx context.Context, req *apiservice.Service) *apiservice.DiscoverResponse { authCtx := svr.collectServiceAuthContext( ctx, []*apiservice.Service{req}, model.Read, "DiscoverLaneRule") - _, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckClientPermission(authCtx) if err != nil { resp := api.NewDiscoverResponse(convertToErrCode(err)) resp.Info = utils.NewStringValue(err.Error()) diff --git a/service/interceptor/auth/faultdetect_config_authability.go b/service/interceptor/auth/faultdetect_config_authability.go index 48e7a3466..fce58ac5f 100644 --- a/service/interceptor/auth/faultdetect_config_authability.go +++ b/service/interceptor/auth/faultdetect_config_authability.go @@ -32,7 +32,7 @@ func (svr *ServerAuthAbility) CreateFaultDetectRules( ctx context.Context, request []*apifault.FaultDetectRule) *apiservice.BatchWriteResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, request, model.Read, "CreateFaultDetectRules") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -44,7 +44,7 @@ func (svr *ServerAuthAbility) DeleteFaultDetectRules( ctx context.Context, request []*apifault.FaultDetectRule) *apiservice.BatchWriteResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, request, model.Read, "DeleteFaultDetectRules") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -56,7 +56,7 @@ func (svr *ServerAuthAbility) UpdateFaultDetectRules( ctx context.Context, request []*apifault.FaultDetectRule) *apiservice.BatchWriteResponse { authCtx := svr.collectFaultDetectAuthContext(ctx, request, model.Read, "UpdateFaultDetectRules") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -67,7 +67,7 @@ func (svr *ServerAuthAbility) UpdateFaultDetectRules( 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 { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() diff --git a/service/interceptor/auth/instance_authability.go b/service/interceptor/auth/instance_authability.go index ecde3b62c..6accfc712 100644 --- a/service/interceptor/auth/instance_authability.go +++ b/service/interceptor/auth/instance_authability.go @@ -34,7 +34,7 @@ func (svr *ServerAuthAbility) CreateInstances(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Create, "CreateInstances") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error()) batchResp := api.NewBatchWriteResponse(apimodel.Code_ExecuteSuccess) @@ -53,7 +53,7 @@ func (svr *ServerAuthAbility) DeleteInstances(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Delete, "DeleteInstances") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error()) batchResp := api.NewBatchWriteResponse(apimodel.Code_ExecuteSuccess) @@ -72,7 +72,7 @@ func (svr *ServerAuthAbility) DeleteInstancesByHost(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Delete, "DeleteInstancesByHost") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -91,7 +91,7 @@ func (svr *ServerAuthAbility) UpdateInstances(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Modify, "UpdateInstances") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -107,7 +107,7 @@ func (svr *ServerAuthAbility) UpdateInstancesIsolate(ctx context.Context, reqs []*apiservice.Instance) *apiservice.BatchWriteResponse { authCtx := svr.collectInstanceAuthContext(ctx, reqs, model.Modify, "UpdateInstancesIsolate") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -122,7 +122,7 @@ func (svr *ServerAuthAbility) UpdateInstancesIsolate(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) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchQueryResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -136,7 +136,7 @@ func (svr *ServerAuthAbility) GetInstances(ctx context.Context, // GetInstancesCount get instances to count func (svr *ServerAuthAbility) GetInstancesCount(ctx context.Context) *apiservice.BatchQueryResponse { authCtx := svr.collectInstanceAuthContext(ctx, nil, model.Read, "GetInstancesCount") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchQueryResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -150,7 +150,7 @@ func (svr *ServerAuthAbility) GetInstanceLabels(ctx context.Context, query map[string]string) *apiservice.Response { authCtx := svr.collectInstanceAuthContext(ctx, nil, model.Read, "GetInstanceLabels") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewResponseWithMsg(convertToErrCode(err), err.Error()) } diff --git a/service/interceptor/auth/ratelimit_config_authability.go b/service/interceptor/auth/ratelimit_config_authability.go index 7ab61bc90..7f2a30487 100644 --- a/service/interceptor/auth/ratelimit_config_authability.go +++ b/service/interceptor/auth/ratelimit_config_authability.go @@ -34,7 +34,7 @@ func (svr *ServerAuthAbility) CreateRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, reqs, model.Create, "CreateRateLimits") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -50,7 +50,7 @@ func (svr *ServerAuthAbility) DeleteRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, reqs, model.Delete, "DeleteRateLimits") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -66,7 +66,7 @@ func (svr *ServerAuthAbility) UpdateRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, reqs, model.Modify, "UpdateRateLimits") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -82,7 +82,7 @@ func (svr *ServerAuthAbility) EnableRateLimits( ctx context.Context, reqs []*apitraffic.Rule) *apiservice.BatchWriteResponse { authCtx := svr.collectRateLimitAuthContext(ctx, nil, model.Read, "EnableRateLimits") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -98,7 +98,7 @@ func (svr *ServerAuthAbility) GetRateLimits( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectRateLimitAuthContext(ctx, nil, model.Read, "GetRateLimits") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchQueryResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } diff --git a/service/interceptor/auth/resource_listen.go b/service/interceptor/auth/resource_listen.go index ed88283a7..1552dc430 100644 --- a/service/interceptor/auth/resource_listen.go +++ b/service/interceptor/auth/resource_listen.go @@ -68,5 +68,5 @@ func (svr *ServerAuthAbility) onServiceResource(ctx context.Context, res *servic authCtx.SetAttachment(model.LinkGroupsKey, utils.StringSliceDeDuplication(groups)) authCtx.SetAttachment(model.RemoveLinkGroupsKey, utils.StringSliceDeDuplication(removeGroups)) - return svr.strategyMgn.AfterResourceOperation(authCtx) + return svr.policyMgr.AfterResourceOperation(authCtx) } diff --git a/service/interceptor/auth/routing_config_v1_authability.go b/service/interceptor/auth/routing_config_v1_authability.go index c73af7fb4..ae79d9051 100644 --- a/service/interceptor/auth/routing_config_v1_authability.go +++ b/service/interceptor/auth/routing_config_v1_authability.go @@ -34,7 +34,7 @@ func (svr *ServerAuthAbility) CreateRoutingConfigs( ctx context.Context, reqs []*apitraffic.Routing) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, reqs, model.Create, "CreateRoutingConfigs") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -50,7 +50,7 @@ func (svr *ServerAuthAbility) DeleteRoutingConfigs( ctx context.Context, reqs []*apitraffic.Routing) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, reqs, model.Delete, "DeleteRoutingConfigs") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -66,7 +66,7 @@ func (svr *ServerAuthAbility) UpdateRoutingConfigs( ctx context.Context, reqs []*apitraffic.Routing) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, reqs, model.Modify, "UpdateRoutingConfigs") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } @@ -82,7 +82,7 @@ func (svr *ServerAuthAbility) GetRoutingConfigs( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectRouteRuleAuthContext(ctx, nil, model.Read, "GetRoutingConfigs") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchQueryResponseWithMsg(apimodel.Code_NotAllowedAccess, err.Error()) } diff --git a/service/interceptor/auth/routing_config_v2_authability.go b/service/interceptor/auth/routing_config_v2_authability.go index 57551cfd0..66470627f 100644 --- a/service/interceptor/auth/routing_config_v2_authability.go +++ b/service/interceptor/auth/routing_config_v2_authability.go @@ -34,7 +34,7 @@ func (svr *ServerAuthAbility) CreateRoutingConfigsV2(ctx context.Context, // TODO not support RouteRuleV2 resource auth, so we set op is read authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "CreateRoutingConfigsV2") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -47,7 +47,7 @@ func (svr *ServerAuthAbility) DeleteRoutingConfigsV2(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "DeleteRoutingConfigsV2") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -60,7 +60,7 @@ func (svr *ServerAuthAbility) UpdateRoutingConfigsV2(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "UpdateRoutingConfigsV2") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() @@ -73,7 +73,7 @@ func (svr *ServerAuthAbility) EnableRoutings(ctx context.Context, req []*apitraffic.RouteRule) *apiservice.BatchWriteResponse { authCtx := svr.collectRouteRuleV2AuthContext(ctx, req, model.Read, "EnableRoutings") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } ctx = authCtx.GetRequestContext() diff --git a/service/interceptor/auth/server_authability.go b/service/interceptor/auth/server_authability.go index 9efa6b8e3..8c0de800a 100644 --- a/service/interceptor/auth/server_authability.go +++ b/service/interceptor/auth/server_authability.go @@ -39,17 +39,17 @@ import ( // // 该层会对请求参数做一些调整,根据具体的请求发起人,设置为数据对应的 owner,不可为为别人进行创建资源 type ServerAuthAbility struct { - nextSvr service.DiscoverServer - userMgn auth.UserServer - strategyMgn auth.StrategyServer + nextSvr service.DiscoverServer + userMgn auth.UserServer + policyMgr auth.StrategyServer } func NewServerAuthAbility(nextSvr service.DiscoverServer, - userMgn auth.UserServer, strategyMgn auth.StrategyServer) service.DiscoverServer { + userMgn auth.UserServer, policyMgr auth.StrategyServer) service.DiscoverServer { proxy := &ServerAuthAbility{ - nextSvr: nextSvr, - userMgn: userMgn, - strategyMgn: strategyMgn, + nextSvr: nextSvr, + userMgn: userMgn, + policyMgr: policyMgr, } actualSvr, ok := nextSvr.(*service.Server) diff --git a/service/interceptor/auth/service_alias_authability.go b/service/interceptor/auth/service_alias_authability.go index 84881e868..5b8da8be4 100644 --- a/service/interceptor/auth/service_alias_authability.go +++ b/service/interceptor/auth/service_alias_authability.go @@ -34,7 +34,7 @@ func (svr *ServerAuthAbility) CreateServiceAlias( authCtx := svr.collectServiceAliasAuthContext( ctx, []*apiservice.ServiceAlias{req}, model.Create, "CreateServiceAlias") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewServiceAliasResponse(convertToErrCode(err), req) } @@ -55,7 +55,7 @@ func (svr *ServerAuthAbility) DeleteServiceAliases(ctx context.Context, reqs []*apiservice.ServiceAlias) *apiservice.BatchWriteResponse { authCtx := svr.collectServiceAliasAuthContext(ctx, reqs, model.Delete, "DeleteServiceAliases") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -71,7 +71,7 @@ func (svr *ServerAuthAbility) UpdateServiceAlias( authCtx := svr.collectServiceAliasAuthContext( ctx, []*apiservice.ServiceAlias{req}, model.Modify, "UpdateServiceAlias") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewServiceAliasResponse(convertToErrCode(err), req) } @@ -86,7 +86,7 @@ func (svr *ServerAuthAbility) GetServiceAliases(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAliasAuthContext(ctx, nil, model.Read, "GetServiceAliases") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponse(convertToErrCode(err)) } @@ -95,23 +95,19 @@ func (svr *ServerAuthAbility) GetServiceAliases(ctx context.Context, resp := svr.nextSvr.GetServiceAliases(ctx, query) if len(resp.Aliases) != 0 { - // 对于服务别名,则是参考源服务是否有编辑权限 - principal := model.Principal{ - PrincipalID: utils.ParseUserID(ctx), - PrincipalRole: model.PrincipalUser, - } for index := range resp.Aliases { alias := resp.Aliases[index] svc := svr.Cache().Service().GetServiceByName(alias.Service.Value, alias.Namespace.Value) if svc == nil { continue } - editable := true - // 如果鉴权能力没有开启,那就默认都可以进行编辑 - if svr.strategyMgn.GetAuthChecker().IsOpenConsoleAuth() { - editable = svr.Cache().AuthStrategy().IsResourceEditable(principal, - apisecurity.ResourceType_Services, svc.ID) - } + editable := svr.policyMgr.GetAuthChecker().AllowResourceOperate(authCtx, &model.ResourceOpInfo{ + ResourceType: apisecurity.ResourceType_Services, + Namespace: svc.Namespace, + ResourceName: svc.Name, + ResourceID: svc.ID, + Operation: authCtx.GetOperation(), + }) alias.Editable = utils.NewBoolValue(editable) } } diff --git a/service/interceptor/auth/service_authability.go b/service/interceptor/auth/service_authability.go index a8229d9bd..84b611a3f 100644 --- a/service/interceptor/auth/service_authability.go +++ b/service/interceptor/auth/service_authability.go @@ -33,7 +33,7 @@ func (svr *ServerAuthAbility) CreateServices( ctx context.Context, reqs []*apiservice.Service) *apiservice.BatchWriteResponse { authCtx := svr.collectServiceAuthContext(ctx, reqs, model.Create, "CreateServices") - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -63,7 +63,7 @@ func (svr *ServerAuthAbility) DeleteServices( delete(accessRes, apisecurity.ResourceType_Namespaces) authCtx.SetAccessResources(accessRes) - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -82,7 +82,7 @@ func (svr *ServerAuthAbility) UpdateServices( delete(accessRes, apisecurity.ResourceType_Namespaces) authCtx.SetAccessResources(accessRes) - _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx) + _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx) if err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -102,7 +102,7 @@ func (svr *ServerAuthAbility) UpdateServiceToken( delete(accessRes, apisecurity.ResourceType_Namespaces) authCtx.SetAccessResources(accessRes) - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -115,7 +115,7 @@ func (svr *ServerAuthAbility) GetAllServices(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetAllServices") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -130,7 +130,7 @@ func (svr *ServerAuthAbility) GetServices( ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServices") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -139,18 +139,15 @@ func (svr *ServerAuthAbility) GetServices( resp := svr.nextSvr.GetServices(ctx, query) if len(resp.Services) != 0 { - principal := model.Principal{ - PrincipalID: utils.ParseUserID(ctx), - PrincipalRole: model.PrincipalUser, - } for index := range resp.Services { svc := resp.Services[index] - editable := true - // 如果鉴权能力没有开启,那就默认都可以进行编辑 - if svr.strategyMgn.GetAuthChecker().IsOpenConsoleAuth() { - editable = svr.Cache().AuthStrategy().IsResourceEditable(principal, - apisecurity.ResourceType_Services, svc.Id.GetValue()) - } + editable := svr.policyMgr.GetAuthChecker().AllowResourceOperate(authCtx, &model.ResourceOpInfo{ + ResourceType: apisecurity.ResourceType_Services, + Namespace: svc.GetNamespace().GetValue(), + ResourceName: svc.GetName().GetValue(), + ResourceID: svc.GetId().GetValue(), + Operation: authCtx.GetOperation(), + }) svc.Editable = utils.NewBoolValue(editable) } } @@ -161,7 +158,7 @@ func (svr *ServerAuthAbility) GetServices( 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 { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -174,7 +171,7 @@ func (svr *ServerAuthAbility) GetServicesCount(ctx context.Context) *apiservice. 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 { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewResponseWithMsg(convertToErrCode(err), err.Error()) } @@ -188,7 +185,7 @@ func (svr *ServerAuthAbility) GetServiceOwner( ctx context.Context, req []*apiservice.Service) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceOwner") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponseWithMsg(convertToErrCode(err), err.Error()) } diff --git a/service/interceptor/auth/service_contract_authability.go b/service/interceptor/auth/service_contract_authability.go index a17f40f24..b413c5614 100644 --- a/service/interceptor/auth/service_contract_authability.go +++ b/service/interceptor/auth/service_contract_authability.go @@ -39,7 +39,7 @@ func (svr *ServerAuthAbility) CreateServiceContracts(ctx context.Context, } authCtx := svr.collectServiceAuthContext(ctx, services, model.Create, "CreateServiceContracts") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -52,7 +52,7 @@ func (svr *ServerAuthAbility) CreateServiceContracts(ctx context.Context, func (svr *ServerAuthAbility) GetServiceContracts(ctx context.Context, query map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceContracts") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponse(convertToErrCode(err)) } @@ -66,7 +66,7 @@ func (svr *ServerAuthAbility) GetServiceContractVersions(ctx context.Context, filter map[string]string) *apiservice.BatchQueryResponse { authCtx := svr.collectServiceAuthContext(ctx, nil, model.Read, "GetServiceContractVersions") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchQueryResponse(convertToErrCode(err)) } @@ -87,7 +87,7 @@ func (svr *ServerAuthAbility) DeleteServiceContracts(ctx context.Context, } authCtx := svr.collectServiceAuthContext(ctx, services, model.Delete, "DeleteServiceContracts") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewBatchWriteResponse(convertToErrCode(err)) } @@ -105,7 +105,7 @@ func (svr *ServerAuthAbility) CreateServiceContractInterfaces(ctx context.Contex Name: utils.NewStringValue(contract.Service), }, }, model.Modify, "CreateServiceContractInterfaces") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewResponse(convertToErrCode(err)) } @@ -123,7 +123,7 @@ func (svr *ServerAuthAbility) AppendServiceContractInterfaces(ctx context.Contex Name: utils.NewStringValue(contract.Service), }, }, model.Modify, "AppendServiceContractInterfaces") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewResponse(convertToErrCode(err)) } @@ -141,7 +141,7 @@ func (svr *ServerAuthAbility) DeleteServiceContractInterfaces(ctx context.Contex Name: utils.NewStringValue(contract.Service), }, }, model.Modify, "DeleteServiceContractInterfaces") - if _, err := svr.strategyMgn.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { + if _, err := svr.policyMgr.GetAuthChecker().CheckConsolePermission(authCtx); err != nil { return api.NewResponse(convertToErrCode(err)) } From c2c80b6fa00c96239b7967f2bd90271ba9c1776b Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 10 Jun 2024 11:44:54 +0800 Subject: [PATCH 12/23] feat:support nacos-address server endpoints --- auth/policy/auth_checker.go | 16 +++-- auth/policy/auth_checker_test.go | 37 ++++++++++-- auth/policy/inteceptor/auth/server.go | 3 +- auth/policy/server.go | 4 +- auth/policy/strategy.go | 7 +-- auth/policy/strategy_test.go | 73 ++++++++++++++++------- auth/user/helper.go | 12 ++-- auth/user/inteceptor/auth/server.go | 21 ++++--- auth/user/user.go | 86 +++++++++------------------ auth/user/user_test.go | 37 ++++++++---- cache/namespace/namespace.go | 22 ++----- common/model/auth.go | 3 + plugin/healthchecker/leader/peer.go | 4 +- 13 files changed, 182 insertions(+), 143 deletions(-) diff --git a/auth/policy/auth_checker.go b/auth/policy/auth_checker.go index 1b1198f62..828da26c5 100644 --- a/auth/policy/auth_checker.go +++ b/auth/policy/auth_checker.go @@ -46,10 +46,6 @@ type DefaultAuthChecker struct { userSvr auth.UserServer } -func (d *DefaultAuthChecker) SetCacheMgr(mgr cachetypes.CacheManager) { - d.cacheMgr = mgr -} - // Initialize 执行初始化动作 func (d *DefaultAuthChecker) Initialize(conf *AuthConfig, s store.Store, cacheMgr cachetypes.CacheManager, userSvr auth.UserServer) error { @@ -249,3 +245,15 @@ func (d *DefaultAuthChecker) checkAction(principal model.Principal, } return true } + +func (d *DefaultAuthChecker) SetCacheMgr(mgr cachetypes.CacheManager) { + d.cacheMgr = mgr +} + +func (d *DefaultAuthChecker) GetConfig() *AuthConfig { + return d.conf +} + +func (d *DefaultAuthChecker) SetConfig(conf *AuthConfig) { + d.conf = conf +} diff --git a/auth/policy/auth_checker_test.go b/auth/policy/auth_checker_test.go index f56144c8b..bddb12440 100644 --- a/auth/policy/auth_checker_test.go +++ b/auth/policy/auth_checker_test.go @@ -488,6 +488,17 @@ func Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict(t *testing.T) { }, }), ) + + dchecker := checker.(*policy.DefaultAuthChecker) + oldConf := dchecker.GetConfig() + defer func() { + dchecker.SetConfig(oldConf) + }() + dchecker.SetConfig(&policy.AuthConfig{ + ConsoleOpen: true, + ConsoleStrict: true, + }) + _, err = checker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") @@ -510,7 +521,17 @@ func Test_DefaultAuthChecker_CheckConsolePermission_Write_Strict(t *testing.T) { }, }), ) - _, err = checker.CheckConsolePermission(authCtx) + dchecker := checker.(*policy.DefaultAuthChecker) + oldConf := dchecker.GetConfig() + defer func() { + dchecker.SetConfig(oldConf) + }() + dchecker.SetConfig(&policy.AuthConfig{ + ConsoleOpen: true, + ConsoleStrict: true, + }) + + _, err = dchecker.CheckConsolePermission(authCtx) t.Logf("%+v", err) assert.Error(t, err, "Should be verify fail") }) @@ -693,7 +714,6 @@ func Test_DefaultAuthChecker_CheckConsolePermission_Read_NoStrict(t *testing.T) authCtx := model.NewAcquireContext( model.WithRequestContext(ctx), model.WithMethod("Test_DefaultAuthChecker_VerifyCredential"), - // model.WithToken(""), model.WithOperation(model.Read), model.WithModule(model.DiscoverModule), model.WithAccessResources(map[apisecurity.ResourceType][]model.ResourceEntry{ @@ -812,6 +832,15 @@ func Test_DefaultAuthChecker_CheckConsolePermission_Read_Strict(t *testing.T) { t.Fatal(err) } checker := svr.GetAuthChecker() + dchecker := checker.(*policy.DefaultAuthChecker) + oldConf := dchecker.GetConfig() + defer func() { + dchecker.SetConfig(oldConf) + }() + dchecker.SetConfig(&policy.AuthConfig{ + ConsoleOpen: true, + ConsoleStrict: true, + }) _ = cacheMgr.TestUpdate() @@ -1016,7 +1045,7 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { ConsoleOpen: true, ClientOpen: true, Strict: false, - ConsoleStrict: true, + ConsoleStrict: false, ClientStrict: false, }, authChecker.GetOptions()) }) @@ -1044,9 +1073,9 @@ func Test_DefaultAuthChecker_Initialize(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &policy.AuthConfig{ ConsoleOpen: true, + ConsoleStrict: false, ClientOpen: true, Strict: false, - ConsoleStrict: true, }, authChecker.GetOptions()) }) diff --git a/auth/policy/inteceptor/auth/server.go b/auth/policy/inteceptor/auth/server.go index 676ee3d2d..be4812d06 100644 --- a/auth/policy/inteceptor/auth/server.go +++ b/auth/policy/inteceptor/auth/server.go @@ -157,8 +157,7 @@ func (svr *Server) verifyAuth(ctx context.Context, isWrite bool, // i. 如果当前只是一个数据的读取操作,则放通 // ii. 如果当前是一个数据的写操作,则只能允许处于正常的 token 进行操作 if err := svr.userSvr.CheckCredential(authCtx); err != nil { - log.Error("[Auth][Server] verify auth token", utils.ZapRequestID(reqId), - zap.Error(err)) + log.Error("[Auth][Server] verify auth token", utils.ZapRequestID(reqId), zap.Error(err)) return nil, api.NewAuthResponse(apimodel.Code_AuthTokenForbidden) } diff --git a/auth/policy/server.go b/auth/policy/server.go index 020c74e4a..804be3123 100644 --- a/auth/policy/server.go +++ b/auth/policy/server.go @@ -56,10 +56,10 @@ func DefaultAuthConfig() *AuthConfig { return &AuthConfig{ // 针对控制台接口,默认开启鉴权操作 ConsoleOpen: true, + // 这里默认开启 OpenAPI 的强 Token 检查模式 + ConsoleStrict: false, // 针对客户端接口,默认不开启鉴权操作 ClientOpen: false, - // 这里默认开启 OpenAPI 的强 Token 检查模式 - ConsoleStrict: true, // 客户端接口默认不开启 token 强检查模式 ClientStrict: false, } diff --git a/auth/policy/strategy.go b/auth/policy/strategy.go index faa42e6b0..3640b6862 100644 --- a/auth/policy/strategy.go +++ b/auth/policy/strategy.go @@ -65,8 +65,7 @@ var ( // handleCreateStrategy 创建鉴权策略 func (svr *Server) handleCreateStrategy(ctx context.Context, req *apisecurity.AuthStrategy) *apiservice.Response { requestID := utils.ParseRequestID(ctx) - ownerId := utils.ParseOwnerID(ctx) - req.Owner = utils.NewStringValue(ownerId) + req.Owner = utils.NewStringValue(utils.ParseOwnerID(ctx)) if checkErrResp := svr.checkCreateStrategy(req); checkErrResp != nil { return checkErrResp @@ -343,7 +342,7 @@ func (svr *Server) handleGetStrategy(ctx context.Context, req *apisecurity.AuthS group := &apisecurity.UserGroup{ Id: wrapperspb.String(principal.PrincipalID), } - if svr.userSvr.GetUserHelper().CheckUserInGroup(context.TODO(), group, curUser) { + if svr.userSvr.GetUserHelper().CheckUserInGroup(ctx, group, curUser) { canView = true break } @@ -393,7 +392,7 @@ func (svr *Server) handleGetPrincipalResources(ctx context.Context, query map[st // 找这个用户所关联的用户组 if model.PrincipalType(principalRole) == model.PrincipalUser { - groups := svr.userSvr.GetUserHelper().GetUserOwnGroup(context.TODO(), &apisecurity.User{ + groups := svr.userSvr.GetUserHelper().GetUserOwnGroup(ctx, &apisecurity.User{ Id: wrapperspb.String(principalId), }) for i := range groups { diff --git a/auth/policy/strategy_test.go b/auth/policy/strategy_test.go index 68daba4da..4ce9a9934 100644 --- a/auth/policy/strategy_test.go +++ b/auth/policy/strategy_test.go @@ -31,9 +31,11 @@ import ( "github.com/polarismesh/polaris/auth" "github.com/polarismesh/polaris/auth/policy" + defaultuser "github.com/polarismesh/polaris/auth/user" "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/model" "github.com/polarismesh/polaris/common/utils" storemock "github.com/polarismesh/polaris/store/mock" @@ -57,7 +59,7 @@ type StrategyTest struct { cacheMgn *cache.CacheManager checker auth.AuthChecker - svr *policy.Server + svr auth.StrategyServer cancel context.CancelFunc @@ -66,6 +68,7 @@ type StrategyTest struct { func newStrategyTest(t *testing.T) *StrategyTest { reset(false) + eventhub.InitEventHub() ctrl := gomock.NewController(t) @@ -98,7 +101,7 @@ func newStrategyTest(t *testing.T) *StrategyTest { if err != nil { t.Fatal(err) } - _ = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ + err = cacheMgn.OpenResourceCache([]cachetypes.ConfigEntry{ { Name: cachetypes.ServiceName, Option: map[string]interface{}{ @@ -110,26 +113,48 @@ func newStrategyTest(t *testing.T) *StrategyTest { Name: cachetypes.InstanceName, }, { - Name: cachetypes.UsersName, + Name: cachetypes.NamespaceName, }, { - Name: cachetypes.StrategyRuleName, + Name: cachetypes.UsersName, }, { - Name: cachetypes.NamespaceName, + Name: cachetypes.StrategyRuleName, }, }...) - + if err != nil { + t.Fatal(err) + } + if err := cache.TestRun(ctx, cacheMgn); err != nil { + t.Fatal(err) + } _ = cacheMgn.TestUpdate() - checker := &policy.DefaultAuthChecker{} - checker.Initialize(&policy.AuthConfig{ - ConsoleOpen: true, - ClientOpen: true, - ConsoleStrict: false, - ClientStrict: false, - }, storage, cacheMgn, nil) - checker.SetCacheMgr(cacheMgn) + _, proxySvr, err := defaultuser.BuildServer() + if err != nil { + t.Fatal(err) + } + proxySvr.Initialize(&auth.Config{ + User: &auth.UserConfig{ + Name: auth.DefaultUserMgnPluginName, + Option: map[string]interface{}{ + "salt": "polarismesh@2021", + }, + }, + }, storage, cacheMgn) + + _, svr, err := newPolicyServer() + if err != nil { + t.Fatal(err) + } + if err := svr.Initialize(&auth.Config{ + Strategy: &auth.StrategyConfig{ + Name: auth.DefaultPolicyPluginName, + }, + }, storage, cacheMgn, proxySvr); err != nil { + t.Fatal(err) + } + checker := svr.GetAuthChecker() t.Cleanup(func() { cacheMgn.Close() @@ -153,6 +178,8 @@ func newStrategyTest(t *testing.T) *StrategyTest { cancel: cancel, + svr: svr, + ctrl: ctrl, } } @@ -167,6 +194,8 @@ func Test_GetPrincipalResources(t *testing.T) { strategyTest := newStrategyTest(t) defer strategyTest.Clean() + _ = strategyTest.cacheMgn.TestUpdate() + valCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, strategyTest.users[1].Token) ret := strategyTest.svr.GetPrincipalResources(valCtx, map[string]string{ @@ -177,7 +206,7 @@ func Test_GetPrincipalResources(t *testing.T) { t.Logf("GetPrincipalResources resp : %+v", ret) assert.EqualValues(t, api.ExecuteSuccess, ret.Code.GetValue(), "need query success") resources := ret.Resources - assert.Equal(t, 2, len(resources.Services), "need query 2 service resources") + assert.Equal(t, 2, len(resources.GetServices()), "need query 2 service resources") } func Test_CreateStrategy(t *testing.T) { @@ -185,6 +214,8 @@ func Test_CreateStrategy(t *testing.T) { strategyTest := newStrategyTest(t) defer strategyTest.Clean() + _ = strategyTest.cacheMgn.TestUpdate() + t.Run("正常创建鉴权策略", func(t *testing.T) { strategyTest.storage.EXPECT().AddStrategy(gomock.Any()).Return(nil) @@ -328,6 +359,8 @@ func Test_UpdateStrategy(t *testing.T) { strategyTest := newStrategyTest(t) defer strategyTest.Clean() + _ = strategyTest.cacheMgn.TestUpdate() + t.Run("正常更新鉴权策略", func(t *testing.T) { strategyTest.storage.EXPECT().GetStrategyDetail(gomock.Any()).Return(strategyTest.strategies[0], nil) strategyTest.storage.EXPECT().UpdateStrategy(gomock.Any()).Return(nil) @@ -549,8 +582,9 @@ func Test_DeleteStrategy(t *testing.T) { strategyTest := newStrategyTest(t) defer strategyTest.Clean() - t.Run("正常删除鉴权策略", func(t *testing.T) { + _ = strategyTest.cacheMgn.TestUpdate() + t.Run("正常删除鉴权策略", func(t *testing.T) { index := rand.Intn(len(strategyTest.strategies)) strategyTest.storage.EXPECT().GetStrategyDetail(gomock.Any()).Return(strategyTest.strategies[index], nil) @@ -907,16 +941,15 @@ func Test_AuthServer_NormalOperateStrategy(t *testing.T) { t.Run("正常更新用户Token", func(t *testing.T) { resp := suit.UserServer().ResetUserToken(suit.DefaultCtx, users[0]) - - if !respSuccess(resp) { + if !api.IsSuccess(resp) { t.Fatal(resp.GetInfo().GetValue()) } _ = suit.CacheMgr().TestUpdate() qresp := suit.UserServer().GetUserToken(suit.DefaultCtx, users[0]) - if !respSuccess(qresp) { - t.Fatal(resp.GetInfo().GetValue()) + if !api.IsSuccess(qresp) { + t.Fatal(qresp.String()) } assert.Equal(t, resp.GetUser().GetAuthToken().GetValue(), qresp.GetUser().GetAuthToken().GetValue()) }) diff --git a/auth/user/helper.go b/auth/user/helper.go index 7fd203b9c..b55c2c69b 100644 --- a/auth/user/helper.go +++ b/auth/user/helper.go @@ -21,6 +21,7 @@ import ( "context" "errors" + "github.com/polarismesh/polaris/common/utils" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" "go.uber.org/zap" ) @@ -88,14 +89,11 @@ func (helper *DefaultUserHelper) GetUser(ctx context.Context, user *apisecurity. if user.GetName().GetValue() == "" { return cacheMgr.User().GetUserByID(user.GetId().GetValue()).ToSpec() } - owner := "" - if user.GetOwner().GetValue() != "" { - ownerUser := cacheMgr.User().GetUserByID(user.GetOwner().GetValue()) - if ownerUser != nil { - owner = ownerUser.ID - } + owner := cacheMgr.User().GetUserByID(utils.ParseUserID(ctx)) + if owner == nil { + return nil } - return cacheMgr.User().GetUserByName(user.GetName().GetValue(), owner).ToSpec() + return cacheMgr.User().GetUserByName(user.GetName().GetValue(), owner.Name).ToSpec() } func (helper *DefaultUserHelper) GetUserByID(ctx context.Context, id string) *apisecurity.User { diff --git a/auth/user/inteceptor/auth/server.go b/auth/user/inteceptor/auth/server.go index 73d9ee3fe..fa37c4b54 100644 --- a/auth/user/inteceptor/auth/server.go +++ b/auth/user/inteceptor/auth/server.go @@ -169,10 +169,15 @@ func (svr *Server) UpdateUserToken(ctx context.Context, user *apisecurity.User) return rsp } helper := svr.GetUserHelper() - targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + targetUser := helper.GetUser(ctx, user) if !checkUserViewPermission(ctx, targetUser) { return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) } + if authcommon.ParseUserRole(ctx) != model.AdminUserRole { + if targetUser.GetUserType().GetValue() != strconv.Itoa(int(model.SubAccountUserRole)) { + return api.NewUserResponseWithMsg(apimodel.Code_NotAllowedAccess, "only disable sub-account token", user) + } + } return svr.nextSvr.UpdateUserToken(ctx, user) } @@ -333,11 +338,10 @@ func (svr *Server) ResetGroupToken(ctx context.Context, group *apisecurity.UserG // verifyAuth 用于 user、group 以及 strategy 模块的鉴权工作检查 func (svr *Server) verifyAuth(ctx context.Context, isWrite bool, needOwner bool) (context.Context, *apiservice.Response) { - reqId := utils.ParseRequestID(ctx) authToken := utils.ParseAuthToken(ctx) if authToken == "" { - log.Error("[Auth][Server] auth token is empty", utils.ZapRequestID(reqId)) + log.Error("[Auth][Server] auth token is empty", utils.RequestID(ctx)) return nil, api.NewAuthResponse(apimodel.Code_EmptyAutToken) } @@ -351,31 +355,30 @@ func (svr *Server) verifyAuth(ctx context.Context, isWrite bool, // i. 如果当前只是一个数据的读取操作,则放通 // ii. 如果当前是一个数据的写操作,则只能允许处于正常的 token 进行操作 if err := svr.CheckCredential(authCtx); err != nil { - log.Error("[Auth][Server] verify auth token", utils.ZapRequestID(reqId), - zap.Error(err)) + log.Error("[Auth][Server] verify auth token", utils.RequestID(ctx), zap.Error(err)) return nil, api.NewAuthResponse(apimodel.Code_AuthTokenForbidden) } attachVal, exist := authCtx.GetAttachment(model.TokenDetailInfoKey) if !exist { - log.Error("[Auth][Server] token detail info not exist", utils.ZapRequestID(reqId)) + log.Error("[Auth][Server] token detail info not exist", utils.RequestID(ctx)) return nil, api.NewAuthResponse(apimodel.Code_TokenNotExisted) } operateInfo := attachVal.(auth.OperatorInfo) if isWrite && operateInfo.Disable { - log.Error("[Auth][Server] token is disabled", utils.ZapRequestID(reqId), + log.Error("[Auth][Server] token is disabled", utils.RequestID(ctx), zap.String("operation", authCtx.GetMethod())) return nil, api.NewAuthResponse(apimodel.Code_TokenDisabled) } if !operateInfo.IsUserToken { - log.Error("[Auth][Server] only user role can access this API", utils.ZapRequestID(reqId)) + log.Error("[Auth][Server] only user role can access this API", utils.RequestID(ctx)) return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) } if needOwner && auth.IsSubAccount(operateInfo) { - log.Error("[Auth][Server] only admin/owner account can access this API", utils.ZapRequestID(reqId)) + log.Error("[Auth][Server] only admin/owner account can access this API", utils.RequestID(ctx)) return nil, api.NewAuthResponse(apimodel.Code_OperationRoleForbidden) } diff --git a/auth/user/user.go b/auth/user/user.go index e7d737113..e04453830 100644 --- a/auth/user/user.go +++ b/auth/user/user.go @@ -73,7 +73,6 @@ func (svr *Server) CreateUsers(ctx context.Context, req []*apisecurity.User) *ap // CreateUser 创建用户 func (svr *Server) CreateUser(ctx context.Context, req *apisecurity.User) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) ownerID := utils.ParseOwnerID(ctx) req.Owner = utils.NewStringValue(ownerID) @@ -89,13 +88,12 @@ func (svr *Server) CreateUser(ctx context.Context, req *apisecurity.User) *apise if ownerID != "" { owner, err := svr.storage.GetUser(ownerID) if err != nil { - log.Error("[Auth][User] get owner user", utils.ZapRequestID(requestID), zap.Error(err), - zap.String("owner", ownerID)) + log.Error("[Auth][User] get owner user", utils.RequestID(ctx), zap.Error(err), zap.String("owner", ownerID)) return api.NewUserResponse(commonstore.StoreCode2APICode(err), req) } if owner.Name == req.Name.GetValue() { - log.Error("[Auth][User] create user name is equal owner", utils.ZapRequestID(requestID), + log.Error("[Auth][User] create user name is equal owner", utils.RequestID(ctx), zap.Error(err), zap.String("name", req.GetName().GetValue())) return api.NewUserResponse(apimodel.Code_UserExisted, req) } @@ -104,7 +102,7 @@ func (svr *Server) CreateUser(ctx context.Context, req *apisecurity.User) *apise // 只有通过 owner + username 才能唯一确定一个用户 user, err := svr.storage.GetUserByName(req.Name.GetValue(), ownerID) if err != nil { - log.Error("[Auth][User] get user by name and owner", utils.ZapRequestID(requestID), + log.Error("[Auth][User] get user by name and owner", utils.RequestID(ctx), zap.Error(err), zap.String("owner", ownerID), zap.String("name", req.GetName().GetValue())) return api.NewUserResponse(commonstore.StoreCode2APICode(err), req) } @@ -116,22 +114,19 @@ func (svr *Server) CreateUser(ctx context.Context, req *apisecurity.User) *apise } func (svr *Server) createUser(ctx context.Context, req *apisecurity.User) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) - data, err := svr.createUserModel(req, authcommon.ParseUserRole(ctx)) if err != nil { - log.Error("[Auth][User] create user model", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] create user model", utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponse(apimodel.Code_ExecuteException) } if err := svr.storage.AddUser(data); err != nil { - log.Error("[Auth][User] add user into store", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] add user into store", utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponse(commonstore.StoreCode2APICode(err)) } - log.Info("[Auth][User] create user", utils.ZapRequestID(requestID), - zap.String("name", req.Name.GetValue())) + log.Info("[Auth][User] create user", utils.RequestID(ctx), zap.String("name", req.GetName().GetValue())) svr.RecordHistory(userRecordEntry(ctx, req, data, model.OCreate)) // 去除 owner 信息 @@ -142,16 +137,13 @@ func (svr *Server) createUser(ctx context.Context, req *apisecurity.User) *apise // UpdateUser 更新用户信息,仅能修改 comment 以及账户密码 func (svr *Server) UpdateUser(ctx context.Context, req *apisecurity.User) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) - if checkErrResp := checkUpdateUser(req); checkErrResp != nil { return checkErrResp } user, err := svr.storage.GetUser(req.Id.GetValue()) if err != nil { - log.Error("[Auth][User] get user", utils.ZapRequestID(requestID), - zap.String("user-id", req.Id.GetValue()), zap.Error(err)) + log.Error("[Auth][User] get user", utils.RequestID(ctx), zap.String("user-id", req.GetId().GetValue()), zap.Error(err)) return api.NewUserResponse(commonstore.StoreCode2APICode(err), req) } if user == nil { @@ -164,19 +156,16 @@ func (svr *Server) UpdateUser(ctx context.Context, req *apisecurity.User) *apise } if !needUpdate { - log.Info("[Auth][User] update user data no change, no need update", - utils.ZapRequestID(requestID), zap.String("user", req.String())) + log.Info("[Auth][User] update user data no change, no need update", utils.RequestID(ctx), zap.String("user", req.String())) return api.NewUserResponse(apimodel.Code_NoNeedUpdate, req) } if err := svr.storage.UpdateUser(data); err != nil { - log.Error("[Auth][User] update user from store", utils.ZapRequestID(requestID), - zap.Error(err)) + log.Error("[Auth][User] update user from store", utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponseWithMsg(commonstore.StoreCode2APICode(err), err.Error()) } - log.Info("[Auth][User] update user", utils.ZapRequestID(requestID), - zap.String("name", req.Name.GetValue())) + log.Info("[Auth][User] update user", utils.RequestID(ctx), zap.String("name", req.GetName().GetValue())) svr.RecordHistory(userRecordEntry(ctx, req, user, model.OUpdate)) return api.NewUserResponse(apimodel.Code_ExecuteSuccess, req) @@ -184,11 +173,9 @@ func (svr *Server) UpdateUser(ctx context.Context, req *apisecurity.User) *apise // UpdateUserPassword 更新用户密码信息 func (svr *Server) UpdateUserPassword(ctx context.Context, req *apisecurity.ModifyUserPassword) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) - user, err := svr.storage.GetUser(req.Id.GetValue()) if err != nil { - log.Error("[Auth][User] get user", utils.ZapRequestID(requestID), + log.Error("[Auth][User] get user", utils.RequestID(ctx), zap.String("user-id", req.Id.GetValue()), zap.Error(err)) return api.NewAuthResponse(commonstore.StoreCode2APICode(err)) } @@ -207,18 +194,17 @@ func (svr *Server) UpdateUserPassword(ctx context.Context, req *apisecurity.Modi if !needUpdate { log.Info("[Auth][User] update user password no change, no need update", - utils.ZapRequestID(requestID), zap.String("user", req.GetId().GetValue())) + utils.RequestID(ctx), zap.String("user", req.GetId().GetValue())) return api.NewAuthResponse(apimodel.Code_NoNeedUpdate) } if err := svr.storage.UpdateUser(data); err != nil { - log.Error("[Auth][User] update user from store", utils.ZapRequestID(requestID), + log.Error("[Auth][User] update user from store", utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponse(commonstore.StoreCode2APICode(err)) } - log.Info("[Auth][User] update user", utils.ZapRequestID(requestID), - zap.String("user-id", req.Id.GetValue())) + log.Info("[Auth][User] update user", utils.RequestID(ctx), zap.String("user-id", req.Id.GetValue())) return api.NewAuthResponse(apimodel.Code_ExecuteSuccess) } @@ -364,36 +350,28 @@ func (svr *Server) GetUserToken(ctx context.Context, req *apisecurity.User) *api // UpdateUserToken 更新用户 token func (svr *Server) UpdateUserToken(ctx context.Context, req *apisecurity.User) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) if checkErrResp := checkUpdateUser(req); checkErrResp != nil { return checkErrResp } - user, err := svr.storage.GetUser(req.Id.GetValue()) + user, err := svr.storage.GetUser(req.GetId().GetValue()) if err != nil { - log.Error("[Auth][User] get user from store", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] get user from store", utils.RequestID(ctx), zap.Error(err)) return api.NewUserResponse(commonstore.StoreCode2APICode(err), req) } if user == nil { return api.NewUserResponse(apimodel.Code_NotFoundUser, req) } - if authcommon.ParseUserRole(ctx) != model.AdminUserRole { - if user.Type != model.SubAccountUserRole { - return api.NewUserResponseWithMsg(apimodel.Code_NotAllowedAccess, "only disable sub-account token", req) - } - } - - user.TokenEnable = req.TokenEnable.GetValue() + user.TokenEnable = req.GetTokenEnable().GetValue() if err := svr.storage.UpdateUser(user); err != nil { - log.Error("[Auth][User] update user token into store", - utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] update user token into store", utils.RequestID(ctx), zap.Error(err)) return api.NewAuthResponseWithMsg(commonstore.StoreCode2APICode(err), err.Error()) } - log.Info("[Auth][User] update user token", utils.ZapRequestID(requestID), - zap.String("id", req.Id.GetValue()), zap.Bool("enable", req.TokenEnable.GetValue())) + log.Info("[Auth][User] update user token", utils.RequestID(ctx), + zap.String("id", req.GetId().GetValue()), zap.Bool("enable", req.GetTokenEnable().GetValue())) svr.RecordHistory(userRecordEntry(ctx, req, user, model.OUpdateToken)) return api.NewUserResponse(apimodel.Code_ExecuteSuccess, req) @@ -401,14 +379,13 @@ func (svr *Server) UpdateUserToken(ctx context.Context, req *apisecurity.User) * // ResetUserToken 重置用户 token func (svr *Server) ResetUserToken(ctx context.Context, req *apisecurity.User) *apiservice.Response { - requestID := utils.ParseRequestID(ctx) if checkErrResp := checkUpdateUser(req); checkErrResp != nil { return checkErrResp } user, err := svr.storage.GetUser(req.Id.GetValue()) if err != nil { - log.Error("[Auth][User] get user from store", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] get user from store", utils.RequestID(ctx), zap.Error(err)) return api.NewUserResponse(commonstore.StoreCode2APICode(err), req) } if user == nil { @@ -417,19 +394,18 @@ func (svr *Server) ResetUserToken(ctx context.Context, req *apisecurity.User) *a newToken, err := createUserToken(user.ID, svr.authOpt.Salt) if err != nil { - log.Error("[Auth][User] update user token", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] update user token", utils.RequestID(ctx), zap.Error(err)) return api.NewUserResponse(apimodel.Code_ExecuteException, req) } user.Token = newToken if err := svr.storage.UpdateUser(user); err != nil { - log.Error("[Auth][User] update user token into store", utils.ZapRequestID(requestID), zap.Error(err)) + log.Error("[Auth][User] update user token into store", utils.RequestID(ctx), zap.Error(err)) return api.NewUserResponse(commonstore.StoreCode2APICode(err), req) } - log.Info("[Auth][User] reset user token", utils.ZapRequestID(requestID), - zap.String("id", req.Id.GetValue())) + log.Info("[Auth][User] reset user token", utils.RequestID(ctx), zap.String("id", req.GetId().GetValue())) svr.RecordHistory(userRecordEntry(ctx, req, user, model.OUpdateToken)) req.AuthToken = utils.NewStringValue(user.Token) @@ -443,13 +419,11 @@ func (svr *Server) ResetUserToken(ctx context.Context, req *apisecurity.User) *a // step 3. 兜底措施:如果开启了鉴权的非严格模式,则根据错误的类型,判断是否转为匿名用户进行访问 // - 如果是访问权限控制相关模块(用户、用户组、权限策略),不得转为匿名用户 func (svr *Server) CheckCredential(authCtx *model.AcquireContext) error { - reqId := utils.ParseRequestID(authCtx.GetRequestContext()) - checkErr := func() error { authToken := utils.ParseAuthToken(authCtx.GetRequestContext()) operator, err := svr.decodeToken(authToken) if err != nil { - log.Error("[Auth][Checker] decode token", zap.Error(err)) + log.Error("[Auth][Checker] decode token", utils.RequestID(authCtx.GetRequestContext()), zap.Error(err)) return model.ErrorTokenInvalid } @@ -467,7 +441,7 @@ func (svr *Server) CheckCredential(authCtx *model.AcquireContext) error { authCtx.SetRequestContext(ctx) svr.parseOperatorInfo(operator, authCtx) if operator.Disable { - log.Warn("[Auth][Checker] token already disabled", utils.ZapRequestID(reqId), + log.Warn("[Auth][Checker] token already disabled", utils.RequestID(authCtx.GetRequestContext()), zap.Any("token", operator.String())) } return nil @@ -477,7 +451,7 @@ func (svr *Server) CheckCredential(authCtx *model.AcquireContext) error { if !canDowngradeAnonymous(authCtx, checkErr) { return checkErr } - log.Warn("[Auth][Checker] parse operator info, downgrade to anonymous", utils.ZapRequestID(reqId), + log.Warn("[Auth][Checker] parse operator info, downgrade to anonymous", utils.RequestID(authCtx.GetRequestContext()), zap.Error(checkErr)) // 操作者信息解析失败,降级为匿名用户 authCtx.SetAttachment(model.TokenDetailInfoKey, auth.NewAnonymous()) @@ -663,13 +637,7 @@ func updateUserPasswordAttribute( } needUpdate = true user.Password = string(pwd) - // newToken, err := createUserToken(user.ID) - // if err != nil { - // return nil, false, err - // } - // user.Token = newToken } - return user, needUpdate, nil } diff --git a/auth/user/user_test.go b/auth/user/user_test.go index 218ae6e1a..af0a5d533 100644 --- a/auth/user/user_test.go +++ b/auth/user/user_test.go @@ -59,7 +59,6 @@ type UserTest struct { } func newUserTest(t *testing.T) *UserTest { - reset(false) ctrl := gomock.NewController(t) commonlog.GetScopeOrDefaultByName(commonlog.AuthLoggerName).SetOutputLevel(commonlog.DebugLevel) @@ -118,6 +117,9 @@ func newUserTest(t *testing.T) *UserTest { }, }, }, storage, cacheMgn) + + _ = cacheMgn.TestUpdate() + return &UserTest{ admin: admin, ownerOne: users[0], @@ -144,9 +146,7 @@ func (g *UserTest) Clean() { } func Test_server_CreateUsers(t *testing.T) { - userTest := newUserTest(t) - defer userTest.Clean() t.Run("主账户创建账户-成功", func(t *testing.T) { @@ -825,32 +825,37 @@ func Test_server_RefreshUserToken(t *testing.T) { } func Test_server_UpdateUserToken(t *testing.T) { - - userTest := newUserTest(t) - defer userTest.Clean() - t.Run("主账户刷新自己的Token状态", func(t *testing.T) { + userTest := newUserTest(t) + defer userTest.Clean() + _ = userTest.cacheMgn.TestUpdate() 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).AnyTimes() - resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ - Id: utils.NewStringValue(userTest.users[0].ID), + Id: utils.NewStringValue(userTest.ownerOne.ID), }) assert.True(t, resp.GetCode().Value == api.NotAllowedAccess, resp.Info.GetValue()) }) t.Run("子账户刷新自己的Token状态", func(t *testing.T) { + userTest := newUserTest(t) + defer userTest.Clean() + _ = userTest.cacheMgn.TestUpdate() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[4].Token) + + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(&model.User{}, nil).AnyTimes() + userTest.storage.EXPECT().UpdateUser(gomock.Any()).Return(nil).AnyTimes() + resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.users[4].ID), }) - assert.True(t, resp.GetCode().Value == api.OperationRoleException, resp.Info.GetValue()) + assert.True(t, resp.GetCode().Value == api.ExecuteSuccess, resp.Info.GetValue()) }) t.Run("主账户刷新子账户的Token状态", func(t *testing.T) { + userTest := newUserTest(t) + defer userTest.Clean() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.ownerOne.Token) userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.users[3].ID)).Return(userTest.users[3], nil) resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ @@ -861,9 +866,11 @@ func Test_server_UpdateUserToken(t *testing.T) { }) t.Run("主账户刷新别的主账户的Token状态", func(t *testing.T) { + userTest := newUserTest(t) + defer userTest.Clean() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.ownerOne.Token) - t.Logf("operator-id : %s, user-two-owner : %s", userTest.ownerOne.ID, userTest.ownerTwo.Owner) + t.Logf("operator-id : %s, user-two-owner : %s", userTest.ownerOne.ID, userTest.ownerTwo.ID) userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerTwo.ID)).Return(userTest.ownerTwo, nil) resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ @@ -874,6 +881,10 @@ func Test_server_UpdateUserToken(t *testing.T) { }) t.Run("主账户刷新不属于自己子账户的Token状态", func(t *testing.T) { + userTest := newUserTest(t) + defer userTest.Clean() + + _ = userTest.cacheMgn.TestUpdate() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.ownerOne.Token) userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.newUsers[3].ID)).Return(userTest.newUsers[3], nil) resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ diff --git a/cache/namespace/namespace.go b/cache/namespace/namespace.go index cf8bd5e34..1447ddb36 100644 --- a/cache/namespace/namespace.go +++ b/cache/namespace/namespace.go @@ -41,7 +41,6 @@ type namespaceCache struct { storage store.Store ids *utils.SyncMap[string, *model.Namespace] updater *singleflight.Group - // exportNamespace 某个命名空间下的所有服务的可见性 exportNamespace *utils.SyncMap[string, *utils.SyncSet[string]] } @@ -154,7 +153,7 @@ func (nsCache *namespaceCache) GetVisibleNamespaces(namespace string) []*model.N return values } -// clear +// Clear . func (nsCache *namespaceCache) Clear() error { nsCache.BaseCache.Clear() nsCache.ids = utils.NewSyncMap[string, *model.Namespace]() @@ -162,34 +161,21 @@ func (nsCache *namespaceCache) Clear() error { return nil } -// name -// -// @return string +// Name . func (nsCache *namespaceCache) Name() string { return types.NamespaceName } -// GetNamespace -// -// @receiver nsCache -// @param id -// @return *model.Namespace +// GetNamespace get namespace by id func (nsCache *namespaceCache) GetNamespace(id string) *model.Namespace { val, ok := nsCache.ids.Load(id) - if !ok { return nil } - return val } -// GetNamespacesByName -// -// @receiver nsCache -// @param names -// @return []*model.Namespace -// @return error +// GetNamespacesByName batch get namespace by name func (nsCache *namespaceCache) GetNamespacesByName(names []string) []*model.Namespace { nsArr := make([]*model.Namespace, 0, len(names)) for _, name := range names { diff --git a/common/model/auth.go b/common/model/auth.go index eef58fb8e..afb94a1e3 100644 --- a/common/model/auth.go +++ b/common/model/auth.go @@ -225,6 +225,9 @@ type User struct { } func (u *User) ToSpec() *apisecurity.User { + if u == nil { + return nil + } return &apisecurity.User{ Id: wrapperspb.String(u.ID), Name: wrapperspb.String(u.Name), diff --git a/plugin/healthchecker/leader/peer.go b/plugin/healthchecker/leader/peer.go index 443b6523f..f077f8aeb 100644 --- a/plugin/healthchecker/leader/peer.go +++ b/plugin/healthchecker/leader/peer.go @@ -310,6 +310,8 @@ func (p *RemotePeer) checkLeaderAlive(ctx context.Context) { select { case <-ctx.Done(): ticker.Stop() + plog.Error("check leader alive job stop", zap.String("host", p.Host()), zap.Uint32("port", p.port)) + return case <-ticker.C: var errCount int for i := 0; i < maxCheckCount; i++ { @@ -320,7 +322,7 @@ func (p *RemotePeer) checkLeaderAlive(ctx context.Context) { } } if errCount >= errCountThreshold { - log.Warn("[Health Check][Leader] leader peer not alive, set leader is dead", zap.String("host", p.Host()), + plog.Warn("[Health Check][Leader] leader peer not alive, set leader is dead", zap.String("host", p.Host()), zap.Uint32("port", p.port)) atomic.StoreInt32(&p.leaderAlive, 0) } else { From f2d9fbdd5e018aefa4b99cb33f713273a13b7a2d Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Fri, 14 Jun 2024 02:21:20 +0800 Subject: [PATCH 13/23] feat:support nacos-address server endpoints --- apiserver/httpserver/utils/handler_test.go | 2 +- auth/user/helper.go | 9 +- auth/user/inteceptor/auth/server.go | 21 +++- auth/user/user_test.go | 16 +-- common/utils/common.go | 2 +- .../paramcheck/config_file_check.go | 4 +- .../paramcheck/config_file_group_check.go | 4 +- .../paramcheck/config_file_release_check.go | 4 +- .../config_file_release_history_check.go | 4 +- service/interceptor/paramcheck/check.go | 114 +++++++++++++----- service/interceptor/paramcheck/client.go | 20 ++- store/mysql/lane.go | 17 +++ store/mysql/service_contract.go | 2 +- 13 files changed, 164 insertions(+), 55 deletions(-) diff --git a/apiserver/httpserver/utils/handler_test.go b/apiserver/httpserver/utils/handler_test.go index 8b4d69108..2df994fa9 100644 --- a/apiserver/httpserver/utils/handler_test.go +++ b/apiserver/httpserver/utils/handler_test.go @@ -19,7 +19,6 @@ package utils import ( "fmt" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "strings" @@ -28,6 +27,7 @@ import ( "github.com/emicklei/go-restful/v3" "github.com/golang/protobuf/ptypes/wrappers" apimodel "github.com/polarismesh/specification/source/go/api/v1/model" + "github.com/stretchr/testify/assert" "github.com/polarismesh/polaris/apiserver/httpserver/i18n" api "github.com/polarismesh/polaris/common/api/v1" diff --git a/auth/user/helper.go b/auth/user/helper.go index b55c2c69b..dcb55418b 100644 --- a/auth/user/helper.go +++ b/auth/user/helper.go @@ -21,9 +21,10 @@ import ( "context" "errors" - "github.com/polarismesh/polaris/common/utils" apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" "go.uber.org/zap" + + "github.com/polarismesh/polaris/common/utils" ) var ( @@ -98,7 +99,11 @@ func (helper *DefaultUserHelper) GetUser(ctx context.Context, user *apisecurity. func (helper *DefaultUserHelper) GetUserByID(ctx context.Context, id string) *apisecurity.User { cacheMgr := helper.svr.cacheMgr - return cacheMgr.User().GetUserByID(id).ToSpec() + saveUser := cacheMgr.User().GetUserByID(id) + if saveUser == nil { + saveUser, _ = helper.svr.storage.GetUser(id) + } + return saveUser.ToSpec() } // GetGroup 查询用户组信息 diff --git a/auth/user/inteceptor/auth/server.go b/auth/user/inteceptor/auth/server.go index fa37c4b54..38171005b 100644 --- a/auth/user/inteceptor/auth/server.go +++ b/auth/user/inteceptor/auth/server.go @@ -102,6 +102,9 @@ func (svr *Server) UpdateUser(ctx context.Context, user *apisecurity.User) *apis } helper := svr.GetUserHelper() targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + if targetUser == nil { + return api.NewAuthResponse(apimodel.Code_NotFoundUser) + } if !checkUserViewPermission(ctx, targetUser) { return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) } @@ -116,6 +119,9 @@ func (svr *Server) UpdateUserPassword(ctx context.Context, req *apisecurity.Modi } helper := svr.GetUserHelper() targetUser := helper.GetUserByID(ctx, req.GetId().GetValue()) + if targetUser == nil { + return api.NewAuthResponse(apimodel.Code_NotFoundUser) + } if !checkUserViewPermission(ctx, targetUser) { return api.NewAuthResponse(apimodel.Code_NotAllowedAccess) } @@ -130,6 +136,18 @@ func (svr *Server) DeleteUsers(ctx context.Context, users []*apisecurity.User) * api.Collect(resp, rsp) return resp } + for i := range users { + user := users[i] + helper := svr.GetUserHelper() + targetUser := helper.GetUserByID(ctx, user.GetId().GetValue()) + // 已经删除的用户没必要在删除一次 + if targetUser == nil { + continue + } + if !checkUserViewPermission(ctx, targetUser) { + return api.NewAuthBatchWriteResponse(apimodel.Code_NotAllowedAccess) + } + } return svr.nextSvr.DeleteUsers(ctx, users) } @@ -406,7 +424,8 @@ func checkUserViewPermission(ctx context.Context, user *apisecurity.User) bool { zap.Any("user", user), zap.String("owner", user.GetOwner().GetValue()), zap.String("operator", userId)) return true } - + log.Warn("check user view permission", utils.RequestID(ctx), + zap.Any("user", user), zap.String("owner", user.GetOwner().GetValue()), zap.String("operator", userId)) return false } diff --git a/auth/user/user_test.go b/auth/user/user_test.go index af0a5d533..93219e528 100644 --- a/auth/user/user_test.go +++ b/auth/user/user_test.go @@ -394,7 +394,7 @@ func Test_server_UpdateUser(t *testing.T) { Comment: &wrappers.StringValue{Value: "update owner account info"}, } - userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[2], nil) + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[2], nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[1].Token) resp := userTest.svr.UpdateUser(reqCtx, req) @@ -562,7 +562,7 @@ func Test_server_UpdateUserPassword(t *testing.T) { NewPassword: &wrappers.StringValue{Value: "users[2].Password"}, } - userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[2], nil) + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.users[2], nil).AnyTimes() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[1].Token) resp := userTest.svr.UpdateUserPassword(reqCtx, req) @@ -605,7 +605,7 @@ func Test_server_DeleteUser(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ - &apisecurity.User{ + { Id: utils.NewStringValue(uid), }, }) @@ -623,7 +623,7 @@ func Test_server_DeleteUser(t *testing.T) { reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.users[0].Token) resp := userTest.svr.DeleteUsers(reqCtx, []*apisecurity.User{ - &apisecurity.User{ + { Id: utils.NewStringValue(userTest.users[1].ID), }, }) @@ -805,7 +805,7 @@ func Test_server_RefreshUserToken(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.Any()).Return(userTest.ownerTwo, nil) + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.ownerTwo, nil).AnyTimes() resp := userTest.svr.ResetUserToken(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.ownerTwo.ID), }) @@ -815,7 +815,7 @@ func Test_server_RefreshUserToken(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.Any()).Return(userTest.newUsers[1], nil) + userTest.storage.EXPECT().GetUser(gomock.Any()).Return(userTest.newUsers[1], nil).AnyTimes() resp := userTest.svr.ResetUserToken(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.newUsers[1].ID), }) @@ -872,7 +872,7 @@ func Test_server_UpdateUserToken(t *testing.T) { t.Logf("operator-id : %s, user-two-owner : %s", userTest.ownerOne.ID, userTest.ownerTwo.ID) - userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerTwo.ID)).Return(userTest.ownerTwo, nil) + userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.ownerTwo.ID)).Return(userTest.ownerTwo, nil).AnyTimes() resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.ownerTwo.ID), }) @@ -886,7 +886,7 @@ func Test_server_UpdateUserToken(t *testing.T) { _ = userTest.cacheMgn.TestUpdate() reqCtx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, userTest.ownerOne.Token) - userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.newUsers[3].ID)).Return(userTest.newUsers[3], nil) + userTest.storage.EXPECT().GetUser(gomock.Eq(userTest.newUsers[3].ID)).Return(userTest.newUsers[3], nil).AnyTimes() resp := userTest.svr.UpdateUserToken(reqCtx, &apisecurity.User{ Id: utils.NewStringValue(userTest.newUsers[3].ID), }) diff --git a/common/utils/common.go b/common/utils/common.go index 176fa1f05..d835e0d05 100644 --- a/common/utils/common.go +++ b/common/utils/common.go @@ -587,7 +587,7 @@ func CheckContractInterfaceTetrad(contractId string, source apiservice.Interface func CalculateContractID(namespace, service, name, protocol, version string) (string, error) { h := sha1.New() - str := fmt.Sprintf("%s##%s##%s##%s##%d", namespace, service, name, protocol, version) + str := fmt.Sprintf("%s##%s##%s##%s##%s", namespace, service, name, protocol, version) if _, err := io.WriteString(h, str); err != nil { return "", err diff --git a/config/interceptor/paramcheck/config_file_check.go b/config/interceptor/paramcheck/config_file_check.go index ba1f42d67..7790694ab 100644 --- a/config/interceptor/paramcheck/config_file_check.go +++ b/config/interceptor/paramcheck/config_file_check.go @@ -55,8 +55,8 @@ func (s *Server) SearchConfigFile(ctx context.Context, return out } searchFilters := map[string]string{ - "offset": strconv.Itoa(int(offset)), - "limit": strconv.Itoa(int(limit)), + "offset": strconv.FormatInt(int64(offset), 10), + "limit": strconv.FormatInt(int64(limit), 10), } for k, v := range filter { // 无效查询参数自动忽略 diff --git a/config/interceptor/paramcheck/config_file_group_check.go b/config/interceptor/paramcheck/config_file_group_check.go index 4dc649c10..fd8ec15f3 100644 --- a/config/interceptor/paramcheck/config_file_group_check.go +++ b/config/interceptor/paramcheck/config_file_group_check.go @@ -49,8 +49,8 @@ func (s *Server) QueryConfigFileGroups(ctx context.Context, } searchFilters := map[string]string{ - "offset": strconv.Itoa(int(offset)), - "limit": strconv.Itoa(int(limit)), + "offset": strconv.FormatInt(int64(offset), 10), + "limit": strconv.FormatInt(int64(limit), 10), } for k, v := range filter { if newK, ok := availableSearch["config_file_group"][k]; ok { diff --git a/config/interceptor/paramcheck/config_file_release_check.go b/config/interceptor/paramcheck/config_file_release_check.go index 6b1f93e6e..00535ac76 100644 --- a/config/interceptor/paramcheck/config_file_release_check.go +++ b/config/interceptor/paramcheck/config_file_release_check.go @@ -107,8 +107,8 @@ func (s *Server) GetConfigFileReleases(ctx context.Context, } searchFilters := map[string]string{ - "offset": strconv.Itoa(int(offset)), - "limit": strconv.Itoa(int(limit)), + "offset": strconv.FormatInt(int64(offset), 10), + "limit": strconv.FormatInt(int64(limit), 10), } for k, v := range filters { if nK, ok := availableSearch["config_file_release"][k]; ok { diff --git a/config/interceptor/paramcheck/config_file_release_history_check.go b/config/interceptor/paramcheck/config_file_release_history_check.go index 144bee2a8..a46157ff3 100644 --- a/config/interceptor/paramcheck/config_file_release_history_check.go +++ b/config/interceptor/paramcheck/config_file_release_history_check.go @@ -38,8 +38,8 @@ func (s *Server) GetConfigFileReleaseHistories(ctx context.Context, } searchFilters := map[string]string{ - "offset": strconv.Itoa(int(offset)), - "limit": strconv.Itoa(int(limit)), + "offset": strconv.FormatInt(int64(offset), 10), + "limit": strconv.FormatInt(int64(limit), 10), } for k, v := range filter { diff --git a/service/interceptor/paramcheck/check.go b/service/interceptor/paramcheck/check.go index 553bc8632..c17ad6f15 100644 --- a/service/interceptor/paramcheck/check.go +++ b/service/interceptor/paramcheck/check.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 paramcheck import ( @@ -12,162 +29,195 @@ import ( ) // AppendServiceContractInterfaces implements service.DiscoverServer. -func (svr *Server) AppendServiceContractInterfaces(ctx context.Context, contract *service_manage.ServiceContract, source service_manage.InterfaceDescriptor_Source) *service_manage.Response { +func (svr *Server) AppendServiceContractInterfaces(ctx context.Context, + contract *service_manage.ServiceContract, + source service_manage.InterfaceDescriptor_Source) *service_manage.Response { return svr.nextSvr.AppendServiceContractInterfaces(ctx, contract, source) } // CreateCircuitBreakerRules implements service.DiscoverServer. -func (svr *Server) CreateCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { +func (svr *Server) CreateCircuitBreakerRules(ctx context.Context, + request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateCircuitBreakerRules(ctx, request) } // CreateCircuitBreakerVersions implements service.DiscoverServer. -func (svr *Server) CreateCircuitBreakerVersions(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { +func (svr *Server) CreateCircuitBreakerVersions(ctx context.Context, + req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateCircuitBreakerVersions(ctx, req) } // CreateCircuitBreakers implements service.DiscoverServer. -func (svr *Server) CreateCircuitBreakers(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { +func (svr *Server) CreateCircuitBreakers(ctx context.Context, + req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateCircuitBreakers(ctx, req) } // CreateFaultDetectRules implements service.DiscoverServer. -func (svr *Server) CreateFaultDetectRules(ctx context.Context, request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { +func (svr *Server) CreateFaultDetectRules(ctx context.Context, + request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateFaultDetectRules(ctx, request) } // CreateInstances implements service.DiscoverServer. -func (svr *Server) CreateInstances(ctx context.Context, reqs []*service_manage.Instance) *service_manage.BatchWriteResponse { +func (svr *Server) CreateInstances(ctx context.Context, + reqs []*service_manage.Instance) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateInstances(ctx, reqs) } // CreateRateLimits implements service.DiscoverServer. -func (svr *Server) CreateRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { +func (svr *Server) CreateRateLimits(ctx context.Context, + request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateRateLimits(ctx, request) } // CreateRoutingConfigs implements service.DiscoverServer. -func (svr *Server) CreateRoutingConfigs(ctx context.Context, req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { +func (svr *Server) CreateRoutingConfigs(ctx context.Context, + req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateRoutingConfigs(ctx, req) } // CreateRoutingConfigsV2 implements service.DiscoverServer. -func (svr *Server) CreateRoutingConfigsV2(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { +func (svr *Server) CreateRoutingConfigsV2(ctx context.Context, + req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateRoutingConfigsV2(ctx, req) } // CreateServiceAlias implements service.DiscoverServer. -func (svr *Server) CreateServiceAlias(ctx context.Context, req *service_manage.ServiceAlias) *service_manage.Response { +func (svr *Server) CreateServiceAlias(ctx context.Context, + req *service_manage.ServiceAlias) *service_manage.Response { return svr.nextSvr.CreateServiceAlias(ctx, req) } // CreateServiceContractInterfaces implements service.DiscoverServer. -func (svr *Server) CreateServiceContractInterfaces(ctx context.Context, contract *service_manage.ServiceContract, source service_manage.InterfaceDescriptor_Source) *service_manage.Response { +func (svr *Server) CreateServiceContractInterfaces(ctx context.Context, + contract *service_manage.ServiceContract, source service_manage.InterfaceDescriptor_Source) *service_manage.Response { return svr.nextSvr.CreateServiceContractInterfaces(ctx, contract, source) } // CreateServiceContracts implements service.DiscoverServer. -func (svr *Server) CreateServiceContracts(ctx context.Context, req []*service_manage.ServiceContract) *service_manage.BatchWriteResponse { +func (svr *Server) CreateServiceContracts(ctx context.Context, + req []*service_manage.ServiceContract) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateServiceContracts(ctx, req) } // CreateServices implements service.DiscoverServer. -func (svr *Server) CreateServices(ctx context.Context, req []*service_manage.Service) *service_manage.BatchWriteResponse { +func (svr *Server) CreateServices(ctx context.Context, + req []*service_manage.Service) *service_manage.BatchWriteResponse { return svr.nextSvr.CreateServices(ctx, req) } // DeleteCircuitBreakerRules implements service.DiscoverServer. -func (svr *Server) DeleteCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteCircuitBreakerRules(ctx context.Context, + request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteCircuitBreakerRules(ctx, request) } // DeleteCircuitBreakers implements service.DiscoverServer. -func (svr *Server) DeleteCircuitBreakers(ctx context.Context, req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteCircuitBreakers(ctx context.Context, + req []*fault_tolerance.CircuitBreaker) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteCircuitBreakers(ctx, req) } // DeleteFaultDetectRules implements service.DiscoverServer. -func (svr *Server) DeleteFaultDetectRules(ctx context.Context, request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteFaultDetectRules(ctx context.Context, + request []*fault_tolerance.FaultDetectRule) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteFaultDetectRules(ctx, request) } // DeleteInstances implements service.DiscoverServer. -func (svr *Server) DeleteInstances(ctx context.Context, req []*service_manage.Instance) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteInstances(ctx context.Context, + req []*service_manage.Instance) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteInstances(ctx, req) } // DeleteInstancesByHost implements service.DiscoverServer. -func (svr *Server) DeleteInstancesByHost(ctx context.Context, req []*service_manage.Instance) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteInstancesByHost(ctx context.Context, + req []*service_manage.Instance) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteInstancesByHost(ctx, req) } // DeleteRateLimits implements service.DiscoverServer. -func (svr *Server) DeleteRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteRateLimits(ctx context.Context, + request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteRateLimits(ctx, request) } // DeleteRoutingConfigs implements service.DiscoverServer. -func (svr *Server) DeleteRoutingConfigs(ctx context.Context, req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteRoutingConfigs(ctx context.Context, + req []*traffic_manage.Routing) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteRoutingConfigs(ctx, req) } // DeleteRoutingConfigsV2 implements service.DiscoverServer. -func (svr *Server) DeleteRoutingConfigsV2(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteRoutingConfigsV2(ctx context.Context, + req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteRoutingConfigsV2(ctx, req) } // DeleteServiceAliases implements service.DiscoverServer. -func (svr *Server) DeleteServiceAliases(ctx context.Context, req []*service_manage.ServiceAlias) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteServiceAliases(ctx context.Context, + req []*service_manage.ServiceAlias) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteServiceAliases(ctx, req) } // DeleteServiceContractInterfaces implements service.DiscoverServer. -func (svr *Server) DeleteServiceContractInterfaces(ctx context.Context, contract *service_manage.ServiceContract) *service_manage.Response { +func (svr *Server) DeleteServiceContractInterfaces(ctx context.Context, + contract *service_manage.ServiceContract) *service_manage.Response { return svr.nextSvr.DeleteServiceContractInterfaces(ctx, contract) } // DeleteServiceContracts implements service.DiscoverServer. -func (svr *Server) DeleteServiceContracts(ctx context.Context, req []*service_manage.ServiceContract) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteServiceContracts(ctx context.Context, + req []*service_manage.ServiceContract) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteServiceContracts(ctx, req) } // DeleteServices implements service.DiscoverServer. -func (svr *Server) DeleteServices(ctx context.Context, req []*service_manage.Service) *service_manage.BatchWriteResponse { +func (svr *Server) DeleteServices(ctx context.Context, + req []*service_manage.Service) *service_manage.BatchWriteResponse { return svr.nextSvr.DeleteServices(ctx, req) } // EnableCircuitBreakerRules implements service.DiscoverServer. -func (svr *Server) EnableCircuitBreakerRules(ctx context.Context, request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { +func (svr *Server) EnableCircuitBreakerRules(ctx context.Context, + request []*fault_tolerance.CircuitBreakerRule) *service_manage.BatchWriteResponse { return svr.nextSvr.EnableCircuitBreakerRules(ctx, request) } // EnableRateLimits implements service.DiscoverServer. -func (svr *Server) EnableRateLimits(ctx context.Context, request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { +func (svr *Server) EnableRateLimits(ctx context.Context, + request []*traffic_manage.Rule) *service_manage.BatchWriteResponse { return svr.nextSvr.EnableRateLimits(ctx, request) } // EnableRoutings implements service.DiscoverServer. -func (svr *Server) EnableRoutings(ctx context.Context, req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { +func (svr *Server) EnableRoutings(ctx context.Context, + req []*traffic_manage.RouteRule) *service_manage.BatchWriteResponse { return svr.nextSvr.EnableRoutings(ctx, req) } // GetAllServices implements service.DiscoverServer. -func (svr *Server) GetAllServices(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetAllServices(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetAllServices(ctx, query) } // GetCircuitBreaker implements service.DiscoverServer. -func (svr *Server) GetCircuitBreaker(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetCircuitBreaker(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetCircuitBreaker(ctx, query) } // GetCircuitBreakerByService implements service.DiscoverServer. -func (svr *Server) GetCircuitBreakerByService(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetCircuitBreakerByService(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetCircuitBreakerByService(ctx, query) } // GetCircuitBreakerRules implements service.DiscoverServer. -func (svr *Server) GetCircuitBreakerRules(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetCircuitBreakerRules(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetCircuitBreakerRules(ctx, query) } diff --git a/service/interceptor/paramcheck/client.go b/service/interceptor/paramcheck/client.go index 74a12b9fa..c829f6b6c 100644 --- a/service/interceptor/paramcheck/client.go +++ b/service/interceptor/paramcheck/client.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 paramcheck import ( @@ -42,7 +59,8 @@ func (s *Server) GetServiceWithCache(ctx context.Context, req *apiservice.Servic } // ServiceInstancesCache Used for client acquisition service instance information -func (s *Server) ServiceInstancesCache(ctx context.Context, filter *apiservice.DiscoverFilter, req *apiservice.Service) *apiservice.DiscoverResponse { +func (s *Server) ServiceInstancesCache(ctx context.Context, filter *apiservice.DiscoverFilter, + req *apiservice.Service) *apiservice.DiscoverResponse { resp := service.CreateCommonDiscoverResponse(req, apiservice.DiscoverResponse_INSTANCE) namespaceName := req.GetNamespace().GetValue() diff --git a/store/mysql/lane.go b/store/mysql/lane.go index b446fafca..068cd3603 100644 --- a/store/mysql/lane.go +++ b/store/mysql/lane.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 sqldb import ( diff --git a/store/mysql/service_contract.go b/store/mysql/service_contract.go index 617714bf7..b8615b095 100644 --- a/store/mysql/service_contract.go +++ b/store/mysql/service_contract.go @@ -311,7 +311,7 @@ func (s *serviceContractStore) GetServiceContracts(ctx context.Context, filter m _ = rows.Close() }() - contractIds := make([]interface{}, 0, limit) + contractIds := make([]interface{}, 0, 32) err = transferEnrichServiceContract(rows, func(contract *model.EnrichServiceContract) { list = append(list, contract) contractIds = append(contractIds, contract.ID) From f939007df16a3d11944d228513e8aaa0d6ef6641 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Sat, 22 Jun 2024 17:37:00 +0800 Subject: [PATCH 14/23] feat:support nacos-address server endpoints --- apiserver/eurekaserver/write_test.go | 1 + apiserver/grpcserver/base_test.go | 31 +--- cache/service/ratelimit_config.go | 13 +- cache/service/service.go | 3 + cache/service/service_contract.go | 10 + plugin/healthchecker/leader/peer.go | 33 +++- plugin/healthchecker/leader/peer_test.go | 13 +- service/healthcheck/dispatch.go | 2 +- service/interceptor/paramcheck/check.go | 42 +++-- service/ratelimit_config_test.go | 7 +- service/service_alias.go | 2 +- service/service_alias_test.go | 225 +++++++++++++---------- service/service_contract.go | 8 +- service/service_contract_test.go | 6 +- store/boltdb/client.go | 8 +- store/boltdb/service.go | 6 + store/boltdb/service_contract.go | 10 +- 17 files changed, 246 insertions(+), 174 deletions(-) diff --git a/apiserver/eurekaserver/write_test.go b/apiserver/eurekaserver/write_test.go index b7f983ab5..255cb7d17 100644 --- a/apiserver/eurekaserver/write_test.go +++ b/apiserver/eurekaserver/write_test.go @@ -121,6 +121,7 @@ func TestEurekaServer_renew(t *testing.T) { }, }, nil) + mockStore.EXPECT().GetMoreServiceContracts(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() 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) diff --git a/apiserver/grpcserver/base_test.go b/apiserver/grpcserver/base_test.go index d73d0b1a7..55d649c36 100644 --- a/apiserver/grpcserver/base_test.go +++ b/apiserver/grpcserver/base_test.go @@ -48,7 +48,7 @@ func TestConvertContext(t *testing.T) { tests := []struct { name string args args - want context.Context + want metadata.MD }{ { name: "", @@ -57,11 +57,8 @@ func TestConvertContext(t *testing.T) { "internal-key-1": "internal-value-1", }), }, - want: func() context.Context { - ctx := context.Background() - + want: func() metadata.MD { md := make(metadata.MD) - testVal := map[string]string{ "internal-key-1": "internal-value-1", } @@ -69,14 +66,7 @@ func TestConvertContext(t *testing.T) { for k := range testVal { md[k] = []string{testVal[k]} } - - ctx = context.WithValue(ctx, utils.ContextGrpcHeader, md) - ctx = context.WithValue(ctx, utils.StringContext("request-id"), "") - ctx = context.WithValue(ctx, utils.StringContext("client-ip"), "") - ctx = context.WithValue(ctx, utils.ContextClientAddress, "") - ctx = context.WithValue(ctx, utils.StringContext("user-agent"), "") - - return ctx + return md }(), }, { @@ -88,8 +78,7 @@ func TestConvertContext(t *testing.T) { "user-agent": "user-agent", }), }, - want: func() context.Context { - + want: func() metadata.MD { md := make(metadata.MD) testVal := map[string]string{ @@ -100,21 +89,13 @@ func TestConvertContext(t *testing.T) { for k := range testVal { md[k] = []string{testVal[k]} } - - ctx := context.Background() - ctx = context.WithValue(ctx, utils.ContextGrpcHeader, md) - ctx = context.WithValue(ctx, utils.StringContext("request-id"), "request-id") - ctx = context.WithValue(ctx, utils.StringContext("client-ip"), "") - ctx = context.WithValue(ctx, utils.ContextClientAddress, "") - ctx = context.WithValue(ctx, utils.StringContext("user-agent"), "user-agent") - - return ctx + return md }(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := utils.ConvertGRPCContext(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { + if got := utils.ConvertGRPCContext(tt.args.ctx); !reflect.DeepEqual(got.Value(utils.ContextGrpcHeader), tt.want) { t.Errorf("ConvertContext() = %v, \n want %v", got, tt.want) } }) diff --git a/cache/service/ratelimit_config.go b/cache/service/ratelimit_config.go index 0f35401fc..375f489b3 100644 --- a/cache/service/ratelimit_config.go +++ b/cache/service/ratelimit_config.go @@ -191,9 +191,10 @@ func (rlc *rateLimitCache) fixRulesServiceInfo() { } svc = svc2 } - - rule.Proto.Namespace = utils.NewStringValue(svc.Namespace) - rule.Proto.Name = utils.NewStringValue(svc.Name) + if svc != nil { + rule.Proto.Namespace = utils.NewStringValue(svc.Namespace) + rule.Proto.Name = utils.NewStringValue(svc.Name) + } delete(rlc.waitFixRules, rule.ID) } } @@ -217,7 +218,9 @@ func (rlc *rateLimitCache) fixRuleServiceInfo(rateLimit *model.RateLimit) { svc = svc2 } - rateLimit.Proto.Namespace = utils.NewStringValue(svc.Namespace) - rateLimit.Proto.Name = utils.NewStringValue(svc.Name) + if svc != nil { + rateLimit.Proto.Namespace = utils.NewStringValue(svc.Namespace) + rateLimit.Proto.Name = utils.NewStringValue(svc.Name) + } delete(rlc.waitFixRules, rateLimit.ID) } diff --git a/cache/service/service.go b/cache/service/service.go index eba2de3ff..03a5d5c4a 100644 --- a/cache/service/service.go +++ b/cache/service/service.go @@ -281,6 +281,9 @@ func (sc *serviceCache) GetServiceByName(name string, namespace string) *model.S } func (sc *serviceCache) fillServicePorts(svc *model.Service) { + if svc == nil { + return + } if svc.Ports != "" { return } diff --git a/cache/service/service_contract.go b/cache/service/service_contract.go index 66ae645a4..a5eddece4 100644 --- a/cache/service/service_contract.go +++ b/cache/service/service_contract.go @@ -51,6 +51,11 @@ type ServiceContractCache struct { // Initialize func (sc *ServiceContractCache) Initialize(c map[string]interface{}) error { + valueCache, err := sc.openBoltCache(c) + if err != nil { + return err + } + sc.valueCache = valueCache sc.data = utils.NewSyncMap[string, *model.EnrichServiceContract]() return nil } @@ -153,13 +158,18 @@ func (fc *ServiceContractCache) upsertValueCache(item *model.EnrichServiceContra func (fc *ServiceContractCache) loadValueCache(release *model.ServiceContract) (*model.EnrichServiceContract, error) { ret := &model.EnrichServiceContract{} + found := false err := fc.valueCache.View(func(tx *bbolt.Tx) error { bucket := tx.Bucket([]byte(release.GetCacheKey())) if bucket == nil { return nil } + found = true val := bucket.Get([]byte(release.GetCacheKey())) return json.Unmarshal(val, ret) }) + if !found { + ret.ServiceContract = &model.ServiceContract{} + } return ret, err } diff --git a/plugin/healthchecker/leader/peer.go b/plugin/healthchecker/leader/peer.go index f077f8aeb..ae05b2002 100644 --- a/plugin/healthchecker/leader/peer.go +++ b/plugin/healthchecker/leader/peer.go @@ -45,6 +45,9 @@ type ( // 仅支持测试场景塞入即可 ConnectFuncContextKey struct{} ConnectPeerFunc func(*RemotePeer) error + + PingFuncContextKey struct{} + PingFunc func() error ) var ( @@ -149,21 +152,30 @@ func (p *RemotePeer) Initialize(conf Config) { p.conf = conf } -func (p *RemotePeer) Serve(_ context.Context, checker *LeaderHealthChecker, +func (p *RemotePeer) Serve(ctx context.Context, checker *LeaderHealthChecker, listenIP string, listenPort uint32) error { - ctx, cancel := context.WithCancel(context.Background()) + subCtx, cancel := context.WithCancel(ctx) p.cancel = cancel p.host = listenIP p.port = listenPort p.cmutex = &sync.RWMutex{} - if err := ConnectPeer(p); err != nil { + if err := execConnectPeer(ctx, p); err != nil { return err } p.Cache = newRemoteBeatRecordCache(p.GetFunc, p.PutFunc, p.DelFunc, p.Ping) - go p.checkLeaderAlive(ctx) + go p.checkLeaderAlive(subCtx) return nil } +func execConnectPeer(ctx context.Context, p *RemotePeer) error { + val := ctx.Value(ConnectFuncContextKey{}) + connectFunc, ok := val.(ConnectPeerFunc) + if !ok { + connectFunc = ConnectPeer + } + return connectFunc(p) +} + func (p *RemotePeer) Host() string { return p.host } @@ -310,12 +322,12 @@ func (p *RemotePeer) checkLeaderAlive(ctx context.Context) { select { case <-ctx.Done(): ticker.Stop() - plog.Error("check leader alive job stop", zap.String("host", p.Host()), zap.Uint32("port", p.port)) + plog.Info("check leader alive job stop", zap.String("host", p.Host()), zap.Uint32("port", p.port)) return case <-ticker.C: var errCount int for i := 0; i < maxCheckCount; i++ { - if err := p.Ping(); err != nil { + if err := execPing(ctx, p); err != nil { plog.Error("check leader is alive fail", zap.String("host", p.Host()), zap.Uint32("port", p.port), zap.Error(err)) errCount++ @@ -405,6 +417,15 @@ func (p *RemotePeer) doReconnect(i int) bool { return true } +func execPing(ctx context.Context, p *RemotePeer) error { + val := ctx.Value(PingFuncContextKey{}) + pingFunc, ok := val.(PingFunc) + if !ok { + pingFunc = p.Ping + } + return pingFunc() +} + func doConnect(p *RemotePeer) error { p.conns = make(map[int]*grpc.ClientConn, streamNum) p.puters = make(map[int]*beatSender, streamNum) diff --git a/plugin/healthchecker/leader/peer_test.go b/plugin/healthchecker/leader/peer_test.go index a2b7197b0..f8fb5139b 100644 --- a/plugin/healthchecker/leader/peer_test.go +++ b/plugin/healthchecker/leader/peer_test.go @@ -122,7 +122,6 @@ func TestRemotePeer(t *testing.T) { t.Cleanup(func() { CreateBeatClientFunc = oldCreateBeatClient remotePeer.(*RemotePeer).cancel() - assert.NoError(t, err) cancel() }) @@ -133,9 +132,21 @@ func TestRemotePeer(t *testing.T) { } ctx = context.WithValue(ctx, ConnectFuncContextKey{}, ConnectPeerFunc(mockSvr.mockRemotePeerConnect)) + ctx = context.WithValue(ctx, PingFuncContextKey{}, PingFunc(func() error { + t.Logf("debug for peer check ping") + return nil + })) err = remotePeer.Serve(ctx, nil, "127.0.0.1", mockPort) assert.NoError(t, err) + for { + if remotePeer.IsAlive() { + break + } + time.Sleep(time.Second) + t.Logf("wait leader checker ready") + } + mockKey := utils.NewUUID() mockVal := time.Now().Unix() diff --git a/service/healthcheck/dispatch.go b/service/healthcheck/dispatch.go index 99fa57816..162a03665 100644 --- a/service/healthcheck/dispatch.go +++ b/service/healthcheck/dispatch.go @@ -158,7 +158,7 @@ func (d *Dispatcher) reloadManagedClients() { }) } log.Infof("[Health Check][Dispatcher]count %d clients has been dispatched to %s, total is %d", - len(nextClients), d.svr.localHost, d.svr.cacheProvider.healthCheckInstances.Count()) + len(nextClients), d.svr.localHost, d.svr.cacheProvider.healthCheckClients.Count()) originClients := d.managedClients d.managedClients = nextClients if len(nextClients) > 0 { diff --git a/service/interceptor/paramcheck/check.go b/service/interceptor/paramcheck/check.go index c17ad6f15..90a6e39a7 100644 --- a/service/interceptor/paramcheck/check.go +++ b/service/interceptor/paramcheck/check.go @@ -222,27 +222,32 @@ func (svr *Server) GetCircuitBreakerRules(ctx context.Context, } // GetCircuitBreakerToken implements service.DiscoverServer. -func (svr *Server) GetCircuitBreakerToken(ctx context.Context, req *fault_tolerance.CircuitBreaker) *service_manage.Response { +func (svr *Server) GetCircuitBreakerToken(ctx context.Context, + req *fault_tolerance.CircuitBreaker) *service_manage.Response { return svr.nextSvr.GetCircuitBreakerToken(ctx, req) } // GetCircuitBreakerVersions implements service.DiscoverServer. -func (svr *Server) GetCircuitBreakerVersions(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetCircuitBreakerVersions(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetCircuitBreakerVersions(ctx, query) } // GetFaultDetectRules implements service.DiscoverServer. -func (svr *Server) GetFaultDetectRules(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetFaultDetectRules(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetFaultDetectRules(ctx, query) } // GetInstanceLabels implements service.DiscoverServer. -func (svr *Server) GetInstanceLabels(ctx context.Context, query map[string]string) *service_manage.Response { +func (svr *Server) GetInstanceLabels(ctx context.Context, + query map[string]string) *service_manage.Response { return svr.nextSvr.GetInstanceLabels(ctx, query) } // GetInstances implements service.DiscoverServer. -func (svr *Server) GetInstances(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetInstances(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetInstances(ctx, query) } @@ -252,47 +257,56 @@ func (svr *Server) GetInstancesCount(ctx context.Context) *service_manage.BatchQ } // GetMasterCircuitBreakers implements service.DiscoverServer. -func (svr *Server) GetMasterCircuitBreakers(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetMasterCircuitBreakers(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetMasterCircuitBreakers(ctx, query) } // GetPrometheusTargets implements service.DiscoverServer. -func (svr *Server) GetPrometheusTargets(ctx context.Context, query map[string]string) *model.PrometheusDiscoveryResponse { +func (svr *Server) GetPrometheusTargets(ctx context.Context, + query map[string]string) *model.PrometheusDiscoveryResponse { return svr.nextSvr.GetPrometheusTargets(ctx, query) } // GetRateLimits implements service.DiscoverServer. -func (svr *Server) GetRateLimits(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetRateLimits(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetRateLimits(ctx, query) } // GetReleaseCircuitBreakers implements service.DiscoverServer. -func (svr *Server) GetReleaseCircuitBreakers(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetReleaseCircuitBreakers(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetReleaseCircuitBreakers(ctx, query) } // GetRoutingConfigs implements service.DiscoverServer. -func (svr *Server) GetRoutingConfigs(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetRoutingConfigs(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetRoutingConfigs(ctx, query) } // GetServiceAliases implements service.DiscoverServer. -func (svr *Server) GetServiceAliases(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetServiceAliases(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetServiceAliases(ctx, query) } // GetServiceContractVersions implements service.DiscoverServer. -func (svr *Server) GetServiceContractVersions(ctx context.Context, filter map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetServiceContractVersions(ctx context.Context, + filter map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetServiceContractVersions(ctx, filter) } // GetServiceContracts implements service.DiscoverServer. -func (svr *Server) GetServiceContracts(ctx context.Context, query map[string]string) *service_manage.BatchQueryResponse { +func (svr *Server) GetServiceContracts(ctx context.Context, + query map[string]string) *service_manage.BatchQueryResponse { return svr.nextSvr.GetServiceContracts(ctx, query) } // GetServiceOwner implements service.DiscoverServer. -func (svr *Server) GetServiceOwner(ctx context.Context, req []*service_manage.Service) *service_manage.BatchQueryResponse { +func (svr *Server) GetServiceOwner(ctx context.Context, + req []*service_manage.Service) *service_manage.BatchQueryResponse { return svr.nextSvr.GetServiceOwner(ctx, req) } diff --git a/service/ratelimit_config_test.go b/service/ratelimit_config_test.go index fd8e43924..bfb14b525 100644 --- a/service/ratelimit_config_test.go +++ b/service/ratelimit_config_test.go @@ -329,11 +329,8 @@ func TestDeleteRateLimit(t *testing.T) { discoverSuit.DefaultCtx = oldCtx }() - if resp := discoverSuit.DiscoverServer().DeleteRateLimits(discoverSuit.DefaultCtx, []*apitraffic.Rule{rateLimitReq}); !respSuccess(resp) { - t.Logf("pass: %s", resp.GetInfo().GetValue()) - } else { - t.Fatal("error") - } + resp := discoverSuit.DiscoverServer().DeleteRateLimits(discoverSuit.DefaultCtx, []*apitraffic.Rule{rateLimitReq}) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) }) t.Run("并发删除限流规则,可以正常删除", func(t *testing.T) { diff --git a/service/service_alias.go b/service/service_alias.go index 694113095..34e00a244 100644 --- a/service/service_alias.go +++ b/service/service_alias.go @@ -320,7 +320,7 @@ func preCheckAlias(req *apiservice.ServiceAlias) (*apiservice.Response, bool) { } if err := utils.CheckResourceName(req.GetAliasNamespace()); err != nil { - return api.NewServiceAliasResponse(apimodel.Code_InvalidNamespaceWithAlias, req), true + return api.NewServiceAliasResponse(apimodel.Code_InvalidNamespaceName, req), true } // 默认类型,需要检查alias是否为空 diff --git a/service/service_alias_test.go b/service/service_alias_test.go index 61b31d7a9..4e6e5913e 100644 --- a/service/service_alias_test.go +++ b/service/service_alias_test.go @@ -29,7 +29,6 @@ import ( apimodel "github.com/polarismesh/specification/source/go/api/v1/model" apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" apitraffic "github.com/polarismesh/specification/source/go/api/v1/traffic_manage" - . "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/wrapperspb" @@ -91,23 +90,23 @@ func TestCreateServiceAlias(t *testing.T) { _, serviceResp := discoverSuit.createCommonService(t, 123) defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) - Convey("正常创建非Sid的别名", t, func() { + t.Run("正常创建非Sid的别名", func(t *testing.T) { alias := fmt.Sprintf("alias.%d", time.Now().Unix()) resp := discoverSuit.createCommonAlias(serviceResp, alias, serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) defer discoverSuit.cleanServiceName(alias, serviceResp.GetNamespace().GetValue()) - So(respSuccess(resp), ShouldEqual, true) - So(resp.Alias.Alias.Value, ShouldEqual, alias) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + assert.Equal(t, resp.Alias.Alias.Value, alias) }) - Convey("正常创建Sid别名", t, func() { + t.Run("正常创建Sid别名", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) - So(isSid(resp.Alias.Alias.Value), ShouldEqual, true) + assert.True(t, isSid(resp.Alias.Alias.Value)) t.Logf("alias sid: %s", resp.Alias.Alias.Value) }) - Convey("使用ctx带上的token可以创建成功", t, func() { + t.Run("使用ctx带上的token可以创建成功", func(t *testing.T) { req := &apiservice.ServiceAlias{ Service: serviceResp.Name, Namespace: serviceResp.Namespace, @@ -117,17 +116,17 @@ func TestCreateServiceAlias(t *testing.T) { ctx := context.WithValue(discoverSuit.DefaultCtx, utils.StringContext("polaris-token"), serviceResp.GetToken().GetValue()) resp := discoverSuit.DiscoverServer().CreateServiceAlias(ctx, req) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) // 带上系统token,也可以成功 ctx = context.WithValue(discoverSuit.DefaultCtx, utils.StringContext("polaris-token"), "polaris@12345678") resp = discoverSuit.DiscoverServer().CreateServiceAlias(ctx, req) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) }) - Convey("不允许为别名创建别名", t, func() { + t.Run("不允许为别名创建别名", func(t *testing.T) { resp := discoverSuit.NamespaceServer().CreateNamespace(discoverSuit.DefaultCtx, &apimodel.Namespace{ Name: &wrapperspb.StringValue{Value: defaultAliasNs}, }) @@ -161,7 +160,7 @@ func TestCreateSid(t *testing.T) { } defer discoverSuit.Destroy() - Convey("创建不同命名空间的sid,可以返回符合规范的sid", t, func() { + t.Run("创建不同命名空间的sid,可以返回符合规范的sid", func(t *testing.T) { for namespace, layout := range service.Namespace2SidLayoutID { service := &apiservice.Service{ Name: utils.NewStringValue("sid-test-xxx"), @@ -172,26 +171,27 @@ func TestCreateSid(t *testing.T) { discoverSuit.cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) serviceResp := discoverSuit.DiscoverServer().CreateServices(discoverSuit.DefaultCtx, []*apiservice.Service{service}) t.Logf("resp : %s", serviceResp.GetInfo().GetValue()) - So(respSuccess(serviceResp), ShouldEqual, true) + assert.True(t, api.IsSuccess(serviceResp), serviceResp.GetInfo().GetValue()) aliasResp := discoverSuit.createCommonAlias(serviceResp.Responses[0].Service, "", namespace, apiservice.AliasType_CL5SID) - So(respSuccess(aliasResp), ShouldEqual, true) + assert.True(t, api.IsSuccess(aliasResp), aliasResp.GetInfo().GetValue()) modID, cmdID := parseStr2Sid(aliasResp.GetAlias().GetAlias().GetValue()) - So(modID, ShouldNotEqual, uint32(0)) - So(cmdID, ShouldNotEqual, uint32(0)) - So(modID>>6, ShouldBeGreaterThanOrEqualTo, 3000001) // module - So(modID&63, ShouldEqual, layout) // 根据保留字段标识服务名 - So(aliasResp.GetAlias().GetNamespace().GetValue(), ShouldEqual, namespace) + assert.NotEqual(t, modID, uint32(0)) + assert.NotEqual(t, cmdID, uint32(0)) + assert.True(t, modID>>6 >= 3000001) + assert.Equal(t, modID&63, layout) + assert.Equal(t, aliasResp.GetAlias().GetNamespace().GetValue(), namespace) discoverSuit.cleanServiceName(aliasResp.GetAlias().GetAlias().GetValue(), namespace) discoverSuit.cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) } }) - Convey("非默认的5个命名空间,不允许创建sid别名", t, func() { + t.Run("非默认的5个命名空间,不允许创建sid别名", func(t *testing.T) { namespace := &apimodel.Namespace{ Name: utils.NewStringValue("other-namespace-xxx"), Owners: utils.NewStringValue("aaa"), } - So(respSuccess(discoverSuit.NamespaceServer().CreateNamespace(discoverSuit.DefaultCtx, namespace)), ShouldEqual, true) + resp := discoverSuit.NamespaceServer().CreateNamespace(discoverSuit.DefaultCtx, namespace) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) defer discoverSuit.cleanNamespace(namespace.Name.Value) service := &apiservice.Service{ @@ -201,10 +201,12 @@ func TestCreateSid(t *testing.T) { Owners: utils.NewStringValue("owners111"), } serviceResp := discoverSuit.DiscoverServer().CreateServices(discoverSuit.DefaultCtx, []*apiservice.Service{service}) - So(respSuccess(serviceResp), ShouldEqual, true) + assert.True(t, api.IsSuccess(serviceResp), serviceResp.GetInfo().GetValue()) + defer discoverSuit.cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) aliasResp := discoverSuit.createCommonAlias(serviceResp.Responses[0].Service, "", namespace.Name.Value, apiservice.AliasType_CL5SID) - So(respSuccess(aliasResp), ShouldEqual, false) + assert.False(t, api.IsSuccess(aliasResp), aliasResp.GetInfo().GetValue()) + t.Logf("%s", aliasResp.GetInfo().GetValue()) }) } @@ -221,7 +223,7 @@ func TestConcurrencyCreateSid(t *testing.T) { _, serviceResp := discoverSuit.createCommonService(t, 234) defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) - Convey("并发创建sid别名,sid不会重复", t, func() { + t.Run("并发创建sid别名,sid不会重复", func(t *testing.T) { c := 20 var wg sync.WaitGroup resultCh := make(chan *apiservice.Response, 1) @@ -260,13 +262,14 @@ func TestConcurrencyCreateSid(t *testing.T) { repeated := make(map[string]bool) for i := 0; i < c; i++ { resp := results[i] - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) - So(isSid(resp.Alias.Alias.Value), ShouldEqual, true) + assert.True(t, isSid(resp.Alias.Alias.Value)) + repeated[resp.Alias.Alias.Value] = true } // 检查是否重复,必须是200个 - So(len(repeated), ShouldEqual, c) + assert.Equal(t, len(repeated), c) }) } @@ -282,56 +285,58 @@ func TestExceptCreateAlias(t *testing.T) { _, serviceResp := discoverSuit.createCommonService(t, 345) defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) - Convey("参数缺失,报错", t, func() { + t.Run("参数缺失,报错", func(t *testing.T) { noService := &apiservice.Service{} resp := discoverSuit.createCommonAlias( noService, "x1.x2.x3", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) noService.Name = utils.NewStringValue("123") resp = discoverSuit.createCommonAlias( noService, "x1.x2.x3", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) noService.Namespace = utils.NewStringValue("456") resp = discoverSuit.createCommonAlias( noService, "x1.x2.x3", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) noService.Token = utils.NewStringValue("567") resp = discoverSuit.createCommonAlias(noService, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) t.Logf("return code: %d", resp.Code.Value) }) - Convey("不存在的源服务,报错", t, func() { + t.Run("不存在的源服务,报错", func(t *testing.T) { noService := &apiservice.Service{ Name: utils.NewStringValue("my.service.2020.02.19"), Namespace: utils.NewStringValue("123123"), Token: utils.NewStringValue("aaa"), } resp := discoverSuit.createCommonAlias(noService, "x1.x2.x3", noService.Namespace.GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) t.Logf("return code: %d", resp.Code.Value) - So(resp.Code.Value, ShouldEqual, api.NotFoundService) + assert.Equal(t, resp.GetCode().GetValue(), api.NotFoundService) }) - Convey("同名alias,报错", t, func() { + t.Run("同名alias,报错", func(t *testing.T) { resp := discoverSuit.createCommonAlias( serviceResp, "x1.x2.x3", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, respSuccess(resp), resp.GetInfo().GetValue()) + defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) resp = discoverSuit.createCommonAlias( serviceResp, "x1.x2.x3", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) t.Logf("same alias return code: %d", resp.Code.Value) }) - Convey("目标服务已经是一个别名", t, func() { + t.Run("目标服务已经是一个别名", func(t *testing.T) { resp := discoverSuit.createCommonAlias( serviceResp, "x1.x2.x3.x4", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, respSuccess(resp), resp.GetInfo().GetValue()) + defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) resp = discoverSuit.createCommonAlias( @@ -339,12 +344,12 @@ func TestExceptCreateAlias(t *testing.T) { Name: utils.NewStringValue("x1.x2.x3.x4"), Namespace: serviceResp.GetNamespace(), }, "x1.x2.x3.x5", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) assert.Equal(t, apimodel.Code_NotAllowCreateAliasForAlias, apimodel.Code(resp.GetCode().GetValue())) t.Logf("same alias return code: %d", resp.Code.Value) }) - Convey("鉴权失败,报错", t, func() { + t.Run("鉴权失败,报错", func(t *testing.T) { service := &apiservice.Service{ Name: serviceResp.Name, Namespace: serviceResp.Namespace, @@ -359,12 +364,14 @@ func TestExceptCreateAlias(t *testing.T) { discoverSuit.DefaultCtx = oldCtx }() + _ = discoverSuit.CacheMgr().TestUpdate() + resp := discoverSuit.createCommonAlias(service, "x1.x2.x3", service.Namespace.GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, respSuccess(resp), resp.GetInfo().GetValue()) t.Logf("error token, return code: %d", resp.Code.Value) }) - Convey("指向的服务不存在(新接口)", t, func() { + t.Run("指向的服务不存在(新接口)", func(t *testing.T) { _, serviceResp2 := discoverSuit.createCommonService(t, 2) discoverSuit.cleanServiceName(serviceResp2.GetName().GetValue(), serviceResp2.GetNamespace().GetValue()) resp := discoverSuit.createCommonAlias(serviceResp2, "", serviceResp2.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) @@ -386,9 +393,9 @@ func TestUpdateServiceAlias(t *testing.T) { _, serviceResp := discoverSuit.createCommonService(t, 3) defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) - Convey("修改别名负责人", t, func() { + t.Run("修改别名负责人", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) defer discoverSuit.cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) // 修改别名负责人 @@ -402,7 +409,7 @@ func TestUpdateServiceAlias(t *testing.T) { } repeatedResp := discoverSuit.DiscoverServer().UpdateServiceAlias(discoverSuit.DefaultCtx, req) - So(respSuccess(repeatedResp), ShouldEqual, true) + assert.True(t, api.IsSuccess(repeatedResp), resp.GetInfo().GetValue()) query := map[string]string{ "alias": req.GetAlias().GetValue(), @@ -410,13 +417,13 @@ func TestUpdateServiceAlias(t *testing.T) { } aliasResponse := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) // 判断负责人是否一致 - So(aliasResponse.GetAliases()[0].GetOwners().GetValue(), ShouldEqual, "alias-owner-new") + assert.Equal(t, aliasResponse.GetAliases()[0].GetOwners().GetValue(), "alias-owner-new") t.Logf("pass, owner is %v", aliasResponse.GetAliases()[0].GetOwners().GetValue()) }) - Convey("修改指向服务", t, func() { + t.Run("修改指向服务", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) defer discoverSuit.cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) // 创建新的服务 @@ -435,7 +442,7 @@ func TestUpdateServiceAlias(t *testing.T) { } repeatedResp := discoverSuit.DiscoverServer().UpdateServiceAlias(discoverSuit.DefaultCtx, req) - So(respSuccess(repeatedResp), ShouldEqual, true) + assert.True(t, api.IsSuccess(repeatedResp), resp.GetInfo().GetValue()) query := map[string]string{ "alias": req.GetAlias().GetValue(), @@ -443,13 +450,14 @@ func TestUpdateServiceAlias(t *testing.T) { } aliasResponse := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) // 判断指向服务是否一致 - So(aliasResponse.GetAliases()[0].GetService().GetValue(), ShouldEqual, serviceResp2.GetName().GetValue()) + assert.Equal(t, aliasResponse.GetAliases()[0].GetService().GetValue(), serviceResp2.GetName().GetValue()) t.Logf("pass, service is %v", aliasResponse.GetAliases()[0].GetService().GetValue()) }) - Convey("要指向的服务不存在", t, func() { + t.Run("要指向的服务不存在", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, respSuccess(resp), resp.GetInfo().GetValue()) + defer discoverSuit.cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) // 创建新的服务并删除 @@ -458,12 +466,13 @@ func TestUpdateServiceAlias(t *testing.T) { // 修改别名指向 req := &apiservice.ServiceAlias{ - Service: serviceResp2.GetName(), - Namespace: serviceResp2.GetNamespace(), - Alias: resp.GetAlias().GetAlias(), - Owners: resp.GetAlias().GetOwners(), - Comment: resp.GetAlias().GetComment(), - ServiceToken: resp.GetAlias().GetServiceToken(), + Service: serviceResp2.GetName(), + Namespace: serviceResp2.GetNamespace(), + Alias: resp.GetAlias().GetAlias(), + AliasNamespace: resp.GetAlias().GetNamespace(), + Owners: resp.GetAlias().GetOwners(), + Comment: resp.GetAlias().GetComment(), + ServiceToken: resp.GetAlias().GetServiceToken(), } repeatedResp := discoverSuit.DiscoverServer().UpdateServiceAlias(discoverSuit.DefaultCtx, req) if respSuccess(repeatedResp) { @@ -472,10 +481,13 @@ func TestUpdateServiceAlias(t *testing.T) { t.Logf("%+v", repeatedResp) }) - Convey("鉴权失败", t, func() { + t.Run("鉴权失败", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, respSuccess(resp), resp.GetInfo().GetValue()) defer discoverSuit.cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) + + _ = discoverSuit.CacheMgr().TestUpdate() + // 修改service token req := resp.GetAlias() req.ServiceToken = utils.NewStringValue("") @@ -500,26 +512,29 @@ func TestDeleteServiceAlias(t *testing.T) { _, serviceResp := discoverSuit.createCommonService(t, 201) defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) - Convey("通过服务别名删除接口可以直接删除别名", t, func() { + t.Run("通过服务别名删除接口可以直接删除别名", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, serviceResp.Name.GetValue()+"_alias", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_DEFAULT) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, resp.Alias.AliasNamespace.Value) discoverSuit.removeCommonServiceAliases(t, []*apiservice.ServiceAlias{resp.Alias}) query := map[string]string{"name": resp.Alias.Alias.Value} queryResp := discoverSuit.DiscoverServer().GetServices(discoverSuit.DefaultCtx, query) - So(respSuccess(queryResp), ShouldEqual, true) - So(len(queryResp.Services), ShouldEqual, 0) + assert.True(t, api.IsSuccess(queryResp), queryResp.GetInfo().GetValue()) + assert.Equal(t, len(queryResp.Services), 0) }) - Convey("通过ctx带上token,可以删除别名成功", t, func() { + t.Run("通过ctx带上token,可以删除别名成功", func(t *testing.T) { resp := discoverSuit.createCommonAlias(serviceResp, "", serviceResp.GetNamespace().GetValue(), apiservice.AliasType_CL5SID) - So(respSuccess(resp), ShouldEqual, true) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) ctx := context.WithValue(discoverSuit.DefaultCtx, utils.StringContext("polaris-token"), "polaris@12345678") - So(respSuccess(discoverSuit.DiscoverServer().DeleteServiceAliases(ctx, []*apiservice.ServiceAlias{resp.Alias})), ShouldEqual, true) + batchResp := discoverSuit.DiscoverServer().DeleteServiceAliases(ctx, []*apiservice.ServiceAlias{resp.Alias}) + assert.True(t, api.IsSuccess(batchResp), batchResp.GetInfo().GetValue()) }) } @@ -540,7 +555,7 @@ func TestServiceAliasRelated(t *testing.T) { t.Fatalf("errror") } defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) - Convey("实例新建,不允许为别名新建实例", t, func() { + t.Run("实例新建,不允许为别名新建实例", func(t *testing.T) { instance := &apiservice.Instance{ Service: resp.Alias.Alias, Namespace: serviceResp.Namespace, @@ -549,20 +564,21 @@ func TestServiceAliasRelated(t *testing.T) { Port: utils.NewUInt32Value(8080), } instanceResp := discoverSuit.DiscoverServer().CreateInstances(discoverSuit.DefaultCtx, []*apiservice.Instance{instance}) - So(respSuccess(instanceResp), ShouldEqual, false) + assert.False(t, api.IsSuccess(instanceResp), instanceResp.GetInfo().GetValue()) + t.Logf("alias create instance ret code(%d), msg(%s)", instanceResp.Code.Value, instanceResp.Info.Value) }) - Convey("实例Discover,别名查询实例,返回源服务的实例信息", t, func() { + t.Run("实例Discover,别名查询实例,返回源服务的实例信息", func(t *testing.T) { _, instanceResp := discoverSuit.createCommonInstance(t, serviceResp, 123) defer discoverSuit.cleanInstance(instanceResp.GetId().GetValue()) _ = discoverSuit.DiscoverServer().Cache().TestUpdate() service := &apiservice.Service{Name: resp.Alias.Alias, Namespace: resp.Alias.Namespace} disResp := discoverSuit.DiscoverServer().ServiceInstancesCache(discoverSuit.DefaultCtx, &apiservice.DiscoverFilter{}, service) - So(respSuccess(disResp), ShouldEqual, true) - So(len(disResp.Instances), ShouldEqual, 1) + assert.True(t, api.IsSuccess(disResp), disResp.GetInfo().GetValue()) + assert.Equal(t, len(disResp.Instances), 1) }) - Convey("路由新建,不允许为别名新建路由", t, func() { + t.Run("路由新建,不允许为别名新建路由", func(t *testing.T) { routing := &apitraffic.Routing{ Service: resp.Alias.Alias, Namespace: resp.Alias.Namespace, @@ -570,7 +586,8 @@ func TestServiceAliasRelated(t *testing.T) { Inbounds: make([]*apitraffic.Route, 0), } routingResp := discoverSuit.DiscoverServer().CreateRoutingConfigs(discoverSuit.DefaultCtx, []*apitraffic.Routing{routing}) - So(respSuccess(routingResp), ShouldEqual, false) + assert.False(t, api.IsSuccess(routingResp), routingResp.GetInfo().GetValue()) + t.Logf("create routing ret code(%d), info(%s)", routingResp.Code.Value, routingResp.Info.Value) }) // Convey("路由Discover,别名查询路由,返回源服务的路由信息", t, func() { @@ -596,7 +613,9 @@ func TestGetServiceAliases(t *testing.T) { defer discoverSuit.Destroy() _, serviceResp := discoverSuit.createCommonService(t, 203) - defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Cleanup(func() { + discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + }) var aliases []*apiservice.Response count := 5 @@ -605,51 +624,53 @@ func TestGetServiceAliases(t *testing.T) { if !respSuccess(resp) { t.Fatalf("error: %+v", resp) } - defer discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + t.Cleanup(func() { + discoverSuit.cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + }) aliases = append(aliases, resp) } - Convey("可以查询到全量别名", t, func() { + t.Run("可以查询到全量别名", func(t *testing.T) { resp := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, nil) - So(respSuccess(resp), ShouldEqual, true) - So(len(resp.Aliases), ShouldBeGreaterThanOrEqualTo, count) - So(resp.Amount.Value, ShouldBeGreaterThanOrEqualTo, count) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + assert.True(t, len(resp.Aliases) >= count) + assert.True(t, int(resp.Amount.Value) >= count) }) - Convey("offset,limit测试", t, func() { + t.Run("offset,limit测试", func(t *testing.T) { query := map[string]string{"offset": "0", "limit": "100"} resp := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) - So(respSuccess(resp), ShouldEqual, true) - So(len(resp.Aliases), ShouldBeGreaterThanOrEqualTo, count) - So(resp.Amount.Value, ShouldBeGreaterThanOrEqualTo, count) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + assert.True(t, len(resp.Aliases) >= count) + assert.True(t, int(resp.Amount.Value) >= count) query["limit"] = "0" resp = discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) - So(respSuccess(resp), ShouldEqual, true) - So(len(resp.Aliases), ShouldEqual, 0) - So(resp.Amount.Value, ShouldBeGreaterThanOrEqualTo, count) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + assert.True(t, len(resp.Aliases) == 0, fmt.Sprintf("actual: %d, expect: %d", len(resp.Aliases), 0)) + assert.True(t, int(resp.Amount.Value) == count, fmt.Sprintf("actual: %d, expect: %d", len(resp.Aliases), count)) }) - Convey("不合法的过滤条件", t, func() { + t.Run("不合法的过滤条件", func(t *testing.T) { query := map[string]string{"xxx": "1", "limit": "100"} resp := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) - So(respSuccess(resp), ShouldEqual, false) + assert.False(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) }) - Convey("过滤条件可以生效", t, func() { + t.Run("过滤条件可以生效", func(t *testing.T) { query := map[string]string{ "alias": aliases[2].Alias.Alias.Value, "service": serviceResp.Name.Value, "namespace": serviceResp.Namespace.Value, } resp := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) - So(respSuccess(resp), ShouldEqual, true) - So(len(resp.Aliases), ShouldEqual, 1) - So(resp.Amount.Value, ShouldEqual, 1) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + assert.True(t, len(resp.Aliases) == 1) + assert.True(t, int(resp.Amount.Value) == 1) }) - Convey("找不到别名", t, func() { + t.Run("找不到别名", func(t *testing.T) { query := map[string]string{"alias": "x1.1.x2.x3"} resp := discoverSuit.DiscoverServer().GetServiceAliases(discoverSuit.DefaultCtx, query) - So(respSuccess(resp), ShouldEqual, true) - So(len(resp.Aliases), ShouldEqual, 0) - So(resp.Amount.Value, ShouldEqual, 0) + assert.True(t, api.IsSuccess(resp), resp.GetInfo().GetValue()) + assert.True(t, len(resp.Aliases) == 0) + assert.True(t, int(resp.Amount.Value) == 0) }) // Convey("支持owner过滤", t, func() { // query := map[string]string{"owner": "service-owner-203"} @@ -741,11 +762,11 @@ func TestServiceAliasDifferentNamespace(t *testing.T) { _, serviceResp := discoverSuit.createCommonService(t, 203) defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) - Convey("正常创建不一样命名空间的非Sid的别名", t, func() { + t.Run("正常创建不一样命名空间的非Sid的别名", func(t *testing.T) { alias := fmt.Sprintf("alias.%d", time.Now().Unix()) resp := discoverSuit.createCommonAlias(serviceResp, alias, defaultAliasNs, apiservice.AliasType_DEFAULT) defer discoverSuit.cleanServiceName(alias, defaultAliasNs) - So(respSuccess(resp), ShouldEqual, true) - So(resp.Alias.Alias.Value, ShouldEqual, alias) + assert.True(t, respSuccess(resp), resp.GetInfo().GetValue()) + assert.Equal(t, resp.Alias.Alias.Value, alias) }) } diff --git a/service/service_contract.go b/service/service_contract.go index d78536928..c6cb2267a 100644 --- a/service/service_contract.go +++ b/service/service_contract.go @@ -40,7 +40,8 @@ var ( "id": "id", "namespace": "namespace", "service": "service", - "name": "name", + "name": "type", + "type": "type", "protocol": "protocol", "version": "version", "brief": "brief", @@ -126,13 +127,14 @@ func (s *Server) GetServiceContracts(ctx context.Context, query map[string]strin searchFilters := map[string]string{} for k, v := range query { - if _, ok := contractSearchFilters[k]; !ok { + newK, ok := contractSearchFilters[k] + if !ok { continue } if v == "" { continue } - searchFilters[k] = v + searchFilters[newK] = v } offset, limit, err := utils.ParseOffsetAndLimit(searchFilters) if err != nil { diff --git a/service/service_contract_test.go b/service/service_contract_test.go index a0b4a68fe..fbec3ac85 100644 --- a/service/service_contract_test.go +++ b/service/service_contract_test.go @@ -45,7 +45,7 @@ func TestServer_CreateServiceContracts(t *testing.T) { expectTotal := 10 mockData := mockServiceContracts(expectTotal, true) - t.Run("正常创建一个服务契约配置", func(t *testing.T) { + t.Run("01-正常创建一个服务契约配置", func(t *testing.T) { resp := discoverSuit.DiscoverServer().CreateServiceContracts(discoverSuit.DefaultCtx, mockData) assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.String()) @@ -128,7 +128,7 @@ func TestServer_CreateServiceContracts(t *testing.T) { }) }) - t.Run("更新服务契约", func(t *testing.T) { + t.Run("02-更新服务契约", func(t *testing.T) { copyData := mockData[expectTotal-1] resp := discoverSuit.DiscoverServer().DeleteServiceContractInterfaces(discoverSuit.DefaultCtx, copyData) @@ -242,7 +242,7 @@ func TestServer_CreateServiceContracts(t *testing.T) { }) }) - t.Run("删除服务契约", func(t *testing.T) { + t.Run("03-删除服务契约", func(t *testing.T) { resp := discoverSuit.DiscoverServer().DeleteServiceContracts(discoverSuit.DefaultCtx, mockData) assert.Equal(t, uint32(apimodel.Code_ExecuteSuccess), resp.GetCode().GetValue(), resp.String()) assert.Equal(t, expectTotal, len(resp.GetResponses()), resp.String()) diff --git a/store/boltdb/client.go b/store/boltdb/client.go index ec2757ec1..729ddab98 100644 --- a/store/boltdb/client.go +++ b/store/boltdb/client.go @@ -106,12 +106,14 @@ func (cs *clientStore) BatchDeleteClients(ids []string) error { // GetMoreClients 根据mtime获取增量clients,返回所有store的变更信息 func (cs *clientStore) GetMoreClients(mtime time.Time, firstUpdate bool) (map[string]*model.Client, error) { - fields := []string{ClientFieldMtime} + fields := []string{ClientFieldMtime, ClientFieldValid} ret, err := cs.handler.LoadValuesByFilter(tblClient, fields, &clientObject{}, func(m map[string]interface{}) bool { if firstUpdate { - return true + // 首次更新,那么就只看 valid 状态 + valid, _ := m[ClientFieldValid].(bool) + return valid } - return m[ClientFieldMtime].(time.Time).After(mtime) + return !m[ClientFieldMtime].(time.Time).Before(mtime) }) if err != nil { diff --git a/store/boltdb/service.go b/store/boltdb/service.go index 4a54c4708..36a84cd31 100644 --- a/store/boltdb/service.go +++ b/store/boltdb/service.go @@ -702,6 +702,12 @@ func (ss *serviceStore) getServiceByID(id string) (*model.Service, error) { log.Errorf("[Store][boltdb] multiple services found %v", svc) return nil, ErrMultipleSvcFound } + if len(svc) == 0 { + return nil, nil + } + if _, ok := svc[id]; !ok { + return nil, nil + } svcRet := toModelService(svc[id].(*Service)) if svcRet.Valid { diff --git a/store/boltdb/service_contract.go b/store/boltdb/service_contract.go index 5a4de9642..aac1da718 100644 --- a/store/boltdb/service_contract.go +++ b/store/boltdb/service_contract.go @@ -237,31 +237,31 @@ func (s *serviceContractStore) GetServiceContracts(ctx context.Context, filter m } if searchNs, ok := filter["namespace"]; ok { saveNs, _ := m[ContractFieldNamespace].(string) - if saveNs != searchNs { + if !utils.IsWildMatch(saveNs, searchNs) { return false } } if searchSvc, ok := filter["service"]; ok { saveSvc, _ := m[ContractFieldService].(string) - if saveSvc != searchSvc { + if !utils.IsWildMatch(saveSvc, searchSvc) { return false } } if searchProtocol, ok := filter["protocol"]; ok { saveProtocol, _ := m[ContractFieldProtocol].(string) - if saveProtocol != searchProtocol { + if !utils.IsWildMatch(saveProtocol, searchProtocol) { return false } } if searchVer, ok := filter["version"]; ok { saveVer, _ := m[ContractFieldVersion].(string) - if searchVer != saveVer { + if !utils.IsWildMatch(saveVer, searchVer) { return false } } if searchType, ok := filter["type"]; ok { saveType, _ := m[ContractFieldType].(string) - if searchType != saveType { + if !utils.IsWildMatch(saveType, searchType) { return false } } From 13dfc0f8590cb4d6a1de14451581ec964b7e9606 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Sun, 23 Jun 2024 22:59:11 +0800 Subject: [PATCH 15/23] feat:support nacos-address server endpoints --- admin/job/clean_deleted_resource.go | 72 ++++++++-- admin/job/job.go | 15 +- cache/config/config_file.go | 131 +++++++----------- common/utils/common.go | 5 + config/client.go | 2 + config/client_test.go | 14 +- config/config_file_release.go | 3 + config/config_file_release_test.go | 12 +- .../paramcheck/config_file_check.go | 2 +- .../paramcheck/config_file_release_check.go | 21 ++- config/interceptor/register.go | 10 +- config/server.go | 5 +- config/test_export.go | 2 +- service/service_alias_test.go | 3 +- store/admin_api.go | 14 +- store/boltdb/admin.go | 15 ++ store/boltdb/config_file_release.go | 30 ++-- store/mock/api_mock.go | 45 ++++++ store/mysql/admin.go | 15 ++ store/mysql/config_file_release.go | 2 +- test/data/polaris-server.yaml | 2 +- test/data/service_test.yaml | 6 +- 22 files changed, 281 insertions(+), 145 deletions(-) diff --git a/admin/job/clean_deleted_resource.go b/admin/job/clean_deleted_resource.go index e1e668d3e..8d315c2cc 100644 --- a/admin/job/clean_deleted_resource.go +++ b/admin/job/clean_deleted_resource.go @@ -27,14 +27,25 @@ import ( ) var cleanFuncMapping = map[string]func(timeout time.Duration, job *cleanDeletedResourceJob){ - "instance": cleanDeletedInstances, - "service": nil, - "clients": cleanDeletedClients, - "circuitbreaker_rule": nil, - "ratelimit_rule": nil, - "router_rule": nil, - "faultdetect_rule": nil, - "config_file_release": nil, + "instance": cleanDeletedInstances, + "service": cleanDeletedServices, + "clients": cleanDeletedClients, + "circuitbreaker_rule": func(timeout time.Duration, job *cleanDeletedResourceJob) { + cleanDeletedRules("circuitbreaker_rule", timeout, job) + }, + "ratelimit_rule": func(timeout time.Duration, job *cleanDeletedResourceJob) { + cleanDeletedRules("ratelimit_rule", timeout, job) + }, + "router_rule": func(timeout time.Duration, job *cleanDeletedResourceJob) { + cleanDeletedRules("router_rule", timeout, job) + }, + "faultdetect_rule": func(timeout time.Duration, job *cleanDeletedResourceJob) { + cleanDeletedRules("faultdetect_rule", timeout, job) + }, + "lane_rule": func(timeout time.Duration, job *cleanDeletedResourceJob) { + cleanDeletedRules("lane_rule", timeout, job) + }, + "config_file_release": cleanDeletedConfigFiles, } type CleanDeletedResource struct { @@ -110,6 +121,36 @@ func (job *cleanDeletedResourceJob) interval() time.Duration { return time.Minute } +func cleanDeletedConfigFiles(timeout time.Duration, job *cleanDeletedResourceJob) { + batchSize := uint32(100) + for { + count, err := job.storage.BatchCleanDeletedConfigFiles(timeout, batchSize) + if err != nil { + log.Errorf("[Maintain][Job][CleanDeletedClients] batch clean deleted client, err: %v", err) + break + } + log.Infof("[Maintain][Job][CleanDeletedClients] clean deleted client count %d", count) + if count < batchSize { + break + } + } +} + +func cleanDeletedServices(timeout time.Duration, job *cleanDeletedResourceJob) { + batchSize := uint32(100) + for { + count, err := job.storage.BatchCleanDeletedServices(timeout, batchSize) + if err != nil { + log.Errorf("[Maintain][Job][CleanDeletedClients] batch clean deleted client, err: %v", err) + break + } + log.Infof("[Maintain][Job][CleanDeletedClients] clean deleted client count %d", count) + if count < batchSize { + break + } + } +} + func cleanDeletedClients(timeout time.Duration, job *cleanDeletedResourceJob) { batchSize := uint32(100) for { @@ -140,3 +181,18 @@ func cleanDeletedInstances(timeout time.Duration, job *cleanDeletedResourceJob) } } } + +func cleanDeletedRules(rule string, timeout time.Duration, job *cleanDeletedResourceJob) { + batchSize := uint32(100) + for { + count, err := job.storage.BatchCleanDeletedRules(rule, timeout, batchSize) + if err != nil { + log.Errorf("[Maintain][Job][CleanDeletedClients] batch clean deleted client, err: %v", err) + break + } + log.Infof("[Maintain][Job][CleanDeletedClients] clean deleted client count %d", count) + if count < batchSize { + break + } + } +} diff --git a/admin/job/job.go b/admin/job/job.go index 0816e4301..7128d8e63 100644 --- a/admin/job/job.go +++ b/admin/job/job.go @@ -61,6 +61,11 @@ func NewMaintainJobs(namingServer service.DiscoverServer, cacheMgn *cache.CacheM // StartMaintainJobs func (mj *MaintainJobs) StartMaintianJobs(configs []JobConfig) error { + if err := mj.storage.StartLeaderElection(store.ElectionKeyMaintainJob); err != nil { + log.Errorf("[Maintain][Job] start leader election err: %v", err) + return err + } + ctx, cancel := context.WithCancel(context.Background()) mj.cancel = cancel for _, cfg := range configs { @@ -81,10 +86,6 @@ func (mj *MaintainJobs) StartMaintianJobs(configs []JobConfig) error { log.Errorf("[Maintain][Job] job (%s) fail to init, err: %v", jobName, err) return fmt.Errorf("[Maintain][Job] job (%s) fail to init", jobName) } - if err := mj.storage.StartLeaderElection(store.ElectionKeyMaintainJobPrefix + jobName); err != nil { - log.Errorf("[Maintain][Job][%s] start leader election err: %v", jobName, err) - return err - } runAdminJob(ctx, jobName, job.interval(), job, mj.storage) mj.startedJobs[jobName] = job } @@ -117,8 +118,8 @@ func (mj *MaintainJobs) StopMaintainJobs() { } func runAdminJob(ctx context.Context, name string, interval time.Duration, job maintainJob, storage store.Store) { - f := func() { - if !storage.IsLeader(store.ElectionKeyMaintainJobPrefix + name) { + safeExec := func() { + if !storage.IsLeader(store.ElectionKeyMaintainJob) { log.Infof("[Maintain][Job][%s] I am follower", name) job.clear() return @@ -135,7 +136,7 @@ func runAdminJob(ctx context.Context, name string, interval time.Duration, job m case <-ctx.Done(): return case <-ticker.C: - f() + safeExec() } } }(ctx) diff --git a/cache/config/config_file.go b/cache/config/config_file.go index be23bf818..de0d7fc86 100644 --- a/cache/config/config_file.go +++ b/cache/config/config_file.go @@ -86,7 +86,7 @@ func (fc *fileCache) Initialize(opt map[string]interface{}) error { fc.metricsReleaseCount = utils.NewSyncMap[string, *utils.SyncMap[string, uint64]]() fc.preMetricsFiles = utils.NewAtomicValue[map[string]map[string]struct{}](map[string]map[string]struct{}{}) fc.lastReportTime = utils.NewAtomicValue[time.Time](time.Time{}) - valueCache, err := fc.openBoltCache(opt) + valueCache, err := openBoltCache(opt) if err != nil { return err } @@ -94,7 +94,7 @@ func (fc *fileCache) Initialize(opt map[string]interface{}) error { return nil } -func (fc *fileCache) openBoltCache(opt map[string]interface{}) (*bbolt.DB, error) { +func openBoltCache(opt map[string]interface{}) (*bbolt.DB, error) { path, _ := opt["cachePath"].(string) if path == "" { path = "./data/cache/config" @@ -138,9 +138,6 @@ func (fc *fileCache) realUpdate() (map[string]time.Time, int64, error) { if len(releases) == 0 { return nil, 0, nil } - if err := fc.setActiveReleases(releases); err != nil { - return nil, 0, err - } lastMimes, update, del, err := fc.setReleases(releases) if err != nil { @@ -152,56 +149,6 @@ func (fc *fileCache) realUpdate() (map[string]time.Time, int64, error) { return lastMimes, int64(len(releases)), err } -func (fc *fileCache) setActiveReleases(releases []*model.ConfigFileRelease) error { - // 按照 namespace->group->file 分类,保存最后一个 version 版本的 release 发布信息 - effectRelease := map[string]map[string]map[string]*model.ConfigFileRelease{} - for i := range releases { - item := releases[i] - oldVal := fc.GetRelease(*item.ConfigFileReleaseKey) - // 判断是否取消发布了 - if oldVal != nil && (oldVal.Active && !item.Active) { - // 如果配置文件发布被取消了,那么这里等同认为 valid == false 的情况,同时记录 version 为最新的 version - item = &model.ConfigFileRelease{ - SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ - ConfigFileReleaseKey: item.ConfigFileReleaseKey, - Valid: false, - Version: item.Version, - Active: true, - }, - } - } - - if item.Active { - if _, ok := effectRelease[item.Namespace]; !ok { - effectRelease[item.Namespace] = make(map[string]map[string]*model.ConfigFileRelease) - } - if _, ok := effectRelease[item.Namespace][item.Group]; !ok { - effectRelease[item.Namespace][item.Group] = make(map[string]*model.ConfigFileRelease) - } - saveVal, ok := effectRelease[item.Namespace][item.Group][item.FileName] - // 只保存最新的 version 的 active 状态的配置发布记录 - // 该逻辑主要是为了避免配置回滚的情况下,客户端有概率性读取到中间态数据,导致程序执行不符合用户预期 - if !ok || saveVal.Version < item.Version { - effectRelease[item.Namespace][item.Group][item.FileName] = item - } - } - } - - for _, groups := range effectRelease { - for _, files := range groups { - for _, releaseFile := range files { - if !releaseFile.Valid { - fc.cleanActiveRelease(releaseFile.SimpleConfigFileRelease) - } else { - fc.saveActiveRelease(releaseFile) - } - fc.sendEvent(releaseFile) - } - } - } - return nil -} - func (fc *fileCache) setReleases(releases []*model.ConfigFileRelease) (map[string]time.Time, int, int, error) { lastMtime := fc.LastMtime().Unix() update := 0 @@ -280,7 +227,15 @@ func (fc *fileCache) handleUpdateRelease(oldVal *model.SimpleConfigFileRelease, files, _ := group.Load(item.FileName) files.Store(item.Name, item.SimpleConfigFileRelease) }() - return nil + + if !item.Active { + if oldVal != nil && oldVal.Active { + return fc.cleanActiveRelease(oldVal) + } + return nil + } + + return fc.saveActiveRelease(item) } // handleDeleteRelease @@ -289,27 +244,32 @@ func (fc *fileCache) handleDeleteRelease(release *model.SimpleConfigFileRelease) return nil } fc.releases.Del(release.Id) + func() { + // 记录 namespace -> group -> file_name -> []SimpleRelease 信息 + if _, ok := fc.name2release.Load(release.Namespace); !ok { + return + } + namespace, _ := fc.name2release.Load(release.Namespace) + if _, ok := namespace.Load(release.Group); !ok { + return + } + group, _ := namespace.Load(release.Group) + if _, ok := group.Load(release.FileName); !ok { + return + } - // 记录 namespace -> group -> file_name -> []SimpleRelease 信息 - if _, ok := fc.name2release.Load(release.Namespace); !ok { - return nil - } - namespace, _ := fc.name2release.Load(release.Namespace) - if _, ok := namespace.Load(release.Group); !ok { - return nil - } - group, _ := namespace.Load(release.Group) - if _, ok := group.Load(release.FileName); !ok { - return nil - } + files, _ := group.Load(release.FileName) + files.Delete(release.Name) - files, _ := group.Load(release.FileName) - files.Delete(release.Name) + if files.Len() == 0 { + group.Delete(release.FileName) + } + }() - if files.Len() == 0 { - group.Delete(release.FileName) + if !release.Active { + return nil } - return nil + return fc.cleanActiveRelease(release) } func (fc *fileCache) saveActiveRelease(item *model.ConfigFileRelease) error { @@ -338,11 +298,22 @@ func (fc *fileCache) saveActiveRelease(item *model.ConfigFileRelease) error { } func (fc *fileCache) cleanActiveRelease(release *model.SimpleConfigFileRelease) error { - if namespace, ok := fc.activeReleases.Load(release.Namespace); ok { - if group, ok := namespace.Load(release.Group); ok { - group.Delete(release.ActiveKey()) - } + namespace, ok := fc.activeReleases.Load(release.Namespace) + if !ok { + return nil } + group, ok := namespace.Load(release.Group) + if !ok { + return nil + } + + oldActive, ok := group.Load(release.ActiveKey()) + // 如果存在,并且发现 active 缓存保留的数据 version >= 当前 release 的 version,直接跳过 + if ok && oldActive.Version > release.Version { + return nil + } + + group.Delete(release.ActiveKey()) if err := fc.valueCache.Update(func(tx *bbolt.Tx) error { bucket := tx.Bucket([]byte(release.OwnerKey())) if bucket == nil { @@ -451,12 +422,12 @@ func (fc *fileCache) GetGroupActiveReleases(namespace, group string) ([]*model.C return ret, revision } -// GetActiveRelease +// GetActiveRelease . func (fc *fileCache) GetActiveRelease(namespace, group, fileName string) *model.ConfigFileRelease { return fc.handleGetActiveRelease(namespace, group, fileName, model.ReleaseTypeFull) } -// GetActiveGrayRelease +// GetActiveGrayRelease . func (fc *fileCache) GetActiveGrayRelease(namespace, group, fileName string) *model.ConfigFileRelease { return fc.handleGetActiveRelease(namespace, group, fileName, model.ReleaseTypeGray) } @@ -487,7 +458,7 @@ func (fc *fileCache) handleGetActiveRelease(namespace, group, fileName string, t return ret } -// GetRelease +// GetRelease . func (fc *fileCache) GetRelease(key model.ConfigFileReleaseKey) *model.ConfigFileRelease { var ( simple *model.SimpleConfigFileRelease diff --git a/common/utils/common.go b/common/utils/common.go index d835e0d05..43bac20b2 100644 --- a/common/utils/common.go +++ b/common/utils/common.go @@ -476,6 +476,11 @@ func ZapReleaseName(fileName string) zap.Field { return zap.String("release-name", fileName) } +// ZapVersion 生成 version 的日志描述 +func ZapVersion(version uint64) zap.Field { + return zap.Uint64("version", version) +} + // CheckDbStrFieldLen 检查name字段是否超过DB中对应字段的最大字符长度限制 func CheckDbStrFieldLen(param *wrappers.StringValue, dbLen int) error { return CheckDbRawStrFieldLen(param.GetValue(), dbLen) diff --git a/config/client.go b/config/client.go index eec6707e6..f960b45de 100644 --- a/config/client.go +++ b/config/client.go @@ -62,6 +62,8 @@ func (s *Server) GetConfigFileWithCache(ctx context.Context, } // 客户端版本号大于服务端版本号,服务端不返回变更 if req.GetVersion().GetValue() > release.Version { + log.Debug("[Config][Service] get config file to client", utils.RequestID(ctx), + zap.Uint64("client-version", req.GetVersion().GetValue()), zap.Uint64("server-version", release.Version)) return api.NewConfigClientResponse(apimodel.Code_DataNoChange, req) } configFile, err := toClientInfo(req, release) diff --git a/config/client_test.go b/config/client_test.go index ba650115f..37df0933e 100644 --- a/config/client_test.go +++ b/config/client_test.go @@ -313,7 +313,7 @@ func TestClientVersionBehindServer(t *testing.T) { configFile.Content = utils.NewStringValue("content" + strconv.Itoa(i)) // 更新 rsp2 := testSuit.ConfigServer().UpdateConfigFile(testSuit.DefaultCtx, configFile) - assert.Equal(t, api.ExecuteSuccess, rsp2.Code.GetValue()) + assert.Equal(t, api.ExecuteSuccess, rsp2.Code.GetValue(), rsp2.GetInfo().GetValue()) // 发布 rsp3 := testSuit.ConfigServer().PublishConfigFile(testSuit.DefaultCtx, assembleConfigFileRelease(configFile)) assert.Equal(t, api.ExecuteSuccess, rsp3.Code.GetValue(), rsp3.GetInfo().GetValue()) @@ -324,9 +324,9 @@ func TestClientVersionBehindServer(t *testing.T) { latestContent := "content4" fileInfo := &apiconfig.ClientConfigFileInfo{ - Namespace: &wrapperspb.StringValue{Value: testNamespace}, - Group: &wrapperspb.StringValue{Value: testGroup}, - FileName: &wrapperspb.StringValue{Value: testFile}, + Namespace: &wrapperspb.StringValue{Value: configFile.GetNamespace().Value}, + Group: &wrapperspb.StringValue{Value: configFile.GetGroup().Value}, + FileName: &wrapperspb.StringValue{Value: configFile.GetName().Value}, Version: &wrapperspb.UInt64Value{Value: clientVersion}, } @@ -334,7 +334,7 @@ func TestClientVersionBehindServer(t *testing.T) { // 拉取配置接口 rsp4 := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, fileInfo) - assert.Equal(t, api.ExecuteSuccess, rsp4.Code.GetValue()) + assert.Equal(t, api.ExecuteSuccess, rsp4.Code.GetValue(), rsp4.GetInfo().GetValue()) assert.NotNil(t, rsp4.ConfigFile) assert.Equal(t, uint64(5), rsp4.ConfigFile.Version.GetValue()) assert.Equal(t, latestContent, rsp4.ConfigFile.Content.GetValue()) @@ -521,8 +521,6 @@ func TestDeleteConfigFile(t *testing.T) { watchConfigFiles[i].Namespace = wrapperspb.String(newMockNs) } - t.Log("add config watcher") - // 删除配置文件 t.Log("remove config file") rsp3 := testSuit.ConfigServer().DeleteConfigFile(testSuit.DefaultCtx, &apiconfig.ConfigFile{ @@ -541,7 +539,7 @@ func TestDeleteConfigFile(t *testing.T) { // 重新拉取配置,获取不到配置文件 rsp4 := testSuit.ConfigServer().GetConfigFileWithCache(testSuit.DefaultCtx, fileInfo) - assert.Equal(t, uint32(api.NotFoundResource), rsp4.Code.GetValue()) + assert.Equal(t, rsp4.Code.GetValue(), api.NotFoundResource, rsp4.GetInfo().GetValue()) } // TestServer_GetConfigFileNamesWithCache diff --git a/config/config_file_release.go b/config/config_file_release.go index a2d23ca69..3f361858c 100644 --- a/config/config_file_release.go +++ b/config/config_file_release.go @@ -139,6 +139,9 @@ func (s *Server) handlePublishConfigFile(ctx context.Context, tx store.Tx, } // 重新激活 if saveRelease != nil { + log.Debug("[Config][Release] re-active config file release.", + utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(group), + utils.ZapFileName(fileName), utils.ZapReleaseName(fileRelease.Name)) if err := s.storage.ActiveConfigFileReleaseTx(tx, fileRelease); err != nil { log.Error("[Config][Release] re-active config file release error.", utils.RequestID(ctx), utils.ZapNamespace(namespace), utils.ZapGroup(group), diff --git a/config/config_file_release_test.go b/config/config_file_release_test.go index 7d92da17e..17eee06c4 100644 --- a/config/config_file_release_test.go +++ b/config/config_file_release_test.go @@ -517,7 +517,7 @@ func Test_GrayConfigFileRelease(t *testing.T) { mockClientIP = "1.1.1.1" ) - t.Run("first publish", func(t *testing.T) { + t.Run("01-first-publish", func(t *testing.T) { resp := testSuit.ConfigServer().UpsertAndReleaseConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFilePublishInfo{ Namespace: utils.NewStringValue(mockNamespace), Group: utils.NewStringValue(mockGroup), @@ -540,7 +540,7 @@ func Test_GrayConfigFileRelease(t *testing.T) { assert.Equal(t, mockContent, resp.GetConfigFileRelease().GetContent().GetValue()) }) - t.Run("gray_publish", func(t *testing.T) { + t.Run("02-gray_publish", func(t *testing.T) { resp := testSuit.ConfigServer().UpdateConfigFile(testSuit.DefaultCtx, &config_manage.ConfigFile{ Namespace: utils.NewStringValue(mockNamespace), Group: utils.NewStringValue(mockGroup), @@ -555,8 +555,8 @@ func Test_GrayConfigFileRelease(t *testing.T) { Namespace: utils.NewStringValue(mockNamespace), Group: utils.NewStringValue(mockGroup), FileName: utils.NewStringValue(mockFileName), - Name: utils.NewStringValue(mockBetaReleaseName), Content: utils.NewStringValue(mockNewContent), + Name: utils.NewStringValue(mockBetaReleaseName), ReleaseType: wrapperspb.String(model.ReleaseTypeGray), BetaLabels: []*apimodel.ClientLabel{ { @@ -608,7 +608,7 @@ func Test_GrayConfigFileRelease(t *testing.T) { 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), @@ -625,7 +625,7 @@ func Test_GrayConfigFileRelease(t *testing.T) { }) // 测试存在灰度发布配置时, 不得发布新的配置文件 - t.Run("normal_publish_when_exist_gray", func(t *testing.T) { + t.Run("03-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), @@ -638,7 +638,7 @@ func Test_GrayConfigFileRelease(t *testing.T) { }) // 删除已发布的灰度配置,获取不到 - t.Run("delete_gray_release", func(t *testing.T) { + t.Run("04-delete_gray_release", func(t *testing.T) { resp := testSuit.ConfigServer().StopGrayConfigFileReleases(testSuit.DefaultCtx, []*config_manage.ConfigFileRelease{ { Namespace: utils.NewStringValue(mockNamespace), diff --git a/config/interceptor/paramcheck/config_file_check.go b/config/interceptor/paramcheck/config_file_check.go index 7790694ab..72c8bac56 100644 --- a/config/interceptor/paramcheck/config_file_check.go +++ b/config/interceptor/paramcheck/config_file_check.go @@ -67,7 +67,7 @@ func (s *Server) SearchConfigFile(ctx context.Context, searchFilters[k] = v } } - return s.nextServer.SearchConfigFile(ctx, filter) + return s.nextServer.SearchConfigFile(ctx, searchFilters) } // UpdateConfigFile 更新配置文件 diff --git a/config/interceptor/paramcheck/config_file_release_check.go b/config/interceptor/paramcheck/config_file_release_check.go index 00535ac76..a43337392 100644 --- a/config/interceptor/paramcheck/config_file_release_check.go +++ b/config/interceptor/paramcheck/config_file_release_check.go @@ -25,14 +25,29 @@ import ( apimodel "github.com/polarismesh/specification/source/go/api/v1/model" api "github.com/polarismesh/polaris/common/api/v1" + "github.com/polarismesh/polaris/common/model" "github.com/polarismesh/polaris/common/utils" ) // PublishConfigFile 发布配置文件 func (s *Server) PublishConfigFile(ctx context.Context, - configFileRelease *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { - - return s.nextServer.PublishConfigFile(ctx, configFileRelease) + req *apiconfig.ConfigFileRelease) *apiconfig.ConfigResponse { + if err := CheckFileName(req.GetFileName()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileName) + } + if err := utils.CheckResourceName(req.GetNamespace()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidNamespaceName) + } + if err := utils.CheckResourceName(req.GetGroup()); err != nil { + return api.NewConfigResponse(apimodel.Code_InvalidConfigFileGroupName) + } + if !s.checkNamespaceExisted(req.GetNamespace().GetValue()) { + return api.NewConfigResponse(apimodel.Code_NotFoundNamespace) + } + if req.GetReleaseType().GetValue() == model.ReleaseTypeGray && len(req.GetBetaLabels()) == 0 { + return api.NewConfigResponse(apimodel.Code_InvalidMatchRule) + } + return s.nextServer.PublishConfigFile(ctx, req) } // GetConfigFileRelease 获取配置文件发布内容 diff --git a/config/interceptor/register.go b/config/interceptor/register.go index cd235a0a1..2953aae32 100644 --- a/config/interceptor/register.go +++ b/config/interceptor/register.go @@ -27,19 +27,15 @@ import ( ) func init() { - err := config.RegisterServerProxy("paramcheck", func(cacheMgr cachetypes.CacheManager, + err := config.RegisterServerProxy("paramcheck", func(cacheMgr cachetypes.CacheManager, s store.Store, next config.ConfigCenterServer, cfg config.Config) (config.ConfigCenterServer, error) { - storage, err := store.GetStore() - if err != nil { - return nil, err - } - return paramcheck.New(next, cacheMgr, storage, cfg), nil + return paramcheck.New(next, cacheMgr, s, cfg), nil }) if err != nil { panic(err) } - err = config.RegisterServerProxy("auth", func(cacheMgr cachetypes.CacheManager, + err = config.RegisterServerProxy("auth", func(cacheMgr cachetypes.CacheManager, s store.Store, next config.ConfigCenterServer, cfg config.Config) (config.ConfigCenterServer, error) { userMgr, err := auth.GetUserServer() if err != nil { diff --git a/config/server.go b/config/server.go index 63169eb4a..1f1db53d4 100644 --- a/config/server.go +++ b/config/server.go @@ -46,7 +46,8 @@ var ( serverProxyFactories = map[string]ServerProxyFactory{} ) -type ServerProxyFactory func(cacheMgr cachetypes.CacheManager, pre ConfigCenterServer, cfg Config) (ConfigCenterServer, error) +type ServerProxyFactory func(cacheMgr cachetypes.CacheManager, s store.Store, + pre ConfigCenterServer, cfg Config) (ConfigCenterServer, error) func RegisterServerProxy(name string, factor ServerProxyFactory) error { if _, ok := serverProxyFactories[name]; ok { @@ -128,7 +129,7 @@ func doInitialize(ctx context.Context, svcConf Config, s store.Store, cacheMgr c return nil, nil, fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) } - tmpSvr, err := factory(cacheMgr, proxySvr, svcConf) + tmpSvr, err := factory(cacheMgr, s, proxySvr, svcConf) if err != nil { return nil, nil, err } diff --git a/config/test_export.go b/config/test_export.go index 0af5f5d5b..6c3366714 100644 --- a/config/test_export.go +++ b/config/test_export.go @@ -56,7 +56,7 @@ func TestInitialize(ctx context.Context, config Config, s store.Store, cacheMgn return nil, nil, fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i]) } - tmpSvr, err := factory(cacheMgn, proxySvr, config) + tmpSvr, err := factory(cacheMgn, s, proxySvr, config) if err != nil { return nil, nil, err } diff --git a/service/service_alias_test.go b/service/service_alias_test.go index 4e6e5913e..c444b6dfe 100644 --- a/service/service_alias_test.go +++ b/service/service_alias_test.go @@ -610,10 +610,9 @@ func TestGetServiceAliases(t *testing.T) { if err := discoverSuit.Initialize(); err != nil { t.Fatal(err) } - defer discoverSuit.Destroy() - _, serviceResp := discoverSuit.createCommonService(t, 203) t.Cleanup(func() { + discoverSuit.Destroy() discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) }) diff --git a/store/admin_api.go b/store/admin_api.go index 9381b82f4..ca44e4fe1 100644 --- a/store/admin_api.go +++ b/store/admin_api.go @@ -25,30 +25,30 @@ import ( const ( ElectionKeySelfServiceChecker = "polaris.checker" - ElectionKeyMaintainJobPrefix = "MaintainJob." + ElectionKeyMaintainJob = "MaintainJob" ) type AdminStore interface { // StartLeaderElection start leader election StartLeaderElection(key string) error - // IsLeader whether it is leader node IsLeader(key string) bool - // ListLeaderElections list all leaderelection ListLeaderElections() ([]*model.LeaderElection, error) - // ReleaseLeaderElection force release leader status ReleaseLeaderElection(key string) error - // BatchCleanDeletedInstances batch clean soft deleted instances BatchCleanDeletedInstances(timeout time.Duration, batchSize uint32) (uint32, error) - // GetUnHealthyInstances get unhealthy instances which mtime time out GetUnHealthyInstances(timeout time.Duration, limit uint32) ([]string, error) - // BatchCleanDeletedClients batch clean soft deleted clients BatchCleanDeletedClients(timeout time.Duration, batchSize uint32) (uint32, error) + // BatchCleanDeletedServices batch clean soft deleted clients + BatchCleanDeletedServices(timeout time.Duration, batchSize uint32) (uint32, error) + // BatchCleanDeletedRules batch clean soft deleted clients + BatchCleanDeletedRules(rule string, timeout time.Duration, batchSize uint32) (uint32, error) + // BatchCleanDeletedConfigFiles batch clean soft deleted clients + BatchCleanDeletedConfigFiles(timeout time.Duration, batchSize uint32) (uint32, error) } // LeaderChangeEvent diff --git a/store/boltdb/admin.go b/store/boltdb/admin.go index b18d4bf47..3301fb5b7 100644 --- a/store/boltdb/admin.go +++ b/store/boltdb/admin.go @@ -254,3 +254,18 @@ func (m *adminStore) BatchCleanDeletedClients(timeout time.Duration, batchSize u } return count, nil } + +// BatchCleanDeletedServices batch clean soft deleted clients +func (m *adminStore) BatchCleanDeletedServices(timeout time.Duration, batchSize uint32) (uint32, error) { + return 0, nil +} + +// BatchCleanDeletedRules batch clean soft deleted clients +func (m *adminStore) BatchCleanDeletedRules(rule string, timeout time.Duration, batchSize uint32) (uint32, error) { + return 0, nil +} + +// BatchCleanDeletedConfigFiles batch clean soft deleted clients +func (m *adminStore) BatchCleanDeletedConfigFiles(timeout time.Duration, batchSize uint32) (uint32, error) { + return 0, nil +} diff --git a/store/boltdb/config_file_release.go b/store/boltdb/config_file_release.go index 610142f76..70030ef1e 100644 --- a/store/boltdb/config_file_release.go +++ b/store/boltdb/config_file_release.go @@ -19,12 +19,14 @@ package boltdb import ( "errors" + "sort" "time" bolt "go.etcd.io/bbolt" "go.uber.org/zap" "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/store" ) @@ -77,7 +79,7 @@ func (cfr *configFileReleaseStore) CreateConfigFileReleaseTx(proxyTx store.Tx, return err } for i := range values { - if ret := cfr.toModelData(values[i].(*ConfigFileRelease)); ret != nil { + if ret := cfr.toValisModelData(values[i].(*ConfigFileRelease)); ret != nil { return store.NewStatusError(store.DuplicateEntryErr, "exist record") } } @@ -103,6 +105,10 @@ func (cfr *configFileReleaseStore) CreateConfigFileReleaseTx(proxyTx store.Tx, fileRelease.Active = true fileRelease.Version = maxVersion + 1 + + log.Debug("[ConfigFileRelease] cur release version", utils.ZapNamespace(fileRelease.Namespace), + utils.ZapGroup(fileRelease.Group), utils.ZapFileName(fileRelease.FileName), utils.ZapVersion(fileRelease.Version)) + err = saveValue(tx, tblConfigFileRelease, fileRelease.ReleaseKey(), cfr.toStoreData(fileRelease)) if err != nil { log.Error("[ConfigFileRelease] save info", zap.Error(err)) @@ -120,7 +126,7 @@ func (cfr *configFileReleaseStore) GetConfigFileRelease(args *model.ConfigFileRe return nil, err } for _, v := range values { - return cfr.toModelData(v.(*ConfigFileRelease)), nil + return cfr.toValisModelData(v.(*ConfigFileRelease)), nil } return nil, nil } @@ -136,7 +142,7 @@ func (cfr *configFileReleaseStore) GetConfigFileReleaseTx(tx store.Tx, return nil, err } for _, v := range values { - return cfr.toModelData(v.(*ConfigFileRelease)), nil + return cfr.toValisModelData(v.(*ConfigFileRelease)), nil } return nil, nil } @@ -185,7 +191,7 @@ func (cfr *configFileReleaseStore) GetConfigFileActiveReleaseTx(tx store.Tx, return nil, err } for _, v := range values { - return cfr.toModelData(v.(*ConfigFileRelease)), nil + return cfr.toValisModelData(v.(*ConfigFileRelease)), nil } return nil, nil } @@ -222,7 +228,7 @@ func (cfr *configFileReleaseStore) GetConfigFileBetaReleaseTx(tx store.Tx, return nil, err } for _, v := range values { - return cfr.toModelData(v.(*ConfigFileRelease)), nil + return cfr.toValisModelData(v.(*ConfigFileRelease)), nil } return nil, nil } @@ -328,6 +334,9 @@ func (cfr *configFileReleaseStore) GetMoreReleaseFile(firstUpdate bool, for _, v := range ret { releases = append(releases, cfr.toModelData(v.(*ConfigFileRelease))) } + sort.Slice(releases, func(i, j int) bool { + return releases[i].Version > releases[j].Version + }) return releases, nil } @@ -356,7 +365,7 @@ func (cfr *configFileReleaseStore) inactiveConfigFileRelease(tx *bolt.Tx, release *model.ConfigFileRelease) (uint64, error) { fields := []string{FileReleaseFieldNamespace, FileReleaseFieldGroup, FileReleaseFieldFileName, - FileReleaseFieldVersion, FileReleaseFieldFlag, FileReleaseFieldActive, FileReleaseFieldType} + FileReleaseFieldVersion, FileReleaseFieldValid, FileReleaseFieldActive, FileReleaseFieldType} values := map[string]interface{}{} var maxVersion uint64 @@ -445,10 +454,15 @@ type ConfigFileRelease struct { Typ string } -func (cfr *configFileReleaseStore) toModelData(data *ConfigFileRelease) *model.ConfigFileRelease { - if !data.Valid { +func (cfr *configFileReleaseStore) toValisModelData(data *ConfigFileRelease) *model.ConfigFileRelease { + saveData := cfr.toModelData(data) + if !saveData.Valid { return nil } + return saveData +} + +func (cfr *configFileReleaseStore) toModelData(data *ConfigFileRelease) *model.ConfigFileRelease { return &model.ConfigFileRelease{ SimpleConfigFileRelease: &model.SimpleConfigFileRelease{ ConfigFileReleaseKey: &model.ConfigFileReleaseKey{ diff --git a/store/mock/api_mock.go b/store/mock/api_mock.go index a7fcda1c7..9fed72254 100644 --- a/store/mock/api_mock.go +++ b/store/mock/api_mock.go @@ -234,6 +234,21 @@ func (mr *MockStoreMockRecorder) BatchCleanDeletedClients(timeout, batchSize int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCleanDeletedClients", reflect.TypeOf((*MockStore)(nil).BatchCleanDeletedClients), timeout, batchSize) } +// BatchCleanDeletedConfigFiles mocks base method. +func (m *MockStore) BatchCleanDeletedConfigFiles(timeout time.Duration, batchSize uint32) (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchCleanDeletedConfigFiles", timeout, batchSize) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BatchCleanDeletedConfigFiles indicates an expected call of BatchCleanDeletedConfigFiles. +func (mr *MockStoreMockRecorder) BatchCleanDeletedConfigFiles(timeout, batchSize interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCleanDeletedConfigFiles", reflect.TypeOf((*MockStore)(nil).BatchCleanDeletedConfigFiles), timeout, batchSize) +} + // BatchCleanDeletedInstances mocks base method. func (m *MockStore) BatchCleanDeletedInstances(timeout time.Duration, batchSize uint32) (uint32, error) { m.ctrl.T.Helper() @@ -249,6 +264,36 @@ func (mr *MockStoreMockRecorder) BatchCleanDeletedInstances(timeout, batchSize i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCleanDeletedInstances", reflect.TypeOf((*MockStore)(nil).BatchCleanDeletedInstances), timeout, batchSize) } +// BatchCleanDeletedRules mocks base method. +func (m *MockStore) BatchCleanDeletedRules(rule string, timeout time.Duration, batchSize uint32) (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchCleanDeletedRules", rule, timeout, batchSize) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BatchCleanDeletedRules indicates an expected call of BatchCleanDeletedRules. +func (mr *MockStoreMockRecorder) BatchCleanDeletedRules(rule, timeout, batchSize interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCleanDeletedRules", reflect.TypeOf((*MockStore)(nil).BatchCleanDeletedRules), rule, timeout, batchSize) +} + +// BatchCleanDeletedServices mocks base method. +func (m *MockStore) BatchCleanDeletedServices(timeout time.Duration, batchSize uint32) (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchCleanDeletedServices", timeout, batchSize) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BatchCleanDeletedServices indicates an expected call of BatchCleanDeletedServices. +func (mr *MockStoreMockRecorder) BatchCleanDeletedServices(timeout, batchSize interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCleanDeletedServices", reflect.TypeOf((*MockStore)(nil).BatchCleanDeletedServices), timeout, batchSize) +} + // BatchDeleteClients mocks base method. func (m *MockStore) BatchDeleteClients(ids []string) error { m.ctrl.T.Helper() diff --git a/store/mysql/admin.go b/store/mysql/admin.go index f491f736b..2d9c56b3c 100644 --- a/store/mysql/admin.go +++ b/store/mysql/admin.go @@ -559,3 +559,18 @@ func (m *adminStore) BatchCleanDeletedClients(timeout time.Duration, batchSize u }) return uint32(rows), err } + +// BatchCleanDeletedServices batch clean soft deleted clients +func (m *adminStore) BatchCleanDeletedServices(timeout time.Duration, batchSize uint32) (uint32, error) { + return 0, nil +} + +// BatchCleanDeletedRules batch clean soft deleted clients +func (m *adminStore) BatchCleanDeletedRules(rule string, timeout time.Duration, batchSize uint32) (uint32, error) { + return 0, nil +} + +// BatchCleanDeletedConfigFiles batch clean soft deleted clients +func (m *adminStore) BatchCleanDeletedConfigFiles(timeout time.Duration, batchSize uint32) (uint32, error) { + return 0, nil +} diff --git a/store/mysql/config_file_release.go b/store/mysql/config_file_release.go index 41ec79501..55e511f5c 100644 --- a/store/mysql/config_file_release.go +++ b/store/mysql/config_file_release.go @@ -309,7 +309,7 @@ func (cfr *configFileReleaseStore) GetMoreReleaseFile(firstUpdate bool, modifyTime = time.Time{} } - s := cfr.baseQuerySql() + " WHERE modify_time > FROM_UNIXTIME(?)" + s := cfr.baseQuerySql() + " WHERE modify_time > FROM_UNIXTIME(?) ORDER BY version DESC" rows, err := cfr.slave.Query(s, timeToTimestamp(modifyTime)) if err != nil { return nil, err diff --git a/test/data/polaris-server.yaml b/test/data/polaris-server.yaml index 7cfc2646e..b7476da5f 100644 --- a/test/data/polaris-server.yaml +++ b/test/data/polaris-server.yaml @@ -23,7 +23,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: info + outputLevel: debug # outputPaths: # - stdout # errorOutputPaths: diff --git a/test/data/service_test.yaml b/test/data/service_test.yaml index bbe182cb4..9be64aefb 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: info + outputLevel: debug # outputPaths: # - stdout # errorOutputPaths: @@ -45,7 +45,7 @@ bootstrap: rotationMaxSize: 100 rotationMaxBackups: 10 rotationMaxAge: 7 - outputLevel: error + outputLevel: debug # outputPaths: # - stdout # errorOutputPaths: @@ -168,7 +168,7 @@ bootstrap: rotationMaxBackups: 10 rotationMaxAge: 7 rotationMaxDurationForHour: 24 - outputLevel: error + outputLevel: info onlyContent: true # outputPaths: # - stdout From 428740be354110ca27db9cd7db6d58fa04e2953a Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 24 Jun 2024 00:08:33 +0800 Subject: [PATCH 16/23] feat:support nacos-address server endpoints --- apiserver/eurekaserver/server.go | 4 ++-- apiserver/httpserver/server.go | 2 +- apiserver/nacosserver/v1/server.go | 2 +- service/service_alias_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apiserver/eurekaserver/server.go b/apiserver/eurekaserver/server.go index 3d8ac2676..7b8cbc8dd 100644 --- a/apiserver/eurekaserver/server.go +++ b/apiserver/eurekaserver/server.go @@ -435,11 +435,11 @@ func (h *EurekaServer) process(req *restful.Request, rsp *restful.Response, chai } func isImportantRequest(req *restful.Request) bool { - if req.Request.Method == "POST" || req.Request.Method == "DELETE" { + if req.Request.Method == http.MethodPost || req.Request.Method == http.MethodDelete { return true } urlStr := req.Request.URL.String() - if req.Request.Method == "PUT" && strings.Contains(urlStr, "/status") { + if req.Request.Method == http.MethodPut && strings.Contains(urlStr, "/status") { return true } return false diff --git a/apiserver/httpserver/server.go b/apiserver/httpserver/server.go index 529ec7525..62d343663 100644 --- a/apiserver/httpserver/server.go +++ b/apiserver/httpserver/server.go @@ -364,7 +364,7 @@ func (h *HTTPServer) createRestfulContainer() (*restful.Container, error) { cors := restful.CrossOriginResourceSharing{ // ExposeHeaders: []string{"X-My-Header"}, AllowedHeaders: []string{"Content-Type", "Accept", "Request-Id"}, - AllowedMethods: []string{"GET", "POST", "PUT"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut}, CookiesAllowed: false, Container: wsContainer} wsContainer.Filter(cors.Filter) diff --git a/apiserver/nacosserver/v1/server.go b/apiserver/nacosserver/v1/server.go index 9cb140ce1..7c4eced4b 100644 --- a/apiserver/nacosserver/v1/server.go +++ b/apiserver/nacosserver/v1/server.go @@ -194,7 +194,7 @@ func (h *NacosV1Server) createRestfulContainer() (*restful.Container, error) { cors := restful.CrossOriginResourceSharing{ // ExposeHeaders: []string{"X-My-Header"}, AllowedHeaders: []string{"Content-Type", "Accept", "Request-Id"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch}, CookiesAllowed: false, Container: wsContainer} wsContainer.Filter(cors.Filter) diff --git a/service/service_alias_test.go b/service/service_alias_test.go index c444b6dfe..2c69f7c61 100644 --- a/service/service_alias_test.go +++ b/service/service_alias_test.go @@ -612,8 +612,8 @@ func TestGetServiceAliases(t *testing.T) { } _, serviceResp := discoverSuit.createCommonService(t, 203) t.Cleanup(func() { - discoverSuit.Destroy() discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + discoverSuit.Destroy() }) var aliases []*apiservice.Response From f7b2c6ad0c3deb7906e0e2c81cea0788594cd0b9 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 24 Jun 2024 01:12:28 +0800 Subject: [PATCH 17/23] feat:support nacos-address server endpoints --- service/ratelimit_config_test.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/service/ratelimit_config_test.go b/service/ratelimit_config_test.go index bfb14b525..e8f89a04b 100644 --- a/service/ratelimit_config_test.go +++ b/service/ratelimit_config_test.go @@ -370,7 +370,7 @@ func TestUpdateRateLimit(t *testing.T) { _, rateLimitResp := discoverSuit.createCommonRateLimit(t, serviceResp, 1) defer discoverSuit.cleanRateLimit(rateLimitResp.GetId().GetValue()) - t.Run("更新单个限流规则,可以正常更新", func(t *testing.T) { + t.Run("01-更新单个限流规则,可以正常更新", func(t *testing.T) { updateRateLimitContent(rateLimitResp, 2) discoverSuit.updateRateLimit(t, rateLimitResp) filters := map[string]string{ @@ -385,7 +385,7 @@ func TestUpdateRateLimit(t *testing.T) { checkRateLimit(t, rateLimitResp, resp.GetRateLimits()[0]) }) - t.Run("更新一个不存在的限流规则", func(t *testing.T) { + t.Run("02-更新一个不存在的限流规则", func(t *testing.T) { discoverSuit.cleanRateLimit(rateLimitResp.GetId().GetValue()) if resp := discoverSuit.DiscoverServer().UpdateRateLimits(discoverSuit.DefaultCtx, []*apitraffic.Rule{rateLimitResp}); !respSuccess(resp) { t.Logf("pass: %s", resp.GetInfo().GetValue()) @@ -394,7 +394,7 @@ func TestUpdateRateLimit(t *testing.T) { } }) - t.Run("更新限流规则时,没有传递token,正常", func(t *testing.T) { + t.Run("03-更新限流规则时,没有传递token,正常", func(t *testing.T) { oldCtx := discoverSuit.DefaultCtx discoverSuit.DefaultCtx = context.Background() @@ -410,7 +410,7 @@ func TestUpdateRateLimit(t *testing.T) { } }) - t.Run("并发更新限流规则时,可以正常更新", func(t *testing.T) { + t.Run("04-并发更新限流规则时,可以正常更新", func(t *testing.T) { var wg sync.WaitGroup errs := make(chan error) for i := 1; i <= 50; i++ { @@ -418,17 +418,22 @@ func TestUpdateRateLimit(t *testing.T) { go func(index int) { defer wg.Done() _, serviceResp := discoverSuit.createCommonService(t, index) - defer discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) discoverSuit.cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) _, rateLimitResp := discoverSuit.createCommonRateLimit(t, serviceResp, index) - defer discoverSuit.cleanRateLimit(rateLimitResp.GetId().GetValue()) updateRateLimitContent(rateLimitResp, index+1) discoverSuit.updateRateLimit(t, rateLimitResp) + + t.Cleanup(func() { + discoverSuit.cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + discoverSuit.cleanRateLimit(rateLimitResp.GetId().GetValue()) + }) + + _ = discoverSuit.DiscoverServer().Cache().TestUpdate() + filters := map[string]string{ "service": serviceResp.GetName().GetValue(), "namespace": serviceResp.GetNamespace().GetValue(), } - _ = discoverSuit.DiscoverServer().Cache().TestUpdate() resp := discoverSuit.DiscoverServer().GetRateLimits(discoverSuit.DefaultCtx, filters) if !respSuccess(resp) { errs <- fmt.Errorf("error : %v", resp) From 45e5bb81e7410bffb9e02a97cc6ec1953d6f6d07 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 24 Jun 2024 01:35:52 +0800 Subject: [PATCH 18/23] feat:support nacos-address server endpoints --- store/boltdb/admin.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/store/boltdb/admin.go b/store/boltdb/admin.go index 3301fb5b7..952a6ceb4 100644 --- a/store/boltdb/admin.go +++ b/store/boltdb/admin.go @@ -239,20 +239,17 @@ func (m *adminStore) BatchCleanDeletedClients(timeout time.Duration, batchSize u return 0, nil } - var count uint32 = 0 keys := make([]string, 0, batchSize) for k := range values { keys = append(keys, k) - count++ - if count >= batchSize { + if uint32(len(keys)) >= batchSize { break } } - err = m.handler.DeleteValues(tblClient, keys) - if err != nil { - return count, err + if err = m.handler.DeleteValues(tblClient, keys); err != nil { + return 0, err } - return count, nil + return uint32(len(keys)), nil } // BatchCleanDeletedServices batch clean soft deleted clients From 5bbe29f612d8918041ff00aa62c016343df5b004 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 24 Jun 2024 12:49:53 +0800 Subject: [PATCH 19/23] feat:support nacos-address server endpoints --- store/boltdb/admin.go | 2 +- store/boltdb/admin_test.go | 2 +- store/boltdb/client.go | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/store/boltdb/admin.go b/store/boltdb/admin.go index 952a6ceb4..09eafc1d1 100644 --- a/store/boltdb/admin.go +++ b/store/boltdb/admin.go @@ -212,7 +212,7 @@ func (m *adminStore) getUnHealthyInstancesBefore(mtime time.Time, limit uint32) func (m *adminStore) BatchCleanDeletedClients(timeout time.Duration, batchSize uint32) (uint32, error) { mtime := time.Now().Add(-timeout) fields := []string{ClientFieldValid, ClientFieldMtime} - values, err := m.handler.LoadValuesByFilter(tblClient, fields, &model.Client{}, + values, err := m.handler.LoadValuesByFilter(tblClient, fields, &clientObject{}, func(m map[string]interface{}) bool { valid, ok := m[ClientFieldValid] if !ok { diff --git a/store/boltdb/admin_test.go b/store/boltdb/admin_test.go index 4f8b895b3..a36bf1c59 100644 --- a/store/boltdb/admin_test.go +++ b/store/boltdb/admin_test.go @@ -75,7 +75,7 @@ func TestAdminStore_BatchCleanDeletedClients(t *testing.T) { t.Fatalf("count not match, expect cnt=%d, actual cnt=%d", 2, count) } - remainClients, err := cStore.GetMoreClients(time.Now(), true) + remainClients, err := cStore.GetMoreClients(time.Time{}, false) if err != nil { t.Fatal(err) } diff --git a/store/boltdb/client.go b/store/boltdb/client.go index 729ddab98..6ec37f043 100644 --- a/store/boltdb/client.go +++ b/store/boltdb/client.go @@ -107,6 +107,9 @@ func (cs *clientStore) BatchDeleteClients(ids []string) error { // GetMoreClients 根据mtime获取增量clients,返回所有store的变更信息 func (cs *clientStore) GetMoreClients(mtime time.Time, firstUpdate bool) (map[string]*model.Client, error) { fields := []string{ClientFieldMtime, ClientFieldValid} + if firstUpdate { + mtime = time.Time{} + } ret, err := cs.handler.LoadValuesByFilter(tblClient, fields, &clientObject{}, func(m map[string]interface{}) bool { if firstUpdate { // 首次更新,那么就只看 valid 状态 From 0ec837035dc948b0b7426085e78493f7dd2e919c Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 24 Jun 2024 14:36:59 +0800 Subject: [PATCH 20/23] feat:support nacos-address server endpoints --- store/mysql/scripts/polaris_server.sql | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/store/mysql/scripts/polaris_server.sql b/store/mysql/scripts/polaris_server.sql index 6a076aa74..24e0234a8 100644 --- a/store/mysql/scripts/polaris_server.sql +++ b/store/mysql/scripts/polaris_server.sql @@ -933,7 +933,7 @@ CREATE TABLE KEY ( `namespace`, `service`, - `name`, + `type`, `version`, `protocol` ) @@ -976,33 +976,33 @@ CREATE TABLE ) ENGINE = InnoDB COMMENT = '灰度资源表'; CREATE TABLE - lane_group ( - id varchar(128) not null comment '泳道分组 ID', - name varchar(64) not null comment '泳道分组名称', - rule text not null comment '规则的 json 字符串', - description varchar(3000) comment '规则描述', - revision VARCHAR(40) NOT NULL comment '规则摘要', - flag tinyint default 0 comment '软删除标识位', - ctime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - mtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `lane_group` ( + `id` varchar(128) not null comment '泳道分组 ID', + `name` varchar(64) not null comment '泳道分组名称', + `rule` text not null comment '规则的 json 字符串', + `description` varchar(3000) comment '规则描述', + `revision` VARCHAR(40) NOT NULL comment '规则摘要', + `flag` tinyint default 0 comment '软删除标识位', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE = InnoDB; CREATE TABLE - lane_rule ( - id varchar(128) not null comment '规则 id', - name varchar(64) not null comment '规则名称', - group_name varchar(64) not null comment '泳道分组名称', - rule text not null comment '规则的 json 字符串', - revision VARCHAR(40) NOT NULL comment '规则摘要', - description varchar(3000) comment '规则描述', - enable tinyint comment '是否启用', - flag tinyint default 0 comment '软删除标识位', - priority bigint NOT NULL DEFAULT 0 comment '泳道规则优先级', - ctime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - etime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - mtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `lane_rule` ( + `id` varchar(128) not null comment '规则 id', + `name` varchar(64) not null comment '规则名称', + `group_name` varchar(64) not null comment '泳道分组名称', + `rule` text not null comment '规则的 json 字符串', + `revision` VARCHAR(40) NOT NULL comment '规则摘要', + `description` varchar(3000) comment '规则描述', + `enable` tinyint comment '是否启用', + `flag` tinyint default 0 comment '软删除标识位', + `priority` bigint NOT NULL DEFAULT 0 comment '泳道规则优先级', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `etime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `name` (`group_name`, `name`) ) ENGINE = InnoDB; \ No newline at end of file From de378f6ac2d3d58dbbc796dd25d4dc782a4b1e77 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Mon, 24 Jun 2024 15:55:31 +0800 Subject: [PATCH 21/23] feat:support nacos-address server endpoints --- test/codecov.sh | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/codecov.sh b/test/codecov.sh index 92a6aa99c..81975f623 100644 --- a/test/codecov.sh +++ b/test/codecov.sh @@ -36,7 +36,8 @@ apiserver_pkg=( # 鉴权模块的包信息 auth_pkg=( - "github.com/polarismesh/polaris/auth/defaultauth" + "github.com/polarismesh/polaris/auth/user" + "github.com/polarismesh/polaris/auth/policy" ) # cache 模块的包信息 @@ -164,13 +165,19 @@ function prepare_cluster_env() { } function test_cluster_auth() { - cd ${cur_dir} # 测试鉴权 export STORE_MODE=sqldb echo "cur STORE MODE=${STORE_MODE}, MYSQL_DB_USER=${MYSQL_DB_USER}, MYSQL_DB_PWD=${MYSQL_DB_PWD}" - pushd ./auth/defaultauth - go mod vendor && go test -v -timeout 40m -v -covermode=count -coverprofile=coverage_sqldb_1.cover -coverpkg=${coverpkg} - mv coverage_sqldb_1.cover ../../ + + cd ${cur_dir} + pushd ./auth/user + go mod vendor && go test -v -timeout 40m -v -covermode=count -coverprofile=user_coverage.cover -coverpkg=${coverpkg} + mv user_coverage.cover ../../ + + cd ${cur_dir} + pushd ./auth/policy + go mod vendor && go test -v -timeout 40m -v -covermode=count -coverprofile=policy_coverage.cover -coverpkg=${coverpkg} + mv policy_coverage.cover ../../ } function test_cluster_config() { From 8d24dd2368765a24295b5c0a1fe11c87553e575e Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Tue, 25 Jun 2024 21:32:21 +0800 Subject: [PATCH 22/23] feat:support nacos-address server endpoints --- store/mysql/service_contract.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/store/mysql/service_contract.go b/store/mysql/service_contract.go index b8615b095..5daca1428 100644 --- a/store/mysql/service_contract.go +++ b/store/mysql/service_contract.go @@ -28,6 +28,7 @@ import ( "go.uber.org/zap" "github.com/polarismesh/polaris/common/model" + "github.com/polarismesh/polaris/common/utils" "github.com/polarismesh/polaris/store" ) @@ -264,6 +265,13 @@ func (s *serviceContractStore) DeleteServiceContractInterfaces(contract *model.E func (s *serviceContractStore) GetServiceContracts(ctx context.Context, filter map[string]string, offset, limit uint32) (uint32, []*model.EnrichServiceContract, error) { + if _, ok := filter["order_field"]; !ok { + filter["order_field"] = "mtime" + } + if _, ok := filter["order_type"]; !ok { + filter["order_type"] = "DESC" + } + querySql := ` SELECT id, type, namespace, service, protocol , version, revision, flag, content @@ -281,8 +289,13 @@ func (s *serviceContractStore) GetServiceContracts(ctx context.Context, filter m if k == "order_field" || k == "order_type" { continue } - conditions = append(conditions, k+" = ? ") - args = append(args, v) + if utils.IsWildName(v) { + conditions = append(conditions, k+" LIKE ? ") + args = append(args, utils.ParseWildNameForSql(v)) + } else { + conditions = append(conditions, k+" = ? ") + args = append(args, v) + } } if len(conditions) > 0 { From 3b041b3355963dbcb56f6ee9cdd9e1a193755574 Mon Sep 17 00:00:00 2001 From: chuntaojun Date: Wed, 26 Jun 2024 12:01:13 +0800 Subject: [PATCH 23/23] feat:support nacos-address server endpoints --- service/instance_check_test.go | 1 + store/mysql/service.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/service/instance_check_test.go b/service/instance_check_test.go index 07bb333f5..c2e321f1c 100644 --- a/service/instance_check_test.go +++ b/service/instance_check_test.go @@ -67,6 +67,7 @@ func TestInstanceCheck(t *testing.T) { time.Sleep(1 * time.Second) } + _ = discoverSuit.DiscoverServer().Cache().TestUpdate() instance1 := discoverSuit.DiscoverServer().Cache().Instance().GetInstance(instanceId1) assert.NotNil(t, instance1) assert.Equal(t, true, instance1.Proto.GetHealthy().GetValue()) diff --git a/store/mysql/service.go b/store/mysql/service.go index bcae68a6d..e7e7bde00 100644 --- a/store/mysql/service.go +++ b/store/mysql/service.go @@ -1226,7 +1226,7 @@ func deleteOwnerServiceMap(tx *BaseTx, service, namespace string) error { // deleteServiceMetadata 删除 service_metadata 中的残留数据 func deleteServiceMetadata(tx *BaseTx, service, namespace string) error { log.Infof("[Store][database] delete service(%s) namespace(%s)", service, namespace) - delSql := "delete from service_metadata where id IN (select id from service where service=? and namespace=?)" + delSql := "delete from service_metadata where id IN (select id from service where name = ? and namespace = ?)" if _, err := tx.Exec(delSql, service, namespace); err != nil { return err }