From 08745eefe1746bdbcea0628a29c6ee4c33ebcebd Mon Sep 17 00:00:00 2001 From: johnypeng Date: Fri, 19 Jul 2024 17:28:31 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20bscp=E5=A2=9E=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=8B=E5=8F=91=E8=B4=9F=E8=BD=BD=E5=B9=B3=E8=A1=A1?= =?UTF-8?q?=E8=83=BD=E5=8A=9B=20(#3308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: bscp增加配置下发负载平衡能力 * chore: adjust code * chore: adjust code * chore: adjust code * feat: add biz rate limiter --- .../cmd/feed-server/etc/feed_server.yaml | 28 ++ .../cmd/feed-server/service/metric.go | 37 +++ .../cmd/feed-server/service/rpc_sidecar.go | 33 +- .../cmd/feed-server/service/service.go | 6 + bcs-services/bcs-bscp/pkg/cc/service.go | 6 + bcs-services/bcs-bscp/pkg/cc/types.go | 98 ++++++ .../protocol/feed-server/feed_server.pb.go | 284 +++++++++--------- .../protocol/feed-server/feed_server.proto | 1 + .../bcs-bscp/pkg/ratelimiter/ratelimiter.go | 166 ++++++++++ .../pkg/ratelimiter/ratelimiter_test.go | 149 +++++++++ 10 files changed, 670 insertions(+), 138 deletions(-) create mode 100644 bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter.go create mode 100644 bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter_test.go diff --git a/bcs-services/bcs-bscp/cmd/feed-server/etc/feed_server.yaml b/bcs-services/bcs-bscp/cmd/feed-server/etc/feed_server.yaml index c595a43acf..7072a39e96 100644 --- a/bcs-services/bcs-bscp/cmd/feed-server/etc/feed_server.yaml +++ b/bcs-services/bcs-bscp/cmd/feed-server/etc/feed_server.yaml @@ -96,6 +96,34 @@ downstream: burst: 500 waitTimeMil: 50 +# feed server's rate limiter related settings. +rateLimiter: + # 是否启用限流器,默认为false(关闭) + enable: + # clientBandwidth为单个客户端下载文件所评估的可用带宽,用于决定单次下载流控阈值,单位为MB/s,默认为50(50MB/s=400Mb/s) + clientBandwidth: + # global为全局限流器配置,整个feed-server粒度 + global: + # limit为流量速率限制,单位为MB/s,默认为1000(1000MB/s=8000Mb/s) + limit: + # burst为允许处理的突发流量上限(允许系统在短时间内处理比速率限制更多的流量),单位为MB,默认为2000(2000MB=16000Mb) + burst: + # biz为业务粒度限流器配置 + biz: + # 业务默认配置(优先级低于显示设置的业务配置) + default: + # limit为流量速率限制,单位为MB/s,默认为100(100MB/s=800Mb/s) + limit: + # burst为允许处理的突发流量上限(允许系统在短时间内处理比速率限制更多的流量),单位为MB,默认为200(200MB=1600Mb) + burst: + # 显示设置的业务配置 + spec: + 2: + # limit为流量速率限制,单位为MB/s + limit: + # burst为允许处理的突发流量上限(允许系统在短时间内处理比速率限制更多的流量),单位为MB + burst: + # feed server's local cache related settings. # Note: # 1. These configurations depend on you host's in-memory cache size, the larger the value of these diff --git a/bcs-services/bcs-bscp/cmd/feed-server/service/metric.go b/bcs-services/bcs-bscp/cmd/feed-server/service/metric.go index 9bde9dc090..44c2ec1d30 100644 --- a/bcs-services/bcs-bscp/cmd/feed-server/service/metric.go +++ b/bcs-services/bcs-bscp/cmd/feed-server/service/metric.go @@ -75,6 +75,31 @@ func initMetric(name string) *metric { }, []string{"bizID", "appName"}) metrics.Register().MustRegister(m.clientCurrentMemUsage) + m.downloadTotalSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: metrics.Namespace, + Subsystem: metrics.FSConfigConsume, + Name: "file_download_total_size_bytes", + Help: "Total size of files downloaded, biz 0 means global", + ConstLabels: labels, + }, []string{"bizID"}) + metrics.Register().MustRegister(m.downloadTotalSize) + m.downloadDelayRequests = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: metrics.Namespace, + Subsystem: metrics.FSConfigConsume, + Name: "file_download_delay_requests", + Help: "Total number of downloaded file delayed requests, biz 0 means global", + ConstLabels: labels, + }, []string{"bizID"}) + metrics.Register().MustRegister(m.downloadDelayRequests) + m.downloadDelayMilliseconds = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: metrics.Namespace, + Subsystem: metrics.FSConfigConsume, + Name: "file_download_delay_milliseconds", + Help: "Delay milliseconds of downloaded file, biz 0 means global", + ConstLabels: labels, + }, []string{"bizID"}) + metrics.Register().MustRegister(m.downloadDelayMilliseconds) + return m } @@ -84,6 +109,7 @@ type metric struct { // watchCounter record the total connection count of sidecars with watch, used to get the new the connection count // within a specified time range. watchCounter *prometheus.CounterVec + // clientMaxCPUUsage The maximum cpu usage of the client was collected clientMaxCPUUsage *prometheus.GaugeVec // clientMaxMemUsage the maximum memory usage was collected @@ -92,4 +118,15 @@ type metric struct { clientCurrentCPUUsage *prometheus.GaugeVec // clientCurrentMemUsage the current memory usage of the client is collected clientCurrentMemUsage *prometheus.GaugeVec + + downloadTotalSize *prometheus.GaugeVec + downloadDelayRequests *prometheus.GaugeVec + downloadDelayMilliseconds *prometheus.GaugeVec +} + +// collectDownload collects metrics for download +func (m *metric) collectDownload(biz string, totalSize, delayRequests, delayMilliseconds int64) { + m.downloadTotalSize.With(prm.Labels{"bizID": biz}).Set(float64(totalSize)) + m.downloadDelayRequests.With(prm.Labels{"bizID": biz}).Set(float64(delayRequests)) + m.downloadDelayMilliseconds.With(prm.Labels{"bizID": biz}).Set(float64(delayMilliseconds)) } diff --git a/bcs-services/bcs-bscp/cmd/feed-server/service/rpc_sidecar.go b/bcs-services/bcs-bscp/cmd/feed-server/service/rpc_sidecar.go index 164036d6ef..9a328d3d1f 100644 --- a/bcs-services/bcs-bscp/cmd/feed-server/service/rpc_sidecar.go +++ b/bcs-services/bcs-bscp/cmd/feed-server/service/rpc_sidecar.go @@ -38,6 +38,7 @@ import ( pbbase "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/protocol/core/base" pbkv "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/protocol/core/kv" pbfs "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/protocol/feed-server" + "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/ratelimiter" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/runtime/jsoni" sfs "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/sf-share" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/tools" @@ -456,7 +457,37 @@ func (s *Service) GetDownloadURL(ctx context.Context, req *pbfs.GetDownloadURLRe return nil, status.Errorf(codes.Aborted, "generate temp download url failed, %s", err.Error()) } - return &pbfs.GetDownloadURLResp{Url: downloadLink}, nil + if !s.rl.Enable() { + return &pbfs.GetDownloadURLResp{Url: downloadLink, WaitTimeMil: 0}, nil + } + // 对于单个大文件下载,受限于单个客户端和服务端之间的带宽(比如为10MB/s=80Mb/s),而在存储服务端支持更高带宽的情况下(比如100MB/s), + // 哪怕单个文件2GB,在限流器阈值比单个客户端下载带宽高的情况下,还是应该允许其他客户端去存储服务端下载, + // 超过客户端下载带宽的大文件暂且按客户端带宽计入流控 + // 多个大文件的持续下载,会影响到限流器流控的精确性,当前暂时只计入每个大文件首次一秒内的流控情况,后续的流量消耗不计入 + var gWaitTimeMil, bWaitTimeMil int64 + bandwidth := int(s.rl.ClientBandwidth()) * ratelimiter.MB + if int(req.FileMeta.CommitSpec.Content.ByteSize) > bandwidth { + gWaitTimeMil = s.rl.Global().WaitTimeMil(bandwidth) + bWaitTimeMil = s.rl.UseBiz(uint(req.BizId)).WaitTimeMil(bandwidth) + } else { + gWaitTimeMil = s.rl.Global().WaitTimeMil(int(req.FileMeta.CommitSpec.Content.ByteSize)) + bWaitTimeMil = s.rl.UseBiz(uint(req.BizId)).WaitTimeMil(int(req.FileMeta.CommitSpec.Content.ByteSize)) + } + + // 分别统计全局和业务粒度流控情况 + gs := s.rl.Global().Stats() + s.mc.collectDownload("0", gs.TotalByteSize, gs.DelayCnt, gs.DelayMilliseconds) + bs := s.rl.UseBiz(uint(req.BizId)).Stats() + logs.V(1).Infof("biz: %d, download file total byte size:%d, delay cnt:%d, delay milliseconds:%d", + req.BizId, bs.TotalByteSize, bs.DelayCnt, bs.DelayMilliseconds) + s.mc.collectDownload(fmt.Sprintf("%d", req.BizId), bs.TotalByteSize, bs.DelayCnt, bs.DelayMilliseconds) + + // 优先使用流控时间长的 + wt := bWaitTimeMil + if bWaitTimeMil < gWaitTimeMil { + wt = gWaitTimeMil + } + return &pbfs.GetDownloadURLResp{Url: downloadLink, WaitTimeMil: wt}, nil } // PullKvMeta pull an app's latest release metadata only when the app's configures is kv type. diff --git a/bcs-services/bcs-bscp/cmd/feed-server/service/service.go b/bcs-services/bcs-bscp/cmd/feed-server/service/service.go index 8b6563fb1e..8d84426df8 100644 --- a/bcs-services/bcs-bscp/cmd/feed-server/service/service.go +++ b/bcs-services/bcs-bscp/cmd/feed-server/service/service.go @@ -31,6 +31,7 @@ import ( "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/dal/repository" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/iam/auth" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/logs" + "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/ratelimiter" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/rest" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/runtime/handler" "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/runtime/shutdown" @@ -51,6 +52,7 @@ type Service struct { name string mc *metric gwMux *runtime.ServeMux + rl *ratelimiter.RL } // NewService create a service instance. @@ -81,6 +83,9 @@ func NewService(sd serviced.Discover, name string) (*Service, error) { return nil, fmt.Errorf("new repository provider failed, err: %v", err) } + rl := ratelimiter.New(cc.FeedServer().RateLimiter) + logs.Infof("init rate limiter, conf: %+v", cc.FeedServer().RateLimiter) + return &Service{ bll: bl, authorizer: authorizer, @@ -89,6 +94,7 @@ func NewService(sd serviced.Discover, name string) (*Service, error) { provider: provider, mc: initMetric(name), gwMux: gwMux, + rl: rl, }, nil } diff --git a/bcs-services/bcs-bscp/pkg/cc/service.go b/bcs-services/bcs-bscp/pkg/cc/service.go index 21e200c666..dbd31bb008 100644 --- a/bcs-services/bcs-bscp/pkg/cc/service.go +++ b/bcs-services/bcs-bscp/pkg/cc/service.go @@ -371,6 +371,7 @@ type FeedServerSetting struct { FSLocalCache FSLocalCache `yaml:"fsLocalCache"` Downstream Downstream `yaml:"downstream"` MRLimiter MatchReleaseLimiter `yaml:"matchReleaseLimiter"` + RateLimiter RateLimiter `yaml:"rateLimiter"` } // trySetFlagBindIP try set flag bind ip. @@ -394,6 +395,7 @@ func (s *FeedServerSetting) trySetDefault() { s.GSE.trySetDefault() s.RedisCluster.trySetDefault() s.MRLimiter.trySetDefault() + s.RateLimiter.trySetDefault() } // Validate FeedServerSetting option. @@ -423,6 +425,10 @@ func (s FeedServerSetting) Validate() error { return err } + if err := s.RateLimiter.validate(); err != nil { + return err + } + if err := s.Esb.validate(); err != nil { return err } diff --git a/bcs-services/bcs-bscp/pkg/cc/types.go b/bcs-services/bcs-bscp/pkg/cc/types.go index acee64955b..3ed488b994 100644 --- a/bcs-services/bcs-bscp/pkg/cc/types.go +++ b/bcs-services/bcs-bscp/pkg/cc/types.go @@ -1098,6 +1098,104 @@ func (lm *MatchReleaseLimiter) trySetDefault() { } } +// RateLimiter defines the rate limiter options for traffic control. +type RateLimiter struct { + Enable bool `yaml:"enable"` + ClientBandwidth uint `yaml:"clientBandwidth"` + Global BasicRL `yaml:"global"` + Biz BizRLs `yaml:"biz"` +} + +// BizRLs defines the rate limiters for biz +type BizRLs struct { + Default BasicRL `yaml:"default"` + Spec map[uint]BasicRL `yaml:"spec"` +} + +// BasicRL defines the basic options for rate limiter. +type BasicRL struct { + Limit uint `yaml:"limit"` + Burst uint `yaml:"burst"` +} + +const ( + // DefaultClientBandwidth default client bandwidth + DefaultClientBandwidth = 50 // 50MB/s = 400Mb/s + // DefaultGlobalRateLimit default global rate limit + DefaultGlobalRateLimit = 1000 // 1000MB/s = 8000Mb/s + // DefaultGlobalRateBurst default global rate burst + DefaultGlobalRateBurst = 2000 // 2000MB = 16000Mb + // DefaultBizRateLimit default biz rate limit + DefaultBizRateLimit = 100 // 100MB/s = 800Mb/s + // DefaultBizRateBurst default biz rate burst + DefaultBizRateBurst = 200 // 200MB = 1600Mb +) + +// validate if the rate limiter is valid or not. +func (rl RateLimiter) validate() error { + if rl.Biz.Default.Burst < rl.Biz.Default.Limit { + return fmt.Errorf("invalid rateLimiter.biz.default.burst value %d, should >= rateLimiter.biz.default.limit "+ + "value %d", rl.Global.Burst, rl.Global.Limit) + } + + if rl.Global.Limit < rl.Biz.Default.Limit { + return fmt.Errorf("invalid rateLimiter.global.limit value %d, should >= rateLimiter.biz.default.limit value %d", + rl.Global.Limit, rl.ClientBandwidth) + } + + if rl.Global.Burst < rl.Biz.Default.Burst { + return fmt.Errorf("invalid rateLimiter.global.burst value %d, should >= rateLimiter.biz.default.burst value %d", + rl.Global.Burst, rl.Global.Limit) + } + + for bizID, l := range rl.Biz.Spec { + if l.Burst < l.Limit { + return fmt.Errorf("invalid rateLimiter.biz.spec.%d.burst value %d, "+ + "should >= rateLimiter.biz.spec.%d.limit value %d", bizID, l.Burst, bizID, l.Limit) + } + } + + return nil +} + +// trySetDefault try set the default value of rate limiter +func (rl *RateLimiter) trySetDefault() { + if rl.ClientBandwidth == 0 { + rl.ClientBandwidth = DefaultClientBandwidth + } + + if rl.Global.Limit == 0 { + rl.Global.Limit = DefaultGlobalRateLimit + } + + if rl.Global.Burst == 0 { + rl.Global.Burst = DefaultGlobalRateBurst + } + + if rl.Biz.Default.Limit == 0 { + rl.Biz.Default.Limit = DefaultBizRateLimit + } + + if rl.Biz.Default.Burst == 0 { + rl.Biz.Default.Burst = DefaultBizRateBurst + } + + for bizID, l := range rl.Biz.Spec { + if l.Limit == 0 { + rl.Biz.Spec[bizID] = BasicRL{ + Limit: DefaultBizRateLimit, + Burst: l.Burst, + } + } + if l.Burst == 0 { + rl.Biz.Spec[bizID] = BasicRL{ + Limit: rl.Biz.Spec[bizID].Limit, + Burst: DefaultBizRateBurst, + } + } + } +} + // Credential credential encryption algorithm and master key type Credential struct { MasterKey string `yaml:"master_key"` diff --git a/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.pb.go b/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.pb.go index e9e4119c62..d292fe833c 100644 --- a/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.pb.go +++ b/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.pb.go @@ -1003,7 +1003,8 @@ type GetDownloadURLResp struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + WaitTimeMil int64 `protobuf:"varint,2,opt,name=wait_time_mil,json=waitTimeMil,proto3" json:"wait_time_mil,omitempty"` // the time(milliseconds) that the client should wait before downloading file } func (x *GetDownloadURLResp) Reset() { @@ -1045,6 +1046,13 @@ func (x *GetDownloadURLResp) GetUrl() string { return "" } +func (x *GetDownloadURLResp) GetWaitTimeMil() int64 { + if x != nil { + return x.WaitTimeMil + } + return 0 +} + // App 对外简单版本 type App struct { state protoimpl.MessageState @@ -1921,145 +1929,147 @@ var file_feed_server_proto_rawDesc = []byte{ 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x26, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x44, 0x6f, + 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x4a, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, - 0x78, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x08, 0x72, - 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x70, 0x62, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, - 0x6d, 0x61, 0x74, 0x63, 0x68, 0x22, 0x2d, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1d, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, - 0x61, 0x70, 0x70, 0x73, 0x22, 0x66, 0x0a, 0x0d, 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x08, - 0x61, 0x70, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, - 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x61, - 0x70, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x22, 0x58, 0x0a, 0x0e, - 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x08, 0x6b, 0x76, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0c, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x6b, - 0x76, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x06, 0x4b, 0x76, 0x4d, 0x65, 0x74, - 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x76, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x76, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x08, - 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x70, 0x62, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x0d, 0x6b, 0x76, - 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x62, 0x6b, 0x76, 0x2e, 0x4b, 0x76, 0x41, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0c, 0x6b, 0x76, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, - 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x73, - 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x62, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x70, 0x65, - 0x63, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x70, 0x65, 0x63, 0x22, 0x62, - 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x12, - 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, - 0x41, 0x70, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x61, 0x70, 0x70, 0x4d, 0x65, 0x74, 0x61, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x22, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x76, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x76, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x10, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, 0x12, - 0x1e, 0x0a, 0x0b, 0x62, 0x6b, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x12, 0x15, - 0x0a, 0x06, 0x70, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x70, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x09, - 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x69, 0x6c, - 0x65, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x69, 0x6c, - 0x65, 0x44, 0x69, 0x72, 0x22, 0x2c, 0x0a, 0x11, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, - 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, - 0x49, 0x64, 0x22, 0x48, 0x0a, 0x16, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x22, 0x0a, 0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x77, 0x61, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, + 0x4d, 0x69, 0x6c, 0x22, 0x78, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x2c, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x62, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x0a, + 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, - 0x7a, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x17, - 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, - 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x3f, 0x0a, 0x13, 0x41, 0x73, - 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0f, - 0x0a, 0x0b, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x32, 0xf3, 0x05, 0x0a, 0x08, - 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x3a, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, - 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x16, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x48, 0x61, 0x6e, - 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x13, 0x2e, - 0x70, 0x62, 0x66, 0x73, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, - 0x67, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x69, - 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, - 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x53, 0x69, - 0x64, 0x65, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x16, 0x2e, 0x70, 0x62, - 0x66, 0x73, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0f, 0x50, 0x75, 0x6c, 0x6c, 0x41, - 0x70, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x70, 0x62, 0x66, - 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x41, 0x70, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, - 0x41, 0x70, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x22, - 0x00, 0x12, 0x45, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x55, 0x52, 0x4c, 0x12, 0x17, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, - 0x62, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, - 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x6a, 0x0a, 0x0a, 0x50, 0x75, 0x6c, 0x6c, - 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x50, 0x75, - 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x70, 0x62, - 0x66, 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, - 0x70, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x3a, 0x01, 0x2a, 0x22, 0x26, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x7b, - 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6b, 0x76, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x6c, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x47, - 0x65, 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x33, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x7b, 0x62, 0x69, 0x7a, 0x5f, - 0x69, 0x64, 0x7d, 0x2f, 0x6b, 0x76, 0x73, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x7b, 0x6b, 0x65, - 0x79, 0x7d, 0x12, 0x33, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x12, 0x11, - 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, - 0x71, 0x1a, 0x12, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0d, 0x41, 0x73, 0x79, 0x6e, 0x63, - 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, - 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, - 0x1a, 0x17, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x13, 0x41, - 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, + 0x7a, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x22, 0x2d, 0x0a, 0x0c, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1d, 0x0a, 0x04, 0x61, 0x70, 0x70, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, + 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x22, 0x66, 0x0a, 0x0d, 0x50, 0x75, 0x6c, 0x6c, + 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, + 0x12, 0x28, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x07, 0x61, 0x70, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, + 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x22, 0x58, 0x0a, 0x0e, 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, + 0x73, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, + 0x64, 0x12, 0x27, 0x0a, 0x08, 0x6b, 0x76, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4b, 0x76, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x07, 0x6b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x06, 0x4b, + 0x76, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x76, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x76, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x2c, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x62, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x37, + 0x0a, 0x0d, 0x6b, 0x76, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x62, 0x6b, 0x76, 0x2e, 0x4b, 0x76, 0x41, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0c, 0x6b, 0x76, 0x41, 0x74, 0x74, + 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x70, 0x62, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x70, + 0x65, 0x63, 0x22, 0x62, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x61, 0x70, + 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, + 0x62, 0x66, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x61, 0x70, 0x70, + 0x4d, 0x65, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x76, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x76, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x10, 0x41, 0x73, 0x79, 0x6e, + 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, + 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x69, + 0x7a, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x62, 0x6b, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6b, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x70, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x70, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x2b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x66, 0x69, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x22, 0x2c, 0x0a, 0x11, 0x41, 0x73, 0x79, 0x6e, + 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x17, 0x0a, + 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x16, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x1a, 0x1d, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, - 0x00, 0x42, 0x57, 0x5a, 0x55, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x54, 0x65, 0x6e, 0x63, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x4b, 0x69, 0x6e, 0x67, 0x2f, - 0x62, 0x6b, 0x2d, 0x62, 0x63, 0x73, 0x2f, 0x62, 0x63, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2f, 0x62, 0x63, 0x73, 0x2d, 0x62, 0x73, 0x63, 0x70, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x2d, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x3b, 0x70, 0x62, 0x66, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x12, 0x15, 0x0a, 0x06, 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x62, 0x69, 0x7a, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, + 0x22, 0x4c, 0x0a, 0x17, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x31, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x62, + 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x3f, + 0x0a, 0x13, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, + 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x32, + 0xf3, 0x05, 0x0a, 0x08, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x3a, 0x0a, 0x09, + 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x16, 0x2e, 0x70, 0x62, 0x66, 0x73, + 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, + 0x6b, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x66, + 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x22, + 0x00, 0x12, 0x38, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, + 0x73, 0x2e, 0x53, 0x69, 0x64, 0x65, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x1a, + 0x16, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0f, 0x50, + 0x75, 0x6c, 0x6c, 0x41, 0x70, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x18, + 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x41, 0x70, 0x70, 0x46, 0x69, 0x6c, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, + 0x50, 0x75, 0x6c, 0x6c, 0x41, 0x70, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x17, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x47, + 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, + 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, + 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x6a, 0x0a, 0x0a, + 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, + 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x1a, + 0x14, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x4b, 0x76, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x65, 0x73, 0x70, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x3a, 0x01, 0x2a, + 0x22, 0x26, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x2f, 0x62, + 0x69, 0x7a, 0x2f, 0x7b, 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6b, 0x76, 0x73, 0x2f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x6c, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4b, + 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x47, 0x65, + 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x70, 0x62, + 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x7b, + 0x62, 0x69, 0x7a, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6b, 0x76, 0x73, 0x2f, 0x64, 0x61, 0x74, 0x61, + 0x2f, 0x7b, 0x6b, 0x65, 0x79, 0x7d, 0x12, 0x33, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, + 0x70, 0x73, 0x12, 0x11, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, + 0x70, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x70, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0d, 0x41, + 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x70, + 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, + 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, + 0x54, 0x0a, 0x13, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, + 0x79, 0x6e, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x70, 0x62, 0x66, 0x73, 0x2e, 0x41, 0x73, 0x79, 0x6e, + 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x57, 0x5a, 0x55, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x65, 0x6e, 0x63, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x75, 0x65, 0x4b, + 0x69, 0x6e, 0x67, 0x2f, 0x62, 0x6b, 0x2d, 0x62, 0x63, 0x73, 0x2f, 0x62, 0x63, 0x73, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x62, 0x63, 0x73, 0x2d, 0x62, 0x73, 0x63, 0x70, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x66, 0x65, + 0x65, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3b, 0x70, 0x62, 0x66, 0x73, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.proto b/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.proto index 9cc9d1d8d4..877f566bbd 100644 --- a/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.proto +++ b/bcs-services/bcs-bscp/pkg/protocol/feed-server/feed_server.proto @@ -148,6 +148,7 @@ message GetDownloadURLReq { message GetDownloadURLResp { string url = 1; + int64 wait_time_mil = 2; // the time(milliseconds) that the client should wait before downloading file } // App 对外简单版本 diff --git a/bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter.go b/bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter.go new file mode 100644 index 0000000000..66ba586a1f --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter.go @@ -0,0 +1,166 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * 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 ratelimiter provides custom rate limiter +package ratelimiter + +import ( + "sync" + "sync/atomic" + "time" + + "golang.org/x/time/rate" + + "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/cc" +) + +// RateLimiter is interface for rate limiter +type RateLimiter interface { + // WaitTimeMil returns the wait time(milliseconds) according to the rate limiter + // 用于引导调用方等待相应时间后再访问服务,从而达到流控效果,避免服务过载 + WaitTimeMil(size int) int64 + // Stats returns the statistics of rate limiter + Stats() *StatsData +} + +// New news a rate limiter +// it is intended for direct use for other package +func New(config cc.RateLimiter) *RL { + globalLimiter := NewGlobalRL(config.Global.Limit, config.Global.Burst) + bizLimiters := NewBizRLs(config.Biz) + return &RL{ + enable: config.Enable, + clientBw: config.ClientBandwidth, + globalRL: globalLimiter, + bizRLs: bizLimiters, + } +} + +// Enable returns enable of rate limiter +func (r *RL) Enable() bool { + return r.enable +} + +// ClientBandwidth returns client bandwidth of rate limiter +func (r *RL) ClientBandwidth() uint { + return r.clientBw +} + +// Global is global rate limiter +func (r *RL) Global() RateLimiter { + return r.globalRL +} + +// UseBiz uses rate limiter of specific biz +func (r *RL) UseBiz(bizID uint) RateLimiter { + return r.bizRLs.getLimiter(bizID) +} + +// RL is rate limiter for unified use +type RL struct { + enable bool + clientBw uint + globalRL *globalRL + bizRLs *bizRLs +} + +// globalRL is rate limiter for global dimension +type globalRL struct { + *baseRL +} + +// NewGlobalRL news a global rate limiter +func NewGlobalRL(limit, burst uint) *globalRL { + return &globalRL{ + baseRL: newBaseRL(limit, burst), + } +} + +// bizRLs is rate limiter for biz dimension +type bizRLs struct { + mutex sync.Mutex + defaultConf cc.BasicRL + bizLimiters map[uint]*baseRL +} + +// NewBizRLs news rate limiters for biz +func NewBizRLs(b cc.BizRLs) *bizRLs { + bizLimiters := make(map[uint]*baseRL) + for bizID, rl := range b.Spec { + bizLimiters[bizID] = newBaseRL(rl.Limit, rl.Burst) + } + return &bizRLs{ + defaultConf: b.Default, + bizLimiters: bizLimiters, + } +} + +// getLimiter get rate limiter for specific biz +func (b *bizRLs) getLimiter(bizID uint) *baseRL { + b.mutex.Lock() + defer b.mutex.Unlock() + if limiter, exists := b.bizLimiters[bizID]; exists { + return limiter + } + defaultLimiter := newBaseRL(b.defaultConf.Limit, b.defaultConf.Burst) + b.bizLimiters[bizID] = defaultLimiter + return defaultLimiter +} + +// baseRL is base rate limiter +type baseRL struct { + limiter *rate.Limiter + totalByteSize int64 + delayCnt int64 + delayMilliseconds int64 +} + +// StatsData is stats data +type StatsData struct { + TotalByteSize int64 + DelayCnt int64 + DelayMilliseconds int64 +} + +// MB means byte size of 1MB +var MB = 1024 * 1024 + +// newBaseRL news a base rate limiter +// limit为流量速率限制,单位为MB/s,burst为允许处理的突发流量上限,单位为MB(允许系统在短时间内处理比速率限制更多的流量) +// 内部实现使用令牌桶算法,令牌恢复速率为limit,在令牌被消耗完且不再有任何令牌消耗时,令牌数恢复至burst需要burst/limit秒 +// 举例说明:limit为100,burst为200,则将创建一个每秒生成100MB令牌、容量为200MB的限流器 +func newBaseRL(limit, burst uint) *baseRL { + return &baseRL{ + limiter: rate.NewLimiter(rate.Limit(int(limit)*MB), int(burst)*MB), + } +} + +// WaitTimeMil returns the wait time(milliseconds) according to the rate limiter +func (r *baseRL) WaitTimeMil(size int) int64 { + atomic.AddInt64(&r.totalByteSize, int64(size)) + reservation := r.limiter.ReserveN(time.Now(), size) + delay := reservation.Delay() + atomic.StoreInt64(&r.delayMilliseconds, delay.Milliseconds()) + if delay > 0 { + atomic.AddInt64(&r.delayCnt, 1) + } + return delay.Milliseconds() +} + +// Stats returns the statistics of rate limiter +func (r *baseRL) Stats() *StatsData { + return &StatsData{ + TotalByteSize: atomic.LoadInt64(&r.totalByteSize), + DelayCnt: atomic.LoadInt64(&r.delayCnt), + DelayMilliseconds: atomic.LoadInt64(&r.delayMilliseconds), + } +} diff --git a/bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter_test.go b/bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter_test.go new file mode 100644 index 0000000000..758f47e2f2 --- /dev/null +++ b/bcs-services/bcs-bscp/pkg/ratelimiter/ratelimiter_test.go @@ -0,0 +1,149 @@ +/* + * Tencent is pleased to support the open source community by making Blueking Container Service available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * 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 ratelimiter + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/cc" +) + +var config = cc.RateLimiter{ + Enable: true, + ClientBandwidth: 10, + Global: cc.BasicRL{ + Limit: 10, + Burst: 20, + }, + Biz: cc.BizRLs{ + Default: cc.BasicRL{ + Limit: 5, + Burst: 5, + }, + Spec: map[uint]cc.BasicRL{ + 1: { + Limit: 10, + Burst: 20, + }, + 2: { + Limit: 10, + Burst: 10, + }, + }, + }, +} + +func TestNewRateLimiter(t *testing.T) { + rl := New(config) + assert.NotNil(t, rl) + assert.NotNil(t, rl.Global()) + assert.NotNil(t, rl.UseBiz(1)) + assert.NotNil(t, rl.UseBiz(2)) + // rl.UseBiz(3) will create new one with default biz config + assert.NotNil(t, rl.UseBiz(3)) + +} + +func TestGlobalWaitTime(t *testing.T) { + r := New(config) + rl := r.Global() + testWaitTime(t, rl) +} + +func TestBizWaitTime(t *testing.T) { + r := New(config) + rl := r.UseBiz(1) + testWaitTime(t, rl) +} + +func TestGlobalStats(t *testing.T) { + config2 := config + config2.Global = cc.BasicRL{ + Limit: 10, + Burst: 10, + } + r := New(config2) + rl := r.Global() + testStats(t, rl) +} + +func TestBizStats(t *testing.T) { + r := New(config) + rl := r.UseBiz(2) + testStats(t, rl) +} + +func TestBizStats2(t *testing.T) { + config3 := config + config3.Biz.Default = cc.BasicRL{ + Limit: 10, + Burst: 10, + } + r := New(config3) + rl := r.UseBiz(3) + testStats(t, rl) +} + +func testWaitTime(t *testing.T, rl RateLimiter) { + + // Add a request that fits within the burst capacity + waitTime := rl.WaitTimeMil(MB * 20) + assert.Equal(t, int64(0), waitTime) + + // Add another request that should be rate-limited + waitTime = rl.WaitTimeMil(MB * 5) + assert.True(t, waitTime > 0) + + // Add another request that should be rate-limited too and wait time should be increased based on last time + waitTime = rl.WaitTimeMil(MB * 10) + assert.True(t, waitTime > 1000) // 1000 milliseconds + assert.True(t, waitTime < 2000) // 2000 milliseconds + t.Logf("waitTime: %d milliseconds", waitTime) + + // After wait for corresponding time, it will be not rate-limited when size is less than the number of tokens + // Note that at this point, the number of tokens has not yet recovered to burst, recover speed is rate limit(10MB/s) + time.Sleep(time.Millisecond * time.Duration(waitTime)) + waitTime = rl.WaitTimeMil(1) + assert.Equal(t, int64(0), waitTime) + time.Sleep(time.Second) // which will be recovered to rate limit + waitTime = rl.WaitTimeMil(MB * 10) + assert.Equal(t, int64(0), waitTime) + time.Sleep(time.Second * 2) // which will be recovered to burst(20MB) + waitTime = rl.WaitTimeMil(MB * 20) + assert.Equal(t, int64(0), waitTime) +} + +func testStats(t *testing.T, rl RateLimiter) { + // Test initial stats + stats := rl.Stats() + assert.NotNil(t, stats) + assert.Equal(t, int64(0), stats.TotalByteSize) + assert.Equal(t, int64(0), stats.DelayCnt) + assert.Equal(t, int64(0), stats.DelayMilliseconds) + + // Simulate some traffic + rl.WaitTimeMil(MB * 10) // which will not be rate-limited + rl.WaitTimeMil(MB * 10) // which will be rate-limited + rl.WaitTimeMil(MB * 10) // which will be rate-limited + + stats = rl.Stats() + assert.NotNil(t, stats) + assert.True(t, stats.TotalByteSize == int64(MB*30)) + assert.True(t, stats.DelayCnt == 2) + assert.True(t, stats.DelayMilliseconds > 1000) + assert.True(t, stats.DelayMilliseconds <= 2000) + t.Logf("stats.DelayMilliseconds: %d", stats.DelayMilliseconds) +} From c85792eae857415228e1de83c4e703bb1606a51d Mon Sep 17 00:00:00 2001 From: Yuikill <105910874+Yuikill@users.noreply.github.com> Date: Fri, 19 Jul 2024 20:41:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=20=E6=96=B0=E5=BB=BA=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E4=BA=8C=E8=BF=9B?= =?UTF-8?q?=E5=88=B6=E6=96=87=E4=BB=B6=E4=B9=B1=E7=A0=81=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D--bug=3D127338589=20(#3388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: kv配置项为空禁用导出按钮 * fix: 上传二进制文件乱码 * fix: 国际化体验优化 * fix: 密钥管理名称和说明编辑交互调整 * fix: 配置文件路径和文件名支持.开头 --- .../ui/src/components/version-log.vue | 9 +- bcs-services/bcs-bscp/ui/src/css/common.scss | 7 + bcs-services/bcs-bscp/ui/src/i18n/en-us.ts | 5 +- bcs-services/bcs-bscp/ui/src/i18n/zh-cn.ts | 5 +- .../ui/src/views/space/credentials/index.vue | 158 ++++++++---------- .../groups/components/group-edit-form.vue | 11 +- .../ui/src/views/space/groups/index.vue | 2 +- .../components/publish-version/index.vue | 1 - .../select-group/group-tree.vue | 5 +- .../config/components/read-file-content.vue | 26 ++- .../version-diff/aside-menu/configs.vue | 7 +- .../config-table-list/config-export.vue | 2 +- .../config-table-list/config-form.vue | 4 +- .../create-config/import-file/index.vue | 2 +- .../create-config/import-kv/index.vue | 3 +- .../tables/table-with-kv.vue | 3 + .../operations/edit-config/edit-config.vue | 8 +- .../move-out-from-pkgs-dialog.vue | 7 + .../ui/src/views/space/variables/index.vue | 1 + bcs-services/bcs-bscp/ui/types/config.ts | 1 + 20 files changed, 148 insertions(+), 119 deletions(-) diff --git a/bcs-services/bcs-bscp/ui/src/components/version-log.vue b/bcs-services/bcs-bscp/ui/src/components/version-log.vue index e304806004..f9b31d76ea 100644 --- a/bcs-services/bcs-bscp/ui/src/components/version-log.vue +++ b/bcs-services/bcs-bscp/ui/src/components/version-log.vue @@ -21,7 +21,7 @@ {{ item.title }} {{ item.date }} - {{ t('log_当前版本') }} + {{ t('当前版本') }} @@ -40,7 +40,7 @@ import { useI18n } from 'vue-i18n'; import type { IVersionLogItem } from '../../types/version-log'; - const { t } = useI18n(); + const { t, locale } = useI18n(); const props = withDefaults( defineProps<{ logList: IVersionLogItem[]; @@ -98,12 +98,15 @@ top: 8px; background-color: #699df4; border-radius: 2px; - width: 58px; + min-width: 58px; height: 20px; display: flex; align-items: center; justify-content: center; color: #ffffff; + &.en { + right: 2px; + } } &.item-active { &::before { diff --git a/bcs-services/bcs-bscp/ui/src/css/common.scss b/bcs-services/bcs-bscp/ui/src/css/common.scss index be813f4a9b..bb5274cd2c 100644 --- a/bcs-services/bcs-bscp/ui/src/css/common.scss +++ b/bcs-services/bcs-bscp/ui/src/css/common.scss @@ -43,9 +43,16 @@ .bk-modal-content { min-height: auto; padding: 0 50px 10px; + .bk-info-sub-title { + word-break: break-word !important; + } } } +.bk-dialog-wrapper .bk-modal-wrapper .bk-dialog-header { + line-height: normal !important; +} + // 去掉组件库表格过滤自带的input .bk-popover .bk-table-head-filter .bk-input { display: none; diff --git a/bcs-services/bcs-bscp/ui/src/i18n/en-us.ts b/bcs-services/bcs-bscp/ui/src/i18n/en-us.ts index 80d83e06ff..9a8d3fef8f 100644 --- a/bcs-services/bcs-bscp/ui/src/i18n/en-us.ts +++ b/bcs-services/bcs-bscp/ui/src/i18n/en-us.ts @@ -74,7 +74,6 @@ export default { 没有匹配到配置项: 'No configuration item was matched', 请先在左侧表单设置关联规则并预览: 'Please first set the association rules in the left form and preview', 删除服务成功: 'Service deleted successfully', - log_当前版本: 'Version', // 导航栏 服务配置中心: 'BSCP', @@ -465,6 +464,8 @@ export default { '文件上传失败,请重新上传文件': 'File upload failed, please re-upload the file', 新版本已生成: 'New version has been generated', 未上线版本的分组: 'Group of unlaunched versions', + '文件格式错误,请重新选择文本文件上传': 'File format error, please re-select the text file to upload', + 只查看差异项: 'Only view differences', // 分组管理 新增分组: 'New group', @@ -494,7 +495,7 @@ export default { '服务名称/服务版本': 'Service name/Service version', 服务版本: 'Service version', '未在配置文件中检测到变量,请确保配置文件中包含变量后再尝试设置变量': 'Variable not detected in the configuration file. Please ensure the configuration file includes the variable before attempting to set it.', - 标签最大数量为5个: 'Label maximum number is 5', + '分组最多支持 5 个标签选择器': 'The group supports a maximum of 5 tag selectors', // 全局变量 配置模板与变量: 'Configure templates and variables', diff --git a/bcs-services/bcs-bscp/ui/src/i18n/zh-cn.ts b/bcs-services/bcs-bscp/ui/src/i18n/zh-cn.ts index ea7028d80d..3bf6e3b9e4 100644 --- a/bcs-services/bcs-bscp/ui/src/i18n/zh-cn.ts +++ b/bcs-services/bcs-bscp/ui/src/i18n/zh-cn.ts @@ -69,7 +69,6 @@ export default { 请输入服务名: '请输入服务名', 同时会删除服务密钥对服务的关联规则: '同时会删除服务密钥对服务的关联规则', 删除服务成功: '删除服务成功', - log_当前版本: '当前版本', // 导航栏 服务配置中心: '服务配置中心', @@ -464,6 +463,8 @@ export default { '文件上传失败,请重新上传文件': '文件上传失败,请重新上传文件', 新版本已生成: '新版本已生成', 未上线版本的分组: '未上线版本的分组', + '文件格式错误,请重新选择文本文件上传': '文件格式错误,请重新选择文本文件上传', + 只查看差异项: '只查看差异项', // 分组管理 新增分组: '新增分组', @@ -492,7 +493,7 @@ export default { 上线服务: '上线服务', '服务名称/服务版本': '服务名称/服务版本', 服务版本: '服务版本', - 标签最大数量为5个: '标签最大数量为5个', + '分组最多支持 5 个标签选择器': '分组最多支持 5 个标签选择器', // 全局变量 配置模板与变量: '配置模板与变量', diff --git a/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue index 4bf0327150..332f98e42f 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/credentials/index.vue @@ -62,22 +62,23 @@ -
-
- {{ row.spec.name || '--' }} -
-
-
- {{ row.spec.name }} -
-
-
- +
+
+ + {{ row.spec.name || '--' }} + + + +
+
@@ -103,22 +104,24 @@ v-if="index === 0 && isCreateCredential" :placeholder="t('请输入密钥说明')" v-model="createCredentialMemo"> -
-
- {{ row.spec.memo || '--' }} -
-
-
- {{ row.spec.memo }} -
-
-
- +
+
+ + {{ row.spec.memo || '--' }} + + + +
+
@@ -497,27 +500,20 @@ }; // 失焦时保存密钥说明或密钥名称 - const handleMemoOrNameBlur = async (credential: ICredentialItem, isEditName = true) => { + const handleMemoOrNameBlur = async (credential: ICredentialItem, isEditName = true, e: FocusEvent) => { const params = { id: credential.id, enable: credential.spec.enable, name: credential.spec.name, memo: credential.spec.memo, }; + const val = (e.target as HTMLInputElement).value.trim(); if (isEditName) { + params.name = val; editingNameId.value = 0; - const name = nameInputRef.value.textContent.trim(); - if (credential.spec.name === name) { - return; - } - params.name = name; } else { + params.memo = val; editingMemoId.value = 0; - const memo = memoInputRef.value.textContent.trim(); - if (credential.spec.memo === memo) { - return; - } - params.memo = memo; } await updateCredential(spaceId.value, params); credential.spec.memo = params.memo; @@ -722,21 +718,48 @@ background: #ffffff; } .credential-table { - overflow: visible !important; :deep(.bk-table-body) { + max-height: calc(100vh - 280px); + overflow: auto; tr.new-row-marked td { background: #f2fff4 !important; } tr.selected td { background: #e1ecff !important; } - } - :deep(.bk-table-body) { - overflow: visible !important; tbody td .cell { overflow: visible !important; } } + .credential-edit { + .content { + width: 100%; + display: flex; + &:hover { + .edit-icon { + display: block; + } + } + .name-text { + margin-right: 4px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + .edit-icon { + display: none; + font-size: 12px; + color: #979ba5; + cursor: pointer; + &:hover { + color: #3a84ff; + } + } + .textarea { + height: 100%; + } + } + } .delete-btn { display: inline-block; } @@ -777,51 +800,6 @@ } } } - .credential-memo { - display: flex; - align-items: center; - &:hover { - .edit-icon { - display: inline-block; - } - } - .memo-content { - max-width: calc(100% - 40px); - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .edit-input { - padding: 6px 10px; - min-height: 60px; - line-height: 20px; - font-size: 12px; - color: #63656e; - border: 1px solid #c4c6cc; - border-radius: 2px; - background: #ffffff; - outline: none; - -webkit-user-modify: read-write-plaintext-only; - &:focus { - border-color: #3a84ff; - box-shadow: 0 0 3px #a3c5fd; - } - } - .edit-name-input { - @extend .edit-input; - min-height: 32px; - } - .edit-icon { - display: none; - padding-left: 16px; - font-size: 14px; - color: #979ba5; - cursor: pointer; - &:hover { - color: #3a84ff; - } - } - } .status-action { display: flex; align-items: center; diff --git a/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue b/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue index caa9d554ed..c2aba763ff 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/groups/components/group-edit-form.vue @@ -90,7 +90,11 @@ v-if="index > 0 || formData.rules.length > 1" class="bk-bscp-icon icon-reduce" @click="handleDeleteRule(index)"> - +
@@ -109,7 +113,6 @@ import { getAppList } from '../../../../api/index'; import { IAppItem } from '../../../../../types/app'; import { Info } from 'bkui-vue/lib/icon'; - import { Message } from 'bkui-vue'; const getDefaultRuleConfig = (): IGroupRuleItem => ({ key: '', op: 'eq', value: '' }); @@ -206,10 +209,6 @@ // 增加规则 const handleAddRule = (index: number) => { if (formData.value.rules.length === 5) { - Message({ - theme: 'error', - message: t('标签最大数量为5个'), - }); return; } const rule = getDefaultRuleConfig(); diff --git a/bcs-services/bcs-bscp/ui/src/views/space/groups/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/groups/index.vue index 36190e07ef..f944d5f71c 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/groups/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/groups/index.vue @@ -508,7 +508,7 @@ } .group-table-wrapper { :deep(.bk-table-body) { - max-height: calc(100vh - 240px); + max-height: calc(100vh - 280px); overflow: auto; } } diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue index 6cea482340..2d674dd73c 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/index.vue @@ -185,7 +185,6 @@ const rules = selector.labels_and || selector.labels_or || []; return { id: group_id, name: group_name, release_id, release_name, rules }; }); - groupListLoading.value = false; }; diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/group-tree.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/group-tree.vue index 2d6299fd63..809522f784 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/group-tree.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/components/publish-version/select-group/group-tree.vue @@ -9,7 +9,8 @@ v-model="searchStr" class="group-search-input" :placeholder="t('搜索版本/分组名称/标签key')" - :clearable="true"> + :clearable="true" + v-bk-tooltips="{ content: t('搜索版本/分组名称/标签key'), disabled: locale === 'zh-cn' }"> @@ -89,7 +90,7 @@ children: IGroupNode[]; } - const { t } = useI18n(); + const { t, locale } = useI18n(); // 将全量分组数据按照版本分组 const categorizingData = () => { diff --git a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/read-file-content.vue b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/read-file-content.vue index acc7eb2af8..49ad5e6f76 100644 --- a/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/read-file-content.vue +++ b/bcs-services/bcs-bscp/ui/src/views/space/service/detail/config/components/read-file-content.vue @@ -7,6 +7,9 @@