diff --git a/Makefile b/Makefile index db126ba4..b50b738d 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ IMG := secretflow/kuscia:${TAG} # TEST_SUITE used by integration test TEST_SUITE ?= all -ENVOY_IMAGE ?= secretflow/kuscia-envoy:0.3.0.dev231122 +ENVOY_IMAGE ?= secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia-envoy:0.4.0.dev20240402 DEPS_IMAGE ?= secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia-deps:0.5.0b0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) diff --git a/WORKSPACE b/WORKSPACE index c02ab31f..dbfd6ca8 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -3,6 +3,7 @@ workspace(name = "kuscia") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + maybe( http_archive, sha256 = "2c6a36c7b5a55accae063667ef3c55f2642e67476d96d355ff0acb13dbb47f09", diff --git a/build/dockerfile/kuscia-anolis.Dockerfile b/build/dockerfile/kuscia-anolis.Dockerfile index 004a5fd2..38fe0656 100644 --- a/build/dockerfile/kuscia-anolis.Dockerfile +++ b/build/dockerfile/kuscia-anolis.Dockerfile @@ -1,5 +1,5 @@ ARG DEPS_IMAGE="secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia-deps:0.5.0b0" -ARG KUSCIA_ENVOY_IMAGE="secretflow/kuscia-envoy:0.3.0.dev231122" +ARG KUSCIA_ENVOY_IMAGE="secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia-envoy:0.4.0.dev20240402" ARG PROM_NODE_EXPORTER="prom/node-exporter:v1.7.0" FROM ${DEPS_IMAGE} as deps diff --git a/cmd/kuscia/confloader/kuscia_config.go b/cmd/kuscia/confloader/kuscia_config.go index a438672a..17958647 100644 --- a/cmd/kuscia/confloader/kuscia_config.go +++ b/cmd/kuscia/confloader/kuscia_config.go @@ -70,11 +70,11 @@ type RunkConfig struct { KubeconfigFile string `yaml:"kubeconfigFile"` } -func (runk RunkConfig) convert2K8sProviderCfg() (k8s config.K8sProviderCfg) { - k8s.Namespace = runk.Namespace - k8s.KubeconfigFile = runk.KubeconfigFile - k8s.DNS.Servers = runk.DNSServers - return +func (runk RunkConfig) overwriteK8sProviderCfg(k8sCfg config.K8sProviderCfg) config.K8sProviderCfg { + k8sCfg.Namespace = runk.Namespace + k8sCfg.KubeconfigFile = runk.KubeconfigFile + k8sCfg.DNS.Servers = runk.DNSServers + return k8sCfg } type ImageConfig struct { @@ -154,14 +154,18 @@ func (lite *LiteKusciaConfig) OverwriteKusciaConfig(kusciaConfig *KusciaConfig) kusciaConfig.DataMesh = lite.DataMesh kusciaConfig.Agent.AllowPrivileged = lite.Agent.AllowPrivileged kusciaConfig.Agent.Provider.Runtime = lite.Runtime - kusciaConfig.Agent.Provider.K8s = lite.Runk.convert2K8sProviderCfg() - kusciaConfig.Agent.Provider.K8s.Backend = lite.Agent.Provider.K8s.Backend - kusciaConfig.Agent.Provider.K8s.LabelsToAdd = lite.Agent.Provider.K8s.LabelsToAdd - kusciaConfig.Agent.Provider.K8s.AnnotationsToAdd = lite.Agent.Provider.K8s.AnnotationsToAdd + kusciaConfig.Agent.Provider.K8s = lite.Runk.overwriteK8sProviderCfg(lite.Agent.Provider.K8s) kusciaConfig.Agent.Capacity = lite.Capacity - if len(lite.Agent.Plugins) > 0 { - kusciaConfig.Agent.Plugins = lite.Agent.Plugins + + for _, p := range lite.Agent.Plugins { + for j, pp := range kusciaConfig.Agent.Plugins { + if p.Name == pp.Name { + kusciaConfig.Agent.Plugins[j] = p + break + } + } } + kusciaConfig.Master.Endpoint = lite.MasterEndpoint kusciaConfig.DomainRoute.DomainCsrData = generateCsrData(lite.DomainID, lite.DomainKeyData, lite.LiteDeployToken) kusciaConfig.Debug = lite.Debug @@ -200,14 +204,18 @@ func (autonomy *AutomonyKusciaConfig) OverwriteKusciaConfig(kusciaConfig *Kuscia kusciaConfig.DomainKeyData = autonomy.DomainKeyData kusciaConfig.Agent.AllowPrivileged = autonomy.Agent.AllowPrivileged kusciaConfig.Agent.Provider.Runtime = autonomy.Runtime - kusciaConfig.Agent.Provider.K8s = autonomy.Runk.convert2K8sProviderCfg() - kusciaConfig.Agent.Provider.K8s.Backend = autonomy.Agent.Provider.K8s.Backend - kusciaConfig.Agent.Provider.K8s.LabelsToAdd = autonomy.Agent.Provider.K8s.LabelsToAdd - kusciaConfig.Agent.Provider.K8s.AnnotationsToAdd = autonomy.Agent.Provider.K8s.AnnotationsToAdd + kusciaConfig.Agent.Provider.K8s = autonomy.Runk.overwriteK8sProviderCfg(autonomy.Agent.Provider.K8s) kusciaConfig.Agent.Capacity = autonomy.Capacity - if len(autonomy.Agent.Plugins) > 0 { - kusciaConfig.Agent.Plugins = autonomy.Agent.Plugins + + for _, p := range autonomy.Agent.Plugins { + for j, pp := range kusciaConfig.Agent.Plugins { + if p.Name == pp.Name { + kusciaConfig.Agent.Plugins[j] = p + break + } + } } + kusciaConfig.ConfLoaders = autonomy.ConfLoaders kusciaConfig.SecretBackends = autonomy.SecretBackends if autonomy.KusciaAPI != nil { diff --git a/cmd/kuscia/modules/domainroute.go b/cmd/kuscia/modules/domainroute.go index f1ff1d6a..b2b86961 100644 --- a/cmd/kuscia/modules/domainroute.go +++ b/cmd/kuscia/modules/domainroute.go @@ -54,8 +54,14 @@ func NewDomainRoute(i *Dependencies) Module { externalTLS = i.DomainRoute.ExternalTLS } - if i.Protocol == common.NOTLS { + protocol := i.Protocol + switch protocol { + case common.NOTLS: externalTLS = nil + case common.TLS, common.MTLS: + externalTLS = &kusciaconfig.TLSConfig{ + EnableTLS: true, + } } if externalTLS != nil && externalTLS.EnableTLS { diff --git a/cmd/kuscia/modules/scheduler.go b/cmd/kuscia/modules/scheduler.go index 43b9e254..e6268c94 100644 --- a/cmd/kuscia/modules/scheduler.go +++ b/cmd/kuscia/modules/scheduler.go @@ -65,6 +65,8 @@ func NewScheduler(i *Dependencies) Module { o.Authentication.TolerateInClusterLookupFailure = true o.Authentication.RemoteKubeConfigFileOptional = true o.Authorization.RemoteKubeConfigFileOptional = true + o.Authorization.RemoteKubeConfigFile = i.KubeconfigFile + o.Authentication.RemoteKubeConfigFile = i.KubeconfigFile // Set the PairName but leave certificate directory blank to generate in-memory by default o.SecureServing.ServerCert.CertDirectory = "" diff --git a/cmd/kuscia/modules/ssexporter.go b/cmd/kuscia/modules/ssexporter.go index 714c1286..999d4ccb 100644 --- a/cmd/kuscia/modules/ssexporter.go +++ b/cmd/kuscia/modules/ssexporter.go @@ -40,7 +40,7 @@ func NewSsExporter(i *Dependencies) Module { domainID: i.DomainID, rootDir: i.RootDir, metricUpdatePeriod: i.MetricUpdatePeriod, - ssExportPort: string(i.SsExportPort), + ssExportPort: i.SsExportPort, } } diff --git a/crds/v1alpha1/kuscia.secretflow_clusterdomainroutes.yaml b/crds/v1alpha1/kuscia.secretflow_clusterdomainroutes.yaml index ebf3fc38..adf471c3 100644 --- a/crds/v1alpha1/kuscia.secretflow_clusterdomainroutes.yaml +++ b/crds/v1alpha1/kuscia.secretflow_clusterdomainroutes.yaml @@ -174,18 +174,28 @@ spec: - tokenGenMethod type: object transit: - description: Transit entity. If it is not empty, the requests between - nodes need to be transferred through a third party. + description: Transit entity. If transitMethod is THIRD-DOMAIN, requests + from source to destination need to be transferred through a third + party, domain field must be set. If transitMethod is REVERSE-TUNNEL, + requests from source to destination need to be transferred through + local gateway chunked transfer encoding. properties: domain: - description: DomainTransit means to forward the request through - the domain. + description: DomainTransit defines the information of the third + domain. properties: domainID: type: string required: - domainID type: object + transitMethod: + description: TransitMethod means to forward the request through + a specific entity, THIRD-DOMAIN by default. + enum: + - THIRD-DOMAIN + - REVERSE-TUNNEL + type: string type: object required: - authenticationType diff --git a/crds/v1alpha1/kuscia.secretflow_domainroutes.yaml b/crds/v1alpha1/kuscia.secretflow_domainroutes.yaml index c9e54326..89a225e2 100644 --- a/crds/v1alpha1/kuscia.secretflow_domainroutes.yaml +++ b/crds/v1alpha1/kuscia.secretflow_domainroutes.yaml @@ -171,18 +171,28 @@ spec: - tokenGenMethod type: object transit: - description: Transit entity. If it is not empty, the requests between - nodes need to be transferred through a third party. + description: Transit entity. If transitMethod is THIRD-DOMAIN, requests + from source to destination need to be transferred through a third + party, domain field must be set. If transitMethod is REVERSE-TUNNEL, + requests from source to destination need to be transferred through + local gateway chunked transfer encoding. properties: domain: - description: DomainTransit means to forward the request through - the domain. + description: DomainTransit defines the information of the third + domain. properties: domainID: type: string required: - domainID type: object + transitMethod: + description: TransitMethod means to forward the request through + a specific entity, THIRD-DOMAIN by default. + enum: + - THIRD-DOMAIN + - REVERSE-TUNNEL + type: string type: object required: - authenticationType diff --git a/docs/deployment/Docker_deployment_kuscia/deploy_master_lite_cn.md b/docs/deployment/Docker_deployment_kuscia/deploy_master_lite_cn.md index ddaca728..ea93dc68 100644 --- a/docs/deployment/Docker_deployment_kuscia/deploy_master_lite_cn.md +++ b/docs/deployment/Docker_deployment_kuscia/deploy_master_lite_cn.md @@ -21,20 +21,27 @@ export KUSCIA_IMAGE=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/k 获取部署脚本,部署脚本会下载到当前目录: ```bash -docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/deploy.sh > deploy.sh && chmod u+x deploy.sh +docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/kuscia.sh > kuscia.sh && chmod u+x kuscia.sh ``` -启动 master,默认会在当前目录下创建 ${USER}-kuscia-master/data、${USER}-kuscia-master/logs、${USER}-kuscia-master/kuscia.yaml 用来存储 master 的数据、日志和配置文件: +生成 master 节点的配置文件: +```bash +# -n 参数传递的是 master 节点 ID,DomainID 需全局唯一,生产环境建议使用公司名称-部门名称-节点名称,如: antgroup-secretflow-master +docker run -it --rm ${KUSCIA_IMAGE} kuscia init --mode master --domain "antgroup-secretflow-master" > kuscia_master.yaml +``` + +启动 master,默认会在当前目录下创建 ${USER}-kuscia-master/{data、logs} 用来存储 master 的数据、日志: ```bash -# -n 参数传递的是Master节点 ID,DomainID需全局唯一,生产环境建议使用公司名称-部门名称-节点名称,如: antgroup-secretflow-master # -p 参数传递的是 master 容器映射到主机的端口,保证和主机上现有的端口不冲突即可 # -k 参数传递的是 master 容器 KusciaAPI 映射到主机的 HTTP 端口,保证和主机上现有的端口不冲突即可 -./deploy.sh master -n antgroup-secretflow-master -p 18080 -k 18082 +./kuscia.sh start -c kuscia_master.yaml -p 18080 -k 18081 ``` + 注意:
1、如果 master 的入口网络存在网关时,为了确保节点与 master 之间通信正常,需要网关符合一些要求,详情请参考[这里](./networkrequirements.md)
-2、master 节点默认使用 sqlite 作为存储,如果生产部署,需要配置链接到 mysql 数据库的连接串,具体配置可以参考[这里](./kuscia_config_cn.md#id3)
+2、master 节点默认使用 sqlite 作为存储,如果生产部署,需要配置链接到 mysql 数据库的连接串,具体配置可以参考[这里](./kuscia_config_cn.md#id3)
+3、需要对合作方暴露的 Kuscia 端口,可参考 [Kuscia 端口介绍](../kuscia_ports_cn.md) 建议使用 curl -kvvv https://ip:port; 检查一下是否访问能通,正常情况下返回的 HTTP 错误码是 401,内容是:unauthorized。 示例如下: @@ -122,18 +129,24 @@ export KUSCIA_IMAGE=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/k 获取部署脚本,部署脚本会下载到当前目录: ```bash -docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/deploy.sh > deploy.sh && chmod u+x deploy.sh +docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/kuscia.sh > kuscia.sh && chmod u+x kuscia.sh +``` + +生成 alice 节点的配置文件: +```bash +# --domain 参数传递的是节点 ID +# --lite-deploy-token 参数传递的是节点部署的 Token +# --master-endpoint 参数传递的是 master 容器对外暴露的 https://IP:PORT,假设 master 对外暴露的 IP 是 1.1.1.1,端口是18080 +docker run -it --rm ${KUSCIA_IMAGE} kuscia init --mode lite --domain "alice" --master-endpoint "https://1.1.1.1:18080" --lite-deploy-token "abcdefg" > lite_alice.yaml ``` 启动 alice,默认会在当前目录下创建 ${USER}-kuscia-lite-alice/data 目录用来存放 alice 的数据: ```bash -# -n 参数传递的是节点 ID -# -t 参数传递的是节点部署的 Token -# -m 参数传递的是 master 容器对外暴露的 https://IP:PORT,假设 master 对外暴露的 IP 是1.1.1.1,端口是18080 # -p 参数传递的是节点容器映射到主机的端口,保证和主机上现有的端口不冲突即可 -./deploy.sh lite -n alice -t abcdefg -m https://1.1.1.1:18080 -p 28080 +# -k 参数传递的是 lite 容器 KusciaAPI 映射到主机的 HTTP 端口,保证和主机上现有的端口不冲突即可 +./kuscia.sh start -c lite_alice.yaml -p 28080 -k 28081 ``` -> 如果 master 与多个 lite 节点部署在同一个物理机上,可以用 -p -k -g 参数指定下端口号(例如:./deploy.sh lite -n alice -t abcdefg -m https://1.1.1.1:18080 -p 28080 -k 2008 -g 2009),防止出现端口冲突 +> 如果 master 与多个 lite 节点部署在同一个物理机上,可以用 -p -k -g -q 参数指定下端口号(例如:./kuscia.sh start -c lite_alice.yaml -p 28080 -k 28081 -g 28082 -q 28083),防止出现端口冲突。 #### 部署 lite 节点 bob @@ -167,18 +180,24 @@ export KUSCIA_IMAGE=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/k 获取部署脚本,部署脚本会下载到当前目录: ```bash -docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/deploy.sh > deploy.sh && chmod u+x deploy.sh +docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/kuscia.sh > kuscia.sh && chmod u+x kuscia.sh +``` + +生成 bob 节点的配置文件: +```bash +# --domain 参数传递的是节点 ID +# --lite-deploy-token 参数传递的是节点部署的 Token +# --master-endpoint 参数传递的是 master 容器对外暴露的 https://IP:PORT,假设 master 对外暴露的 IP 是 1.1.1.1,端口是18080 +docker run -it --rm ${KUSCIA_IMAGE} kuscia init --mode lite --domain "bob" --master-endpoint "https://1.1.1.1:18080" --lite-deploy-token "hijklmn" > lite_bob.yaml ``` 启动 bob,默认会在当前目录下创建 ${USER}-kuscia-lite-bob/data 目录用来存放 bob 的数据: ```bash -# -n 参数传递的是节点 ID -# -t 参数传递的是节点部署的 Token -# -m 参数传递的是 master 容器对外暴露的 https://IP:PORT,假设 master 对外暴露的 IP 是1.1.1.1,端口是18080 # -p 参数传递的是节点容器映射到主机的端口,保证和主机上现有的端口不冲突即可 -./deploy.sh lite -n bob -t hijklmn -m https://1.1.1.1:18080 -p 38080 +# -k 参数传递的是 lite 容器 KusciaAPI 映射到主机的 HTTP 端口,保证和主机上现有的端口不冲突即可 +./kuscia.sh start -c lite_bob.yaml -p 38080 -k 38081 ``` -> 如果 master 与多个 lite 节点部署在同一个物理机上,可以用 -p -k -g 参数指定下端口号(例如:./deploy.sh lite -n alice -t abcdefg -m https://1.1.1.1:18080 -p 38080 -k 2010 -g 2011),防止出现端口冲突 +> 如果 master 与多个 lite 节点部署在同一个物理机上,可以用 -p -k -g -q 参数指定下端口号(例如:./kuscia.sh start -c lite_bob.yaml -p 38080 -k 38081 -g 38082 -q 38083),防止出现端口冲突。 ### 配置授权 diff --git a/docs/deployment/Docker_deployment_kuscia/deploy_p2p_cn.md b/docs/deployment/Docker_deployment_kuscia/deploy_p2p_cn.md index b81c6f16..41272620 100644 --- a/docs/deployment/Docker_deployment_kuscia/deploy_p2p_cn.md +++ b/docs/deployment/Docker_deployment_kuscia/deploy_p2p_cn.md @@ -24,20 +24,28 @@ export KUSCIA_IMAGE=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/k 获取部署脚本,部署脚本会下载到当前目录: ``` -docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/deploy.sh > deploy.sh && chmod u+x deploy.sh +docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/kuscia.sh > kuscia.sh && chmod u+x kuscia.sh ``` -启动节点,默认会在当前目录下创建 ${USER}-kuscia-autonomy-alice/data 目录用来存放 alice 的数据。部署节点需要使用 `deploy.sh` 脚本并传入特定的参数: +生成 alice 节点配置文件: +```bash +# --domain 参数传递的是节点 ID +docker run -it --rm ${KUSCIA_IMAGE} kuscia init --mode autonomy --domain "alice" > autonomy_alice.yaml +``` + +启动节点,默认会在当前目录下创建 ${USER}-kuscia-autonomy-alice/data 目录用来存放 alice 的数据。部署节点需要使用 `kuscia.sh` 脚本并传入节点配置文件: ```bash -# -n 参数传递的是节点 ID。 # -p 参数传递的是节点容器映射到主机的 HTTPS 端口,保证和主机上现有的端口不冲突即可 # -k 参数传递的是节点容器 KusciaAPI 映射到主机的 MTLS 端口,保证和主机上现有的端口不冲突即可 -./deploy.sh autonomy -n alice -p 11080 -k 8082 +./kuscia.sh start -c autonomy_alice.yaml -p 11080 -k 11081 ``` +> 如果多个 lite 节点部署在同一个物理机上,可以用 -p -k -g -q 参数指定下端口号(例如:./kuscia.sh start -c autonomy_alice.yaml -p 11080 -k 11081 -g 11082 -q 11083),防止出现端口冲突。 + 注意:
1、如果节点之间的入口网络存在网关时,为了确保节点与 master 之间通信正常,需要网关符合一些要求,详情请参考[这里](./networkrequirements.md)
-2、alice、bob 节点默认使用 sqlite 作为存储,如果生产部署,需要配置链接到 mysql 数据库的连接串,具体配置可以参考[这里](./kuscia_config_cn.md#id3)
+2、alice、bob 节点默认使用 sqlite 作为存储,如果生产部署,需要配置链接到 mysql 数据库的连接串,具体配置可以参考[这里](./kuscia_config_cn.md#id3)
+3、需要对合作方暴露的 Kuscia 端口,可参考 [Kuscia 端口介绍](../kuscia_ports_cn.md) ### 部署 bob 节点 @@ -86,10 +94,10 @@ docker cp ${USER}-kuscia-autonomy-bob:/home/kuscia/var/certs/domain.crt bob.doma docker cp bob.domain.crt ${USER}-kuscia-autonomy-alice:/home/kuscia/var/certs/ ``` -在 bob 里添加 alice 的证书等信息: +在 alice 里添加 bob 的证书等信息: ```bash -# [bob 机器] 添加 alice 的证书等信息 +# [alice 机器] 添加 alice 的证书等信息 docker exec -it ${USER}-kuscia-autonomy-alice scripts/deploy/add_domain.sh bob p2p ``` diff --git a/docs/deployment/K8s_deployment_kuscia/K8s_master_lite_cn.md b/docs/deployment/K8s_deployment_kuscia/K8s_master_lite_cn.md index 0d74280d..51fa35e6 100644 --- a/docs/deployment/K8s_deployment_kuscia/K8s_master_lite_cn.md +++ b/docs/deployment/K8s_deployment_kuscia/K8s_master_lite_cn.md @@ -26,6 +26,10 @@ kubectl create ns kuscia-master ### 步骤二:创建 Service 获取 [service.yaml](https://github.com/secretflow/kuscia/blob/main/hack/k8s/master/service.yaml) 文件,创建 service + +注意:
+1、需要对合作方暴露的 Kuscia 端口,可参考 [Kuscia 端口介绍](../kuscia_ports_cn.md)
+ ```bash kubectl create -f service.yaml ``` @@ -66,7 +70,14 @@ kubectl create ns lite-alice ### 步骤二:创建 Service -获取 [service.yaml](https://github.com/secretflow/kuscia/blob/main/hack/k8s/lite/service.yaml) 文件,如果 master 与 lite 不在一个 K8s 集群内,可以将 master Service 的端口暴露方式改为 [LoadBalancer](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#loadbalancer)(公有云,例如:[阿里云](https://help.aliyun.com/zh/ack/serverless-kubernetes/user-guide/use-annotations-to-configure-load-balancing)) 或者 [NodePort](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#type-nodeport),并在 Configmap 的 masterEndpoint 字段改为可正常访问的地址,创建 Service +获取 [service.yaml](https://github.com/secretflow/kuscia/blob/main/hack/k8s/lite/service.yaml) 文件,如果 master 与 lite 不在一个 K8s 集群内, +可以将 master Service 的类型改为 [LoadBalancer](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#loadbalancer)(公有云, +例如:[阿里云](https://help.aliyun.com/zh/ack/serverless-kubernetes/user-guide/use-annotations-to-configure-load-balancing))或者 [NodePort](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#type-nodeport), +并在 Configmap 的 masterEndpoint 字段改为可正常访问的地址,创建 Service。 + +注意:
+1、需要对合作方暴露的 Kuscia 端口,可参考 [Kuscia 端口介绍](../kuscia_ports_cn.md)
+ ```bash kubectl create -f service.yaml ``` @@ -244,9 +255,6 @@ kubectl apply -f AppImage.yaml ``` ### 执行测试作业 - -K8s 部署 RunK 模式暂时不支持训练任务。训练任务可以参考[使用 RunP 模式部署节点](../deploy_with_runp_cn.md) - - 登录到 master pod ```bash kubectl exec -it ${master_pod_name} bash -n kuscia-master diff --git a/docs/deployment/K8s_deployment_kuscia/K8s_p2p_cn.md b/docs/deployment/K8s_deployment_kuscia/K8s_p2p_cn.md index feba6d0f..b6c40dd2 100644 --- a/docs/deployment/K8s_deployment_kuscia/K8s_p2p_cn.md +++ b/docs/deployment/K8s_deployment_kuscia/K8s_p2p_cn.md @@ -13,7 +13,7 @@ ### 前置准备 -部署 Autonomy 需提前准备好 Mysql 数据库表并且符合[Kuscia配置](../kuscia_config_cn.md#id3)中的规范,数据库帐号密码等信息配置在步骤三 Configmap 中。 +部署 Autonomy 需提前准备好 Mysql 数据库表并且符合 [Kuscia配置](../kuscia_config_cn.md#id3)中的规范,数据库帐号密码等信息配置在步骤三 Configmap 中。 ### 步骤一:创建 Namespace > 创建 Namespace 需要先获取 create 权限,避免出现 "namespaces is forbidden" 报错 @@ -26,6 +26,10 @@ kubectl create ns autonomy-alice ### 步骤二:创建 Service 获取 [service.yaml](https://github.com/secretflow/kuscia/blob/main/hack/k8s/autonomy/service.yaml) 文件,创建这个 Service + +注意:
+1、需要对合作方暴露的 Kuscia 端口,可参考 [Kuscia 端口介绍](../kuscia_ports_cn.md)
+ ```bash kubectl create -f service.yaml ``` @@ -212,9 +216,6 @@ kubectl apply -f AppImage.yaml ``` ### 执行测试作业 - -k8s 部署 RunK 模式暂时不支持训练任务。训练任务可以参考[使用 RunP 模式部署节点](../deploy_with_runp_cn.md) - 登录到 alice pod ```bash kubectl exec -it ${alice_pod_name} bash -n autonomy-alice diff --git a/docs/deployment/K8s_deployment_kuscia/deploy_with_runp_cn.md b/docs/deployment/K8s_deployment_kuscia/deploy_with_runp_cn.md index e541ccef..3bc20fa1 100644 --- a/docs/deployment/K8s_deployment_kuscia/deploy_with_runp_cn.md +++ b/docs/deployment/K8s_deployment_kuscia/deploy_with_runp_cn.md @@ -27,6 +27,10 @@ 2. 下载 Kuscia [配置示例](https://github.com/secretflow/kuscia/blob/main/scripts/templates/kuscia-autonomy.yaml)(以 autonomy 为例), 将 `runtime` 字段修改为 `runp`,以及填充模板变量,例如 `{{.DOMAIN_ID}}`、`{{.DOMAIN_KEY_DATA}}` 等。 并在启动节点时指定配置文件路径。 + +注意:
+1、需要对合作方暴露的 Kuscia 端口,可参考 [Kuscia 端口介绍](../kuscia_ports_cn.md)
+ ```bash # DOMAIN_KEY_DATA 请用以下命令生成 docker run -it --rm ${KUSCIA_IMAGE} scripts/deploy/generate_rsa_key.sh @@ -35,7 +39,6 @@ ./deploy.sh autonomy -n alice -i 1.1.1.1 -p 11001 -k 11002 -g 11003 -c kuscia-autonomy.yaml ``` - ### 在 K8s 集群上部署 完整的详细流程请参考 [K8s部署中心化集群](./K8s_deployment_kuscia/K8s_master_lite_cn.md) 和 [K8s部署点对点集群](./K8s_deployment_kuscia/K8s_p2p_cn.md)。 diff --git a/docs/deployment/index.rst b/docs/deployment/index.rst index d1a06fa3..677b0bb3 100644 --- a/docs/deployment/index.rst +++ b/docs/deployment/index.rst @@ -13,3 +13,4 @@ logdescription kuscia_monitor kuscia_config_cn + kuscia_ports_cn diff --git a/docs/deployment/kuscia_config_cn.md b/docs/deployment/kuscia_config_cn.md index 9d110a8d..8962cdcc 100644 --- a/docs/deployment/kuscia_config_cn.md +++ b/docs/deployment/kuscia_config_cn.md @@ -44,7 +44,7 @@ runtime: runc runk: # 任务调度到指定的机构 K8s namespace 下 namespace: "" - # 机构 K8s 集群的 pod dns 配置, 用于解析节点的应用域名 + # 机构 K8s 集群的 pod dns 配置,用于解析节点的应用域名,runk 拉起 pod 所使用的 dns 地址,应配置为 kuscia service 的 clusterIP dnsServers: # 机构 K8s 集群的 kubeconfig, 不填默认 serviceaccount; 当前请不填,默认使用 serviceaccount kubeconfigFile: @@ -77,9 +77,11 @@ datastoreEndpoint: "" enableWorkloadApprove: false ``` +{#configuration-detail} + ### 配置项详解 - `mode`: 当前 Kuscia 节点部署模式 支持 Lite、Master、Autonomy(不区分大小写), 不同部署模式详情请参考[这里](../reference/architecture_cn) -- `domainID`: 当前 Kuscia 实例的 [节点 ID](../reference/concepts/domain_cn), 需要符合 DNS 子域名规则要求,详情请参考[这里](https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names), 生产环境使用时建议将domainID设置为全局唯一,建议使用:公司名称-部门名称-节点名称,如: domainID: antgroup-secretflow-trainlite +- `domainID`: 当前 Kuscia 实例的 [节点 ID](../reference/concepts/domain_cn), 需要符合 DNS 子域名规则要求,详情请参考[这里](https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names), 生产环境使用时建议将 domainID 设置为全局唯一,建议使用:公司名称-部门名称-节点名称,如: domainID: antgroup-secretflow-trainlite - `domainKeyData`: 节点私钥配置, 用于节点间的通信认证(通过 2 方的证书来生成通讯的身份令牌),节点应用的证书签发(为了加强通讯安全性,Kuscia 会给每一个任务引擎分配 MTLS 证书,不论引擎访问其他模块(包括外部),还是其他模块访问引擎,都走 MTLS 通讯,以免内部攻破引擎。)。可以通过命令 `docker run -it --rm secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia scripts/deploy/generate_rsa_key.sh` 生成 - `logLevel`: 日志级别 INFO、DEBUG、WARN,默认 INFO - `liteDeployToken`: 节点首次连接到 Master 时使用的是由 Master 颁发的一次性 Token 进行身份验证[获取Token](../deployment/deploy_master_lite_cn.md#lite-alice),该 Token 在节点成功部署后立即失效。在多机部署中,请保持该 Token 不变即可;若节点私钥遗失,必须在 Master 上删除相应节点的公钥并重新获取 Token 部署。详情请参考[私钥丢失如何重新部署](./../reference/troubleshoot/private_key_loss.md) @@ -108,10 +110,10 @@ enableWorkloadApprove: false - 手动建表:如果机构建表是被管控的,或者提供的数据库账号没有建表权限,可以提前手动建立好数据表,kuscia 识别到数据表存在后,会自动跳过建表。 - 自动建表:如果提供的数据库账号有建表权限(账号具有`DDL+DML`权限),并且数据表不存在,kuscia 会尝试自动建表,如果创建失败 kuscia 会启动失败。 - 数据库账户对表中字段至少具有 select、insert、update、delete 操作权限。 -- `protocol`: KusciaAPI 以及节点对外网关使用的通信协议,有三种安全模式可供选择:NOTLS/TLS/MTLS(不区分大小写)。 - - `NOTLS`: 此模式下,通信不使用 TLS 协议,即数据通过未加密的 HTTP 传输,比较安全的内部网络环境或者 Kuscia 已经存在外部网关的情况可以使用该模式。 - - `TLS`: 在此模式下,通信通过 TLS 协议进行加密,即使用 HTTPS 进行安全传输,不需要手动配置证书。 - - `MTLS`: 这种模式也使用 HTTPS 进行通信,但它支持双向 TLS 验证,需要手动交换证书以建立安全连接。 +- `protocol`: KusciaAPI 以及节点对外网关使用的通信协议,有三种通信协议可供选择:NOTLS/TLS/MTLS(不区分大小写)。 + - `NOTLS`: 不使用 TLS 协议,即数据通过未加密的 HTTP 传输,比较安全的内部网络环境或者 Kuscia 已经存在外部网关的情况可以使用该模式。 + - `TLS`: 通过 TLS 协议进行加密,即使用 HTTPS 进行安全传输,不需要手动配置证书。 + - `MTLS`: 使用 HTTPS 进行通信,支持双向 TLS 验证,需要手动交换证书以建立安全连接。 - `enableWorkloadApprove`: 是否开启工作负载审核,默认为 false,即关闭审核。取值范围:[true, false]。 {#configuration-example} @@ -129,6 +131,7 @@ enableWorkloadApprove: false - 容器内路径:/home/kuscia/etc/conf/kuscia.yaml 宿主机路径下修改 kuscia.yaml 配置后,重启容器 `docker restart ${container_name}` 生效。 +> Tips:如果要修改 Protocol 字段,请确保对该字段有充足的理解,否则会导致 KusciaAPI 调用失败或者和其他节点的通讯异常。详情参考[Protocol 通信协议](../reference/troubleshoot/protocol_describe.md)。 ## 指定配置文件 如果使用 [deploy.sh](https://github.com/secretflow/kuscia/blob/main/scripts/deploy/deploy.sh) 脚本部署的 Kuscia,可以指定配置文件,示例: diff --git a/docs/deployment/kuscia_ports_cn.md b/docs/deployment/kuscia_ports_cn.md new file mode 100644 index 00000000..228f8b5d --- /dev/null +++ b/docs/deployment/kuscia_ports_cn.md @@ -0,0 +1,19 @@ +# Kuscia 端口介绍 + +在实际场景中,为了确保 Kuscia 运行在一个安全的网络环境中,用户需要根据本地网络防火墙,管理 Kuscia 对外暴露给合作机构的端口。 + +如果采用 Docker 方式部署,那么在部署的过程中,为了能够跨域和域内访问 Kuscia 节点,需要将 Kuscia 节点的部分内部端口通过端口映射的方式暴露在宿主机上。 + +下面是需要用户感知的端口信息,按是否需要暴露给合作方,分为两类: + +* 是。该类端口需要通过公网或专线能够让合作方访问。 +* 否。该类端口仅需局域网内使用,无需暴露给合作方。 + + +| 协议 | 端口号 | 说明 | 是否需要暴露给合作方 | +| ------------ | -------- |-----------------------------------------------------------------------------------------------------------------------------------------------|------------| +| HTTP/HTTPS | 1080 | 节点之间的认证鉴权端口。在创建节点之间路由时需要指定,可参考[创建节点路由](../reference/apis/domainroute_cn.md#请求createdomainrouterequest) | 是 | +| HTTP | 80 | 访问节点中应用的端口。例如:可通过此端口访问 Serving 服务进行预测打分,可参考[使用 SecretFlow Serving 进行预测](../tutorial/run_sf_serving_with_api_cn.md#使用-secretflow-serving-进行预测) | 否 | +| HTTP/HTTPS | 8082 | 节点 KusciaAPI 的访问端口,可参考[如何使用 KusciaAPI](../reference/apis/summary_cn.md#如何使用-kuscia-api) | 否 | +| GRPC/GRPCS | 8083 | 节点 KusciaAPI 的访问端口,可参考[如何使用 KusciaAPI](../reference/apis/summary_cn.md#如何使用-kuscia-api) | 否 | +| HTTP | 9091 | 节点 Metrics 指标采集端口,可参考 [Kuscia 监控](./kuscia_monitor) | 否 | diff --git a/docs/getting_started/quickstart_cn.md b/docs/getting_started/quickstart_cn.md index e9dbb289..b5a99584 100644 --- a/docs/getting_started/quickstart_cn.md +++ b/docs/getting_started/quickstart_cn.md @@ -66,53 +66,61 @@ export KUSCIA_IMAGE=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/k 获取 Kuscia 安装脚本,安装脚本会下载到当前目录: ``` -docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/start_standalone.sh > start_standalone.sh && chmod u+x start_standalone.sh +docker pull $KUSCIA_IMAGE && docker run --rm $KUSCIA_IMAGE cat /home/kuscia/scripts/deploy/kuscia.sh > kuscia.sh && chmod u+x kuscia.sh ``` ### 中心化组网模式 ```bash # 启动集群,会拉起 3 个 docker 容器,包括一个控制平面 master 和两个 Lite 节点 alice 和 bob。 -./start_standalone.sh center - -# 登入 master 容器。 -docker exec -it ${USER}-kuscia-master bash +./kuscia.sh center # 创建并启动作业(两方 PSI 任务)。 -scripts/user/create_example_job.sh +docker exec -it ${USER}-kuscia-master scripts/user/create_example_job.sh # 查看作业状态。 -kubectl get kj -n cross-domain +docker exec -it ${USER}-kuscia-master kubectl get kj -n cross-domain ``` -:::{tip} +{#p2p-network-mode} -如果希望体验隐语白屏功能([隐语白屏使用手册官方文档](https://www.secretflow.org.cn/docs/quickstart/mvp-platform)),请使用如下命令完成部署。 +### 点对点组网模式 ```bash -# 启动集群,会拉起 4 个 docker 容器,包括一个平台页面容器、一个控制平面 master 、两个 Lite 节点 alice 和 bob。 -./start_standalone.sh center -u web +# 启动集群,会拉起两个 docker 容器,分别表示 Autonomy 节点 alice 和 bob。 +./kuscia.sh p2p +# 登入 alice 节点容器(或 bob 节点容器)创建并启动作业(两方 PSI 任务)。 +docker exec -it ${USER}-kuscia-autonomy-alice scripts/user/create_example_job.sh + +# 查看作业状态。 +docker exec -it ${USER}-kuscia-autonomy-alice kubectl get kj -n cross-domain ``` -::: +### 中心化 x 中心化组网模式 -{#p2p-network-mode} +```bash +# 启动集群,会拉起 4 个 docker 容器,包括两个控制平面 master-alice、master-bob 和两个 Lite 节点 alice、bob。 +./kuscia.sh cxc -### 点对点组网模式 +# 登入 master-alice 容器创建并启动作业(两方 PSI 任务)。 +docker exec -it ${USER}-kuscia-master-alice scripts/user/create_example_job.sh -```bash -# 启动集群,会拉起两个 docker 容器,分别表示 Autonomy 节点 alice 和 bob。 -./start_standalone.sh p2p +# 查看作业状态。 +docker exec -it ${USER}-kuscia-master-alice kubectl get kj -n cross-domain +``` -# 登入 alice 节点容器(或 bob 节点容器)。 -docker exec -it ${USER}-kuscia-autonomy-alice bash +### 中心化 x 点对点组网模式 -# 创建并启动作业(两方 PSI 任务)。 -scripts/user/create_example_job.sh +```bash +# 启动集群,会拉起 3 个 docker 容器,包括一个控制平面 master-alice 和一个 Lite 节点 alice、一个 Autonomy 节点 bob。 +./kuscia.sh cxp + +# 登入 master-alice 容器创建并启动作业(两方 PSI 任务)。 +docker exec -it ${USER}-kuscia-master-alice scripts/user/create_example_job.sh # 查看作业状态。 -kubectl get kj -n cross-domain +docker exec -it ${USER}-kuscia-master-alice kubectl get kj -n cross-domain ``` ## 作业状态 diff --git a/docs/imgs/default_protocol.png b/docs/imgs/default_protocol.png new file mode 100644 index 00000000..821ed27b Binary files /dev/null and b/docs/imgs/default_protocol.png differ diff --git a/docs/imgs/kuscia_job_state.png b/docs/imgs/kuscia_job_state.png new file mode 100644 index 00000000..cfbb614d Binary files /dev/null and b/docs/imgs/kuscia_job_state.png differ diff --git a/docs/imgs/protocol_describe.png b/docs/imgs/protocol_describe.png new file mode 100644 index 00000000..b47f05c5 Binary files /dev/null and b/docs/imgs/protocol_describe.png differ diff --git a/docs/index.md b/docs/index.md index 8b42a9eb..09759746 100644 --- a/docs/index.md +++ b/docs/index.md @@ -42,7 +42,8 @@ Kuscia(Kubernetes-based Secure Collaborative InfrA)是一款基于 K3s 的 - API 参考:[请求和响应][api-request-and-response] | [Domain][api-domain] | [DomainRoute][api-domainroute] | [DomainData][api-domaindata] | [KusciaJob][api-kusciajob] | [Serving][api-serving] | [Health][api-health] [api-overview]: ./reference/apis/summary_cn.md -[api-tutorial]: ./tutorial/run_secretflow_with_api_cn.md +[api-tutorial]: ./tutorial/run_sf_job_with_api_cn.md +[api-tutorial]: ./tutorial/run_sf_serving_with_api_cn.md [api-request-and-response]: ./reference/apis/summary_cn.md#请求和响应约定 [api-domain]: ./reference/apis/domain_cn.md [api-domainroute]: ./reference/apis/domainroute_cn.md @@ -76,10 +77,12 @@ Kuscia(Kubernetes-based Secure Collaborative InfrA)是一款基于 K3s 的 [deploy-kuscia_config_cn]: ./deployment/kuscia_config_cn.md ## 更多指南 +- [如何运行一个 SecretFlow Serving][how-to-bfia] - [如何运行一个互联互通银联 BFIA 协议作业][how-to-bfia] - [如何运行一个 FATE 作业][how-to-fate] - [安全加固方案][how-to-security-plan] +[how-to-serving]: ./tutorial/run_sf_serving_with_api_cn.md [how-to-bfia]: ./tutorial/run_bfia_job_cn.md [how-to-fate]: ./tutorial/run_fate_cn.md [how-to-security-plan]: ./tutorial/security_plan_cn.md diff --git a/docs/reference/apis/datamesh/domaindata_cn.md b/docs/reference/apis/datamesh/domaindata_cn.md index 6faee3b6..78c16437 100644 --- a/docs/reference/apis/datamesh/domaindata_cn.md +++ b/docs/reference/apis/datamesh/domaindata_cn.md @@ -10,7 +10,6 @@ DomainData 表示被 Kuscia 管理的数据,Data Mesh API 提供了从 Domain |--------------------------------------------------|-----------------------------|------------------------------|----------| | [CreateDomainData](#create-domain-data) | CreateDomainDataRequest | CreateDomainDataResponse | 创建数据对象 | | [UpdateDomainData](#update-domain-data) | UpdateDomainDataRequest | UpdateDomainDataResponse | 更新数据对象 | -| [DeleteDomainData](#delete-domain-data) | DeleteDomainDataRequest | DeleteDomainDataResponse | 删除数据对象 | | [QueryDomainData](#query-domain-data) | QueryDomainDataRequest | QueryDomainDataResponse | 查询数据对象 | ## 接口详情 @@ -49,6 +48,52 @@ DomainData 表示被 Kuscia 管理的数据,Data Mesh API 提供了从 Domain | data | CreateDomainDataResponseData | | | | data.domaindata_id | string | 必填 | 数据对象ID | +#### 请求示例 +发起请求: +```bash +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl https://127.0.0.1:8070/api/v1/datamesh/domaindata/create \ +-X POST -H 'content-type: application/json' \ +--cacert ${CTR_CERTS_ROOT}/ca.crt \ +--cert ${CTR_CERTS_ROOT}/ca.crt \ +--key ${CTR_CERTS_ROOT}/ca.key \ +-d '{ + "domain_id": "alice", + "domaindata_id": "alice-001", + "datasource_id": "demo-oss-datasource", + "name": "alice001", + "type": "table", + "relative_uri": "alice.csv", + "columns": [ + { + "name": "id1", + "type": "str", + "comment": "" + }, + { + "name": "marital_single", + "type": "float", + "comment": "" + } + ] +}' +``` +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "domaindata_id": "alice-001" + } +} +``` + {#update-domain-data} ### 更新数据对象 @@ -77,27 +122,48 @@ DomainData 表示被 Kuscia 管理的数据,Data Mesh API 提供了从 Domain |--------|--------------------------------|----|------| | status | [Status](summary_cn.md#status) | 必填 | 状态信息 | -{#delete-domain-data} - -### 删除数据对象 - -#### HTTP路径 -/api/v1/datamesh/domaindata/delete - -#### 请求(DeleteDomainDataRequest) - -| 字段 | 类型 | 选填 | 描述 | -|---------------|----------------------------------------------|----|---------| -| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | -| domaindata_id | string | 必填 | 数据对象ID | - -#### 响应(DeleteDomainDataResponse) - -| 字段 | 类型 | 选填 | 描述 | -|--------|--------------------------------|----|------| -| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | - -{#query-domain-data} +#### 请求示例 +发起请求: +```bash +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl https://127.0.0.1:8070/api/v1/datamesh/domaindata/update \ +-X POST -H 'content-type: application/json' \ +--cacert ${CTR_CERTS_ROOT}/ca.crt \ +--cert ${CTR_CERTS_ROOT}/ca.crt \ +--key ${CTR_CERTS_ROOT}/ca.key \ + -d '{ + "domain_id": "alice", + "domaindata_id": "alice-001", + "datasource_id": "demo-oss-datasource", + "name": "alice0010", + "type": "table", + "relative_uri": "alice.csv", + "columns": [ + { + "name": "id1", + "type": "str", + "comment": "" + }, + { + "name": "marital_single", + "type": "float", + "comment": "" + } + ] +}' +``` +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + } +} +``` ### 查询数据对象 @@ -118,6 +184,58 @@ DomainData 表示被 Kuscia 管理的数据,Data Mesh API 提供了从 Domain | status | [Status](summary_cn.md#status) | 必填 | 状态信息 | | data | [DomainData](#domain-data-entity) | | | +#### 请求示例 +发起请求: +```bash +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl https://127.0.0.1:8070/api/v1/datamesh/domaindata/query \ +-X POST -H 'content-type: application/json' \ +--cacert ${CTR_CERTS_ROOT}/ca.crt \ +--cert ${CTR_CERTS_ROOT}/ca.crt \ +--key ${CTR_CERTS_ROOT}/ca.key \ + -d '{ + "domaindata_id": "alice-001" +}' +``` +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "domaindata_id": "alice-001", + "name": "alice0010", + "type": "table", + "relative_uri": "alice.csv", + "datasource_id": "demo-oss-datasource", + "attributes": {}, + "partition": null, + "columns": [ + { + "name": "id1", + "type": "str", + "comment": "", + "not_nullable": false + }, + { + "name": "marital_single", + "type": "float", + "comment": "", + "not_nullable": false + } + ], + "vendor": "manual", + "file_format": "UNKNOWN", + "author": "alice" + } +} +``` + ## 公共 {#list-domain-data-request-data} diff --git a/docs/reference/apis/datamesh/domaindatagrant_cn.md b/docs/reference/apis/datamesh/domaindatagrant_cn.md deleted file mode 100644 index f39724eb..00000000 --- a/docs/reference/apis/datamesh/domaindatagrant_cn.md +++ /dev/null @@ -1,134 +0,0 @@ -# DomainDataGrant - -DomainDataGrant 表示被 Kuscia 管理的数据对象的授权,Data Mesh API 提供了从 Domain 侧的管理 DomainDataGrant -的能力。 -请参考 [DomainDataGrant](../../concepts/domaindatagrant_cn.md)。 -你可以从 [这里](https://github.com/secretflow/kuscia/blob/main/proto/api/v1alpha1/datamesh/domaindatagrant.proto) 找到对应的 protobuf 文件。 - -## 接口总览 - -| 方法名 | 请求类型 | 响应类型 | 描述 | -|--------------------------------------------------|-----------------------------|------------------------------|----------| -| [CreateDomainDataGrant](#create-domain-data-grant) | CreateDomainDataGrantRequest | CreateDomainDataGrantResponse | 创建数据对象授权 | -| [UpdateDomainDataGrant](#update-domain-data-grant) | UpdateDomainDataGrantRequest | UpdateDomainDataGrantResponse | 更新数据对象授权 | -| [DeleteDomainDataGrant](#delete-domain-data-grant) | DeleteDomainDataGrantRequest | DeleteDomainDataGrantResponse | 删除数据对象授权 | -| [QueryDomainDataGrant](#query-domain-data-grant) | QueryDomainDataGrantRequest | QueryDomainDataGrantResponse | 查询数据对象授权 | - -## 接口详情 - -{#create-domain-data-grant} - -### 创建数据对象授权 - -#### HTTP路径 -/api/v1/datamesh/domaindatagrant/create - -{#create-domain-data-grant-request} - -#### 请求(CreateDomainDataGrantRequest) - -| 字段 | 类型 | 选填 | 描述 | -|---------------|----------------------------------------------|----|----------------------------------------------------------------------------------------------------------------------------------| -| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | -| domaindatagrant_id | string | 可选 | 数据对象授权ID,如果不填,则会由 datamesh 自动生成,并在 response 中返回。如果填写,则会使用填写的值,请注意需满足 [DNS 子域名规则要求](https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) | -| domaindata_id | string | 必填 | 数据对象ID | -| grant_domain | string | 必填 | 被授权节点ID | -| limit | [GrantLimit](#grant-limit-entity) | 选填 | 授权限制条件 | -| description | map | 可选 | 自定义描述 | - -{#create-domain-data-grant-response} - -#### 响应(CreateDomainDataGrantResponse) - -| 字段 | 类型 | 选填 | 描述 | -|--------------------|--------------------------------|----|--------| -| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | -| data | CreateDomainDataGrantResponseData | | | -| data.domaindatagrant_id | string | 必填 | 数据对象授权ID | - -{#update-domain-data-grant} - -### 更新数据对象授权 - -#### HTTP路径 -/api/v1/datamesh/domaindatagrant/update - -#### 请求(UpdateDomainDataGrantRequest) - -| 字段 | 类型 | 选填 | 描述 | -|---------------|----------------------------------------|----|------------------------------------------------------------------------------------------------------------------------------------| -| header | [RequestHeader](summary_cn.md#请求和响应约定) | 可选 | 自定义请求内容 | -| domaindatagrant_id | string | 必填 | 数据对象授权ID | -| domaindata_id | string | 必填 | 数据对象ID | -| grant_domain | string | 必填 | 被授权节点ID | -| limit | [GrantLimit](#grant-limit-entity) | 选填 | 授权限制条件 | -| description | map | 可选 | 自定义描述 | - -#### 响应(UpdateDomainDataGrantResponse) - -| 字段 | 类型 | 选填 | 描述 | -|--------|--------------------------------|----|------| -| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | - -{#delete-domain-data-grant} - -### 删除数据对象授权 - -#### HTTP路径 -/api/v1/datamesh/domaindatagrant/delete - -#### 请求(DeleteDomainDataGrantRequest) - -| 字段 | 类型 | 选填 | 描述 | -|---------------|----------------------------------------------|----|---------| -| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | -| domaindatagrant_id | string | 必填 | 数据对象授权ID | - -#### 响应(DeleteDomainDataGrantResponse) - -| 字段 | 类型 | 选填 | 描述 | -|--------|--------------------------------|----|------| -| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | - -{#query-domain-data-grant} - -### 查询数据对象授权 - -#### HTTP路径 -/api/v1/datamesh/domaindatagrant/query - -#### 请求(QueryDomainGrantRequest) - -| 字段 | 类型 | 选填 | 描述 | -|---------------|----------------------------------------------|----|---------| -| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | -| domaindatagrant_id | string | 必填 | 数据对象授权ID | - -#### 响应(QueryDomainDataGrantResponse) - -| 字段 | 类型 | 选填 | 描述 | -|--------|-----------------------------------|----|------| -| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | -| data | DomainDataGrantData | 选填 | 查询信息 | -| data.domaindatagrant_id | string | 必填 | 数据对象授权ID | -| data.domaindata_id | string | 必填 | 数据对象ID | -| data.grant_domain | string | 必填 | 被授权节点ID | -| data.limit | [GrantLimit](#grant-limit-entity) | 选填 | 授权限制条件 | -| data.description | map | 必填 | 自定义描述 | -| data.author | string | 必填 | 数据授权方节点ID | -| data.signature | string | 选填 | 数据授权信息签名(由授权方私钥签名) | - -## 公共 - -{#grant-limit-entity} - -### GrantLimit - -| 字段 | 类型 | 选填 | 描述 | -|---------------|------------------------------|----|------------------------------------------------------------------------------------------------------------------------------------| -| expiration_time | int64 |选填 | 授权过期时间,Unix 时间戳,精确到纳秒 | -| use_count | int32 |选填 | 授权使用次数 | -| flow_id | string |选填 | 授权对应的任务流ID | -| components | string[] | 选填 | 授权可用的组件ID | -| initiator | string |选填 | 授权指定的发起方 | -| input_config | string |选填 | 授权指定的算子输入参数 | diff --git a/docs/reference/apis/datamesh/domaindatasource_cn.md b/docs/reference/apis/datamesh/domaindatasource_cn.md new file mode 100644 index 00000000..ae6bd7e8 --- /dev/null +++ b/docs/reference/apis/datamesh/domaindatasource_cn.md @@ -0,0 +1,100 @@ +# DomainDataSource + +DomainDataSource 表示 Kuscia 管理的数据源。请参考 [DomainDataSource](../concepts/domaindatasource_cn.md)。 +你可以从 [这里](https://github.com/secretflow/kuscia/tree/main/proto/api/v1alpha1/kusciaapi/domaindatasource.proto) 找到对应的 protobuf 文件。 + +## 接口总览 + +| 方法名 | 请求类型 | 响应类型 | 描述 | +|--------------------------------------------------|-----------------------------|------------------------------|----------| +| [QueryDomainDataSource](#query-domain-data-source) | QueryDomainDataSourceRequest | QueryDomainDataSourceResponse | 查询数据 | + +## 接口详情 + +{#query-domain-data-source} + +### 查询数据源 + +#### HTTP 路径 + +/api/v1/domaindatasource/query + +#### 请求(QueryDomainGrantRequest) + +| 字段 | 类型 | 选填 | 描述 | +|--------|---------------------------------------------------------------|-----|--------------| +| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | +| domain_id | string | 必填 | 节点 ID | +| datasource_id | string | 必填 | 数据源 ID | + +#### 响应(QueryDomainGrantResponse) + +| 字段 | 类型 | 选填 | 描述 | +|--------|--------------------------------------|----|------| +| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | +| data | [DomainDataSource](#domain-data-source-entity) | 可选 | 数据源信息 | + +#### 请求示例 + +发起请求: + +```sh +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl https://127.0.0.1:8070/api/v1/datamesh/domaindatasource/query \ +-X POST -H 'content-type: application/json' \ +--cacert ${CTR_CERTS_ROOT}/ca.crt \ +--cert ${CTR_CERTS_ROOT}/ca.crt \ +--key ${CTR_CERTS_ROOT}/ca.key \ + -d '{ + "datasource_id":"demo-oss-datasource", + "domain_id": "alice" +}' +``` + +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "datasource_id": "demo-oss-datasource", + "name": "DemoDataSource", + "type": "oss", + "status": "Available", + "info": { + "localfs": null, + "oss": { + "endpoint": "https://oss.xxx.cn-xxx.com", + "bucket": "secretflow", + "prefix": "kuscia/", + "access_key_id": "ak-xxxx", + "access_key_secret": "sk-xxxx", + "virtualhost": false, + "version": "", + "storage_type": "" + }, + "database": null + }, + "info_key": "", + "access_directly": true + } +} +``` + +请求响应异常结果:假设传入`datasource_id`不存在 + +```json +{ + "status": { + "code": 12302, + "message": "domaindatasources.kuscia.secretflow \"demo-oss-datasource\" not found", + "details": [] + }, + "data": null +} +``` diff --git a/docs/reference/apis/datamesh/index.rst b/docs/reference/apis/datamesh/index.rst index 77e63e4c..ba50dcdd 100644 --- a/docs/reference/apis/datamesh/index.rst +++ b/docs/reference/apis/datamesh/index.rst @@ -1,6 +1,4 @@ -.. _datamesh: - -DataMesh +Kuscia DataMesh API =========== .. toctree:: @@ -8,4 +6,4 @@ DataMesh summary_cn domaindata_cn - domaindatagrant_cn \ No newline at end of file + domaindatasource_cn \ No newline at end of file diff --git a/docs/reference/apis/datamesh/summary_cn.md b/docs/reference/apis/datamesh/summary_cn.md index 5416578e..b55d4054 100644 --- a/docs/reference/apis/datamesh/summary_cn.md +++ b/docs/reference/apis/datamesh/summary_cn.md @@ -1,5 +1,9 @@ # 概览 +## Data Mesh API 使用场景 + +Data Mesh API 专为隐私计算应用(如 SecretFlow、TrustedFlow)设计,为其提供获取 DomainData 与 DomainDataSource 信息的能力。隐私计算平台(如 SecretPad)可以使用[KusciaApi](../summary_cn.md)接口实现更广泛的功能。 + ## Data Mesh API 约定 Data Mesh API 提供 HTTP 和 GRPC 两种访问方法。 diff --git a/docs/reference/apis/domainroute_cn.md b/docs/reference/apis/domainroute_cn.md index 2eb88f7c..3452f018 100644 --- a/docs/reference/apis/domainroute_cn.md +++ b/docs/reference/apis/domainroute_cn.md @@ -65,7 +65,8 @@ curl -k -X POST 'https://localhost:8082/api/v1/route/create' \ "ports": [ { "port": 1080, - "protocol": "HTTPS" + "protocol": "HTTP", + "isTLS": true } ] }, @@ -393,10 +394,11 @@ curl -k -X POST 'https://localhost:8082/api/v1/route/status/batchQuery' \ ### EndpointPort -| 字段 | 类型 | 选填 | 描述 | -|----------|--------|----|--------------------| -| port | int32 | 必填 | 端口号 | -| protocol | string | 必填 | 端口协议:\[HTTP, GRPC] | +| 字段 | 类型 | 选填 | 描述 | +|----------|--------|------|------------------------| +| port | int32 | 必填 | 端口号 | +| protocol | string | 必填 | 端口协议:\[HTTP, GRPC] | +| isTLS | bool | 选填 | 是否开启 TLS,默认为 false | {#route-endpoint} diff --git a/docs/reference/apis/error_code_cn.md b/docs/reference/apis/error_code_cn.md index 4907726b..94b17f83 100644 --- a/docs/reference/apis/error_code_cn.md +++ b/docs/reference/apis/error_code_cn.md @@ -15,6 +15,9 @@ | 11204 | 删除任务失败 | 删除任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | | 11205 | 停止任务失败 | 停止任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | | 11206 | 审批任务失败 | 审批任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | +| 11207 | 暂停任务失败 | 暂停任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | +| 11208 | 重跑任务失败 | 重跑任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | +| 11209 | 取消任务失败 | 取消任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | | 11300 | 创建节点失败 | 创建节点失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | | 11301 | 查询节点失败 | 查询节点失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | | 11302 | 查询节点状态失败 | 查询节点状态失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因 | diff --git a/docs/reference/apis/kusciajob_cn.md b/docs/reference/apis/kusciajob_cn.md index f5e0645c..f4d87874 100644 --- a/docs/reference/apis/kusciajob_cn.md +++ b/docs/reference/apis/kusciajob_cn.md @@ -13,8 +13,11 @@ protobuf 文件。 | [BatchQueryJobStatus](#batch-query-job-status) | BatchQueryJobStatusRequest | BatchQueryJobStatusResponse | 批量查询 Job 状态 | | [DeleteJob](#delete-job) | DeleteJobRequest | DeleteJobResponse | 删除 Job | | [StopJob](#stop-job) | StopJobRequest | StopJobResponse | 停止 Job | -| [WatchJob](#watch-job) | WatchJobRequest | WatchJobEventResponse stream | 监控 Job | -| [ApproveJob](#{#approval-job) | ApproveJobRequest | ApproveJobResponse | 审批 Job | +| [WatchJob](#watch-job) | WatchJobRequest | WatchJobEventResponse stream | 监听 Job | +| [ApproveJob](#approval-job) | ApproveJobRequest | ApproveJobResponse | 审批 Job | +| [SuspendJob](#suspend-job) | SuspendJobRequest | SuspendJobResponse | 暂停 Job | +| [RestartJob](#restart-job) | RestartJobRequest | RestartJobResponse | 重跑 Job | +| [CancelJob](#cancel-job) | CancelJobRequest | CancelJobResponse | 取消 Job | ## 接口详情 @@ -411,6 +414,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/job/delete' \ |--------|----------------------------------------------|----|---------| | header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | | job_id | string | 必填 | JobID | +| reason | string | 可选 | 停止Job的原因 | #### 响应(StopJobResponse) @@ -466,7 +470,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/job/stop' \ | 字段 | 类型 | 选填 | 描述 | |-----------------|----------------------------------------------|----|---------| | header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | -| timeout_seconds | int64 | 可选 | 请求连接的生命周期,服务端将在超时后断开连接,不管是否有活跃事件。超时时间默认为0,表示不超时。即使在未设置超时时间的情况下, 也会因为网络环境导致断连,客户端根据需求决定是否重新发起请求 | +| timeout_seconds | int64 | 可选 | 请求连接的生命周期,服务端将在超时后断开连接,不管是否有活跃事件。超时时间取值范围:[0, 2^31-1],默认为0,表示不超时。即使在未设置超时时间的情况下, 也会因为网络环境导致断连,客户端根据需求决定是否重新发起请求 | #### 响应(WatchJobEventResponse) @@ -482,7 +486,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/job/stop' \ #### HTTP 路径 -/api/v1/job/approval +/api/v1/job/approve #### 请求(ApprovalJobRequest) @@ -502,6 +506,178 @@ curl -k -X POST 'https://localhost:8082/api/v1/job/stop' \ | data.job_id | string | 必填 | JobID | +{#suspend-job} + +### 暂停 Job + +#### HTTP 路径 + +/api/v1/job/suspend + +#### 请求(SuspendJobRequest) + +| 字段 | 类型 | 选填 | 描述 | +|--------|----------------------------------------------|----|---------| +| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | +| job_id | string | 必填 | JobID | +| reason | string | 可选 | 暂停Job的原因 | + +#### 响应(SuspendJobResponse) + +| 字段 | 类型 | 选填 | 描述 | +|-------------|--------------------------------|----|-------| +| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | +| data | SuspendJobResponseData | | | +| data.job_id | string | 必填 | JobID | + +#### 请求示例 + +发起请求: + +```sh +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl -k -X POST 'https://localhost:8082/api/v1/job/suspend' \ + --header "Token: $(cat ${CTR_CERTS_ROOT}/token)" \ + --header 'Content-Type: application/json' \ + --cert ${CTR_CERTS_ROOT}/kusciaapi-server.crt \ + --key ${CTR_CERTS_ROOT}/kusciaapi-server.key \ + --cacert ${CTR_CERTS_ROOT}/ca.crt \ + -d '{ + "job_id": "job-alice-bob-001" +}' +``` + +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "job_id": "job-alice-bob-001" + } +} +``` + + +{#restart-job} + +### 重跑 Job + +#### HTTP 路径 + +/api/v1/job/restart + +#### 请求(RestartJobRequest) + +| 字段 | 类型 | 选填 | 描述 | +|--------|----------------------------------------------|----|---------| +| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | +| job_id | string | 必填 | JobID | +| reason | string | 可选 | 重跑Job的原因 | + +#### 响应(RestartJobResponse) + +| 字段 | 类型 | 选填 | 描述 | +|-------------|--------------------------------|----|-------| +| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | +| data | RestartJobResponseData | | | +| data.job_id | string | 必填 | JobID | + +#### 请求示例 + +发起请求: + +```sh +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl -k -X POST 'https://localhost:8082/api/v1/job/restart' \ + --header "Token: $(cat ${CTR_CERTS_ROOT}/token)" \ + --header 'Content-Type: application/json' \ + --cert ${CTR_CERTS_ROOT}/kusciaapi-server.crt \ + --key ${CTR_CERTS_ROOT}/kusciaapi-server.key \ + --cacert ${CTR_CERTS_ROOT}/ca.crt \ + -d '{ + "job_id": "job-alice-bob-001" +}' +``` + +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "job_id": "job-alice-bob-001" + } +} +``` + +{#cancel-job} + +### 取消 Job + +#### HTTP 路径 + +/api/v1/job/cancel + +#### 请求(CancelJobRequest) + +| 字段 | 类型 | 选填 | 描述 | +|--------|----------------------------------------------|----|---------| +| header | [RequestHeader](summary_cn.md#requestheader) | 可选 | 自定义请求内容 | +| job_id | string | 必填 | JobID | +| reason | string | 可选 | 取消Job的原因 | + +#### 响应(CancelJobResponse) + +| 字段 | 类型 | 选填 | 描述 | +|-------------|--------------------------------|----|-------| +| status | [Status](summary_cn.md#status) | 必填 | 状态信息 | +| data | CancelJobResponseData | | | +| data.job_id | string | 必填 | JobID | + +#### 请求示例 + +发起请求: + +```sh +# 在容器内执行示例 +export CTR_CERTS_ROOT=/home/kuscia/var/certs +curl -k -X POST 'https://localhost:8082/api/v1/job/cancel' \ + --header "Token: $(cat ${CTR_CERTS_ROOT}/token)" \ + --header 'Content-Type: application/json' \ + --cert ${CTR_CERTS_ROOT}/kusciaapi-server.crt \ + --key ${CTR_CERTS_ROOT}/kusciaapi-server.key \ + --cacert ${CTR_CERTS_ROOT}/ca.crt \ + -d '{ + "job_id": "job-alice-bob-001" +}' +``` + +请求响应成功结果: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "job_id": "job-alice-bob-001" + } +} +``` + ## 公共 {#job-status} @@ -613,6 +789,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/job/stop' \ {#state} ### State +KusciaJob 状态详细介绍见[文档](../concepts/kusciajob_cn.md#kuscia-state)。 | Name | Number | 描述 | |----------- |--------|-------| @@ -621,9 +798,11 @@ curl -k -X POST 'https://localhost:8082/api/v1/job/stop' \ | Running | 2 | 运行中 | | Succeeded | 3 | 成功 | | Failed | 4 | 失败 | -| AwaitingApproval | 5 | 等待参与方审批任务 | -| ApprovalReject | 6 | 任务被审批为拒绝执行 | -| Cancelled | 7 | 任务被取消,被取消的任务不可被再次执行 | +| AwaitingApproval | 5 | 等待参与方审批 Job | +| ApprovalReject | 6 | Job 被审批为拒绝执行 | +| Cancelled | 7 | Job 被取消,被取消的 Job 不可被再次执行 | +| Suspended | 8 | Job 被暂停,可通过 Restart 接口重跑 | +| Initialized | 9 | Job 初始状态 | {#job-party-endpoint} diff --git a/docs/reference/apis/serving_cn.md b/docs/reference/apis/serving_cn.md index 98c31e2f..2055d697 100644 --- a/docs/reference/apis/serving_cn.md +++ b/docs/reference/apis/serving_cn.md @@ -17,7 +17,7 @@ {#create-serving} -### 创建Serving +### 创建 Serving #### HTTP路径 @@ -116,7 +116,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/serving/create' \ {#update-serving} -### 更新Serving +### 更新 Serving #### HTTP路径 /api/v1/serving/update @@ -212,7 +212,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/serving/update' \ {#delete-serving} -### 删除Serving +### 删除 Serving #### HTTP路径 /api/v1/serving/delete @@ -263,7 +263,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/serving/delete' \ {#query-serving} -### 查询Serving +### 查询 Serving #### HTTP路径 @@ -429,7 +429,7 @@ curl -k -X POST 'https://localhost:8082/api/v1/serving/query' \ {#batch-query-serving-status} -### 批量查询Serving状态 +### 批量查询 Serving 状态 #### HTTP路径 @@ -570,32 +570,32 @@ curl -k -X POST 'https://localhost:8082/api/v1/serving/status/batchQuery' \ ### ServingStatusDetail -| 字段 | 类型 | 选填 | 描述 | -|-------------------|-----------------------------------------------|----|-------------------------------| -| state | string | 必填 | Serving状态 | -| reason | string | 可选 | Serving处于该状态的原因,一般用于描述失败的状态 | +| 字段 | 类型 | 选填 | 描述 | +|-------------------|-----------------------------------------------|----|------------------------------| +| state | string | 必填 | Serving状态,参考 [State](#state) | +| reason | string | 可选 | Serving处于该状态的原因,一般用于描述失败的状态 | | message | string | 可选 | Serving处于该状态的详细信息,一般用于描述失败的状态 | -| total_parties | int32 | 必填 | 参与方总数 | -| available_parties | int32 | 必填 | 可用参与方数量 | -| create_time | string | 必填 | 创建时间 | -| party_statuses | [PartyServingStatus](#party-serving-status)[] | 必填 | 参与方状态 | +| total_parties | int32 | 必填 | 参与方总数 | +| available_parties | int32 | 必填 | 可用参与方数量 | +| create_time | string | 必填 | 创建时间 | +| party_statuses | [PartyServingStatus](#party-serving-status)[] | 必填 | 参与方状态 | {#party-serving-status} ### PartyServingStatus -| 字段 | 类型 | 选填 | 描述 | -|----------------------|---------------------------------------------------|----|---------------| -| domain_id | string | 必填 | 节点ID | -| role | string | 可选 | 角色 | -| state | string | 必填 | 状态 | -| replicas | int32 | 必填 | 应用副本总数 | -| available_replicas | int32 | 必填 | 应用可用副本数 | -| unavailable_replicas | int32 | 必填 | 应用不可用副本数 | -| updatedReplicas | int32 | 必填 | 最新版本的应用副本数 | -| create_time | string | 必填 | 创建时间 | -| endpoints | [ServingPartyEndpoint](#serving-party-endpoint)[] | 必填 | 应用对外暴露的访问地址信息 | +| 字段 | 类型 | 选填 | 描述 | +|----------------------|---------------------------------------------------|----|------------------------| +| domain_id | string | 必填 | 节点ID | +| role | string | 可选 | 角色 | +| state | string | 必填 | 状态,参考 [State](#state) | +| replicas | int32 | 必填 | 应用副本总数 | +| available_replicas | int32 | 必填 | 应用可用副本数 | +| unavailable_replicas | int32 | 必填 | 应用不可用副本数 | +| updatedReplicas | int32 | 必填 | 最新版本的应用副本数 | +| create_time | string | 必填 | 创建时间 | +| endpoints | [ServingPartyEndpoint](#serving-party-endpoint)[] | 必填 | 应用对外暴露的访问地址信息 | {#serving-party-endpoint} @@ -645,3 +645,16 @@ curl -k -X POST 'https://localhost:8082/api/v1/serving/status/batchQuery' \ | max_cpu | string | 可选 | 最大cpu数量。例如:"0.1":表示100毫核;"1":表示1核 | | min_memory | string | 可选 | 最小memory数量。单位:"Mi","Gi";例如:"100Mi":表示100兆字节 | | max_memory | string | 可选 | 最大memory数量。单位:"Mi","Gi";例如:"100Mi":表示100兆字节 | + + +{#state} + +### State + +| Name | Number | 描述 | +|----------- |--------|-------------------| +| Unknown | 0 | 未知 | +| Progressing | 1 | 发布中,至少有一方不可用 | +| PartialAvailable | 2 | 发布完成,至少有一方的多实例不是全部可用 | +| Available | 3 | 发布完成,所有方的所有实例都可用 | +| Failed | 4 | 发布失败 | \ No newline at end of file diff --git a/docs/reference/concepts/kusciajob_cn.md b/docs/reference/concepts/kusciajob_cn.md index 8a02d4f1..ea44cf09 100644 --- a/docs/reference/concepts/kusciajob_cn.md +++ b/docs/reference/concepts/kusciajob_cn.md @@ -14,6 +14,23 @@ - **模型训练** 将参与方的训练集作为输入,训练出算法模型。 - **预测** 将算法模型和测试集作为输入,计算出预测结果。 +{#kuscia-state} +## KusciaJob 状态说明 + +下图为 KusciaJob 的状态流转图,其中红色字体表示 KusciaAPI 接口操作。 +![KusciaJobState](../../imgs/kuscia_job_state.png) + +KusciaJob 在其生命周期中会处于以下几种状态: +- Initialized: 此时 Job 刚被发起方在发起方侧创建完成,但 Job 信息还未同步至其他参与方。若 Job 已同步至其他所有参与方则 Job 进入 AwaitingApproval 状态。 +- AwaitingApproval: 进入到 AwaitingApproval 状态的 Job 需要各参与方调用 KusciaAPI 的[ ApproveJob 接口](../apis/kusciajob_cn.md#approval-job)进行审核,若参与方设置了自动审核([参见配置文档enableWorkloadApprove字段](../../deployment/kuscia_config_cn.md#configuration-detail))则无需调用 ApproveJob 接口进行审核。待 Job 的所有参与方审核通过则 Job 进入 Pending 状态。若某一参与方审核拒绝则 Job 进入到 ApprovalReject 状态。 +- Pending: 此时 Job 已被所有参与方审批通过,等待 Job 的发起方发起 Start 命令,待所有参与方接收到 Start 信令后,Job 进入到 Running状态(注:仅 BFIA 互联互通协议时发起方会向各参与方显式的发送 Start 信令,在 Kusica 内部协议中无需显式发送 Start 信令,Job 会自动从 Pending 状态进入到 Running 状态)。 +- Running: 此时 Job 已进入到调度状态,注:Job 是 Running 状态时,'并不意味' 着 Job 中至少有一个 Task 是 Running 状态,因 Job 刚进入 Running 状态时,Task 并未创建完成。 当 Job 的所有 Task 均执行成功时,Job 进入 Succeeded 状态。 当 Job 中的 [关键 Task ](#task-classification) 执行失败,则 Job 进入到 Failed 状态。 +- Suspended: Running 状态的 Job 被某一参与方执行了[ Suspend 操作](../apis/kusciajob_cn.md#suspend-job),则会进入此状态,执行 Suspend 操作后,Job 中处于 Running 状态的 Task 会被终止掉,Task进入到Failed状态。 Suspended 状态的 Job 可通过 [Restart 接口](../apis/kusciajob_cn.md#restart-job)重新运行。 +- Succeeded: 所有 Task 都是 Succeeded 或 Failed 状态且所有 Critical KusciaTask 都是 Succeeded 状态。 Succeeded 状态是一种终态,终态则不会再流转到其他状态。 +- Failed: 所有 Task 都是 Succeeded 或 Failed 状态且至少有一个 Critical KusciaTask 是 Failed 状态。 Failed 状态的 Job 可通过 [Restart 接口](../apis/kusciajob_cn.md#restart-job)重新运行。 +- ApprovalReject: Job 被某一参与方审批为拒绝执行。 ApprovalReject 状态是一种终态,终态则不会再流转到其他状态。 +- Cancelled: Job 被某一方取消,被取消的 Job 不可被再次执行。 Cancelled 状态是一种终态,终态则不会再流转到其他状态。 + ## 用例 以下是一些 KusciaJob 的典型用例: @@ -267,18 +284,6 @@ kubectl delete kj {job-name} -n cross-domain 来取消一个正在执行的 KusciaJob 或者删除一个已经执行完成的 KusciaJob。这会删除这个 KusciaJob 和这个 KusciaJob 创建的 KusciaTask。 -## KusciaJob 的状态 - -KusciaJob 在其生命周期中会处于以下七种状态: - -- Pending: KusciaJob 刚刚被提交,还未进行过调度。 -- Running: 至少有一个 KusciaTask 处于 Running 状态中。 -- Succeeded: 所有 Task 都是 Succeeded 或 Failed 状态且所有 Critical KusciaTask 都是 Succeeded 状态。 -- Failed: 所有 Task 都是 Succeeded 或 Failed 状态且至少有一个 Critical KusciaTask 是 Failed 状态。 -- AwaitingApproval: 等待参与方审批任务。 -- ApprovalReject: 任务被某一参与方审批为拒绝执行。 -- Cancelled: 任务被某一方取消,被取消的任务不可被再次执行。 - ## 参考 ### 完整定义 @@ -350,14 +355,7 @@ KusciaJob `spec`的子字段详细介绍如下: KusciaJob `status`的子字段详细介绍如下: -- `phase`:表示 KusciaJob 当前所处的阶段。当前包括以下几种 Phase: - - `Pending`:表示 KusciaJob 刚刚被提交,还未进行过调度。 - - `Running`:表示 至少有一个 KusciaTask 处于 Running 状态中。 - - `Succeeded`:表示 所有 KusciaTask 都是 Succeeded 或 Failed 状态且所有 Critical KusciaTask 都是 Succeeded 状态。 - - `Failed`:所有 KusciaTask 都是 Succeeded 或 Failed 状态且至少有一个 Critical KusciaTask 是 Failed 状态。 - - `AwaitingApproval`:等待参与方审批任务。 - - `ApprovalReject`:任务被某一参与方审批为拒绝执行。 - - `Cancelled`:任务被某一方取消,被取消的任务不可被再次执行。 +- `phase`:表示 KusciaJob 当前所处的阶段。[状态详见](#kuscia-state)。 - `taskStatus`:表示 KusciaJob 已经启动的 KusciaTask 状态信息, key 为 KusciaTask 的名称,value 为 KusciaTask 的状态。 - `startTime`:表示 KusciaJob 第一次被 Kuscia 控制器处理的时间戳。 - `completionTime`:表示 KusciaJob 运行完成的时间戳。 diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 26e411e7..8f97ffb3 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -8,4 +8,5 @@ architecture_cn concepts/index apis/index + apis/datamesh/index troubleshoot/index \ No newline at end of file diff --git a/docs/reference/troubleshoot/docker_cpp_copy.md b/docs/reference/troubleshoot/docker_cpp_copy.md new file mode 100644 index 00000000..0cfc4c63 --- /dev/null +++ b/docs/reference/troubleshoot/docker_cpp_copy.md @@ -0,0 +1,23 @@ +# Docker 24.0 环境中 C++17 文件复制权限问题 + +### 问题描述 + +在部署了 Docker 24.0 的环境中使用 C++17 标准的 `std::filesystem::copy` 系统调用时,会遇到 `permission denied` 的权限问题。这是由于 Docker 24.0 版本的 HostPath 挂载默认文件的权限为 0600,不支持 0200。然而,在 C++17 标准的 `std::filesystem::copy` 系统调用流程中,该函数首先会创建一个具有仅写权限(0200)的目标文件,然后将源文件的内容写入该目标文件,最后复制源文件 mode 至目标文件,mode 复制到目标文件失败,从而导致目标文件权限报错。 + +### 文件权限介绍 +文件权限种类: +- 读取权限(4):允许读取文件内容。 +- 写入权限(2):允许写入文件内容。 +- 执行权限(1):允许执行文件。 +- 特殊权限(0):禁止访问文件或者特殊权限位。 + +文件权限用户: +- 文件所有者:拥有文件的权限。 +- 文件所属组:拥有文件的权限的组。 +- 其他用户:拥有文件的权限的其他用户。 + +在 Linux 系统中,可以通过 `ls -la` 命令查看文件权限,例如:`-rw-------` 表示文件权限为 `0600`。其中,第一个 `-` 表示文件,其他类型包括如 d 代表目录、l 代表链接等。`rw-` 表示文件所有者具有读写权限,`---` 表示文件所属组无权限,`---` 表示其他用户无权限。 + +### 推荐解决方案 + +推荐的解决方法是使用 C++ 的 `fstream` 库以编程方式复制文件内容,并在创建新文件时指定 `0600` 权限,然后将源文件内容写到目标文件。这样,新文件对拥有者提供读写权限,从而避免了权限问题。 \ No newline at end of file diff --git a/docs/reference/troubleshoot/index.rst b/docs/reference/troubleshoot/index.rst index 11b75ef7..fa0a6fd6 100644 --- a/docs/reference/troubleshoot/index.rst +++ b/docs/reference/troubleshoot/index.rst @@ -13,4 +13,6 @@ FATEdeployfailed FATErunjobfailed userdefinedserviceroute - private_key_loss \ No newline at end of file + private_key_loss + protocol_describe + docker_cpp_copy \ No newline at end of file diff --git a/docs/reference/troubleshoot/protocol_describe.md b/docs/reference/troubleshoot/protocol_describe.md new file mode 100644 index 00000000..f8122207 --- /dev/null +++ b/docs/reference/troubleshoot/protocol_describe.md @@ -0,0 +1,54 @@ +# Protocol 通信协议 + +## 前言 +Protocol 是指 KusciaAPI 以及节点对外网关使用的通信协议,有三种通信协议可供选择:NOTLS/TLS/MTLS(不区分大小写),详情请参考[Kuscia 配置文件](../../deployment/kuscia_config_cn.md#id3)。 + +## 不同 Protocol 对应的 KusicaAPI 请求 +KusicaAPI 支持 NOTLS/TLS/MTLS 三种通信协议,在点对点和中心化部署模式中,KusciaAPI 接口默认使用的是 MTLS 协议,不同协议使用 KusciaAPI 示例如下(此处以查询 domaindatasource 为例): +- NOTLS 协议下,KusciaAPI 接口使用 HTTP 协议,无需指定任何证书文件,示例如下: +```bash +curl -kv -X POST 'http://localhost:8082/api/v1/domaindatasource/query' \ +--header 'Content-Type: application/json' \ +-d '{ + "domain_id": "alice", + "datasource_id": "default-data-source" +}' +``` +- MTLS 协议下,KusciaAPI 接口使用 HTTPS 协议,需要指定 TOKEN、CA 以及 KusciaAPI 证书文件,示例如下: +```bash +curl -X POST 'https://localhost:8082/api/v1/domaindatasource/query' \ +--header "Token: $(cat /home/kuscia/var/certs/token)" \ +--header 'Content-Type: application/json' \ +--cert '/home/kuscia/var/certs/kusciaapi-server.crt' \ +--key '/home/kuscia/var/certs/kusciaapi-server.key' \ +--cacert '/home/kuscia/var/certs/ca.crt' \ +-d '{ + "domain_id": "alice", + "datasource_id": "default-data-source" +}' +``` +- TLS协议下,KusciaAPI 接口使用 HTTPS 协议,需要指定 TOKEN、CA 证书文件,无需指定 KusciaAPI 证书文件,示例如下: +```bash +curl -kv -X POST 'https://localhost:8082/api/v1/domaindatasource/query' \ +--cacert '/home/kuscia/var/certs/ca.crt' \ +--header "Token: $(cat /home/kuscia/var/certs/token)" \ +--header 'Content-Type: application/json' \ +-d '{ + "domain_id": "alice", + "datasource_id": "default-data-source" +}' +``` + + +## 不同 Protocol 对应的节点授权 +Kusica 支持的协议类型请参考[Kuscia 配置文件](../../deployment/kuscia_config_cn.md#id3)。当本节点配置的 Protocol 为 TLS/MTLS 时,对端节点访问本节点使用 HTTPS 协议,当本节点配置的 Protocol 为 NOTLS 时,对端节点访问本节点使用 HTTP 协议,如下图所示。 +![protocol_describe](../../imgs/protocol_describe.png) + +在[点对点模式部署](../../deployment/Docker_deployment_kuscia/deploy_p2p_cn.md)文档中,默认节点配置为 TLS 协议,即节点之间使用 HTTPS 协议进行通信。在[中心化模式部署](../../deployment/Docker_deployment_kuscia/deploy_master_lite_cn.md)文档中,默认 master 节点配置为 TLS 协议,lite 节点配置为 NOTLS 协议,即 lite 节点之间使用 HTTP 协议进行通信,lite 访问 master 使用 HTTPS 协议进行通信,如下图所示。 +![default_protocol](../../imgs/default_protocol.png) + + +## 修改 Protocol 的影响以及解决方法 +用户在修改 [Kusica 配置文件](../../deployment/kuscia_config_cn.md#id4)中的 Protocol 协议之后,会导致 KusciaAPI 使用方式变化以及节点间的路由状态变为不可用,使用以下方法进行解决: +- KusicaApi 调用方式参考上述[不同 Protocol 对应的 KusicaAPI 请求](#protocol-kusicaapi)进行调整。 +- 通过 KusciaAPI 删除节点路由,按照上述[不同 Protocol 对应的节点授权](#id2)再重新创建。 \ No newline at end of file diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index cd81083d..63c0f0ca 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -4,7 +4,8 @@ .. toctree:: :maxdepth: 1 - run_secretflow_with_api_cn + run_sf_job_with_api_cn + run_sf_serving_with_api_cn run_bfia_job_cn run_fate_cn diff --git a/docs/tutorial/run_secretflow_with_api_cn.md b/docs/tutorial/run_sf_job_with_api_cn.md similarity index 100% rename from docs/tutorial/run_secretflow_with_api_cn.md rename to docs/tutorial/run_sf_job_with_api_cn.md diff --git a/docs/tutorial/run_sf_serving_with_api_cn.md b/docs/tutorial/run_sf_serving_with_api_cn.md new file mode 100644 index 00000000..ae3ad2e0 --- /dev/null +++ b/docs/tutorial/run_sf_serving_with_api_cn.md @@ -0,0 +1,472 @@ +# 如何使用 Kuscia API 运行一个 SecretFlow Serving + +本教程将以 Secretflow Serving 内置测试模型为例,介绍如何基于 Kuscia API 运行一个多方的联合预测。 + +## 准备节点 + +准备节点请参考[快速入门](../getting_started/quickstart_cn.md)。 + +本示例在**中心化组网模式**下完成。在点对点组网模式下,证书的配置会有所不同。 + +{#cert-and-token} + +## 确认证书和 Token + +Kuscia API 使用双向 HTTPS,所以需要配置你的客户端库的双向 HTTPS 配置。 + +### 中心化组网模式 + +证书文件在 ${USER}-kuscia-master 节点的`/home/kuscia/var/certs/`目录下: + +| 文件名 | 文件功能 | +| -------------------- | ------------------------------------------------------- | +| kusciaapi-server.key | 服务端私钥文件 | +| kusciaapi-server.crt | 服务端证书文件 | +| ca.crt | CA 证书文件 | +| token | 认证 Token ,在 headers 中添加 Token: { token 文件内容} | + +### 点对点组网模式 + +这里以 alice 节点为例,接口需要的证书文件在 ${USER}-kuscia-autonomy-alice 节点的`/home/kuscia/var/certs/`目录下: + +| 文件名 | 文件功能 | +| -------------------- | ------------------------------------------------------- | +| kusciaapi-server.key | 服务端私钥文件 | +| kusciaapi-server.crt | 服务端证书文件 | +| ca.crt | CA 证书文件 | +| token | 认证 Token ,在 headers 中添加 Token: { token 文件内容} | + +## 准备 SecretFlow Serving 应用镜像模版 + +1. 登陆到 kuscia-master 节点容器中 + +```shell +docker exec -it ${USER}-kuscia-master bash +``` + +如果是点对点组网模式,则需要在 alice 和 bob 节点容器中分别创建上述应用的镜像模版 AppImage。 + +```shell +# 登陆到 alice 节点容器中 +docker exec -it ${USER}-kuscia-autonomy-alice bash + +# 登陆到 bob 节点容器中 +docker exec -it ${USER}-kuscia-autonomy-bob bash +``` + +2. 获取 SecretFlow Serving 应用的镜像模版 AppImage + +从 SecretFlow Serving 官方文档中,获取 AppImage 具体内容,并将其内容保存到 `secretflow-serving-image.yaml` 文件中。 +具体模版内容,可参考 [Serving AppImage](https://www.secretflow.org.cn/zh-CN/docs/serving/0.2.0b0/topics/deployment/serving_on_kuscia)。 + +3. 创建 SecretFlow Serving 应用的镜像模版 AppImage + +```shell +kubectl apply -f secretflow-serving-image.yaml +``` + + +## 提交 SecretFlow Serving + +下面以 alice 和 bob 两方为例,提交一个两方的联合预测。 + +1. 登陆到 kuscia-master 节点容器中 + +```shell +docker exec -it ${USER}-kuscia-master bash +``` + +如果是点对点组网模式,则需要进入任务发起方节点容器,以 alice 节点为例: + +```shell +docker exec -it ${USER}-kuscia-autonomy-alice bash +``` + +2. 创建 SecretFlow Serving + +我们请求[创建 Serving](../reference/apis/serving_cn.md#请求createservingrequest) 接口提交一个两方的联合预测。 + +在 kuscia-master 容器终端中,执行以下命令: + +```shell +curl -X POST 'https://localhost:8082/api/v1/serving/create' \ +--header "Token: $(cat /home/kuscia/var/certs/token)" \ +--header 'Content-Type: application/json' \ +--cert '/home/kuscia/var/certs/kusciaapi-server.crt' \ +--key '/home/kuscia/var/certs/kusciaapi-server.key' \ +--cacert '/home/kuscia/var/certs/ca.crt' \ +-d '{ + "serving_id": "serving-glm-test-1", + "initiator": "alice", + "serving_input_config": "{\"partyConfigs\":{\"alice\":{\"serverConfig\":{\"featureMapping\":{\"v24\":\"x24\",\"v22\":\"x22\",\"v21\":\"x21\",\"v25\":\"x25\",\"v23\":\"x23\"}},\"modelConfig\":{\"modelId\":\"glm-test-1\",\"basePath\":\"/tmp/alice\",\"sourcePath\":\"/root/sf_serving/examples/alice/glm-test.tar.gz\",\"sourceType\":\"ST_FILE\"},\"featureSourceConfig\":{\"mockOpts\":{}},\"channel_desc\":{\"protocol\":\"http\"}},\"bob\":{\"serverConfig\":{\"featureMapping\":{\"v6\":\"x6\",\"v7\":\"x7\",\"v8\":\"x8\",\"v9\":\"x9\",\"v10\":\"x10\"}},\"modelConfig\":{\"modelId\":\"glm-test-1\",\"basePath\":\"/tmp/bob\",\"sourcePath\":\"/root/sf_serving/examples/bob/glm-test.tar.gz\",\"sourceType\":\"ST_FILE\"},\"featureSourceConfig\":{\"mockOpts\":{}},\"channel_desc\":{\"protocol\":\"http\"}}}}", + "parties": [{ + "app_image": "secretflow-serving-image", + "domain_id": "alice" + }, + { + "app_image": "secretflow-serving-image", + "domain_id": "bob" + } + ] +}' +``` + +上述命令中 `serving_input_config` 字段定义了联合预测的相关配置。详细介绍可参考 [SecretFlow Serving 官方文档](https://www.secretflow.org.cn/zh-CN/docs/serving/0.2.0b0/topics/deployment/serving_on_kuscia)。 + +如果提交成功了,你将得到如下返回: + +```json +{"status":{"code":0, "message":"success", "details":[]}} +``` + +恭喜,这说明 alice 和 bob 两方的联合预测已经成功创建。 + +如果遇到 HTTP 错误(即 HTTP Code 不为 200),请参考 [HTTP Error Code 处理](#http-error-code)。 + +此外,在 Kuscia 中,使用 KusciaDeployment 资源对 Serving 类型的常驻服务进行管理。详细介绍可参考 [KusciaDeployment](../reference/concepts/kusciadeployment_cn.md)。 + + +{#query-sf-serving-status} + +## 查询 SecretFlow Serving 状态 + +1. 登陆到 kuscia-master 节点容器中 + +```shell +docker exec -it ${USER}-kuscia-master bash +``` + +如果是点对点组网模式,需要进入节点容器中,以 alice 为例: + +```shell +docker exec -it ${USER}-kuscia-autonomy-alice bash +``` + +2. 查询状态 + +我们可以通过请求[批量查询 Serving 状态](../reference/apis/serving_cn.md#批量查询-serving-状态) 接口来查询 Serving 的状态。 + +请求参数中 `serving_ids` 的值,需要填写前面创建过程中使用的 ID。 + +```shell +curl -k -X POST 'https://localhost:8082/api/v1/serving/status/batchQuery' \ +--header "Token: $(cat /home/kuscia/var/certs/token)" \ +--header 'Content-Type: application/json' \ +--cert '/home/kuscia/var/certs/kusciaapi-server.crt' \ +--key '/home/kuscia/var/certs/kusciaapi-server.key' \ +--cacert '/home/kuscia/var/certs/ca.crt' \ +-d '{ + "serving_ids": ["serving-glm-test-1"] +}' | jq +``` + +如果查询成功了,你将得到如下返回: + +```json +{ + "status": { + "code": 0, + "message": "success", + "details": [] + }, + "data": { + "servings": [ + { + "serving_id": "serving-glm-test-1", + "status": { + "state": "Available", + "reason": "", + "message": "", + "total_parties": 2, + "available_parties": 2, + "create_time": "2024-03-08T08:36:42Z", + "party_statuses": [ + { + "domain_id": "alice", + "role": "", + "state": "Available", + "replicas": 1, + "available_replicas": 1, + "unavailable_replicas": 0, + "updatedReplicas": 1, + "create_time": "2024-03-08T08:36:42Z", + "endpoints": [ + { + "port_name": "communication", + "scope": "Cluster", + "endpoint": "serving-glm-test-1-communication.alice.svc" + }, + { + "port_name": "brpc-builtin", + "scope": "Domain", + "endpoint": "serving-glm-test-1-brpc-builtin.alice.svc:53511" + }, + { + "port_name": "internal", + "scope": "Domain", + "endpoint": "serving-glm-test-1-internal.alice.svc:53510" + }, + { + "port_name": "service", + "scope": "Domain", + "endpoint": "serving-glm-test-1-service.alice.svc:53508" + } + ] + }, + { + "domain_id": "bob", + "role": "", + "state": "Available", + "replicas": 1, + "available_replicas": 1, + "unavailable_replicas": 0, + "updatedReplicas": 1, + "create_time": "2024-03-08T08:36:42Z", + "endpoints": [ + { + "port_name": "internal", + "scope": "Domain", + "endpoint": "serving-glm-test-1-internal.bob.svc:53510" + }, + { + "port_name": "service", + "scope": "Domain", + "endpoint": "serving-glm-test-1-service.bob.svc:53508" + }, + { + "port_name": "communication", + "scope": "Cluster", + "endpoint": "serving-glm-test-1-communication.bob.svc" + }, + { + "port_name": "brpc-builtin", + "scope": "Domain", + "endpoint": "serving-glm-test-1-brpc-builtin.bob.svc:53511" + } + ] + } + ] + } + } + ] + } +} +``` + +其中部分字段含义如下: + +* `data.servings[0].status.state`:表示 Serving 的全局状态,当前状态为 Available。 +* `data.servings[0].status.party_statuses[0].state`: 表示 alice 方 Serving 的状态,当前状态为 Available。 +* `data.servings[0].status.party_statuses[1].state`: 表示 bob 方 Serving 的状态,当前状态为 Available。 +* `data.servings[0].status.party_statuses[0].endpoints`:表示 alice 方应用对外提供的访问地址信息。 +* `data.servings[0].status.party_statuses[1].endpoints`:表示 bob 方应用对外提供的访问地址信息。 + +上述字段详细介绍,请参考[批量查询 Serving 状态](../reference/apis/serving_cn.md#批量查询-serving-状态)。 + + +## 使用 SecretFlow Serving 进行预测 + +下面以 alice 为例,使用内置模型进行预测。在发起预测请求之前,请确保 Serving 的全局状态为 Available。 + +1. 获取 alice 方 Serving 应用访问地址 + +根据前面[查询 SecretFlow Serving 状态](#query-sf-serving-status),获取 alice 方 Serving 应用对外提供的访问地址,这里需要选择 `port_name` 为 `service` 的 endpoint, +当前示例为 `serving-glm-test-1-service.alice.svc:53508`。 + +2. 登陆到 alice 节点容器中 + +```shell +docker exec -it ${USER}-kuscia-lite-alice bash +``` + +如果是点对点组网模式,命令如下: + +```shell +docker exec -it ${USER}-kuscia-autonomy-alice bash +``` + +3. 发起预测请求 + +发起预测请求时,需要配置以下三个 Header: + +* `Host: {服务地址}` +* `Kuscia-Source: {alice 节点的 Domain ID}` +* `Content-Type: application/json` + +```shell +curl --location 'http://127.0.0.1/PredictionService/Predict' \ +--header 'Host: serving-glm-test-1-service.alice.svc:53508' \ +--header 'Kuscia-Source: alice' \ +--header 'Content-Type: application/json' \ +--data '{ + "service_spec": { + "id": "serving-glm-test-1" + }, + "fs_params": { + "alice": { + "query_datas": [ + "a", + "b", + "c" + ], + "query_context": "test" + }, + "bob": { + "query_datas": [ + "a", + "b", + "c" + ], + "query_context": "test" + } + } +}' +``` + +上述命令中请求内容的详细介绍可参考 [SecretFlow Serving 官方文档](https://www.secretflow.org.cn/zh-CN/docs/serving/0.2.0b0/topics/deployment/serving_on_kuscia)。 + +如果预测成功了,你将得到如下返回: + +```json +{"header":{"data":{}},"status":{"code":1,"msg":""},"service_spec":{"id":"serving-glm-test-1"},"results":[{"scores":[{"name":"score","value":0.9553803827339434}]},{"scores":[{"name":"score","value":0.9553803827339434}]},{"scores":[{"name":"score","value":0.9553803827339434}]}]} +``` + +## 更新 SecretFlow Serving + +1. 登陆到 kuscia-master 节点容器中 + +```shell +docker exec -it ${USER}-kuscia-master bash +``` + +如果是点对点组网模式,需要进入任务发起方节点容器中,以 alice 为例: + +```shell +docker exec -it ${USER}-kuscia-autonomy-alice bash +``` + +2. 更新 Serving + +当想要更新 SecretFlow Serving 的镜像或输入参数时,我们可以通过请求[更新 Serving](../reference/apis/serving_cn.md#更新-serving) 接口来更新指定的 Serving。 + +请求参数中 `serving_id` 的值,需要填写前面创建过程中使用的 ID。 + +```shell +curl -k -X POST 'https://localhost:8082/api/v1/serving/update' \ +--header "Token: $(cat /home/kuscia/var/certs/token)" \ +--header 'Content-Type: application/json' \ +--cert '/home/kuscia/var/certs/kusciaapi-server.crt' \ +--key '/home/kuscia/var/certs/kusciaapi-server.key' \ +--cacert '/home/kuscia/var/certs/ca.crt' \ +-d '{ + "serving_id": "serving-glm-test-1", + "serving_input_config": "{serving 的输入配置}", + "parties": [{ + "app_image": "{新的 AppImage 名称}", + "domain_id": "alice" + }, + { + "app_image": "{新的 AppImage 名称}", + "domain_id": "bob" + } + ] +}' +``` + + +## 删除 SecretFlow Serving + +1. 登陆到 kuscia-master 节点容器中 + +```shell +docker exec -it ${USER}-kuscia-master bash +``` + +如果是点对点组网模式,需要进入任务发起方节点容器中,以 alice 为例: + +```shell +docker exec -it ${USER}-kuscia-autonomy-alice bash +``` + +2. 删除 Serving + +我们可以通过请求[删除 Serving](../reference/apis/serving_cn.md#删除-serving) 接口来删除指定的 Serving。 + +请求参数中 `serving_id` 的值,需要填写前面创建过程中使用的 ID。 + +```shell +curl -k -X POST 'https://localhost:8082/api/v1/serving/delete' \ +--header "Token: $(cat /home/kuscia/var/certs/token)" \ +--header 'Content-Type: application/json' \ +--cert '/home/kuscia/var/certs/kusciaapi-server.crt' \ +--key '/home/kuscia/var/certs/kusciaapi-server.key' \ +--cacert '/home/kuscia/var/certs/ca.crt' \ +-d '{ + "serving_id": "serving-glm-test-1" +}' +``` + +如果删除成功了,你将得到如下返回: + +```json +{"status":{"code":0, "message":"success", "details":[]}} +``` + +## 参考 + +### 如何查看 Serving 应用容器日志 + +在 Kuscia 中,可以登陆到节点容器内查看 Serving 应用容器的日志。具体方法如下。 + +1. 登陆到节点容器中,以 alice 为例: + +```shell +docker exec -it ${USER}-kuscia-lite-alice bash +``` + +如果是点对点组网模式,需要登陆到对应 autonomy 容器中。 + +```shell +docker exec -it ${USER}-kuscia-autonomy-alice bash +``` + +2. 查看日志 + +在目录 `/home/kuscia/var/stdout/pods` 下可以看到对应 Serving 应用容器的目录。后续进入到相应目录下,即可查看应用的日志。 + +```yaml +# 查看当前应用容器的目录 +ls /home/kuscia/var/stdout/pods + +# 查看应用容器的日志,示例如下: +cat /home/kuscia/var/stdout/pods/alice_serving-glm-test-1-75d449f848-4gth9_2bd2f45f-51d8-4a18-afa2-ed7105bd47b5/secretflow/0.log +``` + + +{#http-client-error} + +### HTTP 客户端错误处理 + +#### curl: (56) + +curl: (56) OpenSSL SSL_read: error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate, errno 0 + +未配置 SSL 证书和私钥。请[确认证书和 Token](#cert-and-token). + +#### curl: (58) + +curl: (58) unable to set XXX file + +SSL 私钥、 SSL 证书或 CA 证书文件路径错误。请[确认证书和 Token](#cert-and-token). + +{#http-error-code} + +### HTTP Error Code 处理 + +#### 401 Unauthorized + +身份认证失败。请检查是否在 Headers 中配置了正确的 Token 。 Token 内容详见[确认证书和 Token](#cert-and-token). + +#### 404 Page Not Found + +接口 path 错误。请检查请求的 path 是否和文档中的一致。必要时可以提 issue 询问。 \ No newline at end of file diff --git a/etc/conf/domainroute/listeners/external_listeners.json.tmpl b/etc/conf/domainroute/listeners/external_listeners.json.tmpl index 9dfdf243..304fd6cb 100644 --- a/etc/conf/domainroute/listeners/external_listeners.json.tmpl +++ b/etc/conf/domainroute/listeners/external_listeners.json.tmpl @@ -93,6 +93,13 @@ "self_namespace": "{{.Namespace}}" } }, + { + "name": "envoy.filters.http.kuscia_receiver", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.kuscia_receiver.v3.Receiver", + "self_namespace": "{{.Namespace}}" + } + }, { "name": "envoy.filters.http.router", "typed_config": { diff --git a/etc/conf/domainroute/listeners/internal_listeners.json.tmpl b/etc/conf/domainroute/listeners/internal_listeners.json.tmpl index 7d3f3c6b..1533a89f 100644 --- a/etc/conf/domainroute/listeners/internal_listeners.json.tmpl +++ b/etc/conf/domainroute/listeners/internal_listeners.json.tmpl @@ -71,6 +71,19 @@ "self_namespace": "{{.Namespace}}" } }, + { + "name": "envoy.filters.http.kuscia_receiver", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.kuscia_receiver.v3.Receiver", + "self_namespace": "{{.Namespace}}" + } + }, + { + "name": "envoy.filters.http.kuscia_poller", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.kuscia_poller.v3.Poller" + } + }, { "name": "envoy.filters.http.router", "typed_config": { diff --git a/go.mod b/go.mod index 9cbfa4f8..16ee5830 100644 --- a/go.mod +++ b/go.mod @@ -25,14 +25,13 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/moby/sys/mount v0.3.3 github.com/moby/sys/mountinfo v0.6.2 - github.com/natefinch/lumberjack v2.0.0+incompatible github.com/opencontainers/image-spec v1.0.2 github.com/opencontainers/selinux v1.11.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/rosedblabs/rosedb/v2 v2.3.2 - github.com/secretflow/kuscia-envoy v0.0.0-20230705094915-8e153baebabc + github.com/secretflow/kuscia-envoy v0.0.0-20240402083426-b0884d002f48 github.com/shirou/gopsutil/v3 v3.22.6 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 @@ -173,7 +172,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/mrunalp/fileutils v0.5.0 // indirect + github.com/mrunalp/fileutils v0.5.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -254,7 +253,7 @@ require ( ) replace ( - github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.5 + github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.12 //google.golang.org/protobuf => google.golang.org/protobuf v1.28.1 k8s.io/api => k8s.io/api v0.26.11 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.26.11 diff --git a/go.sum b/go.sum index cdea2665..f2dc5788 100644 --- a/go.sum +++ b/go.sum @@ -138,7 +138,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -470,16 +469,14 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q= +github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= -github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -489,8 +486,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= @@ -556,8 +553,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/secretflow/kuscia-envoy v0.0.0-20230705094915-8e153baebabc h1:gPrcTzs/0dTJJ7gHMicPbXXhtoJ5EILDlGx8klcsQzw= -github.com/secretflow/kuscia-envoy v0.0.0-20230705094915-8e153baebabc/go.mod h1:022TT/HpFoj5iYB6Q/XI0NBnqgM5rekwUo6NAY/K8oA= +github.com/secretflow/kuscia-envoy v0.0.0-20240402083426-b0884d002f48 h1:4W2Nx2lalcZebbyytroXk4LltprKzZSBurqD//SMcZ4= +github.com/secretflow/kuscia-envoy v0.0.0-20240402083426-b0884d002f48/go.mod h1:022TT/HpFoj5iYB6Q/XI0NBnqgM5rekwUo6NAY/K8oA= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/shirou/gopsutil/v3 v3.22.6 h1:FnHOFOh+cYAM0C30P+zysPISzlknLC5Z1G4EAElznfQ= github.com/shirou/gopsutil/v3 v3.22.6/go.mod h1:EdIubSnZhbAvBS1yJ7Xi+AShB/hxwLHOMz4MCYz7yMs= @@ -630,6 +627,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= @@ -694,6 +692,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -730,6 +729,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -762,12 +763,15 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -791,6 +795,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -847,19 +853,22 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -870,6 +879,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -925,6 +936,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hack/errorcode/i18n/errorcode.zh-CN.toml b/hack/errorcode/i18n/errorcode.zh-CN.toml index 44fe2e25..86c3e406 100644 --- a/hack/errorcode/i18n/errorcode.zh-CN.toml +++ b/hack/errorcode/i18n/errorcode.zh-CN.toml @@ -22,6 +22,12 @@ error_code_11205_description = "停止任务失败" error_code_11205_solution = "停止任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因" error_code_11206_description = "审批任务失败" error_code_11206_solution = "审批任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因" +error_code_11207_description = "暂停任务失败" +error_code_11207_solution = "暂停任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因" +error_code_11208_description = "重跑任务失败" +error_code_11208_solution = "重跑任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因" +error_code_11209_description = "取消任务失败" +error_code_11209_solution = "取消任务失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因" error_code_11300_description = "创建节点失败" error_code_11300_solution = "创建节点失败:接口 API 请求异常,具体原因可通过报错信息与日志确认具体原因" error_code_11301_description = "查询节点失败" diff --git a/hack/k8s/autonomy/configmap.yaml b/hack/k8s/autonomy/configmap.yaml index 69ae5272..b3cfcbee 100644 --- a/hack/k8s/autonomy/configmap.yaml +++ b/hack/k8s/autonomy/configmap.yaml @@ -25,7 +25,7 @@ data: runk: # 任务调度到指定的机构 K8s namespace 下 namespace: autonomy-alice - # K8s 集群的 pod dns 配置, 用于解析节点的应用域名, dns 的地址为 pod service 地址, 此处以 "1.1.1.1" 为例 + # K8s 集群的 pod dns 配置, 用于解析节点的应用域名, runk 拉起的 pod 所使用 dns 地址,应配置为 kuscia-autonomy service 的 clusterIP, 此处以 "1.1.1.1" 为例 dnsServers: # - kuscia-dns-lb-server - 1.1.1.1 diff --git a/hack/k8s/lite/configmap.yaml b/hack/k8s/lite/configmap.yaml index 36dcebef..ad23c7a2 100644 --- a/hack/k8s/lite/configmap.yaml +++ b/hack/k8s/lite/configmap.yaml @@ -33,7 +33,7 @@ data: runk: # 任务调度到指定的机构 k8s namespace 下 namespace: lite-alice - # 机构 k8s 集群的 pod dns 配置, 用于解析节点的应用域名, dns 的地址为 pod service 地址, 此处以 "1.1.1.1" 为例 + # 机构 k8s 集群的 pod dns 配置, 用于解析节点的应用域名, runk 拉起 pod 所使用的 dns 地址,应配置为 kuscia-lite service 的 clusterIP, 此处以 "1.1.1.1" 为例 dnsServers: # - kuscia-dns-lb-server - 1.1.1.1 diff --git a/pkg/agent/commands/root.go b/pkg/agent/commands/root.go index efbd2921..b90110df 100644 --- a/pkg/agent/commands/root.go +++ b/pkg/agent/commands/root.go @@ -35,7 +35,6 @@ import ( "github.com/secretflow/kuscia/pkg/agent/provider" "github.com/secretflow/kuscia/pkg/agent/resource" "github.com/secretflow/kuscia/pkg/agent/source" - "github.com/secretflow/kuscia/pkg/utils/kubeconfig" "github.com/secretflow/kuscia/pkg/utils/nlog" ) @@ -79,16 +78,16 @@ func RunRootCommand(ctx context.Context, agentConfig *config.AgentConfig, kubeCl break // context cancelled, agent is shutting down } - // Create another shared informer factory for Kubernetes secrets and configmaps (not subject to any selectors). - scmInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions( + // Create shared informer factory for Kubernetes pods, secrets and configmaps (not subject to any selectors). + resourceInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions( kubeClient, 0, kubeinformers.WithNamespace(agentConfig.Namespace)) rm := resource.NewResourceManager( kubeClient, agentConfig.Namespace, - scmInformerFactory.Core().V1().Pods().Lister().Pods(agentConfig.Namespace), - scmInformerFactory.Core().V1().Secrets().Lister().Secrets(agentConfig.Namespace), - scmInformerFactory.Core().V1().ConfigMaps().Lister().ConfigMaps(agentConfig.Namespace)) + resourceInformerFactory.Core().V1().Pods().Lister().Pods(agentConfig.Namespace), + resourceInformerFactory.Core().V1().Secrets().Lister().Secrets(agentConfig.Namespace), + resourceInformerFactory.Core().V1().ConfigMaps().Lister().ConfigMaps(agentConfig.Namespace)) // init provider factory providerFactory, err := provider.NewFactory(agentConfig) @@ -174,7 +173,7 @@ func RunRootCommand(ctx context.Context, agentConfig *config.AgentConfig, kubeCl podsController.RegisterProvider(podProvider) chStopKubeClient := make(chan struct{}) - go scmInformerFactory.Start(chStopKubeClient) + go resourceInformerFactory.Start(chStopKubeClient) chSourceManager := make(chan struct{}) if err := sourceManager.Run(chSourceManager); err != nil { @@ -200,15 +199,3 @@ func RunRootCommand(ctx context.Context, agentConfig *config.AgentConfig, kubeCl nlog.Info("Agent exited") return nlog.Sync() } - -func newKubeClient(cfg *config.ApiserverSourceCfg) (*kubernetes.Clientset, error) { - clientConfig, err := kubeconfig.BuildClientConfigFromKubeconfig(cfg.KubeconfigFile, cfg.Endpoint) - if err != nil { - return nil, fmt.Errorf("error building client config, detail-> %v", err) - } - - clientConfig.QPS = cfg.QPS - clientConfig.Burst = cfg.Burst - clientConfig.Timeout = cfg.Timeout - return kubernetes.NewForConfig(clientConfig) -} diff --git a/pkg/agent/local/runtime/process/process.go b/pkg/agent/local/runtime/process/process.go index ef9695bc..d33a8ce8 100644 --- a/pkg/agent/local/runtime/process/process.go +++ b/pkg/agent/local/runtime/process/process.go @@ -394,7 +394,8 @@ func (r *Runtime) ImageStatus(ctx context.Context, image *runtimeapi.ImageSpec, resp := &runtimeapi.ImageStatusResponse{ Image: &runtimeapi.Image{ - Id: imageManifest.ID, + Id: image.Image, + RepoTags: []string{imageManifest.ID}, }, } return resp, nil diff --git a/pkg/agent/local/runtime/process/process_test.go b/pkg/agent/local/runtime/process/process_test.go index 30d6ef8d..dff110a1 100644 --- a/pkg/agent/local/runtime/process/process_test.go +++ b/pkg/agent/local/runtime/process/process_test.go @@ -64,7 +64,7 @@ func Test_RuntimeSandboxAndContainers(t *testing.T) { imageStatus, err := runtime.ImageStatus(ctx, &runtimeapi.ImageSpec{Image: "test:01"}, false) assert.NoError(t, err) - assert.Equal(t, imageStatus.Image.Id, "abc") + assert.Equal(t, imageStatus.Image.Id, "test:01") sandboxID, err := runtime.RunPodSandbox(ctx, &runtimeapi.PodSandboxConfig{ Metadata: &runtimeapi.PodSandboxMetadata{ diff --git a/pkg/agent/local/store/ieport.go b/pkg/agent/local/store/ieport.go index faf05bf0..502a8c91 100644 --- a/pkg/agent/local/store/ieport.go +++ b/pkg/agent/local/store/ieport.go @@ -63,6 +63,8 @@ func (s *store) RegisterImage(image, manifestFile string) error { } if manifest.ID == "" { manifest.ID = image + } else { + manifest.ID = kii.FormatImageID(manifest.ID) } imageName, err := kii.NewImageName(image) @@ -215,7 +217,7 @@ func fromDockerManifest(tempDir string, dockerManifest *DockerPackageManifestIte // image_id.json => image_id if strings.HasSuffix(dockerManifest.Config, ".json") { - manifest.ID = dockerManifest.Config[:len(dockerManifest.Config)-len(".json")] + manifest.ID = kii.FormatImageID(dockerManifest.Config[:len(dockerManifest.Config)-len(".json")]) } // fill meta info diff --git a/pkg/agent/local/store/ieport_test.go b/pkg/agent/local/store/ieport_test.go new file mode 100644 index 00000000..409ac6d2 --- /dev/null +++ b/pkg/agent/local/store/ieport_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package store + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/secretflow/kuscia/pkg/agent/local/store/kii" +) + +func TestStore_RegisterImage(t *testing.T) { + rootDir := t.TempDir() + s, err := NewStore(filepath.Join(rootDir, "store")) + assert.NoError(t, err) + + testManifest := kii.Manifest{ + ID: "12345", + } + testManifestContent, err := json.Marshal(testManifest) + assert.NoError(t, err) + testManifestFile := filepath.Join(rootDir, "manifest.json") + assert.NoError(t, os.WriteFile(testManifestFile, testManifestContent, 0644)) + + tests := []struct { + image string + manifest string + imageID string + }{ + { + image: "secretflow/secretflow:0.1", + manifest: "", + imageID: "secretflow/secretflow:0.1", + }, + { + image: "secretflow/secretflow:0.2", + manifest: testManifestFile, + imageID: "sha256:12345", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("Test %d", i), func(t *testing.T) { + assert.NoError(t, s.RegisterImage(tt.image, tt.manifest)) + image, err := kii.NewImageName(tt.image) + assert.NoError(t, err) + assert.True(t, s.CheckImageExist(image)) + manifest, err := s.GetImageManifest(image) + assert.NoError(t, err) + assert.Equal(t, tt.imageID, manifest.ID) + }) + } +} + +func TestStore_LoadImage(t *testing.T) { + _, filename, _, ok := runtime.Caller(0) + assert.True(t, ok) + dir := filepath.Dir(filename) + for i := 0; i < 4; i++ { + dir = filepath.Dir(dir) + } + pauseTarFile := filepath.Join(dir, "build/pause/pause.tar") + + file, err := os.Open(pauseTarFile) + assert.NoError(t, err) + defer file.Close() + rootDir := t.TempDir() + + s, err := NewStore(rootDir) + assert.NoError(t, err) + + assert.NoError(t, s.LoadImage(file)) + image, err := kii.NewImageName("secretflow/pause:3.6") + assert.NoError(t, err) + assert.True(t, s.CheckImageExist(image)) + + image, err = kii.NewImageName("docker.io/secretflow/pause:3.6") + assert.NoError(t, err) + assert.True(t, s.CheckImageExist(image)) +} diff --git a/pkg/agent/local/store/kii/kii.go b/pkg/agent/local/store/kii/kii.go index 2e9c20ed..f74725e9 100644 --- a/pkg/agent/local/store/kii/kii.go +++ b/pkg/agent/local/store/kii/kii.go @@ -17,6 +17,7 @@ package kii import ( "fmt" "path/filepath" + "strings" "time" oci "github.com/opencontainers/image-spec/specs-go/v1" @@ -98,3 +99,11 @@ type Manifest struct { // Type defines the type of the image. Default is Standard. Type ImageType `json:"type"` } + +func FormatImageID(id string) string { + if strings.HasPrefix(id, "sha256:") { + return id + } + + return fmt.Sprintf("sha256:%s", id) +} diff --git a/pkg/agent/local/store/kii/kii_test.go b/pkg/agent/local/store/kii/kii_test.go index 65358c9a..5fae8d0a 100644 --- a/pkg/agent/local/store/kii/kii_test.go +++ b/pkg/agent/local/store/kii/kii_test.go @@ -68,3 +68,25 @@ func TestParseImageNameFromPath(t *testing.T) { }) } } + +func TestFormatImageID(t *testing.T) { + tests := []struct { + srcID string + dstID string + }{ + { + "abc", + "sha256:abc", + }, + { + "sha256:abc", + "sha256:abc", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("Test %d", i), func(t *testing.T) { + assert.Equal(t, tt.dstID, FormatImageID(tt.srcID)) + }) + } +} diff --git a/pkg/agent/middleware/plugins/hook/certissuance/cert_issuance.go b/pkg/agent/middleware/plugins/hook/certissuance/cert_issuance.go index 66f41856..203db840 100644 --- a/pkg/agent/middleware/plugins/hook/certissuance/cert_issuance.go +++ b/pkg/agent/middleware/plugins/hook/certissuance/cert_issuance.go @@ -169,8 +169,8 @@ func (ci *certIssuance) handleSyncPodContext(ctx *hook.K8sProviderSyncPodContext certsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-certs", ctx.BkPod.Name), - Namespace: ctx.BkPod.Namespace, + Name: "certs", + Namespace: ctx.Pod.Namespace, }, StringData: map[string]string{}, } diff --git a/pkg/agent/middleware/plugins/hook/imagesecurity/image_security.go b/pkg/agent/middleware/plugins/hook/imagesecurity/image_security.go index 4e402eb6..0c7bb9b7 100644 --- a/pkg/agent/middleware/plugins/hook/imagesecurity/image_security.go +++ b/pkg/agent/middleware/plugins/hook/imagesecurity/image_security.go @@ -99,11 +99,17 @@ func (cr *imageSecurity) ExecHook(ctx hook.Context) (*hook.Result, error) { if err != nil { return nil, err } + expectedImageID := imageStatus.Image.Id - if imageStatus.Image.Id != imageID { + // adapt process runtime + if !strings.HasPrefix(imageStatus.Image.Id, "sha256:") && len(imageStatus.Image.RepoTags) > 0 { + expectedImageID = imageStatus.Image.RepoTags[0] + } + + if expectedImageID != imageID { return &hook.Result{ Terminated: true, - Msg: fmt.Sprintf("image %q ID mismatch, expected %q, actual %q", c.Image, imageID, imageStatus.Image.Id), + Msg: fmt.Sprintf("image %q ID mismatch, expected %q, actual %q", c.Image, expectedImageID, imageID), }, nil } } diff --git a/pkg/agent/provider/pod/k8s_provider.go b/pkg/agent/provider/pod/k8s_provider.go index 2ba6a2b3..9874b720 100644 --- a/pkg/agent/provider/pod/k8s_provider.go +++ b/pkg/agent/provider/pod/k8s_provider.go @@ -16,6 +16,7 @@ package pod import ( "context" + "crypto/sha256" "errors" "fmt" "os" @@ -29,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" kubeinformers "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" @@ -48,6 +50,15 @@ import ( "github.com/secretflow/kuscia/pkg/utils/nlog" ) +const ( + labelOwnerPodName = "kuscia.secretflow/owner-pod-name" +) + +var ( + resourceMinLifeCycle = 30 * time.Second + resourceNameLimit = 253 +) + type K8sProviderDependence struct { NodeName string Namespace string @@ -216,6 +227,11 @@ func (kp *K8sProvider) SyncPod(ctx context.Context, pod *v1.Pod, podStatus *pkgc newPod := pod.DeepCopy() newPod.ObjectMeta = *kp.normalizeMeta(pod.UID, &pod.ObjectMeta) + if _, err := kp.podLister.Get(pod.Name); err == nil { + nlog.Infof("Pod %q is already exist, skipping", pod.Name) + return nil + } + newPod.Spec.DNSPolicy = v1.DNSPolicy(kp.podDNSPolicy) newPod.Spec.DNSConfig = kp.podDNSConfig newPod.Spec.NodeName = "" @@ -268,23 +284,36 @@ func (kp *K8sProvider) SyncPod(ctx context.Context, pod *v1.Pod, podStatus *pkgc secrets[secret.Name] = secret } - for i, cm := range configMaps { + for _, cm := range configMaps { newCM := cm.DeepCopy() newCM.ObjectMeta = *kp.normalizeMeta(pod.UID, &cm.ObjectMeta) - masterCM, err := createSubResource[*v1.ConfigMap](ctx, newCM, kp.configMapLister, kp.bkClient.CoreV1().ConfigMaps(kp.bkNamespace)) + normalizeSubResourceMeta(&newCM.ObjectMeta, newPod.Name) + + _, err := createSubResource[*v1.ConfigMap](ctx, newCM, kp.configMapLister, kp.bkClient.CoreV1().ConfigMaps(kp.bkNamespace)) if err != nil { return fmt.Errorf("failed to create configmap %v, detail-> %v", newCM.Name, err) } - configMaps[i] = masterCM + for i, v := range newPod.Spec.Volumes { + if v.ConfigMap != nil && v.ConfigMap.Name == cm.Name { + newPod.Spec.Volumes[i].ConfigMap.Name = newCM.Name + } + } } - for i, secret := range secrets { + + for _, secret := range secrets { newSecret := secret.DeepCopy() newSecret.ObjectMeta = *kp.normalizeMeta(pod.UID, &secret.ObjectMeta) - masterSecret, err := createSubResource[*v1.Secret](ctx, newSecret, kp.secretLister, kp.bkClient.CoreV1().Secrets(kp.bkNamespace)) + normalizeSubResourceMeta(&newSecret.ObjectMeta, newPod.Name) + + _, err := createSubResource[*v1.Secret](ctx, newSecret, kp.secretLister, kp.bkClient.CoreV1().Secrets(kp.bkNamespace)) if err != nil { return fmt.Errorf("failed to create secret %v, detail-> %v", newSecret.Name, err) } - secrets[i] = masterSecret + for i, v := range newPod.Spec.Volumes { + if v.Secret != nil && v.Secret.SecretName == secret.Name { + newPod.Spec.Volumes[i].Secret.SecretName = newSecret.Name + } + } } for k, v := range kp.labelsToAdd { @@ -296,28 +325,10 @@ func (kp *K8sProvider) SyncPod(ctx context.Context, pod *v1.Pod, podStatus *pkgc kp.backendPlugin.PreSyncPod(newPod) - bkMasterPod, err := kp.applyPod(ctx, newPod) + _, err := kp.applyPod(ctx, newPod) if err != nil { return fmt.Errorf("failed to apply pod %v, detail-> %v", format.Pod(newPod), err) } - owner := &metav1.OwnerReference{ - APIVersion: "v1", - Kind: "Pod", - UID: bkMasterPod.UID, - Name: bkMasterPod.Name, - } - for _, cm := range configMaps { - newCM := cm.DeepCopy() - if err = setSubResourceOwnerReference[*v1.ConfigMap](ctx, newCM, owner, kp.bkClient.CoreV1().ConfigMaps(kp.bkNamespace)); err != nil { - return fmt.Errorf("failed to update configmap %v owner reference, detail-> %v", newCM.Name, err) - } - } - for _, secret := range secrets { - newSecret := secret.DeepCopy() - if err = setSubResourceOwnerReference[*v1.Secret](ctx, newSecret, owner, kp.bkClient.CoreV1().Secrets(kp.bkNamespace)); err != nil { - return fmt.Errorf("failed to update secret %v owner reference, detail-> %v", newSecret.Name, err) - } - } return nil } @@ -330,7 +341,7 @@ func (kp *K8sProvider) normalizeMeta(sourcePodUID types.UID, meta *metav1.Object for k, v := range meta.Labels { newMeta.Labels[k] = v } - newMeta.Labels[common.LabelNodeNamespace] = meta.Namespace + newMeta.Labels[common.LabelNodeNamespace] = kp.namespace newMeta.Labels[common.LabelNodeName] = kp.nodeName newMeta.Labels[common.LabelPodUID] = string(sourcePodUID) for k, v := range meta.Annotations { @@ -342,11 +353,29 @@ func (kp *K8sProvider) normalizeMeta(sourcePodUID types.UID, meta *metav1.Object return newMeta } +func normalizeSubResourceMeta(meta *metav1.ObjectMeta, ownerPodName string) { + name := fmt.Sprintf("%s-%s", ownerPodName, meta.Name) + if len(name) > resourceNameLimit { + hash := sha256.Sum256([]byte(name)) + name = fmt.Sprintf("%x", hash) + + if len(name) > resourceNameLimit { + name = name[:resourceNameLimit] + } + } + + meta.Name = name + if meta.Labels == nil { + meta.Labels = map[string]string{} + } + meta.Labels[labelOwnerPodName] = ownerPodName +} + func (kp *K8sProvider) mountResolveConfig(bkPod *v1.Pod) *v1.ConfigMap { cm := &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kp.bkNamespace, - Name: fmt.Sprintf("%s-resolv-config", bkPod.Name), + Namespace: kp.namespace, + Name: "resolv-config", }, Data: map[string]string{ "resolv.conf": kp.resolveConfigData, @@ -547,18 +576,6 @@ func (kp *K8sProvider) GetPods(ctx context.Context, all bool) ([]*pkgcontainer.P return runningPods, nil } -func (kp *K8sProvider) scanConfigMapInVolumes(pod *v1.Pod) []string { - var configmaps []string - for i := range pod.Spec.Volumes { - v := &pod.Spec.Volumes[i] - if v.ConfigMap != nil { - configmaps = append(configmaps, v.ConfigMap.Name) - } - } - - return configmaps -} - func (kp *K8sProvider) handleBackendPodChanged(obj interface{}) { var ( object metav1.Object @@ -599,19 +616,19 @@ type resourceLister[T metav1.Object] interface { type resourceStub[T metav1.Object] interface { Create(ctx context.Context, object T, opts metav1.CreateOptions) (T, error) - Update(ctx context.Context, object T, opts metav1.UpdateOptions) (T, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error } func createSubResource[T metav1.Object](ctx context.Context, object T, lister resourceLister[T], stub resourceStub[T]) (T, error) { - newObject, err := lister.Get(object.GetName()) + oldObject, err := lister.Get(object.GetName()) if err == nil { nlog.Infof("Resource(%T) %v already exists, skip applying", object, object.GetName()) - return newObject, nil + return oldObject, nil } else if !k8serrors.IsNotFound(err) { - return newObject, fmt.Errorf("failed to get resource(%T) %v, detail-> %v", object, object.GetName(), err) + return oldObject, fmt.Errorf("failed to get resource(%T) %v, detail-> %v", object, object.GetName(), err) } - newObject, err = stub.Create(ctx, object, metav1.CreateOptions{}) + newObject, err := stub.Create(ctx, object, metav1.CreateOptions{}) if err != nil { return newObject, fmt.Errorf("failed to create resource(%T) %v, detail-> %v", object, object.GetName(), err) } @@ -621,27 +638,33 @@ func createSubResource[T metav1.Object](ctx context.Context, object T, lister re return newObject, nil } -func setSubResourceOwnerReference[T metav1.Object](ctx context.Context, object T, owner *metav1.OwnerReference, stub resourceStub[T]) error { - if object.GetOwnerReferences() != nil { - for _, curOwner := range object.GetOwnerReferences() { - if curOwner.UID == owner.UID { - nlog.Infof("Resource(%T) %v already has owner reference %v, skip updating", object, object.GetName(), owner) - return nil - } - } +func cleanupSubResource[T metav1.Object](ctx context.Context, object T, podGetter corev1client.PodInterface, stub resourceStub[T]) error { + if time.Since(object.GetCreationTimestamp().Time) < resourceMinLifeCycle { + return nil } - object.SetOwnerReferences([]metav1.OwnerReference{*owner}) - return updateSubResource[T](ctx, object, stub) -} + objLabels := object.GetLabels() + if objLabels == nil { + return nil + } -func updateSubResource[T metav1.Object](ctx context.Context, object T, stub resourceStub[T]) error { - _, err := stub.Update(ctx, object, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("failed to update resource(%T) %v, detail-> %v", object, object.GetName(), err) + ownerPodName, ok := objLabels[labelOwnerPodName] + if !ok { + return nil + } + + _, err := podGetter.Get(ctx, ownerPodName, metav1.GetOptions{}) + if err == nil { + return nil + } else if !k8serrors.IsNotFound(err) { + return fmt.Errorf("failed to get owner pod %s, detail-> %v", ownerPodName, err) + } + + if err := stub.Delete(ctx, object.GetName(), metav1.DeleteOptions{}); err != nil { + return err } - nlog.Infof("Update resource(%T) %v successfully", object, object.GetName()) + nlog.Infof("Cleanup resource(%T) %v successfully", object, object.GetName()) return nil } diff --git a/pkg/agent/provider/pod/k8s_provider_leader.go b/pkg/agent/provider/pod/k8s_provider_leader.go index baa1c557..0adbf1b5 100644 --- a/pkg/agent/provider/pod/k8s_provider_leader.go +++ b/pkg/agent/provider/pod/k8s_provider_leader.go @@ -153,6 +153,32 @@ func (kp *K8sProvider) deleteZombiePod(ctx context.Context, bkPod *v1.Pod) error return nil } +func (kp *K8sProvider) cleanupSubResources(ctx context.Context) error { + configMaps, err := kp.configMapLister.List(labels.SelectorFromSet(labels.Set{common.LabelNodeNamespace: kp.namespace})) + if err != nil { + return fmt.Errorf("failed to list configmap, detail-> %v", err) + } + + secrets, err := kp.secretLister.List(labels.SelectorFromSet(labels.Set{common.LabelNodeNamespace: kp.namespace})) + if err != nil { + return fmt.Errorf("failed to list secret, detail-> %v", err) + } + + for _, configMap := range configMaps { + if err := cleanupSubResource[*v1.ConfigMap](ctx, configMap, kp.bkClient.CoreV1().Pods(kp.bkNamespace), kp.bkClient.CoreV1().ConfigMaps(kp.bkNamespace)); err != nil { + nlog.Warnf("Failed to cleanup configmap %q: %v", configMap.Name, err) + } + } + + for _, secret := range secrets { + if err := cleanupSubResource[*v1.Secret](ctx, secret, kp.bkClient.CoreV1().Pods(kp.bkNamespace), kp.bkClient.CoreV1().Secrets(kp.bkNamespace)); err != nil { + nlog.Warnf("Failed to cleanup secret %q: %v", secret.Name, err) + } + } + + return nil +} + // onNewLeader is executed when leader is changed. func (kp *K8sProvider) onNewLeader(identity string) { nlog.Infof("New leader has been elected: %s", identity) @@ -173,6 +199,10 @@ func (kp *K8sProvider) onStartedLeading(ctx context.Context) { if err := kp.cleanupZombieResources(context.Background()); err != nil { nlog.Errorf("Failed to cleanup zombie pods: %v", err) } + + if err := kp.cleanupSubResources(context.Background()); err != nil { + nlog.Errorf("Failed to cleanup sub resource: %v", err) + } } } } diff --git a/pkg/agent/provider/pod/k8s_provider_leader_test.go b/pkg/agent/provider/pod/k8s_provider_leader_test.go index 416c14c8..7c233559 100644 --- a/pkg/agent/provider/pod/k8s_provider_leader_test.go +++ b/pkg/agent/provider/pod/k8s_provider_leader_test.go @@ -34,6 +34,8 @@ import ( ) func TestK8sProvider_cleanupZombieResources(t *testing.T) { + resourceMinLifeCycle = 0 + node1 := &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", @@ -58,6 +60,15 @@ func TestK8sProvider_cleanupZombieResources(t *testing.T) { }, }, } + testSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "default", + }, + Data: map[string][]byte{ + "key": []byte("123456"), + }, + } rootDir := t.TempDir() resolveConfig := filepath.Join(rootDir, "resolve.conf") @@ -69,32 +80,32 @@ func TestK8sProvider_cleanupZombieResources(t *testing.T) { ResolverConfig: resolveConfig, }, } - rm := resourcetest.FakeResourceManager("test-namespace", node1, node2) + rm := resourcetest.FakeResourceManager("default", node1, node2, testSecret) kp := createTestK8sProvider(t, cfg, rm) kp.namespace = "default" kp.nodeName = "node1" - pod1 := createTestPod("001", "default", "pod1") + pod1 := createTestPod("001", "default", "pod1", "test-secret") assert.NoError(t, kp.SyncPod(context.Background(), pod1, nil, nil)) kp.nodeName = "node2" - pod2 := createTestPod("002", "default", "pod2") + pod2 := createTestPod("002", "default", "pod2", "test-secret") assert.NoError(t, kp.SyncPod(context.Background(), pod2, nil, nil)) kp.nodeName = "node3" - pod3 := createTestPod("003", "default", "pod3") + pod3 := createTestPod("003", "default", "pod3", "test-secret") assert.NoError(t, kp.SyncPod(context.Background(), pod3, nil, nil)) kp.nodeName = "node4" - pod4 := createTestPod("004", "default", "pod4") + pod4 := createTestPod("004", "default", "pod4", "test-secret") assert.NoError(t, kp.SyncPod(context.Background(), pod4, nil, nil)) assert.NoError(t, kp.KillPod(context.Background(), pod4, pkgcontainer.Pod{}, nil)) ctx, cancel := context.WithCancel(context.Background()) kp.kubeInformerFactory.Start(ctx.Done()) - if !cache.WaitForCacheSync(ctx.Done(), kp.podsSynced, kp.configMapSynced) { + if !cache.WaitForCacheSync(ctx.Done(), kp.podsSynced, kp.configMapSynced, kp.secretSynced) { t.Fatal("timeout waiting for caches to sync") } @@ -104,16 +115,17 @@ func TestK8sProvider_cleanupZombieResources(t *testing.T) { assertCachedBackendResource(t, kp, "pod4", false, true) assert.NoError(t, kp.cleanupZombieResources(ctx)) + assert.NoError(t, kp.cleanupSubResources(ctx)) - assertBackendResource(t, kp, "pod1", true) - assertBackendResource(t, kp, "pod2", false) - assertBackendResource(t, kp, "pod3", false) - assertBackendResource(t, kp, "pod3", false) + assertBackendResource(t, kp, "pod1", true, true) + assertBackendResource(t, kp, "pod2", false, false) + assertBackendResource(t, kp, "pod3", false, false) + assertBackendResource(t, kp, "pod3", false, false) cancel() } -func createTestPod(uid types.UID, namespace, name string) *v1.Pod { +func createTestPod(uid types.UID, namespace, name, secretName string) *v1.Pod { pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ UID: uid, @@ -126,6 +138,22 @@ func createTestPod(uid types.UID, namespace, name string) *v1.Pod { Name: "ctr01", Command: []string{"sleep 60"}, Image: "aa/bb:001", + VolumeMounts: []v1.VolumeMount{ + { + Name: "test-v", + MountPath: "/etc/key", + }, + }, + }, + }, + Volumes: []v1.Volume{ + { + Name: "test-v", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: secretName, + }, + }, }, }, }, @@ -133,15 +161,25 @@ func createTestPod(uid types.UID, namespace, name string) *v1.Pod { return pod } -func assertCachedBackendResource(t *testing.T, kp *K8sProvider, podName string, podExist, cmExist bool) { +func assertCachedBackendResource(t *testing.T, kp *K8sProvider, podName string, podExist, subResExist bool) { _, err := kp.podLister.Get(podName) assert.Equal(t, podExist, err == nil) _, err = kp.configMapLister.Get(fmt.Sprintf("%s-resolv-config", podName)) - assert.Equal(t, cmExist, err == nil) + assert.Equal(t, subResExist, err == nil) + + secret, err := kp.secretLister.Get(fmt.Sprintf("%s-test-secret", podName)) + assert.Equal(t, subResExist, err == nil) + t.Logf("%#v", secret) } -func assertBackendResource(t *testing.T, kp *K8sProvider, podName string, podExist bool) { +func assertBackendResource(t *testing.T, kp *K8sProvider, podName string, podExist, subResExist bool) { _, err := kp.bkClient.CoreV1().Pods(kp.bkNamespace).Get(context.Background(), podName, metav1.GetOptions{}) assert.Equal(t, podExist, err == nil) + + _, err = kp.bkClient.CoreV1().ConfigMaps(kp.bkNamespace).Get(context.Background(), fmt.Sprintf("%s-resolv-config", podName), metav1.GetOptions{}) + assert.Equal(t, subResExist, err == nil) + + _, err = kp.bkClient.CoreV1().Secrets(kp.bkNamespace).Get(context.Background(), fmt.Sprintf("%s-test-secret", podName), metav1.GetOptions{}) + assert.Equal(t, subResExist, err == nil) } diff --git a/pkg/agent/provider/pod/k8s_provider_test.go b/pkg/agent/provider/pod/k8s_provider_test.go index aee77b7f..6175d852 100644 --- a/pkg/agent/provider/pod/k8s_provider_test.go +++ b/pkg/agent/provider/pod/k8s_provider_test.go @@ -16,6 +16,7 @@ package pod import ( "context" + "fmt" "os" "path/filepath" "testing" @@ -27,6 +28,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/client-go/tools/record" "github.com/secretflow/kuscia/pkg/agent/config" pkgcontainer "github.com/secretflow/kuscia/pkg/agent/container" @@ -46,6 +48,7 @@ func createTestK8sProvider(t *testing.T, cfg *config.K8sProviderCfg, rm *resourc PodSyncHandler: frameworktest.FakeSyncHandler{}, ResourceManager: rm, K8sProviderCfg: cfg, + Recorder: &record.FakeRecorder{}, } kp, err := NewK8sProvider(podProviderDep) @@ -180,26 +183,20 @@ func TestK8sProvider_SyncAndKillPod(t *testing.T) { assert.Equal(t, "resolv-config", newPod.Spec.Volumes[2].Name) assert.Equal(t, "runc", *newPod.Spec.RuntimeClassName) - newPodConfig, err := kp.configMapLister.Get("config-template") + newPodConfig, err := kp.configMapLister.Get("pod01-config-template") assert.NoError(t, err) assert.Equal(t, "abc", newPodConfig.Labels[common.LabelPodUID]) assert.Equal(t, "aa=${AA}", newPodConfig.Data["config.yaml"]) - assert.Equal(t, 1, len(newPodConfig.OwnerReferences)) - assert.Equal(t, newPod.Name, newPodConfig.OwnerReferences[0].Name) newResolveConfig, err := kp.configMapLister.Get("pod01-resolv-config") assert.NoError(t, err) assert.Equal(t, "abc", newPodConfig.Labels[common.LabelPodUID]) assert.Equal(t, "nameserver 127.0.0.1", newResolveConfig.Data["resolv.conf"]) - assert.Equal(t, 1, len(newPodConfig.OwnerReferences)) - assert.Equal(t, newPod.Name, newPodConfig.OwnerReferences[0].Name) - newSecret, err := kp.secretLister.Get("test-secret") + newSecret, err := kp.secretLister.Get("pod01-test-secret") assert.NoError(t, err) assert.Equal(t, "abc", newSecret.Labels[common.LabelPodUID]) assert.Equal(t, "123", newSecret.StringData["secret"]) - assert.Equal(t, 1, len(newSecret.OwnerReferences)) - assert.Equal(t, newPod.Name, newSecret.OwnerReferences[0].Name) // kill pod assert.NoError(t, kp.KillPod(context.Background(), pod, pkgcontainer.Pod{}, nil)) @@ -208,3 +205,34 @@ func TestK8sProvider_SyncAndKillPod(t *testing.T) { cancel() } + +func TestNormalizeSubResourceMeta(t *testing.T) { + resourceNameLimit = 10 + tests := []struct { + meta *metav1.ObjectMeta + ownerPodName string + wantMetaName string + }{ + { + meta: &metav1.ObjectMeta{ + Name: "aaa", + }, + ownerPodName: "pod-01", + wantMetaName: "pod-01-aaa", + }, + { + meta: &metav1.ObjectMeta{ + Name: "aaa", + }, + ownerPodName: "pod-001", + wantMetaName: "dd2d7b3cd0", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("Test %d", i), func(t *testing.T) { + normalizeSubResourceMeta(tt.meta, tt.ownerPodName) + assert.Equal(t, tt.wantMetaName, tt.meta.Name) + }) + } +} diff --git a/pkg/common/constants.go b/pkg/common/constants.go index a7b6b9a9..6b4de8c1 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -57,6 +57,7 @@ const ( LabelJobStage = "kuscia.secretflow/job-stage" // LabelJobStageTrigger is a label to specify who trigger the current stage of job. LabelJobStageTrigger = "kuscia.secretflow/job-stage-trigger" + LabelJobStageVersion = "kuscia.secretflow/job-stage-version" LabelKusciaDeploymentAppType = "kuscia.secretflow/app-type" LabelKusciaDeploymentUID = "kuscia.secretflow/kd-uid" @@ -95,6 +96,7 @@ const ( KusciaSchedulerName = "kuscia-scheduler" KusciaCrossDomain = "cross-domain" JobCustomFieldsLabelPrefix = "kuscia.job.custom-fields/" + ReceiverServiceName = "receiver" ) // annotations diff --git a/pkg/controllers/domainroute/check.go b/pkg/controllers/domainroute/check.go index d5f0287a..c3ece225 100644 --- a/pkg/controllers/domainroute/check.go +++ b/pkg/controllers/domainroute/check.go @@ -133,8 +133,8 @@ func DoValidate(spec *kusciaapisv1alpha1.DomainRouteSpec) error { func (c *controller) needRollingToNext(ctx context.Context, dr *kusciaapisv1alpha1.DomainRoute) bool { if !dr.Status.TokenStatus.RevisionToken.IsReady { - rollEslpsedTime := time.Since(dr.Status.TokenStatus.RevisionToken.RevisionTime.Time) - if rollEslpsedTime > domainRouteSyncPeriod { + rollElapsedTime := time.Since(dr.Status.TokenStatus.RevisionToken.RevisionTime.Time) + if rollElapsedTime > domainRouteSyncPeriod { nlog.Warnf("Domainroute %s/%s token is waiting more than %d minutes for ready, so need to re-handshake", dr.Namespace, dr.Name, domainRouteSyncPeriod/time.Minute) return true } diff --git a/pkg/controllers/domainroute/controller.go b/pkg/controllers/domainroute/controller.go index cb496856..4980745a 100644 --- a/pkg/controllers/domainroute/controller.go +++ b/pkg/controllers/domainroute/controller.go @@ -169,7 +169,7 @@ func (c *controller) syncHandler(ctx context.Context, key string) error { return nil } - // Get the ClusterDomainRoute resource with this namespace/name + // Get the DomainRoute resource with this namespace/name dr, err := c.domainRouteLister.DomainRoutes(namespace).Get(name) if err != nil { if k8serrors.IsNotFound(err) { diff --git a/pkg/controllers/domainroute/rolling.go b/pkg/controllers/domainroute/rolling.go index 9b76d7ed..bf5a682a 100644 --- a/pkg/controllers/domainroute/rolling.go +++ b/pkg/controllers/domainroute/rolling.go @@ -111,7 +111,10 @@ func (c *controller) postRollingSourceDomainRoute(ctx context.Context, dr *kusci func (c *controller) postRollingDestinationDomainRoute(ctx context.Context, dr *kusciaapisv1alpha1.DomainRoute) error { n := len(dr.Status.TokenStatus.Tokens) - if dr.Status.TokenStatus.RevisionToken.Token != "" && (n == 0 || dr.Status.TokenStatus.Tokens[n-1].Revision != dr.Status.TokenStatus.RevisionToken.Revision) { + + if time.Since(dr.Status.TokenStatus.RevisionToken.ExpirationTime.Time) < 0 && + dr.Status.TokenStatus.RevisionToken.Token != "" && + (n == 0 || dr.Status.TokenStatus.Tokens[n-1].Revision != dr.Status.TokenStatus.RevisionToken.Revision) { dr = dr.DeepCopy() dr.Status.TokenStatus.Tokens = append(dr.Status.TokenStatus.Tokens, dr.Status.TokenStatus.RevisionToken) _, err := c.kusciaClient.KusciaV1alpha1().DomainRoutes(dr.Namespace).UpdateStatus(ctx, dr, metav1.UpdateOptions{}) diff --git a/pkg/controllers/kusciadeployment/controller.go b/pkg/controllers/kusciadeployment/controller.go index 7be1028a..9812ea6d 100644 --- a/pkg/controllers/kusciadeployment/controller.go +++ b/pkg/controllers/kusciadeployment/controller.go @@ -396,7 +396,7 @@ func (c *Controller) syncHandler(ctx context.Context, key string) error { if err != nil { if k8serrors.IsNotFound(err) { nlog.Infof("KusciaDeployment %s/%s maybe deleted, delete resources", ns, key) - if err := c.cleanKusciaDeploymentEvent(ctx, ns, name); err != nil { + if err = c.cleanKusciaDeploymentEvent(ctx, ns, name); err != nil { nlog.Errorf("Clean kd %s/%s resources failed: %s", ns, name, err) return err } diff --git a/pkg/controllers/kusciadeployment/reconcile.go b/pkg/controllers/kusciadeployment/reconcile.go index 0af2799e..37d01a21 100644 --- a/pkg/controllers/kusciadeployment/reconcile.go +++ b/pkg/controllers/kusciadeployment/reconcile.go @@ -50,8 +50,9 @@ func (c *Controller) ProcessKusciaDeployment(ctx context.Context, kd *kusciav1al // We update the spec and status separately. if updated { - if _, err = c.kusciaClient.KusciaV1alpha1().KusciaDeployments(kd.Namespace).Update(ctx, kd, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("error updating kuscia deployment %v, %v", kd.Name, err) + _, err = c.kusciaClient.KusciaV1alpha1().KusciaDeployments(kd.Namespace).Update(ctx, kd, metav1.UpdateOptions{}) + if err != nil && !k8serrors.IsConflict(err) { + return fmt.Errorf("failed to updating kuscia deployment %v, %v", kd.Name, err) } return nil } @@ -164,8 +165,10 @@ func (c *Controller) refreshPartyDeploymentStatuses(kd *kusciav1alpha1.KusciaDep deployment, err := c.deploymentLister.Deployments(partyKitInfo.domainID).Get(partyKitInfo.dkInfo.deploymentName) if err != nil { - nlog.Warnf("Failed to update party deployment %v/%v status for kuscia deployment %v, %v", - partyKitInfo.domainID, partyKitInfo.dkInfo.deploymentName, kd.Name, err) + if !k8serrors.IsNotFound(err) { + nlog.Warnf("Failed to update party deployment %v/%v status for kuscia deployment %v, %v", + partyKitInfo.domainID, partyKitInfo.dkInfo.deploymentName, kd.Name, err) + } continue } @@ -784,7 +787,7 @@ func (c *Controller) updateDeployment(ctx context.Context, partyKitInfo *PartyKi if needUpdate { _, err = c.kubeClient.AppsV1().Deployments(deploymentCopy.Namespace).Update(ctx, deploymentCopy, metav1.UpdateOptions{}) - if err != nil { + if err != nil && !k8serrors.IsConflict(err) { return fmt.Errorf("failed to update deployment %v/%v, %v", deploymentCopy.Namespace, deployment.Name, err) } } diff --git a/pkg/controllers/kusciadeployment/util.go b/pkg/controllers/kusciadeployment/util.go index 65d224fb..377fad9f 100644 --- a/pkg/controllers/kusciadeployment/util.go +++ b/pkg/controllers/kusciadeployment/util.go @@ -21,6 +21,7 @@ import ( "reflect" "strings" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kusciav1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" @@ -425,8 +426,9 @@ func (c *Controller) updateKusciaDeploymentStatus(ctx context.Context, kd *kusci kd.Status.TotalParties = len(kd.Spec.Parties) } - if _, err = c.kusciaClient.KusciaV1alpha1().KusciaDeployments(kd.Namespace).UpdateStatus(ctx, kd, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("error updating kuscia deployment %v status, %v", kd.Name, err) + _, err = c.kusciaClient.KusciaV1alpha1().KusciaDeployments(kd.Namespace).UpdateStatus(ctx, kd, metav1.UpdateOptions{}) + if err != nil && !k8serrors.IsConflict(err) { + return fmt.Errorf("failed to updating kuscia deployment %v status, %v", kd.Name, err) } return nil diff --git a/pkg/controllers/kusciajob/handler/cancelled.go b/pkg/controllers/kusciajob/handler/cancelled.go index 63c237e8..c6c21bd5 100644 --- a/pkg/controllers/kusciajob/handler/cancelled.go +++ b/pkg/controllers/kusciajob/handler/cancelled.go @@ -1,4 +1,4 @@ -// Copyright 2023 Ant Group Co., Ltd. +// Copyright 2024 Ant Group Co., Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controllers/kusciajob/handler/cancelled_test.go b/pkg/controllers/kusciajob/handler/cancelled_test.go new file mode 100644 index 00000000..e1c606bd --- /dev/null +++ b/pkg/controllers/kusciajob/handler/cancelled_test.go @@ -0,0 +1,93 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + kubeinformers "k8s.io/client-go/informers" + kubefake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/record" + + kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" + kusciafake "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/fake" + kusciascheme "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/scheme" + kusciainformers "github.com/secretflow/kuscia/pkg/crd/informers/externalversions" + "github.com/secretflow/kuscia/pkg/utils/nlog" +) + +func TestCancelledHandler_HandlePhase(t *testing.T) { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(nlog.Infof) + eventBroadcaster.StartRecordingToSink( + &typedcorev1.EventSinkImpl{Interface: kubefake.NewSimpleClientset().CoreV1().Events("default")}) + assert.NoError(t, kusciascheme.AddToScheme(scheme.Scheme)) + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kuscia-job-controller"}) + kubeFakeClient := kubefake.NewSimpleClientset() + kubeInformersFactory := kubeinformers.NewSharedInformerFactory(kubeFakeClient, 0) + + nsInformer := kubeInformersFactory.Core().V1().Namespaces() + type fields struct { + recorder record.EventRecorder + } + type args struct { + kusciaJob *kusciaapisv1alpha1.KusciaJob + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr assert.ErrorAssertionFunc + }{ + { + name: "BestEffort mode task{a,[a->b],[a->c],[c->d]} maxParallelism{1} and succeeded{a,b,c,d} should return needUpdate{true} err{nil}", + fields: fields{ + recorder: recorder, + }, + args: args{ + kusciaJob: makeKusciaJob(KusciaJobForShapeTree, + kusciaapisv1alpha1.KusciaJobScheduleModeBestEffort, 2, nil), + }, + want: false, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kusciaClient := kusciafake.NewSimpleClientset() + kusciaInformerFactory := kusciainformers.NewSharedInformerFactory(kusciaClient, 5*time.Minute) + deps := &Dependencies{ + KusciaClient: kusciaClient, + KusciaTaskLister: kusciaInformerFactory.Kuscia().V1alpha1().KusciaTasks().Lister(), + NamespaceLister: nsInformer.Lister(), + DomainLister: kusciaInformerFactory.Kuscia().V1alpha1().Domains().Lister(), + EnableWorkloadApprove: true, + } + s := NewCancelledHandler(deps) + got, err := s.HandlePhase(tt.args.kusciaJob) + if !tt.wantErr(t, err, fmt.Sprintf("HandlePhase(%v)", tt.args.kusciaJob)) { + return + } + assert.Equalf(t, tt.want, got, "HandlePhase(%v)", tt.args.kusciaJob) + }) + } +} diff --git a/pkg/controllers/kusciajob/handler/factory.go b/pkg/controllers/kusciajob/handler/factory.go index 22efd4f9..7129d6cd 100644 --- a/pkg/controllers/kusciajob/handler/factory.go +++ b/pkg/controllers/kusciajob/handler/factory.go @@ -46,6 +46,7 @@ func NewKusciaJobPhaseHandlerFactory(deps *Dependencies) *KusciaJobPhaseHandlerF kusciaapisv1alpha1.KusciaJobAwaitingApproval: NewAwaitingApprovalHandler(deps), kusciaapisv1alpha1.KusciaJobPending: NewPendingHandler(deps), kusciaapisv1alpha1.KusciaJobRunning: NewRunningHandler(deps), + kusciaapisv1alpha1.KusciaJobSuspended: NewSuspendedHandler(deps), kusciaapisv1alpha1.KusciaJobSucceeded: NewSucceededHandler(deps), kusciaapisv1alpha1.KusciaJobFailed: NewFailedHandler(deps), kusciaapisv1alpha1.KusciaJobApprovalReject: NewApprovalRejectHandler(deps), diff --git a/pkg/controllers/kusciajob/handler/failed.go b/pkg/controllers/kusciajob/handler/failed.go index a8db1d0b..240b35d3 100644 --- a/pkg/controllers/kusciajob/handler/failed.go +++ b/pkg/controllers/kusciajob/handler/failed.go @@ -16,54 +16,40 @@ package handler import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/record" "github.com/secretflow/kuscia/pkg/common" "github.com/secretflow/kuscia/pkg/controllers/kusciajob/metrics" kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" - kuscialistersv1alpha1 "github.com/secretflow/kuscia/pkg/crd/listers/kuscia/v1alpha1" "github.com/secretflow/kuscia/pkg/utils/nlog" - utilsres "github.com/secretflow/kuscia/pkg/utils/resources" ) // FailedHandler will handle kuscia job in Failed phase. type FailedHandler struct { - kusciaTaskLister kuscialistersv1alpha1.KusciaTaskLister - namespaceLister corelisters.NamespaceLister - recorder record.EventRecorder + *JobScheduler } // NewFailedHandler return FailedHandler to handle Failed kuscia job. func NewFailedHandler(deps *Dependencies) *FailedHandler { return &FailedHandler{ - recorder: deps.Recorder, - kusciaTaskLister: deps.KusciaTaskLister, - namespaceLister: deps.NamespaceLister, + JobScheduler: NewJobScheduler(deps), } } // HandlePhase implements the KusciaJobPhaseHandler interface. // It will do some tail-in work when the job phase is failed. -func (h *FailedHandler) HandlePhase(kusciaJob *kusciaapisv1alpha1.KusciaJob) (needUpdate bool, err error) { +func (h *FailedHandler) HandlePhase(job *kusciaapisv1alpha1.KusciaJob) (needUpdate bool, err error) { now := metav1.Now().Rfc3339Copy() - asInitiator := false - if utilsres.SelfClusterAsInitiator(h.namespaceLister, kusciaJob.Spec.Initiator, kusciaJob.Annotations) { - asInitiator = true + // handle stage command, check if the stage command matches the phase of job + if hasReconciled, err := h.handleStageCommand(now, job); err != nil || hasReconciled { + return true, err } - allTaskFinished := true - for taskID, phase := range kusciaJob.Status.TaskStatus { + for taskID, phase := range job.Status.TaskStatus { if phase != kusciaapisv1alpha1.TaskFailed && phase != kusciaapisv1alpha1.TaskSucceeded { - if !asInitiator { - kusciaJob.Status.TaskStatus[taskID] = kusciaapisv1alpha1.TaskFailed - continue - } - task, err := h.kusciaTaskLister.KusciaTasks(common.KusciaCrossDomain).Get(taskID) if err != nil { nlog.Warnf("Get kuscia task %v failed, %v", taskID, err) - kusciaJob.Status.TaskStatus[taskID] = kusciaapisv1alpha1.TaskFailed + job.Status.TaskStatus[taskID] = kusciaapisv1alpha1.TaskFailed needUpdate = true continue } @@ -73,19 +59,18 @@ func (h *FailedHandler) HandlePhase(kusciaJob *kusciaapisv1alpha1.KusciaJob) (ne } if phase != task.Status.Phase { - kusciaJob.Status.TaskStatus[taskID] = task.Status.Phase + job.Status.TaskStatus[taskID] = task.Status.Phase needUpdate = true } } } if allTaskFinished { - if kusciaJob.Status.CompletionTime == nil || !kusciaJob.Status.CompletionTime.Equal(&now) { - kusciaJob.Status.CompletionTime = &now + if job.Status.CompletionTime == nil { + job.Status.CompletionTime = &now needUpdate = true + metrics.JobResultStats.WithLabelValues(metrics.Failed).Inc() } } - - metrics.JobResultStats.WithLabelValues(metrics.Failed).Inc() return needUpdate, nil } diff --git a/pkg/controllers/kusciajob/handler/failed_test.go b/pkg/controllers/kusciajob/handler/failed_test.go index 5e3cfddb..8b131cc9 100644 --- a/pkg/controllers/kusciajob/handler/failed_test.go +++ b/pkg/controllers/kusciajob/handler/failed_test.go @@ -17,6 +17,7 @@ package handler import ( "fmt" "testing" + "time" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" @@ -26,11 +27,35 @@ import ( typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/record" + "github.com/secretflow/kuscia/pkg/common" kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" + kusciafake "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/fake" kusciascheme "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/scheme" + kusciainformers "github.com/secretflow/kuscia/pkg/crd/informers/externalversions" "github.com/secretflow/kuscia/pkg/utils/nlog" ) +const ( + testCaseFailedNormal = iota + testCaseFailedRestart +) + +func setJobStageRestart(job *kusciaapisv1alpha1.KusciaJob) { + job.Labels = make(map[string]string) + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobRestartStage) + job.Labels[common.LabelJobStageTrigger] = "alice" + job.Status.Phase = kusciaapisv1alpha1.KusciaJobFailed +} + +func setJobStage(job *kusciaapisv1alpha1.KusciaJob, testCase int) { + switch testCase { + case testCaseFailedRestart: + setJobStageRestart(job) + case testCaseFailedNormal: + return + } +} + func TestFailedHandler_HandlePhase(t *testing.T) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(nlog.Infof) @@ -40,6 +65,7 @@ func TestFailedHandler_HandlePhase(t *testing.T) { recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kuscia-job-controller"}) kubeFakeClient := kubefake.NewSimpleClientset() kubeInformersFactory := kubeinformers.NewSharedInformerFactory(kubeFakeClient, 0) + nsInformer := kubeInformersFactory.Core().V1().Namespaces() type fields struct { recorder record.EventRecorder @@ -48,14 +74,15 @@ func TestFailedHandler_HandlePhase(t *testing.T) { kusciaJob *kusciaapisv1alpha1.KusciaJob } tests := []struct { - name string - fields fields - args args - want bool - wantErr assert.ErrorAssertionFunc + name string + fields fields + args args + want bool + wantErr assert.ErrorAssertionFunc + testCase int }{ { - name: "BestEffort mode task{a,[a->b],[a->c],[c->d]} maxParallelism{1} and succeeded{a,b,c,d} should return needUpdate{true} err{nil}", + name: "test normal failed", fields: fields{ recorder: recorder, }, @@ -65,14 +92,33 @@ func TestFailedHandler_HandlePhase(t *testing.T) { }, want: true, wantErr: assert.NoError, + }, { + name: "test restart stage", + fields: fields{ + recorder: recorder, + }, + args: args{ + kusciaJob: makeKusciaJob(KusciaJobForShapeTree, + kusciaapisv1alpha1.KusciaJobScheduleModeBestEffort, 2, nil), + }, + want: true, + wantErr: assert.NoError, + testCase: testCaseFailedRestart, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &FailedHandler{ - namespaceLister: nsInformer.Lister(), - recorder: tt.fields.recorder, + setJobStage(tt.args.kusciaJob, tt.testCase) + kusciaClient := kusciafake.NewSimpleClientset() + kusciaInformerFactory := kusciainformers.NewSharedInformerFactory(kusciaClient, 5*time.Minute) + deps := &Dependencies{ + KusciaClient: kusciaClient, + KusciaTaskLister: kusciaInformerFactory.Kuscia().V1alpha1().KusciaTasks().Lister(), + NamespaceLister: nsInformer.Lister(), + DomainLister: kusciaInformerFactory.Kuscia().V1alpha1().Domains().Lister(), + EnableWorkloadApprove: true, } + s := NewFailedHandler(deps) got, err := s.HandlePhase(tt.args.kusciaJob) if !tt.wantErr(t, err, fmt.Sprintf("HandlePhase(%v)", tt.args.kusciaJob)) { return diff --git a/pkg/controllers/kusciajob/handler/running.go b/pkg/controllers/kusciajob/handler/running.go index 7bcfe36c..655c638f 100644 --- a/pkg/controllers/kusciajob/handler/running.go +++ b/pkg/controllers/kusciajob/handler/running.go @@ -101,6 +101,7 @@ func (h *RunningHandler) handleRunning(job *kusciaapisv1alpha1.KusciaJob) (needU if err != nil { nlog.Errorf("Get exist task %v failed: %v", t.Name, err) setKusciaJobStatus(now, &job.Status, kusciaapisv1alpha1.KusciaJobFailed, "CreateTaskFailed", err.Error()) + job.Status.CompletionTime = &now return true, nil } } @@ -109,6 +110,7 @@ func (h *RunningHandler) handleRunning(job *kusciaapisv1alpha1.KusciaJob) (needU message := fmt.Sprintf("Failed to create task %v because a task with the same name already exists", t.Name) nlog.Error(message) setKusciaJobStatus(now, &job.Status, kusciaapisv1alpha1.KusciaJobFailed, "CreateTaskFailed", message) + job.Status.CompletionTime = &now return true, nil } } else { diff --git a/pkg/controllers/kusciajob/handler/running_test.go b/pkg/controllers/kusciajob/handler/running_test.go index 4707e712..04e35002 100644 --- a/pkg/controllers/kusciajob/handler/running_test.go +++ b/pkg/controllers/kusciajob/handler/running_test.go @@ -45,6 +45,34 @@ func taskSucceededAssertFunc(t *kusciaapisv1alpha1.KusciaTask) bool { return t != nil && t.Status.Phase == kusciaapisv1alpha1.TaskSucceeded } +const ( + testCaseRunningStart = iota + testCaseRunningRestart + testCaseRunningSuspend + testCaseRunningStop + testCaseRunningCancel +) + +func setRunningJobStage(job *kusciaapisv1alpha1.KusciaJob, testCase int) { + job.Labels = make(map[string]string) + job.Labels[common.LabelJobStageTrigger] = "alice" + job.Status.Phase = kusciaapisv1alpha1.KusciaJobRunning + switch testCase { + case testCaseRunningStart: + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobStartStage) + case testCaseRunningRestart: + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobRestartStage) + case testCaseRunningSuspend: + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobSuspendStage) + case testCaseRunningStop: + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobStopStage) + case testCaseRunningCancel: + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobCancelStage) + default: + return + } +} + func TestRunningHandler_HandlePhase(t *testing.T) { bestEffortIndependent := makeKusciaJob(KusciaJobForShapeIndependent, kusciaapisv1alpha1.KusciaJobScheduleModeBestEffort, 2, nil) @@ -74,6 +102,7 @@ func TestRunningHandler_HandlePhase(t *testing.T) { wantErr assert.ErrorAssertionFunc wantJobPhase kusciaapisv1alpha1.KusciaJobPhase wantFinalTasks map[string]taskAssertionFunc + testcase int }{ { name: "BestEffort mode task{a,b,c,d} maxParallelism{2} and succeeded{a} should return needUpdate{true} err{nil} phase{Running}", @@ -257,10 +286,101 @@ func TestRunningHandler_HandlePhase(t *testing.T) { "b": taskExistAssertFunc, "c": taskExistAssertFunc, }, + }, { + name: "test running start", + fields: fields{ + kusciaClient: kusciafake.NewSimpleClientset( + makeTestKusciaTask("a", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskSucceeded), + makeTestKusciaTask("b", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskFailed), + strictTolerableBLinear.DeepCopy(), + ), + kubeClient: kubefake.NewSimpleClientset(), + }, + args: args{ + kusciaJob: strictTolerableBLinear.DeepCopy(), + }, + wantNeedUpdate: true, + wantErr: assert.NoError, + wantJobPhase: kusciaapisv1alpha1.KusciaJobRunning, + wantFinalTasks: map[string]taskAssertionFunc{ + "a": taskSucceededAssertFunc, + "b": taskExistAssertFunc, + "c": taskExistAssertFunc, + }, + testcase: testCaseRunningStart, + }, { + name: "test running restart", + fields: fields{ + kusciaClient: kusciafake.NewSimpleClientset( + makeTestKusciaTask("a", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskSucceeded), + makeTestKusciaTask("b", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskFailed), + strictTolerableBLinear.DeepCopy(), + ), + kubeClient: kubefake.NewSimpleClientset(), + }, + args: args{ + kusciaJob: strictTolerableBLinear.DeepCopy(), + }, + wantNeedUpdate: true, + wantErr: assert.NoError, + wantJobPhase: kusciaapisv1alpha1.KusciaJobRunning, + testcase: testCaseRunningRestart, + }, { + name: "test running suspend", + fields: fields{ + kusciaClient: kusciafake.NewSimpleClientset( + makeTestKusciaTask("a", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskSucceeded), + makeTestKusciaTask("b", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskFailed), + strictTolerableBLinear.DeepCopy(), + ), + kubeClient: kubefake.NewSimpleClientset(), + }, + args: args{ + kusciaJob: strictTolerableBLinear.DeepCopy(), + }, + wantNeedUpdate: true, + wantErr: assert.NoError, + wantJobPhase: kusciaapisv1alpha1.KusciaJobSuspended, + testcase: testCaseRunningSuspend, + }, { + name: "test running cancel", + fields: fields{ + kusciaClient: kusciafake.NewSimpleClientset( + makeTestKusciaTask("a", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskSucceeded), + makeTestKusciaTask("b", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskFailed), + strictTolerableBLinear.DeepCopy(), + ), + kubeClient: kubefake.NewSimpleClientset(), + }, + args: args{ + kusciaJob: strictTolerableBLinear.DeepCopy(), + }, + wantNeedUpdate: true, + wantErr: assert.NoError, + wantJobPhase: kusciaapisv1alpha1.KusciaJobCancelled, + testcase: testCaseRunningCancel, + }, { + name: "test running stop", + fields: fields{ + kusciaClient: kusciafake.NewSimpleClientset( + makeTestKusciaTask("a", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskSucceeded), + makeTestKusciaTask("b", strictTolerableBLinear.Name, kusciaapisv1alpha1.TaskFailed), + strictTolerableBLinear.DeepCopy(), + ), + kubeClient: kubefake.NewSimpleClientset(), + }, + args: args{ + kusciaJob: strictTolerableBLinear.DeepCopy(), + }, + wantNeedUpdate: true, + wantErr: assert.NoError, + wantJobPhase: kusciaapisv1alpha1.KusciaJobFailed, + testcase: testCaseRunningStop, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + setRunningJobStage(tt.args.kusciaJob, tt.testcase) kusciaInformerFactory := kusciainformers.NewSharedInformerFactory(tt.fields.kusciaClient, 5*time.Minute) kubeInformerFactory := informers.NewSharedInformerFactory(tt.fields.kubeClient, 5*time.Minute) nsInformer := kubeInformerFactory.Core().V1().Namespaces() diff --git a/pkg/controllers/kusciajob/handler/scheduler.go b/pkg/controllers/kusciajob/handler/scheduler.go index ec04dbc8..32b3fcec 100644 --- a/pkg/controllers/kusciajob/handler/scheduler.go +++ b/pkg/controllers/kusciajob/handler/scheduler.go @@ -15,9 +15,11 @@ package handler import ( + "context" "fmt" "reflect" "sort" + "strconv" "strings" corev1 "k8s.io/api/core/v1" @@ -32,6 +34,7 @@ import ( kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" "github.com/secretflow/kuscia/pkg/crd/clientset/versioned" kuscialistersv1alpha1 "github.com/secretflow/kuscia/pkg/crd/listers/kuscia/v1alpha1" + intercommon "github.com/secretflow/kuscia/pkg/interconn/kuscia/common" "github.com/secretflow/kuscia/pkg/utils/nlog" utilsres "github.com/secretflow/kuscia/pkg/utils/resources" ) @@ -66,106 +69,176 @@ func (h *JobScheduler) handleStageCommand(now metav1.Time, job *kusciaapisv1alph if !ok { return } - if stage, ok := h.needReconcileStageCmd(kusciaapisv1alpha1.JobStage(stageCmd), job.Status.Phase); ok { - nlog.Infof("handleStageCommand trigger: %s send command: %s.", cmdTrigger, stageCmd) - switch stage { - case kusciaapisv1alpha1.JobStartStage: - err = h.handleStageCmdStart(now, job) - return true, err - case kusciaapisv1alpha1.JobStopStage: - err = h.handleStageCmdStop(now, job) - return true, err - case kusciaapisv1alpha1.JobCancelStage: - err = h.handleStageCmdCancelled(now, job) - return true, err - case kusciaapisv1alpha1.JobRestartStage: - } - } - return -} -func (h *JobScheduler) needReconcileStageCmd(stageCmd kusciaapisv1alpha1.JobStage, curPhase kusciaapisv1alpha1.KusciaJobPhase) (handleStage kusciaapisv1alpha1.JobStage, needReconcile bool) { - switch stageCmd { - case kusciaapisv1alpha1.JobCreateStage, kusciaapisv1alpha1.JobStartStage: - // do nothing - return kusciaapisv1alpha1.JobCreateStage, false + nlog.Infof("Handle stage trigger: %s send command: %s.", cmdTrigger, stageCmd) + switch kusciaapisv1alpha1.JobStage(stageCmd) { + case kusciaapisv1alpha1.JobStartStage: + return h.handleStageCmdStart(now, job) case kusciaapisv1alpha1.JobStopStage: - // - if curPhase != kusciaapisv1alpha1.KusciaJobFailed { - return kusciaapisv1alpha1.JobStopStage, true - } + err = h.handleStageCmdStop(now, job) + return true, err case kusciaapisv1alpha1.JobCancelStage: - if curPhase != kusciaapisv1alpha1.KusciaJobCancelled { - return kusciaapisv1alpha1.JobCancelStage, true - } + err = h.handleStageCmdCancelled(now, job) + return true, err case kusciaapisv1alpha1.JobRestartStage: - // TODO restart need consider the job run failed again - return kusciaapisv1alpha1.JobRestartStage, false + return h.handleStageCmdRestart(now, job) + case kusciaapisv1alpha1.JobSuspendStage: + return h.handleStageCmdSuspend(now, job) } - return kusciaapisv1alpha1.JobStartStage, false + return } // handleStageCmdStart handles job-start stage. -func (h *JobScheduler) handleStageCmdStart(now metav1.Time, job *kusciaapisv1alpha1.KusciaJob) (err error) { - // set own stage status to stopSuccess +func (h *JobScheduler) handleStageCmdStart(now metav1.Time, job *kusciaapisv1alpha1.KusciaJob) (hasReconciled bool, err error) { + // get own party ownP, _, _ := h.getAllParties(job) if job.Status.StageStatus == nil { job.Status.StageStatus = make(map[string]kusciaapisv1alpha1.JobStagePhase) } - // set own stage status to start succeeded + // set own stage status for p := range ownP { + if v, ok := job.Status.StageStatus[p]; ok && v == kusciaapisv1alpha1.JobStartStageSucceeded { + continue + } job.Status.StageStatus[p] = kusciaapisv1alpha1.JobStartStageSucceeded + hasReconciled = true + // print command log + cmd, cmdTrigger, _ := h.getStageCmd(job) + nlog.Infof("Job: %s party: %s execute the cmd: %s, set own party: %s stage to 'startSuccess'.", job.Name, cmdTrigger, cmd, p) } - // set job phase to failed + return hasReconciled, nil +} + +// handleStageCmdReStart handles job-restart stage. +func (h *JobScheduler) handleStageCmdRestart(now metav1.Time, job *kusciaapisv1alpha1.KusciaJob) (hasReconciled bool, err error) { + // print command log cmd, cmdTrigger, _ := h.getStageCmd(job) - nlog.Infof("job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) - return nil + nlog.Infof("Job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) + // handle 'failed' and 'suspend' phase + if job.Status.Phase == kusciaapisv1alpha1.KusciaJobFailed || job.Status.Phase == kusciaapisv1alpha1.KusciaJobSuspended { + // set job phase to 'running' + job.Status.Phase = kusciaapisv1alpha1.KusciaJobRunning + partyStage := kusciaapisv1alpha1.JobRestartStageSucceeded + job.Status.Message = fmt.Sprintf("This job is restarted by %s", cmdTrigger) + job.Status.Reason = fmt.Sprintf("Party: %s execute the cmd: %s.", cmdTrigger, cmd) + // delete the failed task + if err := h.deleteNotSuccessTasks(job); err != nil { + // delete the failed task failed + partyStage = kusciaapisv1alpha1.JobRestartStageFailed + job.Status.Message = fmt.Sprintf("Restart this job and delete the 'failed' phase task failed, error: %s.", err.Error()) + } + // set own party stage status to 'restartSuccess' or 'restartFailed' + ownP, _, _ := h.getAllParties(job) + if job.Status.StageStatus == nil { + job.Status.StageStatus = make(map[string]kusciaapisv1alpha1.JobStagePhase) + } + for p := range ownP { + job.Status.StageStatus[p] = partyStage + } + return true, nil + } + // handle running phase + if job.Status.Phase == kusciaapisv1alpha1.KusciaJobRunning { + // begin schedule tasks if all party restart success + isComplete := h.isAllPartyRestartComplete(job) + // not all party complete handling 'restart' stage + if !isComplete { + // wait for all party restart success or any party restart failed + return true, nil + } + // Since a Job can be restarted more than once, we need to set the job's status to 'start' so that the job can be restarted again. + // To avoid conflicts, only the initiator sets the stage + if ok, _ := intercommon.SelfClusterIsInitiator(h.domainLister, job); ok { + if err = h.setJobStage(job.Name, job.Spec.Initiator, string(kusciaapisv1alpha1.JobStartStage)); err != nil { + nlog.Errorf("Set job stage to 'start' failed, error: %s.", err.Error()) + // wait for all party restart success or any party restart failed + return true, err + } + return true, nil + } + return false, nil + } + nlog.Errorf("Unexpect phase: %s of job: %s.", job.Status.Phase, job.Name) + return false, nil } // handleStageCmdStop handles job-stop stage. func (h *JobScheduler) handleStageCmdStop(now metav1.Time, job *kusciaapisv1alpha1.KusciaJob) (err error) { - // set own stage status to stopSuccess + // print command log + cmd, cmdTrigger, _ := h.getStageCmd(job) + nlog.Infof("Job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) + stageStatus := kusciaapisv1alpha1.JobStopStageSucceeded ownP, _, _ := h.getAllParties(job) if job.Status.StageStatus == nil { job.Status.StageStatus = make(map[string]kusciaapisv1alpha1.JobStagePhase) } - // set own stage status to stop succeeded + // stop the running task + if err = h.stopTasks(now, job); err != nil { + nlog.Errorf("Stop 'runnning' task of job: %s failed, error: %s.", job.Name, err.Error()) + return err + } + // set own stage status for p := range ownP { - job.Status.StageStatus[p] = kusciaapisv1alpha1.JobStopStageSucceeded + job.Status.StageStatus[p] = stageStatus } // set job phase to failed - cmd, cmdTrigger, _ := h.getStageCmd(job) - nlog.Infof("job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) - reason := fmt.Sprintf("party: %s execute the cmd: %s.", cmdTrigger, cmd) + reason := fmt.Sprintf("Party: %s execute the cmd: %s.", cmdTrigger, cmd) setKusciaJobStatus(now, &job.Status, kusciaapisv1alpha1.KusciaJobFailed, reason, "") - // stop the running task - if err = h.stopTasks(now, job); err != nil { - nlog.Errorf("handleStageCmdStop stop job: %s failed, error: %s.", job.Name, err.Error()) - } return nil } -// handleJobStopStage handles job-stop stage. +// handleStageCmdCancelled handles job 'cancel' stage. func (h *JobScheduler) handleStageCmdCancelled(now metav1.Time, job *kusciaapisv1alpha1.KusciaJob) (err error) { - // set own stage status to stopSuccess + // print command log + cmd, cmdTrigger, _ := h.getStageCmd(job) + nlog.Infof("Job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) + stageStatus := kusciaapisv1alpha1.JobCancelStageSucceeded + // get own party ownP, _, _ := h.getAllParties(job) if job.Status.StageStatus == nil { job.Status.StageStatus = make(map[string]kusciaapisv1alpha1.JobStagePhase) } - // set own stage status to stop succeeded + // stop the running task + if err = h.stopTasks(now, job); err != nil { + nlog.Errorf("Stop 'runnning' task of job: %s failed, error: %s.", job.Name, err.Error()) + return err + } + // set own stage status for p := range ownP { - job.Status.StageStatus[p] = kusciaapisv1alpha1.JobStopStageSucceeded + job.Status.StageStatus[p] = stageStatus } - // set job phase to canncelled - cmd, cmdTrigger, _ := h.getStageCmd(job) - nlog.Infof("job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) - reason := fmt.Sprintf("party: %s execute the cmd: %s.", cmdTrigger, cmd) + // set job phase to cancelled + reason := fmt.Sprintf("Party: %s execute the cmd: %s.", cmdTrigger, cmd) setKusciaJobStatus(now, &job.Status, kusciaapisv1alpha1.KusciaJobCancelled, reason, "") + return nil +} + +// handleStageCmdSuspend handles job 'suspend' stage. +func (h *JobScheduler) handleStageCmdSuspend(now metav1.Time, job *kusciaapisv1alpha1.KusciaJob) (hasReconciled bool, err error) { + if job.Status.Phase != kusciaapisv1alpha1.KusciaJobRunning { + return false, nil + } + // print command log + cmd, cmdTrigger, _ := h.getStageCmd(job) + nlog.Infof("Job: %s party: %s execute the cmd: %s.", job.Name, cmdTrigger, cmd) + stageStatus := kusciaapisv1alpha1.JobSuspendStageSucceeded + ownP, _, _ := h.getAllParties(job) + if job.Status.StageStatus == nil { + job.Status.StageStatus = make(map[string]kusciaapisv1alpha1.JobStagePhase) + } // stop the running task if err = h.stopTasks(now, job); err != nil { - nlog.Errorf("handleStageCmdStop stop job: %s failed, error: %s.", job.Name, err.Error()) + nlog.Errorf("Stop 'runnning' task of job: %s failed, error: %s.", job.Name, err.Error()) + return false, err } - return nil + // set own stage status + for p := range ownP { + job.Status.StageStatus[p] = stageStatus + } + // set job phase to suspended + reason := fmt.Sprintf("Party: %s execute the cmd: %s.", cmdTrigger, cmd) + setKusciaJobStatus(now, &job.Status, kusciaapisv1alpha1.KusciaJobSuspended, reason, "") + return true, nil } func (h *JobScheduler) getStageCmd(job *kusciaapisv1alpha1.KusciaJob) (stageCmd, cmdTrigger string, ok bool) { @@ -301,6 +374,35 @@ func (h *JobScheduler) somePartyStartFailed(job *kusciaapisv1alpha1.KusciaJob) ( return false, "", nil } +func (h *JobScheduler) isAllPartyRestartSuccess(job *kusciaapisv1alpha1.KusciaJob) (bool, error) { + for _, party := range h.getParties(job) { + domain, err := h.domainLister.Get(party.DomainID) + if err != nil { + nlog.Errorf("Check party 'restartSuccess' status failed, error: %s.", err.Error()) + return false, err + } + if stageStatus, ok := job.Status.StageStatus[domain.Name]; ok && stageStatus == kusciaapisv1alpha1.JobRestartStageSucceeded { + continue + } + return false, nil + } + return true, nil +} + +func (h *JobScheduler) isAllPartyRestartComplete(job *kusciaapisv1alpha1.KusciaJob) bool { + + for _, party := range h.getParties(job) { + stageStatus, ok := job.Status.StageStatus[party.DomainID] + if !ok { + return false + } + if stageStatus != kusciaapisv1alpha1.JobRestartStageFailed && stageStatus != kusciaapisv1alpha1.JobRestartStageSucceeded { + nlog.Infof("Party: %s is not complete to handle 'restart' stage.", party.DomainID) + return false + } + } + return true +} func (h *JobScheduler) getAllParties(job *kusciaapisv1alpha1.KusciaJob) (ownParties, otherParties map[string]kusciaapisv1alpha1.Party, err error) { partyMap := make(map[string]kusciaapisv1alpha1.Party) ownParties = make(map[string]kusciaapisv1alpha1.Party) @@ -353,7 +455,9 @@ func (h *JobScheduler) stopTasks(now metav1.Time, kusciaJob *kusciaapisv1alpha1. if utilsres.IsOuterBFIAInterConnDomain(h.namespaceLister, party.DomainID) { continue } - + if utilsres.IsPartnerDomain(h.namespaceLister, party.DomainID) { + continue + } found := false for i := range copyKt.Status.PartyTaskStatus { if copyKt.Status.PartyTaskStatus[i].DomainID == party.DomainID && copyKt.Status.PartyTaskStatus[i].Role == party.Role { @@ -375,6 +479,30 @@ func (h *JobScheduler) stopTasks(now metav1.Time, kusciaJob *kusciaapisv1alpha1. return nil } +func (h *JobScheduler) deleteNotSuccessTasks(kusciaJob *kusciaapisv1alpha1.KusciaJob) error { + var tasks []string + for taskID, phase := range kusciaJob.Status.TaskStatus { + if phase == kusciaapisv1alpha1.TaskSucceeded { + continue + } + kt, err := h.kusciaTaskLister.KusciaTasks(common.KusciaCrossDomain).Get(taskID) + if err != nil && !k8serrors.IsNotFound(err) { + nlog.Warnf("Get kuscia task %v failed, so skip delete this task, error: %s.", taskID, err.Error()) + return err + } + err = h.kusciaClient.KusciaV1alpha1().KusciaTasks(common.KusciaCrossDomain).Delete(context.Background(), kt.Name, metav1.DeleteOptions{}) + if err != nil && !k8serrors.IsNotFound(err) { + nlog.Warnf("Delete kuscia task %v failed, so skip delete this task, error: %s.", taskID, err.Error()) + return err + } + tasks = append(tasks, taskID) + } + for _, v := range tasks { + delete(kusciaJob.Status.TaskStatus, v) + } + return nil +} + // kusciaJobValidate check whether kusciaJob is valid. func (h *JobScheduler) kusciaJobValidate(kusciaJob *kusciaapisv1alpha1.KusciaJob) error { if _, err := h.namespaceLister.Get(kusciaJob.Spec.Initiator); err != nil { @@ -513,6 +641,32 @@ func (h *JobScheduler) annotateInterConn(job *kusciaapisv1alpha1.KusciaJob) (err return } +func (h *JobScheduler) setJobStage(jobID string, trigger, stage string) (err error) { + job, err := h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Get(context.Background(), jobID, metav1.GetOptions{}) + if err != nil { + nlog.Errorf("Get job: %s failed, error: %s.", jobID, err.Error()) + return err + } + if job.Labels == nil { + job.Labels = make(map[string]string) + } + job.Labels[common.LabelJobStage] = stage + job.Labels[common.LabelJobStageTrigger] = trigger + jobVersion := "1" + if v, ok := job.Labels[common.LabelJobStageVersion]; ok { + if iV, err := strconv.Atoi(v); err == nil { + jobVersion = strconv.Itoa(iV + 1) + } + } + job.Labels[common.LabelJobStageVersion] = jobVersion + _, err = h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Update(context.Background(), job, metav1.UpdateOptions{}) + if err != nil { + nlog.Errorf("Update job: %s failed, error: %s.", job.Name, err.Error()) + return err + } + return nil +} + func domainListToString(domains []string) (labelValue string) { for _, v := range domains { if labelValue == "" { @@ -663,6 +817,13 @@ func buildJobStatus(now metav1.Time, kjStatus.Phase = currentJobStatusPhase } + if currentJobStatusPhase == kusciaapisv1alpha1.KusciaJobSucceeded || currentJobStatusPhase == kusciaapisv1alpha1.KusciaJobFailed { + if kjStatus.CompletionTime == nil { + needUpdate = true + kjStatus.CompletionTime = &now + } + } + if !reflect.DeepEqual(kjStatus.TaskStatus, currentSubTasksStatus) { needUpdate = true kjStatus.TaskStatus = currentSubTasksStatus @@ -726,14 +887,7 @@ func jobStatusPhaseFrom(job *kusciaapisv1alpha1.KusciaJob, currentSubTasksStatus // ShouldReconcile return whether this job need to reconcile again. func ShouldReconcile(job *kusciaapisv1alpha1.KusciaJob) bool { - // Todo: Add cancel feature, Cancel job could not dispatch again - if job.DeletionTimestamp != nil { - nlog.Infof("KusciaJob %s was deleted, skipping", job.Name) - return false - } - - if job.Status.CompletionTime != nil { - nlog.Infof("KusciaJob %s was finished, skipping", job.Name) + if job.Status.Phase == kusciaapisv1alpha1.KusciaJobCancelled || job.Status.Phase == kusciaapisv1alpha1.KusciaJobSucceeded { return false } return true diff --git a/pkg/controllers/kusciajob/handler/suspended.go b/pkg/controllers/kusciajob/handler/suspended.go new file mode 100644 index 00000000..12c7cf22 --- /dev/null +++ b/pkg/controllers/kusciajob/handler/suspended.go @@ -0,0 +1,50 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" +) + +// SuspendedHandler will handle kuscia job in suspend phase. +type SuspendedHandler struct { + *JobScheduler +} + +// NewSuspendedHandler return SuspendedHandler to handle 'suspend' kuscia job. +func NewSuspendedHandler(deps *Dependencies) *SuspendedHandler { + return &SuspendedHandler{ + JobScheduler: NewJobScheduler(deps), + } +} + +// HandlePhase implements the KusciaJobPhaseHandler interface. +func (h *SuspendedHandler) HandlePhase(job *kusciaapisv1alpha1.KusciaJob) (needUpdate bool, err error) { + now := metav1.Now().Rfc3339Copy() + // handle stage command, check if the stage command matches the phase of job + if hasReconciled, err := h.handleStageCommand(now, job); err != nil || hasReconciled { + return true, err + } + + for taskID, phase := range job.Status.TaskStatus { + if phase != kusciaapisv1alpha1.TaskFailed && phase != kusciaapisv1alpha1.TaskSucceeded { + job.Status.TaskStatus[taskID] = kusciaapisv1alpha1.TaskFailed + needUpdate = true + } + } + return needUpdate, nil +} diff --git a/pkg/controllers/kusciajob/handler/suspended_test.go b/pkg/controllers/kusciajob/handler/suspended_test.go new file mode 100644 index 00000000..21dadf32 --- /dev/null +++ b/pkg/controllers/kusciajob/handler/suspended_test.go @@ -0,0 +1,129 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + kubeinformers "k8s.io/client-go/informers" + kubefake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/record" + + "github.com/secretflow/kuscia/pkg/common" + kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" + kusciafake "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/fake" + kusciascheme "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/scheme" + kusciainformers "github.com/secretflow/kuscia/pkg/crd/informers/externalversions" + "github.com/secretflow/kuscia/pkg/utils/nlog" +) + +const ( + testCaseSuspendNormal = iota + testCaseSuspendRestart +) + +func setSuspendJobStageRestart(job *kusciaapisv1alpha1.KusciaJob) { + job.Labels = make(map[string]string) + job.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobRestartStage) + job.Labels[common.LabelJobStageTrigger] = "alice" + job.Status.Phase = kusciaapisv1alpha1.KusciaJobFailed +} + +func setSuspendJobStage(job *kusciaapisv1alpha1.KusciaJob, testCase int) { + switch testCase { + case testCaseFailedRestart: + setSuspendJobStageRestart(job) + case testCaseFailedNormal: + return + } +} + +func TestSuspendedHandler_HandlePhase(t *testing.T) { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(nlog.Infof) + eventBroadcaster.StartRecordingToSink( + &typedcorev1.EventSinkImpl{Interface: kubefake.NewSimpleClientset().CoreV1().Events("default")}) + assert.NoError(t, kusciascheme.AddToScheme(scheme.Scheme)) + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kuscia-job-controller"}) + kubeFakeClient := kubefake.NewSimpleClientset() + kubeInformersFactory := kubeinformers.NewSharedInformerFactory(kubeFakeClient, 0) + + nsInformer := kubeInformersFactory.Core().V1().Namespaces() + type fields struct { + recorder record.EventRecorder + } + type args struct { + kusciaJob *kusciaapisv1alpha1.KusciaJob + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr assert.ErrorAssertionFunc + testCase int + }{ + { + name: "test normal suspend", + fields: fields{ + recorder: recorder, + }, + args: args{ + kusciaJob: makeKusciaJob(KusciaJobForShapeTree, + kusciaapisv1alpha1.KusciaJobScheduleModeBestEffort, 2, nil), + }, + want: false, + wantErr: assert.NoError, + }, { + name: "test restart stage", + fields: fields{ + recorder: recorder, + }, + args: args{ + kusciaJob: makeKusciaJob(KusciaJobForShapeTree, + kusciaapisv1alpha1.KusciaJobScheduleModeBestEffort, 2, nil), + }, + want: true, + wantErr: assert.NoError, + testCase: testCaseFailedRestart, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setSuspendJobStage(tt.args.kusciaJob, tt.testCase) + kusciaClient := kusciafake.NewSimpleClientset() + kusciaInformerFactory := kusciainformers.NewSharedInformerFactory(kusciaClient, 5*time.Minute) + deps := &Dependencies{ + KusciaClient: kusciaClient, + KusciaTaskLister: kusciaInformerFactory.Kuscia().V1alpha1().KusciaTasks().Lister(), + NamespaceLister: nsInformer.Lister(), + DomainLister: kusciaInformerFactory.Kuscia().V1alpha1().Domains().Lister(), + EnableWorkloadApprove: true, + } + s := NewSuspendedHandler(deps) + got, err := s.HandlePhase(tt.args.kusciaJob) + if !tt.wantErr(t, err, fmt.Sprintf("HandlePhase(%v)", tt.args.kusciaJob)) { + return + } + assert.Equalf(t, tt.want, got, "HandlePhase(%v)", tt.args.kusciaJob) + }) + } +} diff --git a/pkg/controllers/kusciatask/controller.go b/pkg/controllers/kusciatask/controller.go index 21ca77ad..26589ba1 100644 --- a/pkg/controllers/kusciatask/controller.go +++ b/pkg/controllers/kusciatask/controller.go @@ -447,7 +447,6 @@ func (c *Controller) processNextWorkItem() bool { metrics.TaskRequeueCount.WithLabelValues(key).Inc() // Put the item back on the taskQueue to handle any transient errors. c.taskQueue.AddRateLimited(key) - nlog.Warnf("Error handling %q, re-queuing", key) return fmt.Errorf("error handling %q, %v", key, err) } @@ -540,11 +539,11 @@ func (c *Controller) syncHandler(key string) (retErr error) { } // Update kusciatask - if err = c.updateTaskStatus(sharedTask, kusciaTask); err != nil { + if err = c.updateTaskStatus(sharedTask, kusciaTask); err != nil && !k8serrors.IsConflict(err) { return fmt.Errorf("failed to update status for kusciaTask %q, %v", key, err) } - nlog.Infof("Finished syncing kusciatask %q (%v)", key, time.Since(startTime)) + nlog.Infof("Finish syncing KusciaTask %q (%v)", key, time.Since(startTime)) return nil } diff --git a/pkg/controllers/kusciatask/handler/pending_handler.go b/pkg/controllers/kusciatask/handler/pending_handler.go index 2e58bd48..388fdfd9 100644 --- a/pkg/controllers/kusciatask/handler/pending_handler.go +++ b/pkg/controllers/kusciatask/handler/pending_handler.go @@ -103,6 +103,7 @@ func (h *PendingHandler) Handle(kusciaTask *kusciaapisv1alpha1.KusciaTask) (need curKtStatus := kusciaTask.Status.DeepCopy() refreshKtResourcesStatus(h.kubeClient, h.podsLister, h.servicesLister, curKtStatus) + h.initPartyTaskStatus(kusciaTask, curKtStatus) if !reflect.DeepEqual(kusciaTask.Status, curKtStatus) { needUpdate = true kusciaTask.Status = *curKtStatus @@ -213,6 +214,26 @@ func (h *PendingHandler) createTaskResources(kusciaTask *kusciaapisv1alpha1.Kusc return nil } +func (h *PendingHandler) initPartyTaskStatus(kusciaTask *kusciaapisv1alpha1.KusciaTask, ktStatus *kusciaapisv1alpha1.KusciaTaskStatus) { + for _, party := range kusciaTask.Spec.Parties { + setPartyTaskStatus(party, ktStatus) + } +} + +func setPartyTaskStatus(partyInfo kusciaapisv1alpha1.PartyInfo, ktStatus *kusciaapisv1alpha1.KusciaTaskStatus) { + for _, ptStatus := range ktStatus.PartyTaskStatus { + if ptStatus.DomainID == partyInfo.DomainID && ptStatus.Role == partyInfo.Role { + return + } + } + + ktStatus.PartyTaskStatus = append(ktStatus.PartyTaskStatus, kusciaapisv1alpha1.PartyTaskStatus{ + DomainID: partyInfo.DomainID, + Role: partyInfo.Role, + Phase: kusciaapisv1alpha1.TaskPending, + }) +} + func (h *PendingHandler) taskRunning(now metav1.Time, kusciaTask *kusciaapisv1alpha1.KusciaTask) bool { // Check if there is a pod in running status, // If there is, it indicates that the task status can be converted to running diff --git a/pkg/controllers/kusciatask/handler/running_handler.go b/pkg/controllers/kusciatask/handler/running_handler.go index 80083a13..0d8d95e8 100644 --- a/pkg/controllers/kusciatask/handler/running_handler.go +++ b/pkg/controllers/kusciatask/handler/running_handler.go @@ -361,7 +361,9 @@ func buildOuterPartyTaskStatus(taskStatus *kusciaapisv1alpha1.KusciaTaskStatus, outerPartyTaskStatus := &kusciaapisv1alpha1.PartyTaskStatus{ DomainID: trgParty.DomainID, Role: trgParty.Role, + Phase: kusciaapisv1alpha1.TaskPending, } + for _, s := range taskStatus.PartyTaskStatus { if s.DomainID == outerPartyTaskStatus.DomainID && s.Role == outerPartyTaskStatus.Role { outerPartyTaskStatus.Phase = s.Phase diff --git a/pkg/controllers/taskresourcegroup/controller.go b/pkg/controllers/taskresourcegroup/controller.go index 760b0bd8..abe37363 100644 --- a/pkg/controllers/taskresourcegroup/controller.go +++ b/pkg/controllers/taskresourcegroup/controller.go @@ -513,6 +513,10 @@ func (c *Controller) updateTaskResourceGroupStatus(ctx context.Context, oldTrg, return nil } + if k8serrors.IsConflict(err) { + return nil + } + nlog.Warnf("Failed to update task resource group %q status, %v", newTrg.Name, err) if i >= statusUpdateRetries { break diff --git a/pkg/crd/apis/kuscia/v1alpha1/domainroute_types.go b/pkg/crd/apis/kuscia/v1alpha1/domainroute_types.go index 2a0f66ea..b812b97d 100644 --- a/pkg/crd/apis/kuscia/v1alpha1/domainroute_types.go +++ b/pkg/crd/apis/kuscia/v1alpha1/domainroute_types.go @@ -194,7 +194,11 @@ type DomainRouteSpec struct { // Endpoint defines address for the source to access destination. // +optional Endpoint DomainEndpoint `json:"endpoint,omitempty"` - // Transit entity. If it is not empty, the requests between nodes need to be transferred through a third party. + // Transit entity. If transitMethod is THIRD-DOMAIN, + // requests from source to destination need to be transferred through + // a third party, domain field must be set. If transitMethod is + // REVERSE-TUNNEL, requests from source to destination need to be + // transferred through local gateway chunked transfer encoding. // +optional Transit *Transit `json:"transit,omitempty"` // AuthenticationType describes how destination authenticates the source's request. @@ -242,11 +246,24 @@ type DomainPort struct { // Transit defines the information of the transit entity used to forward the request. type Transit struct { - // DomainTransit means to forward the request through the domain. + // TransitMethod means to forward the request through a specific entity, THIRD-DOMAIN by default. + // +kubebuilder:validation:Enum=THIRD-DOMAIN;REVERSE-TUNNEL + // +optional + TransitMethod TransitMethodType `json:"transitMethod"` + // DomainTransit defines the information of the third domain. // +optional Domain *DomainTransit `json:"domain,omitempty"` } +type TransitMethodType string + +const ( + // TransitMethodThirdDomain means to forward the request through the third domain. + TransitMethodThirdDomain TransitMethodType = "THIRD-DOMAIN" + // TransitMethodReverseTunnel means to forward the request through reverse tunneling between envoys. + TransitMethodReverseTunnel TransitMethodType = "REVERSE-TUNNEL" +) + // DomainTransit defines the information of the transit domain. type DomainTransit struct { DomainID string `json:"domainID"` diff --git a/pkg/crd/apis/kuscia/v1alpha1/kusciajob_types.go b/pkg/crd/apis/kuscia/v1alpha1/kusciajob_types.go index 153b8132..f3820c18 100644 --- a/pkg/crd/apis/kuscia/v1alpha1/kusciajob_types.go +++ b/pkg/crd/apis/kuscia/v1alpha1/kusciajob_types.go @@ -59,6 +59,7 @@ const ( JobStopStage JobStage = "Stop" JobCancelStage JobStage = "Cancel" JobRestartStage JobStage = "Restart" + JobSuspendStage JobStage = "Suspend" ) // KusciaJobSpec defines the information of kuscia job spec. @@ -292,6 +293,9 @@ const ( // KusciaJobApprovalReject means the job is rejected by some parties. KusciaJobApprovalReject KusciaJobPhase = "ApprovalReject" + + // KusciaJobSuspended means the job has been suspended by some parties. + KusciaJobSuspended KusciaJobPhase = "Suspended" ) type JobStagePhase string @@ -303,8 +307,17 @@ const ( JobStartStageSucceeded JobStagePhase = "JobStartStageSucceeded" JobStartStageFailed JobStagePhase = "JobStartStageFailed" + JobRestartStageSucceeded JobStagePhase = "JobRestartStageSucceeded" + JobRestartStageFailed JobStagePhase = "JobRestartStageFailed" + JobStopStageSucceeded JobStagePhase = "JobStopStageSucceeded" JobStopStageFailed JobStagePhase = "JobStopStageFailed" + + JobCancelStageSucceeded JobStagePhase = "JobCancelStageSucceeded" + JobCancelStageFailed JobStagePhase = "JobCancelStageFailed" + + JobSuspendStageSucceeded JobStagePhase = "JobSuspendStageSucceeded" + JobSuspendStageFailed JobStagePhase = "JobSuspendStageFailed" ) type JobApprovePhase string diff --git a/pkg/datamesh/service/domaindatasource.go b/pkg/datamesh/service/domaindatasource.go index a35a61f9..8fba0fa3 100644 --- a/pkg/datamesh/service/domaindatasource.go +++ b/pkg/datamesh/service/domaindatasource.go @@ -152,7 +152,7 @@ func (s domainDataSourceService) QueryDomainDataSource(ctx context.Context, requ encryptedInfo, exist := kusciaDomainDataSource.Spec.Data[encryptedInfo] if !exist { return &datamesh.QueryDomainDataSourceResponse{ - Status: utils.BuildErrorResponseStatus(errorcode.ErrQueryDomainDataSource, err.Error()), + Status: utils.BuildErrorResponseStatus(errorcode.ErrQueryDomainDataSource, "datasource crd encryptedInfo field is not exist"), } } info, err = s.decryptInfo(encryptedInfo) diff --git a/pkg/gateway/clusters/master.go b/pkg/gateway/clusters/master.go index e58cc1d7..bc94d77f 100644 --- a/pkg/gateway/clusters/master.go +++ b/pkg/gateway/clusters/master.go @@ -43,47 +43,47 @@ func GetMasterClusterName() string { return fmt.Sprintf("service-%s", utils.ServiceMasterProxy) } +func AddMasterProxyClusters(ctx context.Context, namespace string, config *config.MasterConfig) error { + masterProxyCluster, err := generateDefaultCluster(utils.ServiceMasterProxy, config.MasterProxy) + if err != nil { + nlog.Fatalf("Generate masterProxy Cluster fail, %v", err) + return err + } + + if err := xds.SetKeepAliveForDstCluster(masterProxyCluster, false); err != nil { + nlog.Error(err) + return err + } + if err := xds.AddOrUpdateCluster(masterProxyCluster); err != nil { + nlog.Error(err) + return err + } + nlog.Infof("add Master cluster:%s", utils.ServiceMasterProxy) + waitMasterProxyReady(ctx, config.MasterProxy.Path, config, namespace) + return nil +} + func AddMasterClusters(ctx context.Context, namespace string, config *config.MasterConfig) error { - if !config.Master { - masterProxyCluster, err := generateDefaultCluster(utils.ServiceMasterProxy, config.MasterProxy) - if err != nil { - nlog.Fatalf("Generate masterProxy Cluster fail, %v", err) + config.Namespace = namespace + if config.APIServer != nil { + if err := addMasterCluster(utils.ServiceAPIServer, namespace, config.APIServer, config.APIWhitelist); err != nil { return err } + } - if err := xds.SetKeepAliveForDstCluster(masterProxyCluster, false); err != nil { - nlog.Error(err) - return err - } - if err := xds.AddOrUpdateCluster(masterProxyCluster); err != nil { - nlog.Error(err) + if config.KusciaStorage != nil { + if err := addMasterCluster(utils.ServiceKusciaStorage, namespace, config.KusciaStorage, nil); err != nil { return err } - nlog.Infof("add Master cluster:%s", utils.ServiceMasterProxy) - waitMasterProxyReady(ctx, config.MasterProxy.Path, config, namespace) - } else { - config.Namespace = namespace - if config.APIServer != nil { - if err := addMasterCluster(utils.ServiceAPIServer, namespace, config.APIServer, config.APIWhitelist); err != nil { - return err - } - } - - if config.KusciaStorage != nil { - if err := addMasterCluster(utils.ServiceKusciaStorage, namespace, config.KusciaStorage, nil); err != nil { - return err - } - } + } - if config.KusciaAPI != nil { - if err := addMasterCluster(utils.ServiceKusciaAPI, namespace, config.KusciaAPI, nil); err != nil { - return err - } + if config.KusciaAPI != nil { + if err := addMasterCluster(utils.ServiceKusciaAPI, namespace, config.KusciaAPI, nil); err != nil { + return err } - addMasterHandshakeRoute(xds.InternalRoute) - addMasterHandshakeRoute(xds.ExternalRoute) } - + addMasterHandshakeRoute(xds.InternalRoute) + addMasterHandshakeRoute(xds.ExternalRoute) return nil } diff --git a/pkg/gateway/commands/root.go b/pkg/gateway/commands/root.go index 1c9fade4..a6b5e131 100644 --- a/pkg/gateway/commands/root.go +++ b/pkg/gateway/commands/root.go @@ -16,6 +16,7 @@ package commands import ( "context" + "crypto/rsa" "fmt" "path/filepath" "time" @@ -24,10 +25,12 @@ import ( kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + "github.com/secretflow/kuscia/pkg/common" informers "github.com/secretflow/kuscia/pkg/crd/informers/externalversions" "github.com/secretflow/kuscia/pkg/gateway/clusters" "github.com/secretflow/kuscia/pkg/gateway/config" "github.com/secretflow/kuscia/pkg/gateway/controller" + "github.com/secretflow/kuscia/pkg/gateway/controller/poller" "github.com/secretflow/kuscia/pkg/gateway/metrics" "github.com/secretflow/kuscia/pkg/gateway/utils" "github.com/secretflow/kuscia/pkg/gateway/xds" @@ -53,45 +56,40 @@ func Run(ctx context.Context, gwConfig *config.GatewayConfig, clients *kubeconfi priKeyData := tls.EncodePKCS1PublicKey(gwConfig.DomainKey) // start xds server and envoy - err := StartXds(gwConfig) - if err != nil { + if err := StartXds(gwConfig); err != nil { return fmt.Errorf("start xds server fail with err: %v", err) } nlog.Infof("Start xds success") - // add master config - masterConfig, err := config.LoadMasterConfig(gwConfig.MasterConfig, clients.Kubeconfig) - nlog.Debugf("masterConfig is: %v", masterConfig) + var isMaster bool + var masterNamespace string - if err != nil { - return fmt.Errorf("failed to load masterConfig, detail-> %v", err) - } + isMaster = gwConfig.MasterConfig.IsMaster() - // add master Clusters - err = clusters.AddMasterClusters(ctx, gwConfig.DomainID, masterConfig) - if err != nil { - return fmt.Errorf("add master clusters fail, detail-> %v", err) - } - if !masterConfig.Master { - err = controller.RegisterDomain(gwConfig.DomainID, masterConfig.MasterProxy.Path, gwConfig.CsrData, prikey, afterRegisterHook) + if isMaster { + // add master config + masterConfig, err := config.LoadMasterConfig(gwConfig.MasterConfig, clients.Kubeconfig) if err != nil { - return fmt.Errorf("register self domain [%s] cert to master error: %s", gwConfig.DomainID, err.Error()) + return fmt.Errorf("failed to load masterConfig, detail-> %v", err) } - - for i := 1; i <= defaultHandshakeRetryCount; i++ { - err = controller.HandshakeToMaster(gwConfig.DomainID, masterConfig.MasterProxy.Path, prikey) - if err == nil { - break - } - nlog.Warnf("HandshakeToMaster error: %v", err) - if i == defaultHandshakeRetryCount { - return err - } - time.Sleep(defaultHandshakeRetryInterval) + nlog.Debugf("masterConfig is: %v", masterConfig) + // add master Clusters + err = clusters.AddMasterClusters(ctx, gwConfig.DomainID, masterConfig) + if err != nil { + return fmt.Errorf("add master clusters fail, detail-> %v", err) + } + nlog.Infof("Add master clusters successfully") + } else { + if err := utils.ProbePeerEndpoint(gwConfig.MasterConfig.Endpoint); err != nil { + return fmt.Errorf("[PROBE] failed to probe master endpoint %s, detail-> %v", gwConfig.MasterConfig.Endpoint, err) } - checkMasterProxyReady(ctx, gwConfig.DomainID, clients.KubeClient) + nlog.Infof("[PROBE] success to probe master endpoint %s", gwConfig.MasterConfig.Endpoint) + var err error + if masterNamespace, err = ConnectToMaster(ctx, gwConfig, clients, afterRegisterHook); err != nil { + return err + } + nlog.Infof("Add master proxy clusters successfully") } - nlog.Infof("Add master clusters success") // add interconn cluster interConnClusterConfig, err := config.LoadInterConnClusterConfig(gwConfig.TransportConfig, @@ -127,7 +125,7 @@ func Run(ctx context.Context, gwConfig *config.GatewayConfig, clients *kubeconfi if err != nil { return fmt.Errorf("load innerClientTLS fail, detail-> %v", err) } - ec, err := controller.NewEndpointsController(masterConfig.Master, clients.KubeClient, serviceInformer, + ec, err := controller.NewEndpointsController(isMaster, clients.KubeClient, serviceInformer, endpointsInformer, gwConfig.WhiteListFile, clientCert) @@ -140,8 +138,8 @@ func Run(ctx context.Context, gwConfig *config.GatewayConfig, clients *kubeconfi drInformer := kusciaInformerFactory.Kuscia().V1alpha1().DomainRoutes() drConfig := &controller.DomainRouteConfig{ Namespace: gwConfig.DomainID, - MasterNamespace: masterConfig.Namespace, - MasterConfig: masterConfig, + MasterNamespace: masterNamespace, + IsMaster: isMaster, CAKey: gwConfig.CAKey, CACert: gwConfig.CACert, Prikey: prikey, @@ -151,6 +149,9 @@ func Run(ctx context.Context, gwConfig *config.GatewayConfig, clients *kubeconfi drc := controller.NewDomainRouteController(drConfig, clients.KubeClient, clients.KusciaClient, drInformer) go drc.Run(ctx, concurrentSyncs*2, ctx.Done()) + pm, err := poller.NewPollManager(isMaster, gwConfig.DomainID, gwc.GatewayName(), serviceInformer, drInformer) + go pm.Run(concurrentSyncs, ctx.Done()) + // start runtime metrics collector go metrics.MonitorRuntimeMetrics(ctx.Done()) @@ -171,17 +172,69 @@ func Run(ctx context.Context, gwConfig *config.GatewayConfig, clients *kubeconfi return nil } -func checkMasterProxyReady(ctx context.Context, domainID string, kubeClient kubernetes.Interface) { +func ConnectToMaster(ctx context.Context, gwConfig *config.GatewayConfig, clients *kubeconfig.KubeClients, afterRegisterHook controller.AfterRegisterDomainHook) (string, error) { + // load master proxy config + masterProxyConfig, err := config.LoadMasterProxyConfig(gwConfig.MasterConfig) + if err != nil { + return "", fmt.Errorf("load master proxy config faild, detail -> %v", err) + } + prikey := gwConfig.DomainKey + domainID := gwConfig.DomainID + pathPrefix := masterProxyConfig.MasterProxy.Path + // add master proxy Clusters + err = clusters.AddMasterProxyClusters(ctx, domainID, masterProxyConfig) + if err != nil { + return "", fmt.Errorf("add master clusters failed, detail-> %v", err) + } + masterNamespace := masterProxyConfig.Namespace + // register domain cert to master + err = controller.RegisterDomain(domainID, pathPrefix, gwConfig.CsrData, prikey, afterRegisterHook) + if err != nil { + return "", fmt.Errorf("register self domain [%s] cert to master failed, detail -> %v", domainID, err) + } + // handshake to master + revisionToken, err := handshakeToMasterWithRetry(domainID, pathPrefix, prikey) + if err != nil { + return "", fmt.Errorf("handshake to master failed, detail -> %v", err) + } + // check master proxy ready + if err := checkMasterProxyReady(ctx, domainID, clients.KubeClient); err != nil { + return "", fmt.Errorf("check MasterProxy failed, detail -> %v", err) + } + // update domain route revision token + if err := controller.UpdateDomainRouteRevisionToken(clients.KusciaClient, domainID, common.GenDomainRouteName(domainID, masterNamespace), revisionToken); err != nil { + return "", fmt.Errorf("update domainroute revision token failed, detail -> %v", err) + } + return masterNamespace, nil +} + +func checkMasterProxyReady(ctx context.Context, domainID string, kubeClient kubernetes.Interface) error { var err error times := 5 for i := 0; i < times; i++ { if _, err = kubeClient.CoreV1().Pods(domainID).List(ctx, metav1.ListOptions{Limit: 1}); err == nil { nlog.Info("Check MasterProxy ready") - return + return nil } time.Sleep(time.Second) } - nlog.Fatalf("Check MasterProxy failed: %v", err.Error()) + return err +} + +func handshakeToMasterWithRetry(domainID, pathPrefix string, prikey *rsa.PrivateKey) (*controller.RevisionToken, error) { + var err error + for i := 1; i <= defaultHandshakeRetryCount; i++ { + revisionToken, err := controller.HandshakeToMaster(domainID, pathPrefix, prikey) + if err == nil { + return revisionToken, nil + } + nlog.Warnf("HandshakeToMaster error: %v", err) + if i == defaultHandshakeRetryCount { + return nil, err + } + time.Sleep(defaultHandshakeRetryInterval) + } + return nil, err } func StartXds(gwConfig *config.GatewayConfig) error { diff --git a/pkg/gateway/config/inner_config.go b/pkg/gateway/config/inner_config.go index 83126c4e..608b5044 100644 --- a/pkg/gateway/config/inner_config.go +++ b/pkg/gateway/config/inner_config.go @@ -26,62 +26,57 @@ import ( "github.com/secretflow/kuscia/pkg/utils/nlog" ) +func LoadMasterProxyConfig(masterConfig *kusciaconfig.MasterConfig) (*MasterConfig, error) { + proxyCluster, err := LoadClusterConfig(masterConfig.TLSConfig, masterConfig.Endpoint) + if err != nil { + return nil, err + } + return &MasterConfig{ + Master: false, + MasterProxy: proxyCluster, + }, nil +} + func LoadMasterConfig(masterConfig *kusciaconfig.MasterConfig, kubeConfig *restclient.Config) (*MasterConfig, error) { - isMaster := false - if masterConfig.APIServer != nil { - isMaster = true + apiCert, err := LoadTLSCertByKubeConfig(kubeConfig) + if err != nil { + return nil, err + } + + protocol, host, port, path, err := utils.ParseURL(kubeConfig.Host) + if err != nil { + return nil, err } - if isMaster { - apiCert, err := LoadTLSCertByKubeConfig(kubeConfig) + var storageCluster *ClusterConfig + if masterConfig.KusciaStorage != nil { + storageCluster, err = LoadClusterConfig(masterConfig.KusciaStorage.TLSConfig, + masterConfig.KusciaStorage.Endpoint) if err != nil { return nil, err } + } - protocol, host, port, path, err := utils.ParseURL(kubeConfig.Host) + var kusciaAPICluster *ClusterConfig + if masterConfig.KusciaAPI != nil { + kusciaAPICluster, err = LoadClusterConfig(masterConfig.KusciaAPI.TLSConfig, masterConfig.KusciaAPI.Endpoint) if err != nil { return nil, err } - - var storageCluster *ClusterConfig - if masterConfig.KusciaStorage != nil { - storageCluster, err = LoadClusterConfig(masterConfig.KusciaStorage.TLSConfig, - masterConfig.KusciaStorage.Endpoint) - if err != nil { - return nil, err - } - } - - var kusciaAPICluster *ClusterConfig - if masterConfig.KusciaAPI != nil { - kusciaAPICluster, err = LoadClusterConfig(masterConfig.KusciaAPI.TLSConfig, masterConfig.KusciaAPI.Endpoint) - if err != nil { - return nil, err - } - } - - return &MasterConfig{ - Master: true, - APIServer: &ClusterConfig{ - Host: host, - Port: port, - Path: path, - Protocol: protocol, - TLSCert: apiCert, - }, - KusciaStorage: storageCluster, - KusciaAPI: kusciaAPICluster, - APIWhitelist: masterConfig.APIWhitelist, - }, nil } - proxyCluster, err := LoadClusterConfig(masterConfig.TLSConfig, masterConfig.Endpoint) - if err != nil { - return nil, err - } return &MasterConfig{ - Master: false, - MasterProxy: proxyCluster, + Master: true, + APIServer: &ClusterConfig{ + Host: host, + Port: port, + Path: path, + Protocol: protocol, + TLSCert: apiCert, + }, + KusciaStorage: storageCluster, + KusciaAPI: kusciaAPICluster, + APIWhitelist: masterConfig.APIWhitelist, }, nil } diff --git a/pkg/gateway/controller/domain_route.go b/pkg/gateway/controller/domain_route.go index bd5755bd..312432f3 100644 --- a/pkg/gateway/controller/domain_route.go +++ b/pkg/gateway/controller/domain_route.go @@ -47,6 +47,8 @@ import ( kusciacrypt "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_crypt/v3" headerDecorator "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_header_decorator/v3" + kusciapoller "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_poller/v3" + kusciareceiver "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_receiver/v3" kusciatokenauth "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_token_auth/v3" "github.com/secretflow/kuscia/pkg/common" @@ -55,7 +57,6 @@ import ( kusciaextv1alpha1 "github.com/secretflow/kuscia/pkg/crd/informers/externalversions/kuscia/v1alpha1" kuscialistersv1alpha1 "github.com/secretflow/kuscia/pkg/crd/listers/kuscia/v1alpha1" "github.com/secretflow/kuscia/pkg/gateway/clusters" - "github.com/secretflow/kuscia/pkg/gateway/config" "github.com/secretflow/kuscia/pkg/gateway/controller/interconn" "github.com/secretflow/kuscia/pkg/gateway/utils" "github.com/secretflow/kuscia/pkg/gateway/xds" @@ -75,7 +76,7 @@ const ( type DomainRouteConfig struct { Namespace string MasterNamespace string - MasterConfig *config.MasterConfig + IsMaster bool CAKey *rsa.PrivateKey CACert *x509.Certificate Prikey *rsa.PrivateKey @@ -86,7 +87,7 @@ type DomainRouteConfig struct { type DomainRouteController struct { gateway *kusciaapisv1alpha1.Gateway masterNamespace string - masterConfig *config.MasterConfig + isMaser bool CaCertData []byte CaCert *x509.Certificate CaKey *rsa.PrivateKey @@ -133,7 +134,7 @@ func NewDomainRouteController( CaCertData: drConfig.CACert.Raw, CaCert: drConfig.CACert, CaKey: drConfig.CAKey, - masterConfig: drConfig.MasterConfig, + isMaser: drConfig.IsMaster, prikey: drConfig.Prikey, prikeyData: drConfig.PrikeyData, kubeClient: kubeClient, @@ -252,13 +253,19 @@ func (c *DomainRouteController) syncHandler(ctx context.Context, key string) err return err } - if dr.Spec.Source == c.gateway.Namespace && dr.Spec.Transit == nil && dr.Spec.Destination != c.masterNamespace { + // try to update poller && receiver rule first + if err := c.updatePollerReceiverFilter(dr); err != nil { + nlog.Warnf("Failed to update poller or receiver filter: %v", err) + } + + is3rdParty := utils.IsThirdPartyTransit(dr.Spec.Transit) + if dr.Spec.Source == c.gateway.Namespace && !is3rdParty && dr.Spec.Destination != c.masterNamespace { if err := c.addClusterWithEnvoy(dr); err != nil { return fmt.Errorf("add envoy cluster failed with %s", err.Error()) } } - if (dr.Spec.BodyEncryption != nil || (dr.Spec.AuthenticationType == kusciaapisv1alpha1.DomainAuthenticationToken && dr.Spec.Transit == nil)) && + if (dr.Spec.BodyEncryption != nil || (dr.Spec.AuthenticationType == kusciaapisv1alpha1.DomainAuthenticationToken && !is3rdParty)) && (dr.Spec.TokenConfig.TokenGenMethod == kusciaapisv1alpha1.TokenGenMethodRSA || dr.Spec.TokenConfig.TokenGenMethod == kusciaapisv1alpha1.TokenGenUIDRSA) { if dr.Spec.Source == c.gateway.Namespace && dr.Status.TokenStatus.RevisionInitializer == c.gateway.Name { if dr.Status.TokenStatus.RevisionToken.Token == "" { @@ -267,7 +274,7 @@ func (c *DomainRouteController) syncHandler(ctx context.Context, key string) err c.handshakeCache.Add(dr.Name, dr.Name, 2*time.Minute) defer c.handshakeCache.Delete(dr.Name) if err := func() error { - if dr.Spec.Transit == nil { + if !is3rdParty { if err := c.setKeepAliveForDstClusters(dr, false); err != nil { return fmt.Errorf("disable keep-alive fail for DomainRoute: %s err: %v", key, err) } @@ -321,14 +328,13 @@ func (c *DomainRouteController) addDomainRoute(obj interface{}) { return } - if newDomainRoute.Spec.Source == c.gateway.Namespace && newDomainRoute.Spec.Transit == nil { + if newDomainRoute.Spec.Source == c.gateway.Namespace && !utils.IsThirdPartyTransit(newDomainRoute.Spec.Transit) { drs, err := c.domainRouteLister.List(labels.Everything()) if err != nil { utilruntime.HandleError(fmt.Errorf("list DomainRoute failed with: %s", err.Error())) - } for _, dr := range drs { - if dr.Spec.Transit != nil && dr.Spec.Transit.Domain.DomainID == newDomainRoute.Spec.Destination { + if utils.IsThirdPartyTransit(dr.Spec.Transit) && dr.Spec.Transit.Domain.DomainID == newDomainRoute.Spec.Destination { c.workqueue.Add(fmt.Sprintf("%s/%s", dr.Namespace, dr.Name)) } } @@ -422,7 +428,34 @@ func (c *DomainRouteController) addClusterWithEnvoy(dr *kusciaapisv1alpha1.Domai return nil } +func (c *DomainRouteController) updatePollerReceiverFilter(dr *kusciaapisv1alpha1.DomainRoute) error { + if dr.Spec.Source == c.gateway.Namespace { // internal + if utils.IsGatewayTceTransit(dr.Spec.Transit) { + rule := kusciareceiver.ReceiverRule{ + Source: dr.Spec.Source, + Destination: dr.Spec.Destination, + } + if err := xds.UpdateReceiverRules(&rule, true); err != nil { + return err + } + } + + } else if dr.Spec.Destination == c.gateway.Namespace { // external + if !utils.IsThirdPartyTransit(dr.Spec.Transit) { + if utils.IsGatewayTceTransit(dr.Spec.Transit) { + pollHeader := generatePollHeaders(dr) + if err := xds.UpdatePoller(pollHeader, true); err != nil { + return err + } + } + } + } + + return nil +} + func (c *DomainRouteController) updateEnvoyRule(dr *kusciaapisv1alpha1.DomainRoute, tokens []*Token) error { + // TODO domain route from current ns to master can't be transit route, is it correct, or else why? if dr.Spec.Destination == c.masterNamespace && dr.Spec.Source == c.gateway.Namespace { token := tokens[len(tokens)-1] cl, err := xds.QueryCluster(clusters.GetMasterClusterName()) @@ -460,7 +493,7 @@ func (c *DomainRouteController) updateEnvoyRule(dr *kusciaapisv1alpha1.DomainRou // next step with two cases // case1: transit route, just clone routing rule from source-to-transitDomainID - if dr.Spec.Transit != nil { + if utils.IsThirdPartyTransit(dr.Spec.Transit) { return c.updateRoutingRule(dr) } @@ -472,7 +505,7 @@ func (c *DomainRouteController) updateEnvoyRule(dr *kusciaapisv1alpha1.DomainRou return c.setKeepAliveForDstClusters(dr, true) } else if dr.Spec.Destination == c.gateway.Namespace { // external - if dr.Spec.Transit == nil { + if !utils.IsThirdPartyTransit(dr.Spec.Transit) { var tokenVals []string for _, token := range tokens { tokenVals = append(tokenVals, token.Token) @@ -502,14 +535,24 @@ func (c *DomainRouteController) deleteEnvoyRule(dr *kusciaapisv1alpha1.DomainRou if err := xds.DeleteVirtualHost(name, xds.InternalRoute); err != nil { return fmt.Errorf("delete virtual host %s failed with %v", name, err) } + if utils.IsGatewayTceTransit(dr.Spec.Transit) { + rule := kusciareceiver.ReceiverRule{ + Source: dr.Spec.Source, + Destination: dr.Spec.Destination, + } + if err := xds.UpdateReceiverRules(&rule, false); err != nil { + return err + } + } if dr.Spec.BodyEncryption != nil { rule := &kusciacrypt.CryptRule{ Source: dr.Spec.Source, Destination: dr.Spec.Destination, } + // directly return, why? return xds.UpdateEncryptRules(rule, false) } - if dr.Spec.Transit == nil { + if !utils.IsThirdPartyTransit(dr.Spec.Transit) { return xds.DeleteCluster(name) } } else if dr.Spec.Destination == c.gateway.Namespace { @@ -522,8 +565,7 @@ func (c *DomainRouteController) deleteEnvoyRule(dr *kusciaapisv1alpha1.DomainRou return err } } - - if dr.Spec.Transit == nil { + if !utils.IsThirdPartyTransit(dr.Spec.Transit) { sourceToken := &kusciatokenauth.TokenAuth_SourceToken{ Source: dr.Spec.Source, } @@ -534,6 +576,14 @@ func (c *DomainRouteController) deleteEnvoyRule(dr *kusciaapisv1alpha1.DomainRou if err := xds.UpdateTokenAuthAndHeaderDecorator(sourceToken, sourceHeader, false); err != nil { return err } + if utils.IsGatewayTceTransit(dr.Spec.Transit) { + sourceHeader := &kusciapoller.Poller_SourceHeader{ + Source: dr.Spec.Source, + } + if err := xds.UpdatePoller(sourceHeader, false); err != nil { + return err + } + } } } @@ -805,6 +855,23 @@ func generateRequestHeaders(dr *kusciaapisv1alpha1.DomainRoute) *headerDecorator return sourceHeader } +func generatePollHeaders(dr *kusciaapisv1alpha1.DomainRoute) *kusciapoller.Poller_SourceHeader { + if len(dr.Spec.RequestHeadersToAdd) == 0 { + return nil + } + sourceHeader := &kusciapoller.Poller_SourceHeader{ + Source: dr.Spec.Source, + } + for k, v := range dr.Spec.RequestHeadersToAdd { + entry := &kusciapoller.Poller_HeaderEntry{ + Key: k, + Value: v, + } + sourceHeader.Headers = append(sourceHeader.Headers, entry) + } + return sourceHeader +} + func (c *DomainRouteController) getClusterNamesByDomainRoute(dr *kusciaapisv1alpha1.DomainRoute) []string { var names []string if dr.Spec.Destination == c.masterNamespace && c.masterNamespace != c.gateway.Namespace { @@ -835,7 +902,7 @@ func (c *DomainRouteController) getDefaultClusterNameByDomainRoute(dr *kusciaapi func getHandshakeHost(dr *kusciaapisv1alpha1.DomainRoute) string { ns := dr.Spec.Destination - if dr.Spec.Transit != nil { + if utils.IsThirdPartyTransit(dr.Spec.Transit) { ns = dr.Spec.Transit.Domain.DomainID } return fmt.Sprintf("%s.%s.svc", utils.ServiceHandshake, ns) diff --git a/pkg/gateway/controller/gateway.go b/pkg/gateway/controller/gateway.go index a4a95034..a9835287 100644 --- a/pkg/gateway/controller/gateway.go +++ b/pkg/gateway/controller/gateway.go @@ -81,6 +81,10 @@ func NewGatewayController(namespace string, prikey *rsa.PrivateKey, kusciaClient return controller, nil } +func (c *GatewayController) GatewayName() string { + return c.hostname +} + // Run begins watching and syncing. func (c *GatewayController) Run(threadiness int, stopCh <-chan struct{}) { defer utilruntime.HandleCrash() diff --git a/pkg/gateway/controller/handshake.go b/pkg/gateway/controller/handshake.go index 2744bb25..c893dc93 100644 --- a/pkg/gateway/controller/handshake.go +++ b/pkg/gateway/controller/handshake.go @@ -33,6 +33,7 @@ import ( "github.com/secretflow/kuscia/pkg/common" kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" + clientset "github.com/secretflow/kuscia/pkg/crd/clientset/versioned" "github.com/secretflow/kuscia/pkg/gateway/clusters" "github.com/secretflow/kuscia/pkg/gateway/utils" "github.com/secretflow/kuscia/pkg/gateway/xds" @@ -65,12 +66,19 @@ type Token struct { Version int64 } +type RevisionToken struct { + RawToken []byte + PublicKey *rsa.PublicKey + ExpirationTime int64 + Revision int32 +} + type AfterRegisterDomainHook func(response *handshake.RegisterResponse) func (c *DomainRouteController) startHandShakeServer(port uint32) { mux := http.NewServeMux() mux.HandleFunc(utils.GetHandshakePathSuffix(), c.handShakeHandle) - if c.masterConfig != nil && c.masterConfig.Master { + if c.isMaser { mux.HandleFunc("/register", c.registerHandle) } @@ -307,25 +315,42 @@ func (c *DomainRouteController) sourceInitiateHandShake(dr *kusciaapisv1alpha1.D } // The final token is encrypted with the local private key and stored in the status of domainroute - tokenEncrypted, err := encryptToken(&c.prikey.PublicKey, token) + revisionToken := &RevisionToken{ + RawToken: token, + PublicKey: &c.prikey.PublicKey, + Revision: resp.Token.Revision, + ExpirationTime: resp.Token.ExpirationTime, + } + + return UpdateDomainRouteRevisionToken(c.kusciaClient, dr.Namespace, dr.Name, revisionToken) +} + +func UpdateDomainRouteRevisionToken(kusciaClient clientset.Interface, namespace, drName string, revisionToken *RevisionToken) error { + tokenEncrypted, err := encryptToken(revisionToken.PublicKey, revisionToken.RawToken) + if err != nil { + return err + } + drLatest, err := kusciaClient.KusciaV1alpha1().DomainRoutes(namespace).Get(context.Background(), drName, metav1.GetOptions{}) if err != nil { return err } - drLatest, _ := c.domainRouteLister.DomainRoutes(dr.Namespace).Get(dr.Name) - drCopy := drLatest.DeepCopy() tn := metav1.Now() - drCopy.Status.IsDestinationAuthorized = true - drCopy.Status.TokenStatus.RevisionToken.Token = tokenEncrypted - drCopy.Status.TokenStatus.RevisionToken.Revision = int64(resp.Token.Revision) - drCopy.Status.TokenStatus.RevisionToken.IsReady = true - drCopy.Status.TokenStatus.RevisionToken.RevisionTime = tn - if drCopy.Spec.TokenConfig.RollingUpdatePeriod == 0 { - drCopy.Status.TokenStatus.RevisionToken.ExpirationTime = metav1.NewTime(tn.AddDate(100, 0, 0)) + drUpdate := drLatest.DeepCopy() + drUpdateStatus := &drUpdate.Status + drUpdateRevisionToken := &drUpdateStatus.TokenStatus.RevisionToken + drUpdateStatus.IsDestinationAuthorized = true + drUpdateRevisionToken.Token = tokenEncrypted + drUpdateRevisionToken.Revision = int64(revisionToken.Revision) + drUpdateRevisionToken.IsReady = true + drUpdateRevisionToken.RevisionTime = tn + if drUpdate.Spec.TokenConfig.RollingUpdatePeriod == 0 { + drUpdateRevisionToken.ExpirationTime = metav1.NewTime(tn.AddDate(100, 0, 0)) } else { - tTx := time.Unix(resp.Token.ExpirationTime/int64(time.Second), resp.Token.ExpirationTime%int64(time.Second)) - drCopy.Status.TokenStatus.RevisionToken.ExpirationTime = metav1.NewTime(tTx) + expirationTime := time.Unix(revisionToken.ExpirationTime/int64(time.Second), revisionToken.ExpirationTime%int64(time.Second)) + drUpdateRevisionToken.ExpirationTime = metav1.NewTime(expirationTime) } - _, err = c.kusciaClient.KusciaV1alpha1().DomainRoutes(drCopy.Namespace).UpdateStatus(context.Background(), drCopy, metav1.UpdateOptions{}) + + _, err = kusciaClient.KusciaV1alpha1().DomainRoutes(drUpdate.Namespace).UpdateStatus(context.Background(), drUpdate, metav1.UpdateOptions{}) return err } @@ -573,15 +598,17 @@ func (c *DomainRouteController) DestReplyHandshake(req *handshake.HandShakeReque func (c *DomainRouteController) parseToken(dr *kusciaapisv1alpha1.DomainRoute, routeKey string) ([]*Token, error) { var tokens []*Token var err error + var is3rdParty bool - if (dr.Spec.Transit != nil && dr.Spec.BodyEncryption == nil) || - (dr.Spec.Transit == nil && dr.Spec.AuthenticationType == kusciaapisv1alpha1.DomainAuthenticationMTLS) || - (dr.Spec.Transit == nil && dr.Spec.AuthenticationType == kusciaapisv1alpha1.DomainAuthenticationNone) { + is3rdParty = utils.IsThirdPartyTransit(dr.Spec.Transit) + if (is3rdParty && dr.Spec.BodyEncryption == nil) || + (!is3rdParty && dr.Spec.AuthenticationType == kusciaapisv1alpha1.DomainAuthenticationMTLS) || + (!is3rdParty && dr.Spec.AuthenticationType == kusciaapisv1alpha1.DomainAuthenticationNone) { tokens = append(tokens, &Token{Token: NoopToken}) return tokens, err } - if (dr.Spec.Transit == nil && dr.Spec.AuthenticationType != kusciaapisv1alpha1.DomainAuthenticationToken) || + if (!is3rdParty && dr.Spec.AuthenticationType != kusciaapisv1alpha1.DomainAuthenticationToken) || dr.Spec.TokenConfig == nil { return tokens, fmt.Errorf("invalid DomainRoute: %v", dr.Spec) } @@ -671,7 +698,7 @@ func exists(slice []string, val string) bool { return false } -func HandshakeToMaster(domainID string, pathPrefix string, prikey *rsa.PrivateKey) error { +func HandshakeToMaster(domainID string, pathPrefix string, prikey *rsa.PrivateKey) (*RevisionToken, error) { handshankeReq := &handshake.HandShakeRequest{ DomainId: domainID, RequestTime: time.Now().UnixNano(), @@ -710,29 +737,34 @@ func HandshakeToMaster(domainID string, pathPrefix string, prikey *rsa.PrivateKe if resp.Status.Code != 0 { err := fmt.Errorf("handshake to master fail, return error:%v", resp.Status.Message) nlog.Error(err) - return err + return nil, err } token, err := decryptToken(prikey, resp.Token.Token, tokenByteSize) if err != nil { nlog.Errorf("decrypt auth token from master error: %s", err.Error()) - return err + return nil, err } c, err := xds.QueryCluster(clusters.GetMasterClusterName()) if err != nil { nlog.Error(err) - return err + return nil, err } if err := clusters.AddMasterProxyVirtualHost(c.Name, pathPrefix, utils.ServiceMasterProxy, domainID, base64.StdEncoding.EncodeToString(token)); err != nil { nlog.Error(err) - return err + return nil, err } if err = xds.SetKeepAliveForDstCluster(c, true); err != nil { nlog.Error(err) - return err + return nil, err } if err = xds.AddOrUpdateCluster(c); err != nil { nlog.Error(err) - return err + return nil, err } - return nil + return &RevisionToken{ + RawToken: token, + PublicKey: &prikey.PublicKey, + ExpirationTime: resp.Token.ExpirationTime, + Revision: resp.Token.Revision, + }, nil } diff --git a/pkg/gateway/controller/poller/poll_client.go b/pkg/gateway/controller/poller/poll_client.go new file mode 100644 index 00000000..8fcd9c28 --- /dev/null +++ b/pkg/gateway/controller/poller/poll_client.go @@ -0,0 +1,184 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package poller + +import ( + "context" + "errors" + "fmt" + "io" + "math/rand" + "net/http" + "time" + + "github.com/secretflow/kuscia/pkg/common" + "github.com/secretflow/kuscia/pkg/utils/nlog" +) + +const ( + defaultRequestTimeout = time.Second * 300 + defaultReceiverTimeoutSeconds = 300 + defaultForceReconnectIntervalBase = time.Second * 270 + defaultForceReconnectIntervalMaxJitter = 10 * 1000 + defaultPollRetryInterval = time.Second * 10 +) + +type PollConnection struct { + connID string + client *http.Client + receiverAddress string + serviceName string + connected chan struct{} + disconnected chan struct{} + cancel context.CancelFunc + + receiverTimeoutSeconds int + requestTimeout time.Duration + forceReconnectIntervalBase time.Duration + forceReconnectIntervalMaxJitter int + pollRetryInterval time.Duration +} + +func NewPollConnection(index int, client *http.Client, receiverDomain, serviceName string) *PollConnection { + return &PollConnection{ + connID: fmt.Sprintf("%s:%s:%d", serviceName, receiverDomain, index), + client: client, + receiverAddress: fmt.Sprintf("%s.%s.svc", common.ReceiverServiceName, receiverDomain), + serviceName: serviceName, + connected: make(chan struct{}), + disconnected: make(chan struct{}), + receiverTimeoutSeconds: defaultReceiverTimeoutSeconds, + requestTimeout: defaultRequestTimeout, + forceReconnectIntervalBase: defaultForceReconnectIntervalBase, + forceReconnectIntervalMaxJitter: defaultForceReconnectIntervalMaxJitter, + pollRetryInterval: defaultPollRetryInterval, + } +} + +func (conn *PollConnection) Start(ctx context.Context) { + go func() { + defer close(conn.disconnected) + if err := conn.connect(ctx); err != nil { + if errors.Is(err, context.Canceled) { + nlog.Infof("Poll connection %q canceled", conn.connID) + } else if errors.Is(err, io.EOF) { + nlog.Infof("Poll connection %q closed", conn.connID) + } else if errors.Is(err, context.DeadlineExceeded) { + nlog.Infof("Poll connection %q context deadline exceeded", conn.connID) + } else if errors.Is(err, io.ErrUnexpectedEOF) { + nlog.Warnf("Poll connection %q encountered Unexpected EOF", conn.connID) + } else { + nlog.Errorf("Poll connection %q error, detail-> %v", conn.connID, err) + time.Sleep(conn.pollRetryInterval) + } + } + }() + + select { + case <-conn.disconnected: // connection disconnected + nlog.Infof("Poll connection %q disconnected", conn.connID) + return + case <-conn.connected: + } + + reconnectDuration := conn.forceReconnectIntervalBase + time.Duration(rand.Intn(conn.forceReconnectIntervalMaxJitter))*time.Millisecond + select { + case <-conn.disconnected: // connection disconnected + nlog.Infof("Poll connection %q disconnected", conn.connID) + return + case <-time.After(reconnectDuration): + nlog.Infof("Poll connection %q lasted for %v, prepare to reconnect", conn.connID, reconnectDuration) + return + } +} + +func (conn *PollConnection) Stop() { + if conn.cancel != nil { + conn.cancel() + } +} + +func (conn *PollConnection) connect(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, conn.requestTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://%s/poll?service=%s&timeout=%ds", + conn.receiverAddress, conn.serviceName, conn.receiverTimeoutSeconds), nil) + if err != nil { + return err + } + + resp, err := conn.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code %v", resp.StatusCode) + } + + close(conn.connected) + + nlog.Infof("Poll connection %q succeed, read body....", conn.connID) + + _, err = io.ReadAll(resp.Body) + return err +} + +type PollClient struct { + stopCh chan struct{} + client *http.Client + serviceName string + receiverDomain string +} + +func newPollClient(client *http.Client, serviceName, receiverDomain string) *PollClient { + return &PollClient{ + client: client, + serviceName: serviceName, + stopCh: make(chan struct{}), + receiverDomain: receiverDomain, + } +} + +func (pc *PollClient) Start() { + go pc.pollReceiver() +} + +func (pc *PollClient) pollReceiver() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + index := 0 + + for { + select { + case <-pc.stopCh: + nlog.Infof("Stop poll connection %v", buildPollerKey(pc.serviceName, pc.receiverDomain)) + return + default: + conn := NewPollConnection(index, pc.client, pc.receiverDomain, pc.serviceName) + conn.Start(ctx) + index++ + if index > 999999 { + index = 0 + } + } + } +} + +func (pc *PollClient) Stop() { + close(pc.stopCh) +} diff --git a/pkg/gateway/controller/poller/poll_client_test.go b/pkg/gateway/controller/poller/poll_client_test.go new file mode 100644 index 00000000..0fa39449 --- /dev/null +++ b/pkg/gateway/controller/poller/poll_client_test.go @@ -0,0 +1,104 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package poller + +import ( + "context" + "net" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func runTestReceiverServer(t *testing.T) { + http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Transfer-Encoding", "chunked") + flusher.Flush() + + <-req.Context().Done() + t.Log("Http request context done") + }) + + assert.NoError(t, http.ListenAndServe(":12345", nil)) +} + +func createTestPollConnection(index int) *PollConnection { + dialer := &net.Dialer{ + Timeout: time.Millisecond * 500, + } + client := &http.Client{ + Transport: &http.Transport{ + DialContext: dialer.DialContext, + }, + } + pc := NewPollConnection(index, client, "test", "test.alice.svc") + pc.receiverAddress = "127.0.0.1:12345" + pc.forceReconnectIntervalBase = time.Millisecond * 1000 + pc.forceReconnectIntervalMaxJitter = 1 + pc.requestTimeout = time.Millisecond * 2000 + pc.pollRetryInterval = time.Millisecond * 1 + return pc +} + +func TestPollConnection_Start(t *testing.T) { + go runTestReceiverServer(t) + time.Sleep(time.Second) + + t.Run("Connected", func(t *testing.T) { + done := make(chan struct{}) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + pc := createTestPollConnection(1) + pc.Start(ctx) + close(done) + }() + + select { + case <-done: + break + case <-time.After(time.Millisecond * 1800): + t.Fatal("timeout") + } + }) + + t.Run("Canceled", func(t *testing.T) { + done := make(chan struct{}) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + pc := createTestPollConnection(2) + pc.Start(ctx) + close(done) + }() + + cancel() + select { + case <-done: + break + case <-time.After(time.Millisecond * 1800): + t.Fatal("timeout") + } + }) +} diff --git a/pkg/gateway/controller/poller/poll_manager.go b/pkg/gateway/controller/poller/poll_manager.go new file mode 100644 index 00000000..4a77e3a7 --- /dev/null +++ b/pkg/gateway/controller/poller/poll_manager.go @@ -0,0 +1,506 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package poller + +import ( + "context" + "fmt" + "net" + "net/http" + "strings" + "sync" + "time" + + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + corev1informers "k8s.io/client-go/informers/core/v1" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + "github.com/secretflow/kuscia/pkg/common" + kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" + kusciaextv1alpha1 "github.com/secretflow/kuscia/pkg/crd/informers/externalversions/kuscia/v1alpha1" + kuscialistersv1alpha1 "github.com/secretflow/kuscia/pkg/crd/listers/kuscia/v1alpha1" + "github.com/secretflow/kuscia/pkg/gateway/utils" + "github.com/secretflow/kuscia/pkg/utils/nlog" + "github.com/secretflow/kuscia/pkg/utils/queue" +) + +const ( + processPeriod = time.Second + defaultSyncPeriod = 10 * time.Minute + maxRetries = 16 + + svcPollQueueName = "service-poll-queue" + drPollQueueName = "domain-route-poll-queue" +) + +type PollState int + +const ( + PollStateUnknown PollState = iota + PollStateNotPoll + PollStatePolling +) + +type PollManager struct { + serviceLister corelisters.ServiceLister + serviceListerSynced cache.InformerSynced + domainRouteLister kuscialistersv1alpha1.DomainRouteLister + domainRouteListerSynced cache.InformerSynced + svcQueue workqueue.RateLimitingInterface + drQueue workqueue.RateLimitingInterface + gatewayName string + selfNamespace string + isMaster bool + + client *http.Client + pollers map[string]map[string]*PollClient + pollersLock sync.Mutex + + sourcePollState map[string]PollState + pollStateLock sync.RWMutex +} + +func NewPollManager(isMaster bool, selfNamespace, gatewayName string, serviceInformer corev1informers.ServiceInformer, domainRouteInformer kusciaextv1alpha1.DomainRouteInformer) (*PollManager, error) { + dialer := &net.Dialer{ + Timeout: 10 * time.Second, + } + client := &http.Client{ + Transport: &http.Transport{ + DialContext: dialer.DialContext, + }, + } + pm := &PollManager{ + isMaster: isMaster, + gatewayName: gatewayName, + selfNamespace: selfNamespace, + serviceLister: serviceInformer.Lister(), + serviceListerSynced: serviceInformer.Informer().HasSynced, + domainRouteLister: domainRouteInformer.Lister(), + domainRouteListerSynced: domainRouteInformer.Informer().HasSynced, + svcQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), svcPollQueueName), + drQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), drPollQueueName), + client: client, + pollers: map[string]map[string]*PollClient{}, + sourcePollState: map[string]PollState{}, + } + + if err := pm.addServiceEventHandler(serviceInformer); err != nil { + return nil, err + } + if err := pm.addDomainRouteEventHandler(domainRouteInformer); err != nil { + return nil, err + } + + return pm, nil +} + +func (pm *PollManager) Run(workers int, stopCh <-chan struct{}) { + defer func() { + utilruntime.HandleCrash() + pm.svcQueue.ShutDown() + pm.drQueue.ShutDown() + }() + + nlog.Info("Waiting for informer caches to sync") + if !cache.WaitForNamedCacheSync("poll manager", stopCh, pm.serviceListerSynced, pm.domainRouteListerSynced) { + nlog.Fatal("failed to wait for caches to sync") + } + + nlog.Info("Starting poll manager ") + for i := 0; i < workers; i++ { + go wait.Until(pm.runServiceWorker, processPeriod, stopCh) + go wait.Until(pm.runDomainRouteWorker, processPeriod, stopCh) + } + + <-stopCh + nlog.Info("Shutting down poll manager") +} + +func (pm *PollManager) runServiceWorker() { + for queue.HandleQueueItem(context.Background(), svcPollQueueName, pm.svcQueue, pm.syncHandlerService, maxRetries) { + } +} + +func (pm *PollManager) runDomainRouteWorker() { + for queue.HandleQueueItem(context.Background(), drPollQueueName, pm.drQueue, pm.syncHandleDomainRoute, maxRetries) { + } +} + +func (pm *PollManager) addServiceEventHandler(serviceInformer corev1informers.ServiceInformer) error { + _, err := serviceInformer.Informer().AddEventHandlerWithResyncPeriod( + cache.FilteringResourceEventHandler{ + FilterFunc: func(obj interface{}) bool { + switch t := obj.(type) { + case *v1.Service: + return serviceFilter(t) + case cache.DeletedFinalStateUnknown: + if svc, ok := t.Obj.(*v1.Service); ok { + return serviceFilter(svc) + } + nlog.Errorf("Failed to decoding object, invalid type %T", t.Obj) + return false + default: + nlog.Errorf("Invalid service object") + return false + } + }, + Handler: cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + pm.enqueueService(obj) + }, + UpdateFunc: func(_, newObj interface{}) { + pm.enqueueService(newObj) + }, + DeleteFunc: func(obj interface{}) { + pm.enqueueService(obj) + }, + }, + }, + defaultSyncPeriod, + ) + + return err +} + +func serviceFilter(obj apismetav1.Object) bool { + if objLabels := obj.GetLabels(); objLabels != nil { + if objLabels[common.LabelPortScope] == string(kusciaapisv1alpha1.ScopeCluster) { + return true + } + } + return false +} + +func (pm *PollManager) enqueueService(obj interface{}) { + queue.EnqueueObjectWithKey(obj, pm.svcQueue) +} + +func (pm *PollManager) addDomainRouteEventHandler(domainRouteInformer kusciaextv1alpha1.DomainRouteInformer) error { + _, err := domainRouteInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + pm.handleDomainRouteObj(obj) + }, + UpdateFunc: func(_, newObj interface{}) { + pm.handleDomainRouteObj(newObj) + }, + DeleteFunc: func(obj interface{}) { + pm.handleDomainRouteObj(obj) + }, + }, + ) + + return err +} + +func (pm *PollManager) handleDomainRouteObj(obj interface{}) { + var domainRoute *kusciaapisv1alpha1.DomainRoute + + switch t := obj.(type) { + case *kusciaapisv1alpha1.DomainRoute: + domainRoute = t + case cache.DeletedFinalStateUnknown: + dr, ok := t.Obj.(*kusciaapisv1alpha1.DomainRoute) + if !ok { + nlog.Errorf("Failed to decoding object, invalid type %T", t.Obj) + return + } + + domainRoute = dr + default: + nlog.Errorf("Invalid domain route object") + return + } + + if !pm.domainRouteFilter(domainRoute) { + return + } + + key := buildDomainRouteKey(domainRoute.Name, domainRoute.Spec.Source) + + pm.drQueue.Add(key) + nlog.Debugf("Enqueue key: %q", key) +} + +func (pm *PollManager) domainRouteFilter(dr *kusciaapisv1alpha1.DomainRoute) bool { + if dr.Spec.Destination != pm.selfNamespace { + return false + } + + return true +} + +func (pm *PollManager) syncHandlerService(ctx context.Context, key string) error { + startTime := time.Now() + defer func() { + nlog.Debugf("Finished syncing service %q (%v)", key, time.Since(startTime)) + }() + + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return err + } + + service, err := pm.serviceLister.Services(namespace).Get(name) + if k8serrors.IsNotFound(err) { + pm.removePollConnection(name, nil) + return nil + } else if err != nil { + return err + } + + domains := pm.buildReceiverDomainsByService(service) + + pm.addPollConnection(name, domains) + + return nil +} + +func (pm *PollManager) buildReceiverDomainsByService(service *v1.Service) []string { + var retDomains []string + if service.Annotations != nil && service.Annotations[common.AccessDomainAnnotationKey] != "" { + domains := strings.Split(service.Annotations[common.AccessDomainAnnotationKey], ",") + for _, domain := range domains { + state := pm.getDomainPollState(domain) + if !isPollingState(state) { + nlog.Debugf("Need not to poll domain %s", domain) + continue + } + + // TODO Check if this instance needs to be processed + retDomains = append(retDomains, domain) + } + } else { + retDomains = pm.getPollingDomains() + } + + return retDomains +} + +func (pm *PollManager) syncHandleDomainRoute(ctx context.Context, key string) error { + startTime := time.Now() + defer func() { + nlog.Debugf("Finished syncing domain route %q (%v)", key, time.Since(startTime)) + }() + + name, sourceDomain, err := splitDomainRouteKey(key) + if err != nil { + return err + } + + dr, err := pm.domainRouteLister.DomainRoutes(pm.selfNamespace).Get(name) + if k8serrors.IsNotFound(err) { + pm.domainRouteDeleted(sourceDomain) + return nil + } else if err != nil { + return err + } + + if dr.Spec.Transit == nil || dr.Spec.Transit.TransitMethod != kusciaapisv1alpha1.TransitMethodReverseTunnel { + pm.needNotPollSource(dr) + return nil + } + + if err := pm.needPollSource(dr); err != nil { + return err + } + + return nil +} + +func (pm *PollManager) getDomainPollState(domain string) PollState { + pm.pollStateLock.RLock() + defer pm.pollStateLock.RUnlock() + + return pm.sourcePollState[domain] +} + +func (pm *PollManager) getPollingDomains() []string { + pm.pollStateLock.RLock() + defer pm.pollStateLock.RUnlock() + + var domains []string + for domain, state := range pm.sourcePollState { + if isPollingState(state) { + domains = append(domains, domain) + } + } + + return domains +} + +func (pm *PollManager) removeDRPollConnection(receiverDomain string) { + pm.removePollConnection(utils.ServiceAPIServer, []string{receiverDomain}) + pm.removePollConnection(utils.ServiceHandshake, []string{receiverDomain}) + + pm.handleServicesByDomainRoute(receiverDomain, func(serviceName string, receiverDomain string) { + nlog.Infof("Prepare to remove poll connection %q", buildPollerKey(serviceName, receiverDomain)) + pm.removePollConnection(serviceName, []string{receiverDomain}) + }) +} + +func (pm *PollManager) domainRouteDeleted(sourceDomain string) { + pm.pollStateLock.Lock() + defer pm.pollStateLock.Unlock() + + if isPollingState(pm.sourcePollState[sourceDomain]) { + delete(pm.sourcePollState, sourceDomain) + pm.removeDRPollConnection(sourceDomain) + } +} + +func (pm *PollManager) needNotPollSource(dr *kusciaapisv1alpha1.DomainRoute) { + pm.pollStateLock.Lock() + defer pm.pollStateLock.Unlock() + + if isPollingState(pm.sourcePollState[dr.Spec.Source]) { + pm.sourcePollState[dr.Spec.Source] = PollStateNotPoll + pm.removeDRPollConnection(dr.Spec.Source) + } +} + +func (pm *PollManager) needPollSource(dr *kusciaapisv1alpha1.DomainRoute) error { + pm.pollStateLock.Lock() + defer pm.pollStateLock.Unlock() + + source := dr.Spec.Source + if pm.sourcePollState[source] == PollStatePolling { + nlog.Debugf("DomainRoute %s has triggered polling", dr.Name) + return nil + } + pm.sourcePollState[source] = PollStatePolling + + receiverDomains := []string{source} + + if pm.isMaster { + pm.addPollConnection(utils.ServiceAPIServer, receiverDomains) + } + pm.addPollConnection(utils.ServiceHandshake, receiverDomains) + + pm.handleServicesByDomainRoute(source, func(serviceName string, receiverDomain string) { + nlog.Infof("Prepare to add poll connection %q", buildPollerKey(serviceName, receiverDomain)) + pm.addPollConnection(serviceName, []string{receiverDomain}) + }) + + return nil +} + +func (pm *PollManager) handleServicesByDomainRoute(sourceDomain string, pollConnectionHandler func(string, string)) { + services, err := pm.serviceLister.Services(pm.selfNamespace).List(labels.Everything()) + if err != nil { + nlog.Errorf("Failed to list services: %v", err) + return + } + + for _, service := range services { + if !serviceFilter(service) { + continue + } + if service.Annotations != nil && service.Annotations[common.AccessDomainAnnotationKey] != "" { + domains := strings.Split(service.Annotations[common.AccessDomainAnnotationKey], ",") + for _, domain := range domains { + if domain == sourceDomain { + pollConnectionHandler(service.Name, domain) + break + } + } + } else { + pollConnectionHandler(service.Name, sourceDomain) + } + } +} + +func (pm *PollManager) addPollConnection(serviceName string, domainList []string) { + if len(domainList) == 0 { + nlog.Debugf("No address for serviceName: %v, skip", serviceName) + return + } + + pm.pollersLock.Lock() + defer pm.pollersLock.Unlock() + + svcPoller, ok := pm.pollers[serviceName] + if !ok { + svcPoller = map[string]*PollClient{} + pm.pollers[serviceName] = svcPoller + } + + for _, domain := range domainList { + if _, ok := svcPoller[domain]; ok { + continue + } + + poller := newPollClient(pm.client, serviceName, domain) + svcPoller[domain] = poller + poller.Start() + + nlog.Infof("Add poll connection: %v", buildPollerKey(serviceName, domain)) + } +} + +func (pm *PollManager) removePollConnection(serviceName string, domainList []string) { + pm.pollersLock.Lock() + defer pm.pollersLock.Unlock() + + svcPoller, ok := pm.pollers[serviceName] + if !ok { + return + } + + if len(domainList) == 0 { + for domain, poller := range svcPoller { + poller.Stop() + nlog.Infof("Remove poll connection: %v", buildPollerKey(serviceName, domain)) + } + + delete(pm.pollers, serviceName) + return + } + + for _, domain := range domainList { + if poller, ok := svcPoller[domain]; ok { + poller.Stop() + delete(svcPoller, domain) + nlog.Infof("Remove poll connection: %v", buildPollerKey(serviceName, domain)) + } + } +} + +func buildPollerKey(svcName string, receiverDomain string) string { + return fmt.Sprintf("%s:%s", svcName, receiverDomain) +} + +func buildDomainRouteKey(drName string, sourceDomain string) string { + return fmt.Sprintf("%s:%s", drName, sourceDomain) +} + +func splitDomainRouteKey(key string) (string, string, error) { + arr := strings.Split(key, ":") + if len(arr) != 2 { + return "", "", fmt.Errorf("invalid domain route key %q", key) + } + return arr[0], arr[1], nil +} + +func isPollingState(state PollState) bool { + return state == PollStatePolling +} diff --git a/pkg/gateway/controller/poller/poll_manager_test.go b/pkg/gateway/controller/poller/poll_manager_test.go new file mode 100644 index 00000000..6f5cf837 --- /dev/null +++ b/pkg/gateway/controller/poller/poll_manager_test.go @@ -0,0 +1,257 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package poller + +import ( + "context" + "testing" + "time" + + "k8s.io/client-go/tools/cache" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + kubeinformers "k8s.io/client-go/informers" + kubefake "k8s.io/client-go/kubernetes/fake" + + "github.com/secretflow/kuscia/pkg/common" + kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" + kusciafake "github.com/secretflow/kuscia/pkg/crd/clientset/versioned/fake" + kusciainformers "github.com/secretflow/kuscia/pkg/crd/informers/externalversions" +) + +func TestPollManager_Run(t *testing.T) { + testServices := makeTestServices() + kubeClient := kubefake.NewSimpleClientset(testServices...) + kusciaClient := kusciafake.NewSimpleClientset(makeTestDomainRoutes()...) + + kubeInformersFactory := kubeinformers.NewSharedInformerFactory(kubeClient, 0) + kusciaInformerFactory := kusciainformers.NewSharedInformerFactory(kusciaClient, 0) + svcInformer := kubeInformersFactory.Core().V1().Services() + drInformer := kusciaInformerFactory.Kuscia().V1alpha1().DomainRoutes() + + pm, err := NewPollManager(true, "alice", "alice-01", svcInformer, drInformer) + assert.NoError(t, err) + stopCh := make(chan struct{}) + defer close(stopCh) + go pm.Run(1, stopCh) + go kubeInformersFactory.Start(stopCh) + go kusciaInformerFactory.Start(stopCh) + + cache.WaitForNamedCacheSync("poll manager", stopCh, pm.serviceListerSynced, pm.domainRouteListerSynced) + + pollerExist := func(serviceName, receiverDomain string) bool { + pm.pollersLock.Lock() + defer pm.pollersLock.Unlock() + pollers, ok := pm.pollers[serviceName] + if !ok { + return false + } + if _, ok := pollers[receiverDomain]; !ok { + return false + } + return true + } + + t.Run("test service pollers", func(t *testing.T) { + pollClientExist := false + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + if pollerExist("test-service-02", "bob") && pollerExist("test-service-03", "bob") { + pollClientExist = true + break + } + } + assert.True(t, pollClientExist) + + assert.NoError(t, kubeClient.CoreV1().Services("alice").Delete(context.Background(), "test-service-03", metav1.DeleteOptions{})) + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + if pollerExist("test-service-02", "bob") && !pollerExist("test-service-03", "bob") { + pollClientExist = false + break + } + } + assert.False(t, pollClientExist) + }) + + t.Run("default domain route pollers", func(t *testing.T) { + pollClientExist := false + + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + if pollerExist("apiserver", "bob") && pollerExist("kuscia-handshake", "bob") { + pollClientExist = true + break + } + } + assert.True(t, pollClientExist) + }) + + t.Run("update domain route", func(t *testing.T) { + dr, err := kusciaClient.KusciaV1alpha1().DomainRoutes("alice").Get(context.Background(), "carol-alice", metav1.GetOptions{}) + assert.NoError(t, err) + dr.Spec.Transit = &kusciaapisv1alpha1.Transit{ + TransitMethod: kusciaapisv1alpha1.TransitMethodReverseTunnel, + } + _, err = kusciaClient.KusciaV1alpha1().DomainRoutes("alice").Update(context.Background(), dr, metav1.UpdateOptions{}) + assert.NoError(t, err) + + ok := false + + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + if pollerExist("apiserver", "carol") && pollerExist("kuscia-handshake", "carol") && + pollerExist("test-service-02", "carol") { + ok = true + break + } + } + assert.True(t, ok) + + err = kusciaClient.KusciaV1alpha1().DomainRoutes("alice").Delete(context.Background(), "carol-alice", metav1.DeleteOptions{}) + assert.NoError(t, err) + + ok = false + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + if !pollerExist("apiserver", "carol") && !pollerExist("kuscia-handshake", "carol") && + !pollerExist("test-service-02", "carol") { + ok = true + break + } + } + assert.True(t, ok) + + dr, err = kusciaClient.KusciaV1alpha1().DomainRoutes("alice").Get(context.Background(), "bob-alice", metav1.GetOptions{}) + assert.NoError(t, err) + dr.Spec.Transit = nil + _, err = kusciaClient.KusciaV1alpha1().DomainRoutes("alice").Update(context.Background(), dr, metav1.UpdateOptions{}) + assert.NoError(t, err) + + ok = false + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + if !pollerExist("apiserver", "bob") && !pollerExist("kuscia-handshake", "bob") && + !pollerExist("test-service-02", "bob") { + ok = true + break + } + } + assert.True(t, ok) + }) + +} + +func makeTestServices() []runtime.Object { + return []runtime.Object{ + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service-01", + Namespace: "alice", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Name: "http", + Protocol: "TCP", + Port: 80, + TargetPort: intstr.FromInt(8080), + }, + }, + }, + }, + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service-02", + Namespace: "alice", + Labels: map[string]string{ + common.LabelPortScope: string(kusciaapisv1alpha1.ScopeCluster), + }, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Name: "http", + Protocol: "TCP", + Port: 80, + TargetPort: intstr.FromInt(8080), + }, + }, + }, + }, + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service-03", + Namespace: "alice", + Labels: map[string]string{ + common.LabelPortScope: string(kusciaapisv1alpha1.ScopeCluster), + }, + Annotations: map[string]string{ + common.AccessDomainAnnotationKey: "bob,carol,denis", + }, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Name: "http", + Protocol: "TCP", + Port: 80, + TargetPort: intstr.FromInt(8080), + }, + }, + }, + }, + } +} + +func makeTestDomainRoutes() []runtime.Object { + return []runtime.Object{ + &kusciaapisv1alpha1.DomainRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bob-alice", + Namespace: "alice", + Labels: map[string]string{ + common.KusciaSourceKey: "bob", + common.KusciaDestinationKey: "alice", + }, + }, + Spec: kusciaapisv1alpha1.DomainRouteSpec{ + Source: "bob", + Destination: "alice", + Transit: &kusciaapisv1alpha1.Transit{ + TransitMethod: kusciaapisv1alpha1.TransitMethodReverseTunnel, + }, + }, + }, + &kusciaapisv1alpha1.DomainRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "carol-alice", + Namespace: "alice", + Labels: map[string]string{ + common.KusciaSourceKey: "carol", + common.KusciaDestinationKey: "alice", + }, + }, + Spec: kusciaapisv1alpha1.DomainRouteSpec{ + Source: "carol", + Destination: "alice", + }, + }, + } +} diff --git a/pkg/gateway/metrics/monitor.go b/pkg/gateway/metrics/monitor.go index 1cacd311..c69cdd51 100644 --- a/pkg/gateway/metrics/monitor.go +++ b/pkg/gateway/metrics/monitor.go @@ -30,6 +30,7 @@ import ( kusciaapisv1alpha1 "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" kuscialistersv1alpha1 "github.com/secretflow/kuscia/pkg/crd/listers/kuscia/v1alpha1" "github.com/secretflow/kuscia/pkg/gateway/controller" + "github.com/secretflow/kuscia/pkg/gateway/utils" "github.com/secretflow/kuscia/pkg/utils/nlog" ) @@ -119,7 +120,7 @@ func (c *ClusterMetricsCollector) collect() { if drs, err := c.domainRouteLister.List(labels.Everything()); err == nil { for _, dr := range drs { - if dr.Spec.Source != dr.Namespace || dr.Spec.Transit != nil { + if dr.Spec.Source != dr.Namespace || utils.IsThirdPartyTransit(dr.Spec.Transit) { continue } diff --git a/pkg/gateway/utils/handshake_test.go b/pkg/gateway/utils/handshake_test.go new file mode 100644 index 00000000..8b59bebc --- /dev/null +++ b/pkg/gateway/utils/handshake_test.go @@ -0,0 +1,178 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "testing" + + "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" +) + +func TestGetPrefixIfPresent(t *testing.T) { + type args struct { + endpoint v1alpha1.DomainEndpoint + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + { + name: "case 0", + args: args{ + endpoint: v1alpha1.DomainEndpoint{ + Host: "localhost", + Ports: []v1alpha1.DomainPort{ + { + Name: "port", + Protocol: "http", + PathPrefix: "/prefix", + IsTLS: false, + Port: 8080, + }, + }, + }, + }, + want: "/prefix", + }, + { + name: "case 1", + args: args{ + endpoint: v1alpha1.DomainEndpoint{ + Host: "localhost", + Ports: []v1alpha1.DomainPort{ + { + Name: "port", + Protocol: "http", + IsTLS: false, + Port: 8080, + }, + }, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetPrefixIfPresent(tt.args.endpoint); got != tt.want { + t.Errorf("GetPrefixIfPresent() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetHandshakePathSuffix(t *testing.T) { + tests := []struct { + name string + want string + }{ + // TODO: Add test cases. + { + name: "case 0", + want: "/handshake", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetHandshakePathSuffix(); got != tt.want { + t.Errorf("GetHandshakePathSuffix() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetHandshakePathOfEndpoint(t *testing.T) { + type args struct { + endpoint v1alpha1.DomainEndpoint + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + { + name: "case 0", + args: args{ + endpoint: v1alpha1.DomainEndpoint{ + Host: "localhost", + Ports: []v1alpha1.DomainPort{ + { + Name: "port", + Protocol: "http", + PathPrefix: "/prefix", + IsTLS: false, + Port: 8080, + }, + }, + }, + }, + want: "/prefix/handshake", + }, + { + name: "case 1", + args: args{ + endpoint: v1alpha1.DomainEndpoint{ + Host: "localhost", + Ports: []v1alpha1.DomainPort{ + { + Name: "port", + Protocol: "http", + IsTLS: false, + Port: 8080, + }, + }, + }, + }, + want: "/handshake", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetHandshakePathOfEndpoint(tt.args.endpoint); got != tt.want { + t.Errorf("GetHandshakePathOfEndpoint() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetHandshakePathOfPrefix(t *testing.T) { + type args struct { + pathPrefix string + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + { + name: "case 0", + args: args{ + pathPrefix: "/prefix", + }, + want: "/prefix/handshake", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetHandshakePathOfPrefix(tt.args.pathPrefix); got != tt.want { + t.Errorf("GetHandshakePathOfPrefix() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/gateway/utils/http.go b/pkg/gateway/utils/http.go index bd2a5c79..41eaf0ab 100644 --- a/pkg/gateway/utils/http.go +++ b/pkg/gateway/utils/http.go @@ -16,6 +16,7 @@ package utils import ( "bytes" + "crypto/tls" "encoding/json" "fmt" "io" @@ -23,8 +24,12 @@ import ( "strconv" "strings" "time" + + "github.com/secretflow/kuscia/pkg/utils/nlog" ) +const KusciaEnvoyMsgHeaderKey = "Kuscia-Error-Message" + type HTTPParam struct { Method string Path string @@ -109,7 +114,9 @@ func DoHTTP(in interface{}, out interface{}, hp *HTTPParam) error { for key, val := range hp.Headers { req.Header.Set(key, val) } - client := &http.Client{} + client := &http.Client{ + Timeout: time.Second * 10, + } resp, err := client.Do(req) if err != nil { return fmt.Errorf("send request error, detail -> %s", err.Error()) @@ -136,3 +143,50 @@ func DoHTTP(in interface{}, out interface{}, hp *HTTPParam) error { } return nil } + +func ProbePeerEndpoint(endpointURL string) error { + + if endpointURL == "" { + return fmt.Errorf("endpoint URL is empty") + } + + req, err := http.NewRequest("GET", endpointURL, nil) + if err != nil { + return fmt.Errorf("endpoint URL is invalid: %v", err) + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Timeout: time.Second * 5, Transport: tr} + + resp, err := client.Do(req) + + if err != nil { + return fmt.Errorf("sending request error: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized && hasNonEmptyHeaderValue(resp.Header, KusciaEnvoyMsgHeaderKey) { + return nil + } + nlog.Infof("response header: %+v", resp.Header) + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unexpected status code: %d, read response body error, detail -> %s", resp.StatusCode, err.Error()) + } + respbody := string(body) + return fmt.Errorf("unexpected status code: %d, response body: %s", resp.StatusCode, respbody) + +} + +func hasNonEmptyHeaderValue(header http.Header, key string) bool { + + if values, ok := header[key]; ok { + if len(values) == 0 { + return false + } + return values[0] != "" + } + return false +} diff --git a/pkg/gateway/utils/http_test.go b/pkg/gateway/utils/http_test.go new file mode 100644 index 00000000..bcbb0c69 --- /dev/null +++ b/pkg/gateway/utils/http_test.go @@ -0,0 +1,77 @@ +package utils + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestProbeEndpoint(t *testing.T) { + + type args struct { + requestUrl string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "endpoint is 'https://test.example.com' should fail", + args: args{ + requestUrl: "https://test.example.com", + }, + wantErr: true, + }, + { + name: "endpoint is 'http://test.example.com' should fail", + args: args{ + requestUrl: "http://test.example.com", + }, + wantErr: true, + }, + { + name: "endpoint is 'https://127.0.0.1:1080' should fail", + args: args{ + requestUrl: "https://127.0.0.1:1080", + }, + wantErr: true, + }, + { + name: "endpoint is 'http://127.0.0.1:1080' should fail", + args: args{ + requestUrl: "http://127.0.0.1:1080", + }, + wantErr: true, + }, + { + name: "endpoint: 'user-kuscia-master:1080' is not valid", + args: args{ + requestUrl: "user-kuscia-master:1080", + }, + wantErr: true, + }, + { + name: "endpoint: 'valid url' is not valid", + args: args{ + requestUrl: "valid url", + }, + wantErr: true, + }, + { + name: "endpoint: 'https::///test.example.com' is not valid", + args: args{ + requestUrl: "https::///test.example.com", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ProbePeerEndpoint(tt.args.requestUrl) + assert.Equal(t, tt.wantErr, err != nil) + }) + } + +} diff --git a/pkg/gateway/utils/transit.go b/pkg/gateway/utils/transit.go new file mode 100644 index 00000000..1cf5eb46 --- /dev/null +++ b/pkg/gateway/utils/transit.go @@ -0,0 +1,27 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" +) + +func IsThirdPartyTransit(transit *v1alpha1.Transit) bool { + return transit != nil && (transit.TransitMethod == "" || transit.TransitMethod == v1alpha1.TransitMethodThirdDomain) +} + +func IsGatewayTceTransit(transit *v1alpha1.Transit) bool { + return transit != nil && transit.TransitMethod == v1alpha1.TransitMethodReverseTunnel +} diff --git a/pkg/gateway/utils/transit_test.go b/pkg/gateway/utils/transit_test.go new file mode 100644 index 00000000..fd19c9ee --- /dev/null +++ b/pkg/gateway/utils/transit_test.go @@ -0,0 +1,125 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "testing" + + "github.com/secretflow/kuscia/pkg/crd/apis/kuscia/v1alpha1" +) + +func TestIsThirdPartyTransit(t *testing.T) { + type args struct { + transit *v1alpha1.Transit + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + { + name: "case 0", + args: args{ + &v1alpha1.Transit{ + TransitMethod: v1alpha1.TransitMethodReverseTunnel, + }, + }, + want: false, + }, + { + name: "case 1", + args: args{ + &v1alpha1.Transit{ + TransitMethod: v1alpha1.TransitMethodThirdDomain, + }, + }, + want: true, + }, + { + name: "case 2", + args: args{ + nil, + }, + want: false, + }, + { + name: "case 3", + args: args{ + &v1alpha1.Transit{}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsThirdPartyTransit(tt.args.transit); got != tt.want { + t.Errorf("IsThirdPartyTransit() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsGatewayTceTransit(t *testing.T) { + type args struct { + transit *v1alpha1.Transit + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + { + name: "case 0", + args: args{ + &v1alpha1.Transit{ + TransitMethod: v1alpha1.TransitMethodReverseTunnel, + }, + }, + want: true, + }, + { + name: "case 1", + args: args{ + &v1alpha1.Transit{ + TransitMethod: v1alpha1.TransitMethodThirdDomain, + }, + }, + want: false, + }, + { + name: "case 2", + args: args{ + nil, + }, + want: false, + }, + { + name: "case 3", + args: args{ + &v1alpha1.Transit{}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsGatewayTceTransit(tt.args.transit); got != tt.want { + t.Errorf("IsGatewayTceTransit() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/gateway/xds/sort_filter.go b/pkg/gateway/xds/sort_filter.go index a1245e64..40568e98 100644 --- a/pkg/gateway/xds/sort_filter.go +++ b/pkg/gateway/xds/sort_filter.go @@ -25,7 +25,9 @@ var ( "envoy.filters.http.grpc_http1_reverse_bridge": 0, "envoy.filters.http.kuscia_gress": 1, "envoy.filters.http.kuscia_crypt": 2, - "envoy.filters.http.router": 3, + "envoy.filters.http.kuscia_receiver": 3, + "envoy.filters.http.kuscia_poller": 4, + "envoy.filters.http.router": 5, } externalHTTPFilters = map[string]int{ @@ -34,7 +36,8 @@ var ( "envoy.filters.http.kuscia_token_auth": 2, "envoy.filters.http.kuscia_header_decorator": 3, "envoy.filters.http.kuscia_crypt": 4, - "envoy.filters.http.router": 5, + "envoy.filters.http.kuscia_receiver": 5, + "envoy.filters.http.router": 6, } ) diff --git a/pkg/gateway/xds/xds.go b/pkg/gateway/xds/xds.go index 15ca4cc4..971daba8 100644 --- a/pkg/gateway/xds/xds.go +++ b/pkg/gateway/xds/xds.go @@ -58,6 +58,8 @@ import ( kusciacrypt "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_crypt/v3" headerdecorator "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_header_decorator/v3" + kusciapoller "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_poller/v3" + kusciareceiver "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_receiver/v3" kusciatoken "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_token_auth/v3" "github.com/secretflow/kuscia/pkg/utils/nlog" @@ -81,6 +83,8 @@ const ( tokenAuthFilterName = "envoy.filters.http.kuscia_token_auth" cryptFilterName = "envoy.filters.http.kuscia_crypt" headerDecoratorFilterName = "envoy.filters.http.kuscia_header_decorator" + receiverFilterName = "envoy.filters.http.kuscia_receiver" + pollerFilterName = "envoy.filters.http.kuscia_poller" ) var ( @@ -99,10 +103,12 @@ var ( ctx context.Context config *InitConfig - encryptRules []*kusciacrypt.CryptRule // for outbound, on port 80 - decryptRules []*kusciacrypt.CryptRule // for inbound, on port 1080 - sourceTokens []*kusciatoken.TokenAuth_SourceToken - appendHeaders []*headerdecorator.HeaderDecorator_SourceHeader + encryptRules []*kusciacrypt.CryptRule // for outbound, on port 80 + decryptRules []*kusciacrypt.CryptRule // for inbound, on port 1080 + sourceTokens []*kusciatoken.TokenAuth_SourceToken + appendHeaders []*headerdecorator.HeaderDecorator_SourceHeader + pollAppendHeaders []*kusciapoller.Poller_SourceHeader + receiverRules []*kusciareceiver.ReceiverRule ) type InitConfig struct { @@ -144,7 +150,7 @@ func runServer(ctx context.Context, srv server.Server, port uint32) { grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams)) grpcServer := grpc.NewServer(grpcOptions...) - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { nlog.Fatal(err) } @@ -170,7 +176,6 @@ func registerServer(grpcServer *grpc.Server, server server.Server) { func InitSnapshot(ns, instance string, initConfig *InitConfig) { config = initConfig - // Add the snapshot to the cache var err error configTemplate := ConfigTemplate{ @@ -418,25 +423,49 @@ func AddOrUpdateCluster(conf *envoycluster.Cluster) error { lock.Lock() defer lock.Unlock() clusters := snapshot.Resources[types.Cluster].Items - delete(clusters, conf.Name) - clusters[conf.Name] = types.ResourceWithTTL{Resource: conf} + items := make(map[string]types.ResourceWithTTL) + for k, v := range clusters { + if k == conf.Name { + continue + } + items[k] = v + } + items[conf.Name] = types.ResourceWithTTL{Resource: conf} + + if err := resetSnapshot(types.Cluster, items); err != nil { + return err + } - oldVersion, _ := strconv.Atoi(snapshot.Resources[types.Cluster].Version) - snapshot.Resources[types.Cluster].Version = fmt.Sprintf("%d", oldVersion+1) nlog.Infof("Add cluster:%s", conf.Name) - return snapshotCache.SetSnapshot(ctx, nodeID, snapshot) + return nil } func DeleteCluster(name string) error { lock.Lock() defer lock.Unlock() clusters := snapshot.Resources[types.Cluster].Items - delete(clusters, name) + if len(clusters) == 0 { + return nil + } + + if _, ok := clusters[name]; !ok { + return nil + } + + items := make(map[string]types.ResourceWithTTL) + for k, v := range clusters { + if k == name { + continue + } + items[k] = v + } + + if err := resetSnapshot(types.Cluster, items); err != nil { + return err + } - oldVersion, _ := strconv.Atoi(snapshot.Resources[types.Cluster].Version) - snapshot.Resources[types.Cluster].Version = fmt.Sprintf("%d", oldVersion+1) nlog.Debugf("Delete cluster:%s", name) - return snapshotCache.SetSnapshot(ctx, nodeID, snapshot) + return nil } func QueryCluster(name string) (*envoycluster.Cluster, error) { @@ -486,13 +515,23 @@ func AddOrUpdateVirtualHost(vh *route.VirtualHost, routeName string) error { lock.Lock() defer lock.Unlock() routes := snapshot.Resources[types.Route].Items - rs, ok := routes[routeName] + _, ok := routes[routeName] if !ok { nlog.Errorf("Unknown route config name: %s", routeName) return fmt.Errorf("unknown route config name: %s", routeName) } - routeConfig, ok := rs.Resource.(*route.RouteConfiguration) + items := make(map[string]types.ResourceWithTTL) + for k, v := range routes { + if k == routeName { + res := proto.Clone(routes[k].Resource).(*route.RouteConfiguration) + items[k] = types.ResourceWithTTL{Resource: res} + } else { + items[k] = v + } + } + + routeConfig, ok := items[routeName].Resource.(*route.RouteConfiguration) if !ok { return fmt.Errorf("resource cannot cast to RouteConfiguration") } @@ -505,22 +544,33 @@ func AddOrUpdateVirtualHost(vh *route.VirtualHost, routeName string) error { } routeConfig.VirtualHosts = append([]*route.VirtualHost{vh}, routeConfig.VirtualHosts...) - oldVersion, _ := strconv.Atoi(snapshot.Resources[types.Route].Version) - snapshot.Resources[types.Route].Version = fmt.Sprintf("%d", oldVersion+1) - return snapshotCache.SetSnapshot(ctx, nodeID, snapshot) + if err := resetSnapshot(types.Route, items); err != nil { + return err + } + return nil } func DeleteVirtualHost(name, routeName string) error { lock.Lock() defer lock.Unlock() routes := snapshot.Resources[types.Route].Items - rs, ok := routes[routeName] + _, ok := routes[routeName] if !ok { nlog.Errorf("unknown route config name: %s", routeName) return fmt.Errorf("unknown route config name: %s", routeName) } - routeConfig, ok := rs.Resource.(*route.RouteConfiguration) + items := make(map[string]types.ResourceWithTTL) + for k, v := range routes { + if k == routeName { + res := proto.Clone(routes[k].Resource).(*route.RouteConfiguration) + items[k] = types.ResourceWithTTL{Resource: res} + } else { + items[k] = v + } + } + + routeConfig, ok := items[routeName].Resource.(*route.RouteConfiguration) if !ok { return fmt.Errorf("resource cannot cast to RouteConfiguration") } @@ -532,21 +582,31 @@ func DeleteVirtualHost(name, routeName string) error { } } - oldVersion, _ := strconv.Atoi(snapshot.Resources[types.Route].Version) - snapshot.Resources[types.Route].Version = fmt.Sprintf("%d", oldVersion+1) - return snapshotCache.SetSnapshot(ctx, nodeID, snapshot) + if err := resetSnapshot(types.Route, items); err != nil { + return err + } + return nil } func DeleteRoute(name, vhName, routeName string) error { lock.Lock() defer lock.Unlock() routes := snapshot.Resources[types.Route].Items - rs, ok := routes[routeName] + _, ok := routes[routeName] if !ok { return fmt.Errorf("unknown route config name: %s", routeName) } - routeConfig, ok := rs.Resource.(*route.RouteConfiguration) + items := make(map[string]types.ResourceWithTTL) + for k, v := range routes { + if k == routeName { + res := proto.Clone(routes[k].Resource).(*route.RouteConfiguration) + items[k] = types.ResourceWithTTL{Resource: res} + } else { + items[k] = v + } + } + routeConfig, ok := items[routeName].Resource.(*route.RouteConfiguration) if !ok { return fmt.Errorf("resource cannot cast to RouteConfiguration") } @@ -569,9 +629,10 @@ func DeleteRoute(name, vhName, routeName string) error { } } - oldVersion, _ := strconv.Atoi(snapshot.Resources[types.Route].Version) - snapshot.Resources[types.Route].Version = fmt.Sprintf("%d", oldVersion+1) - return snapshotCache.SetSnapshot(ctx, nodeID, snapshot) + if err := resetSnapshot(types.Route, items); err != nil { + return err + } + return nil } func getHTTPFilterConfig(filterName, listenerName string) (*anypb.Any, error) { @@ -710,6 +771,37 @@ func generateCryptRules(newRule *kusciacrypt.CryptRule, config []*kusciacrypt.Cr return config } +func generateReceiverRulesByListener(newRule *kusciareceiver.ReceiverRule, listenerName string, + add bool) []*kusciareceiver.ReceiverRule { + // TODO internal/external listener is not the same + // if listenerName == InternalListener { + // receiverRules = generateReceiverRules(newRule, encryptRules, add) + // return receiverRules + // } + receiverRules = generateReceiverRules(newRule, receiverRules, add) + return receiverRules +} + +func generateReceiverRules(newRule *kusciareceiver.ReceiverRule, config []*kusciareceiver.ReceiverRule, + add bool) []*kusciareceiver.ReceiverRule { + for i, rule := range config { + if rule.Source == newRule.Source && rule.Destination == newRule.Destination && rule.Svc == newRule.Svc { + if add { + config[i] = newRule + return config + } + config = append(config[:i], config[i+1:]...) + return config + } + } + + // if not return yet, means not found + if add { + config = append(config, newRule) + } + return config +} + func UpdateTokenAuthConfig(config *anypb.Any, newToken *kusciatoken.TokenAuth_SourceToken, add bool) (*hcm.HttpFilter, error) { var tokenAuthFilter kusciatoken.TokenAuth @@ -878,6 +970,87 @@ func GetTokenAuth() (*kusciatoken.TokenAuth, error) { return &tokenAuthFilter, nil } +func UpdatePoller(newHeader *kusciapoller.Poller_SourceHeader, add bool) error { + lock.Lock() + defer lock.Unlock() + + protoConfig, err := getHTTPFilterConfig(pollerFilterName, InternalListener) + if err != nil { + return err + } + var pollerFilter kusciapoller.Poller + if err := protoConfig.UnmarshalTo(&pollerFilter); err != nil { + return fmt.Errorf("unmarshal kuscia filter failed with %s", err.Error()) + } + + // generate sourceHeaders list + found := false + for i, header := range pollAppendHeaders { + if newHeader.Source == header.Source { + if add { + pollAppendHeaders[i] = newHeader + } else { + pollAppendHeaders = append(pollAppendHeaders[:i], pollAppendHeaders[i+1:]...) + } + found = true + break + } + } + if add && !found { + pollAppendHeaders = append(pollAppendHeaders, newHeader) + } + + pollerFilter.AppendHeaders = pollAppendHeaders + pollerFilterConf, err := anypb.New(&pollerFilter) + if err != nil { + return fmt.Errorf("marshal kuscia filter failed with %v", err) + } + httpFilter := &hcm.HttpFilter{ + Name: pollerFilterName, + ConfigType: &hcm.HttpFilter_TypedConfig{ + TypedConfig: pollerFilterConf, + }, + } + return updateHTTPFilter(httpFilter, InternalListener) +} + +func UpdateReceiverRules(rule *kusciareceiver.ReceiverRule, add bool) error { + lock.Lock() + defer lock.Unlock() + + if err := updateReceiverRules(rule, add, ExternalListener); err != nil { + return err + } + if err := updateReceiverRules(rule, add, InternalListener); err != nil { + return err + } + return nil +} + +func updateReceiverRules(rule *kusciareceiver.ReceiverRule, add bool, listenerName string) error { + protoConfig, err := getHTTPFilterConfig(receiverFilterName, listenerName) + if err != nil { + return err + } + var receiverFilter kusciareceiver.Receiver + if err := protoConfig.UnmarshalTo(&receiverFilter); err != nil { + return fmt.Errorf("unmarshal kuscia filter failed with %s", err.Error()) + } + receiverFilter.Rules = generateReceiverRulesByListener(rule, listenerName, add) + receiverFilterConf, err := anypb.New(&receiverFilter) + if err != nil { + return fmt.Errorf("marshal kuscia filter failed with %v", err) + } + + httpFilter := &hcm.HttpFilter{ + Name: receiverFilterName, + ConfigType: &hcm.HttpFilter_TypedConfig{ + TypedConfig: receiverFilterConf, + }, + } + return updateHTTPFilter(httpFilter, listenerName) +} + func updateHTTPFilter(httpFilter *hcm.HttpFilter, listenerName string) error { httpFilters := []*hcm.HttpFilter{ httpFilter, @@ -885,14 +1058,26 @@ func updateHTTPFilter(httpFilter *hcm.HttpFilter, listenerName string) error { return updateHTTPFilters(httpFilters, listenerName) } +// TODO filter add/remove func updateHTTPFilters(filters []*hcm.HttpFilter, listenerName string) error { listeners := snapshot.Resources[types.Listener].Items - rs, ok := listeners[listenerName] + _, ok := listeners[listenerName] if !ok { return fmt.Errorf("unknown listener name: %s", listenerName) } - lis, ok := rs.Resource.(*listener.Listener) + + items := make(map[string]types.ResourceWithTTL) + for k, v := range listeners { + if k == listenerName { + res := proto.Clone(listeners[k].Resource).(*listener.Listener) + items[k] = types.ResourceWithTTL{Resource: res} + } else { + items[k] = v + } + } + + lis, ok := items[listenerName].Resource.(*listener.Listener) if !ok { return fmt.Errorf("resource cannot cast to listener") } @@ -944,9 +1129,10 @@ func updateHTTPFilters(filters []*hcm.HttpFilter, listenerName string) error { listeners[tlsLis.Name] = types.ResourceWithTTL{Resource: tlsLis} } - oldVersion, _ := strconv.Atoi(snapshot.Resources[types.Listener].Version) - snapshot.Resources[types.Listener].Version = fmt.Sprintf("%d", oldVersion+1) - return snapshotCache.SetSnapshot(ctx, nodeID, snapshot) + if err = resetSnapshot(types.Listener, items); err != nil { + return err + } + return nil } func AddDefaultTimeout(action *route.RouteAction) *route.RouteAction { @@ -958,3 +1144,60 @@ func AddDefaultTimeout(action *route.RouteAction) *route.RouteAction { } return action } + +func resetSnapshot(ty types.ResponseType, items map[string]types.ResourceWithTTL) error { + oldVersion, _ := strconv.Atoi(snapshot.Resources[ty].Version) + newVersion := fmt.Sprintf("%d", oldVersion+1) + + var clusterResources, routeResources, listenerResources []types.Resource + if ty == types.Cluster { + clusterResources = buildResourceFromResourcesItems(items) + } else { + clusterResources = buildResourcesFromSnapshot(types.Cluster) + } + + if ty == types.Route { + routeResources = buildResourceFromResourcesItems(items) + } else { + routeResources = buildResourcesFromSnapshot(types.Route) + } + + if ty == types.Listener { + listenerResources = buildResourceFromResourcesItems(items) + } else { + listenerResources = buildResourcesFromSnapshot(types.Listener) + } + + newSnapshot, err := cache.NewSnapshot(newVersion, map[resource.Type][]types.Resource{ + resource.ClusterType: clusterResources, + resource.RouteType: routeResources, + resource.ListenerType: listenerResources, + }) + if err != nil { + return err + } + + err = snapshotCache.SetSnapshot(ctx, nodeID, newSnapshot) + if err != nil { + return err + } + snapshot = newSnapshot + return nil +} + +func buildResourceFromResourcesItems(items map[string]types.ResourceWithTTL) []types.Resource { + ret := make([]types.Resource, 0, len(items)) + for _, resourceWithTTL := range items { + ret = append(ret, resourceWithTTL.Resource) + } + return ret +} + +func buildResourcesFromSnapshot(ty types.ResponseType) []types.Resource { + items := snapshot.Resources[ty].Items + ret := make([]types.Resource, 0, len(items)) + for _, resourceWithTTL := range items { + ret = append(ret, resourceWithTTL.Resource) + } + return ret +} diff --git a/pkg/interconn/kuscia/common/common.go b/pkg/interconn/kuscia/common/common.go index 91051744..3cbcfc20 100644 --- a/pkg/interconn/kuscia/common/common.go +++ b/pkg/interconn/kuscia/common/common.go @@ -143,25 +143,27 @@ func GetJobStage(stage string) v1alpha1.JobStage { return v1alpha1.JobCancelStage case string(v1alpha1.JobRestartStage): return v1alpha1.JobRestartStage + case string(v1alpha1.JobSuspendStage): + return v1alpha1.JobSuspendStage default: return "" } } func UpdateJobStage(job *v1alpha1.KusciaJob, jobSummary *v1alpha1.KusciaJobSummary) bool { - if jobSummary.Spec.Stage != v1alpha1.JobStopStage { + if job.Status.Phase == v1alpha1.KusciaJobCancelled { return false } - - jobStage := GetJobStage(GetObjectLabel(job, common.LabelJobStage)) - if jobStage == jobSummary.Spec.Stage { + jobStageVersion := GetObjectLabel(job, common.LabelJobStageVersion) + jobSummaryStageVersion := GetObjectLabel(jobSummary, common.LabelJobStageVersion) + if jobSummaryStageVersion <= jobStageVersion { return false } if job.Labels == nil { job.Labels = make(map[string]string) } - + job.Labels[common.LabelJobStageVersion] = jobSummaryStageVersion job.Labels[common.LabelJobStage] = string(jobSummary.Spec.Stage) job.Labels[common.LabelJobStageTrigger] = jobSummary.Spec.StageTrigger return true diff --git a/pkg/interconn/kuscia/common/common_test.go b/pkg/interconn/kuscia/common/common_test.go index 0cc793eb..d5e60e81 100644 --- a/pkg/interconn/kuscia/common/common_test.go +++ b/pkg/interconn/kuscia/common/common_test.go @@ -292,23 +292,23 @@ func TestUpdateJobStage(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "job-1", Namespace: "alice", + Labels: map[string]string{}, }, } - // job summary start is stop, should return false - kjs.Spec.Stage = kusciaapisv1alpha1.JobStartStage + // phase is cancelled: return false + kjs.Spec.Stage = kusciaapisv1alpha1.JobCancelStage + kj.Status.Phase = kusciaapisv1alpha1.KusciaJobCancelled got := UpdateJobStage(kj, kjs) assert.Equal(t, false, got) - - // job and job summary stage are same, should return false - kjs.Spec.Stage = kusciaapisv1alpha1.JobStopStage - kj.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobStopStage) + // no version: return false + kjs.Spec.Stage = kusciaapisv1alpha1.JobStartStage + kj.Status.Phase = kusciaapisv1alpha1.KusciaJobRunning got = UpdateJobStage(kj, kjs) assert.Equal(t, false, got) - - // job and job summary stage are same, should return false - kjs.Spec.Stage = kusciaapisv1alpha1.JobStopStage - kj.Labels[common.LabelJobStage] = string(kusciaapisv1alpha1.JobStartStage) + // job summary update stage + kjs.Spec.Stage = kusciaapisv1alpha1.JobStartStage + kjs.Labels[common.LabelJobStageVersion] = "1" got = UpdateJobStage(kj, kjs) assert.Equal(t, true, got) } diff --git a/pkg/interconn/kuscia/hostresources/job.go b/pkg/interconn/kuscia/hostresources/job.go index 1fee60d7..ad9eb342 100644 --- a/pkg/interconn/kuscia/hostresources/job.go +++ b/pkg/interconn/kuscia/hostresources/job.go @@ -99,7 +99,6 @@ func (c *hostResourcesController) syncJobHandler(ctx context.Context, key string } return err } - return c.createJob(ctx, hJob) } diff --git a/pkg/interconn/kuscia/hostresources/jobsummary.go b/pkg/interconn/kuscia/hostresources/jobsummary.go index 0bda93fb..86901be3 100644 --- a/pkg/interconn/kuscia/hostresources/jobsummary.go +++ b/pkg/interconn/kuscia/hostresources/jobsummary.go @@ -92,7 +92,7 @@ func (c *hostResourcesController) updateMemberJobByJobSummary(ctx context.Contex selfDomainIDs := ikcommon.GetSelfClusterPartyDomainIDs(originalJob) if len(selfDomainIDs) == 0 { - nlog.Warnf("Failed to get self party domain ids from job %v, skip processing it", ikcommon.GetObjectNamespaceName(originalJob)) + nlog.Infof("Party domain ids from job %v not found, skip processing it", ikcommon.GetObjectNamespaceName(originalJob)) return nil } @@ -104,7 +104,11 @@ func (c *hostResourcesController) updateMemberJobByJobSummary(ctx context.Contex needUpdate := false if ikcommon.UpdateJobStage(job, jobSummary) { - needUpdate = true + job.Status.LastReconcileTime = ikcommon.GetCurrentTime() + if _, err = c.memberKusciaClient.KusciaV1alpha1().KusciaJobs(job.Namespace).Update(ctx, job, metav1.UpdateOptions{}); err != nil { + return err + } + return nil } if updateJobApproveStatus(job, jobSummary, domainIDMap) { diff --git a/pkg/interconn/kuscia/hostresources/tasksummary.go b/pkg/interconn/kuscia/hostresources/tasksummary.go index 263b1ad4..c83870a3 100644 --- a/pkg/interconn/kuscia/hostresources/tasksummary.go +++ b/pkg/interconn/kuscia/hostresources/tasksummary.go @@ -231,16 +231,20 @@ func (c *hostResourcesController) updateMemberTaskResource(ctx context.Context, } for _, status := range statuses { + if status.MemberTaskResourceName == "" { + continue + } + originalTr, err := c.memberTaskResourceLister.TaskResources(domainID).Get(status.MemberTaskResourceName) if err != nil { if k8serrors.IsNotFound(err) { originalTr, err = c.memberKusciaClient.KusciaV1alpha1().TaskResources(domainID).Get(ctx, status.MemberTaskResourceName, metav1.GetOptions{}) } if err != nil { - nlog.Errorf("Failed to get taskResource %v, %v", fmt.Sprintf("%v/%v", domainID, status.MemberTaskResourceName), err) if k8serrors.IsNotFound(err) { return nil } + nlog.Errorf("Failed to get taskResource %v, %v", fmt.Sprintf("%v/%v", domainID, status.MemberTaskResourceName), err) return err } } @@ -274,7 +278,6 @@ func (c *hostResourcesController) updateMemberTaskResource(ctx context.Context, } } } - } return nil diff --git a/pkg/interconn/kuscia/job.go b/pkg/interconn/kuscia/job.go index d727bd88..7a6fc16a 100644 --- a/pkg/interconn/kuscia/job.go +++ b/pkg/interconn/kuscia/job.go @@ -357,12 +357,20 @@ func (c *Controller) updateJobSummary(ctx context.Context, job *v1alpha1.KusciaJ func updateJobSummaryStage(job *v1alpha1.KusciaJob, jobSummary *v1alpha1.KusciaJobSummary) bool { // if the stage of job changed to stop, we will prevent updating the job stage - if jobSummary.Spec.Stage == v1alpha1.JobStopStage { + if jobSummary.Spec.Stage == v1alpha1.JobCancelStage { return false } - jobStage := ikcommon.GetJobStage(ikcommon.GetObjectLabel(job, common.LabelJobStage)) jobStageTrigger := ikcommon.GetObjectLabel(job, common.LabelJobStageTrigger) + jobStageVersion := ikcommon.GetObjectLabel(job, common.LabelJobStageVersion) + jobSummaryStageVersion := ikcommon.GetObjectLabel(jobSummary, common.LabelJobStageVersion) + if jobStageVersion <= jobSummaryStageVersion { + return false + } + if jobSummary.Labels == nil { + jobSummary.Labels = make(map[string]string) + } + jobSummary.Labels[common.LabelJobStageVersion] = jobStageVersion if jobStage != jobSummary.Spec.Stage { jobSummary.Spec.Stage = jobStage jobSummary.Spec.StageTrigger = jobStageTrigger @@ -530,19 +538,19 @@ func (c *Controller) updateHostJobSummary(ctx context.Context, } func updateHostJobSummaryStage(job *v1alpha1.KusciaJob, jobSummary *v1alpha1.KusciaJobSummary, masterDomainID string) bool { - // if the stage of job becomes stop, we will prevent updating the job stage - if jobSummary.Spec.Stage == v1alpha1.JobStopStage { + jobStageVersion := ikcommon.GetObjectLabel(job, common.LabelJobStageVersion) + jobSummaryStageVersion := ikcommon.GetObjectLabel(jobSummary, common.LabelJobStageVersion) + if jobStageVersion <= jobSummaryStageVersion { return false } - jobStage := ikcommon.GetJobStage(ikcommon.GetObjectLabel(job, common.LabelJobStage)) - if jobStage == v1alpha1.JobStopStage && jobStage != jobSummary.Spec.Stage { - jobSummary.Spec.Stage = jobStage - jobSummary.Spec.StageTrigger = masterDomainID - return true + if jobSummary.Labels == nil { + jobSummary.Labels = make(map[string]string) } - - return false + jobSummary.Labels[common.LabelJobStageVersion] = jobStageVersion + jobSummary.Spec.Stage = jobStage + jobSummary.Spec.StageTrigger = masterDomainID + return true } func updateHostJobSummaryApproveStatus(job *v1alpha1.KusciaJob, jobSummary *v1alpha1.KusciaJobSummary, domainIDs []string) bool { diff --git a/pkg/interconn/kuscia/job_test.go b/pkg/interconn/kuscia/job_test.go index 3e2fc826..65e20cae 100644 --- a/pkg/interconn/kuscia/job_test.go +++ b/pkg/interconn/kuscia/job_test.go @@ -261,16 +261,22 @@ func TestCreateOrUpdateJobSummary(t *testing.T) { } func TestUpdateJobSummaryStage(t *testing.T) { - // stage in job summary is stop, should return false + // cancel stage: don't update kj := makeMockJob("cross-domain", "job-1") kjs := makeMockJobSummary("bob", "job-1") - kjs.Spec.Stage = v1alpha1.JobStopStage + kjs.Spec.Stage = v1alpha1.JobCancelStage got := updateJobSummaryStage(kj, kjs) assert.Equal(t, false, got) - - // stage in job and job summary are different, should return true + // not version: don't update + kj = makeMockJob("cross-domain", "job-1") + kjs = makeMockJobSummary("bob", "job-1") + kjs.Spec.Stage = v1alpha1.JobStartStage + got = updateJobSummaryStage(kj, kjs) + assert.Equal(t, false, got) + // new version: update kj = makeMockJob("cross-domain", "job-1") kj.Labels[common.LabelJobStage] = "Start" + kj.Labels[common.LabelJobStageVersion] = "1" kjs = makeMockJobSummary("bob", "job-1") kjs.Spec.Stage = v1alpha1.JobCreateStage got = updateJobSummaryStage(kj, kjs) diff --git a/pkg/interconn/kuscia/jobsummary.go b/pkg/interconn/kuscia/jobsummary.go index 85966e14..cbc7dd71 100644 --- a/pkg/interconn/kuscia/jobsummary.go +++ b/pkg/interconn/kuscia/jobsummary.go @@ -110,7 +110,11 @@ func (c *Controller) updateJob(ctx context.Context, jobSummary *v1alpha1.KusciaJ job := originalJob.DeepCopy() needUpdate := false if updated := ikcommon.UpdateJobStage(job, jobSummary); updated { - needUpdate = true + job.Status.LastReconcileTime = ikcommon.GetCurrentTime() + if _, err = c.kusciaClient.KusciaV1alpha1().KusciaJobs(job.Namespace).Update(ctx, job, metav1.UpdateOptions{}); err != nil { + return err + } + return nil } if updated := updateJobStatusInfo(job, jobSummary, partyDomainIDs); updated { diff --git a/pkg/kusciaapi/bean/http_server_bean.go b/pkg/kusciaapi/bean/http_server_bean.go index 06c5f8bf..bf0d56cc 100644 --- a/pkg/kusciaapi/bean/http_server_bean.go +++ b/pkg/kusciaapi/bean/http_server_bean.go @@ -206,6 +206,21 @@ func (s *httpServerBean) registerGroupRoutes(e framework.ConfBeanRegistry, bean RelativePath: "approve", Handlers: []gin.HandlerFunc{protoDecorator(e, job.NewApproveJobHandler(jobService))}, }, + { + HTTPMethod: http.MethodPost, + RelativePath: "suspend", + Handlers: []gin.HandlerFunc{protoDecorator(e, job.NewSuspendJobHandler(jobService))}, + }, + { + HTTPMethod: http.MethodPost, + RelativePath: "restart", + Handlers: []gin.HandlerFunc{protoDecorator(e, job.NewRestartJobHandler(jobService))}, + }, + { + HTTPMethod: http.MethodPost, + RelativePath: "cancel", + Handlers: []gin.HandlerFunc{protoDecorator(e, job.NewCancelJobHandler(jobService))}, + }, }, }, // domain group routes diff --git a/pkg/kusciaapi/errorcode/error_code.go b/pkg/kusciaapi/errorcode/error_code.go index a9d54edc..db5ada0f 100644 --- a/pkg/kusciaapi/errorcode/error_code.go +++ b/pkg/kusciaapi/errorcode/error_code.go @@ -34,6 +34,9 @@ const ( ErrDeleteJob = 11204 ErrStopJob = 11205 ErrApproveJob = 11206 + ErrSuspendJob = 11207 + ErrRestartJob = 11208 + ErrCancelJob = 11209 ErrCreateDomain = 11300 ErrQueryDomain = 11301 @@ -102,6 +105,16 @@ func GetDomainRouteErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCo return defaultErrorCode } +func CreateDomainDataErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCode) errorcode.KusciaErrorCode { + if errors.IsNotFound(err) { + return ErrDomainNotExists + } + if errors.IsAlreadyExists(err) { + return ErrDomainDataExists + } + return defaultErrorCode +} + func GetDomainDataErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCode) errorcode.KusciaErrorCode { if errors.IsNotFound(err) { return ErrDomainDataNotExists @@ -112,6 +125,16 @@ func GetDomainDataErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCod return defaultErrorCode } +func CreateDomainDataGrantErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCode) errorcode.KusciaErrorCode { + if errors.IsNotFound(err) { + return ErrDomainNotExists + } + if errors.IsAlreadyExists(err) { + return ErrDomainDataGrantExists + } + return defaultErrorCode +} + func GetDomainDataGrantErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCode) errorcode.KusciaErrorCode { if errors.IsNotFound(err) { return ErrDomainDataGrantNotExists @@ -122,6 +145,16 @@ func GetDomainDataGrantErrorCode(err error, defaultErrorCode errorcode.KusciaErr return defaultErrorCode } +func CreateDomainDataSourceErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCode) errorcode.KusciaErrorCode { + if errors.IsNotFound(err) { + return ErrDomainNotExists + } + if errors.IsAlreadyExists(err) { + return ErrDomainDataSourceExists + } + return defaultErrorCode +} + func GetDomainDataSourceErrorCode(err error, defaultErrorCode errorcode.KusciaErrorCode) errorcode.KusciaErrorCode { if errors.IsNotFound(err) { return ErrDomainDataSourceNotExists diff --git a/pkg/kusciaapi/handler/grpchandler/job_handler.go b/pkg/kusciaapi/handler/grpchandler/job_handler.go index 89e981cd..6da2a6ce 100644 --- a/pkg/kusciaapi/handler/grpchandler/job_handler.go +++ b/pkg/kusciaapi/handler/grpchandler/job_handler.go @@ -53,6 +53,21 @@ func (h jobHandler) StopJob(ctx context.Context, request *kusciaapi.StopJobReque return res, nil } +func (h jobHandler) SuspendJob(ctx context.Context, request *kusciaapi.SuspendJobRequest) (*kusciaapi.SuspendJobResponse, error) { + res := h.jobService.SuspendJob(ctx, request) + return res, nil +} + +func (h jobHandler) RestartJob(ctx context.Context, request *kusciaapi.RestartJobRequest) (*kusciaapi.RestartJobResponse, error) { + res := h.jobService.RestartJob(ctx, request) + return res, nil +} + +func (h jobHandler) CancelJob(ctx context.Context, request *kusciaapi.CancelJobRequest) (*kusciaapi.CancelJobResponse, error) { + res := h.jobService.CancelJob(ctx, request) + return res, nil +} + func (h jobHandler) BatchQueryJobStatus(ctx context.Context, request *kusciaapi.BatchQueryJobStatusRequest) (*kusciaapi.BatchQueryJobStatusResponse, error) { res := h.jobService.BatchQueryJobStatus(ctx, request) return res, nil diff --git a/pkg/kusciaapi/handler/httphandler/job/cancel.go b/pkg/kusciaapi/handler/httphandler/job/cancel.go new file mode 100644 index 00000000..2ab3d571 --- /dev/null +++ b/pkg/kusciaapi/handler/httphandler/job/cancel.go @@ -0,0 +1,46 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +import ( + "reflect" + + "github.com/secretflow/kuscia/pkg/kusciaapi/service" + "github.com/secretflow/kuscia/pkg/web/api" + "github.com/secretflow/kuscia/pkg/web/errorcode" + "github.com/secretflow/kuscia/proto/api/v1alpha1/kusciaapi" +) + +type cancelJobHandler struct { + jobService service.IJobService +} + +func NewCancelJobHandler(jobService service.IJobService) api.ProtoHandler { + return &cancelJobHandler{ + jobService: jobService, + } +} + +func (q cancelJobHandler) Validate(context *api.BizContext, request api.ProtoRequest, errs *errorcode.Errs) { +} + +func (q cancelJobHandler) Handle(context *api.BizContext, request api.ProtoRequest) api.ProtoResponse { + req, _ := request.(*kusciaapi.CancelJobRequest) + return q.jobService.CancelJob(context.Context, req) +} + +func (q cancelJobHandler) GetType() (reqType, respType reflect.Type) { + return reflect.TypeOf(kusciaapi.CancelJobRequest{}), reflect.TypeOf(kusciaapi.CancelJobResponse{}) +} diff --git a/pkg/kusciaapi/handler/httphandler/job/restart.go b/pkg/kusciaapi/handler/httphandler/job/restart.go new file mode 100644 index 00000000..204d3c6e --- /dev/null +++ b/pkg/kusciaapi/handler/httphandler/job/restart.go @@ -0,0 +1,46 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +import ( + "reflect" + + "github.com/secretflow/kuscia/pkg/kusciaapi/service" + "github.com/secretflow/kuscia/pkg/web/api" + "github.com/secretflow/kuscia/pkg/web/errorcode" + "github.com/secretflow/kuscia/proto/api/v1alpha1/kusciaapi" +) + +type restartJobHandler struct { + jobService service.IJobService +} + +func NewRestartJobHandler(jobService service.IJobService) api.ProtoHandler { + return &restartJobHandler{ + jobService: jobService, + } +} + +func (q restartJobHandler) Validate(context *api.BizContext, request api.ProtoRequest, errs *errorcode.Errs) { +} + +func (q restartJobHandler) Handle(context *api.BizContext, request api.ProtoRequest) api.ProtoResponse { + req, _ := request.(*kusciaapi.RestartJobRequest) + return q.jobService.RestartJob(context.Context, req) +} + +func (q restartJobHandler) GetType() (reqType, respType reflect.Type) { + return reflect.TypeOf(kusciaapi.RestartJobRequest{}), reflect.TypeOf(kusciaapi.RestartJobResponse{}) +} diff --git a/pkg/kusciaapi/handler/httphandler/job/suspend.go b/pkg/kusciaapi/handler/httphandler/job/suspend.go new file mode 100644 index 00000000..1948113d --- /dev/null +++ b/pkg/kusciaapi/handler/httphandler/job/suspend.go @@ -0,0 +1,46 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +import ( + "reflect" + + "github.com/secretflow/kuscia/pkg/kusciaapi/service" + "github.com/secretflow/kuscia/pkg/web/api" + "github.com/secretflow/kuscia/pkg/web/errorcode" + "github.com/secretflow/kuscia/proto/api/v1alpha1/kusciaapi" +) + +type suspendJobHandler struct { + jobService service.IJobService +} + +func NewSuspendJobHandler(jobService service.IJobService) api.ProtoHandler { + return &suspendJobHandler{ + jobService: jobService, + } +} + +func (q suspendJobHandler) Validate(context *api.BizContext, request api.ProtoRequest, errs *errorcode.Errs) { +} + +func (q suspendJobHandler) Handle(context *api.BizContext, request api.ProtoRequest) api.ProtoResponse { + req, _ := request.(*kusciaapi.SuspendJobRequest) + return q.jobService.SuspendJob(context.Context, req) +} + +func (q suspendJobHandler) GetType() (reqType, respType reflect.Type) { + return reflect.TypeOf(kusciaapi.SuspendJobRequest{}), reflect.TypeOf(kusciaapi.SuspendJobResponse{}) +} diff --git a/pkg/kusciaapi/handler/httphandler/middleware/rbac/casbin_policy.csv b/pkg/kusciaapi/handler/httphandler/middleware/rbac/casbin_policy.csv index 314db657..3cd3c328 100644 --- a/pkg/kusciaapi/handler/httphandler/middleware/rbac/casbin_policy.csv +++ b/pkg/kusciaapi/handler/httphandler/middleware/rbac/casbin_policy.csv @@ -5,6 +5,9 @@ p, domain, /api/v1/job/stop, POST p, domain, /api/v1/job/watch, POST p, domain, /api/v1/job/status/batchQuery, POST p, domain, /api/v1/job/approve, POST +p, domain, /api/v1/job/suspend, POST +p, domain, /api/v1/job/cancel, POST +p, domain, /api/v1/job/restart, POST p, domain, /api/v1/domain/update, POST p, domain, /api/v1/domain/query, POST diff --git a/pkg/kusciaapi/proxy/client.go b/pkg/kusciaapi/proxy/client.go index 4e7c37c3..d9e1d18d 100644 --- a/pkg/kusciaapi/proxy/client.go +++ b/pkg/kusciaapi/proxy/client.go @@ -52,6 +52,9 @@ const ( DeleteJobPath = "/api/v1/job/delete" QueryJobPath = "/api/v1/job/query" StopJobPath = "/api/v1/job/stop" + SuspendJobPath = "/api/v1/job/suspend" + RestartJobPath = "/api/v1/job/restart" + CancelJobPath = "/api/v1/job/cancel" ApproveJobPath = "/api/v1/job/approve" BatchQueryJobPath = "/api/v1/job/status/batchQuery" WatchJobPath = "/api/v1/job/watch" @@ -102,6 +105,12 @@ type KusciaAPIClient interface { StopJob(ctx context.Context, request *kusciaapi.StopJobRequest) (response *kusciaapi.StopJobResponse, err error) + SuspendJob(ctx context.Context, request *kusciaapi.SuspendJobRequest) (response *kusciaapi.SuspendJobResponse, err error) + + RestartJob(ctx context.Context, request *kusciaapi.RestartJobRequest) (response *kusciaapi.RestartJobResponse, err error) + + CancelJob(ctx context.Context, request *kusciaapi.CancelJobRequest) (response *kusciaapi.CancelJobResponse, err error) + BatchQueryJob(ctx context.Context, request *kusciaapi.BatchQueryJobStatusRequest) (response *kusciaapi.BatchQueryJobStatusResponse, err error) WatchJob(ctx context.Context, request *kusciaapi.WatchJobRequest, eventCh chan<- *kusciaapi.WatchJobEventResponse) error @@ -235,6 +244,24 @@ func (c *KusciaAPIHttpClient) StopJob(ctx context.Context, request *kusciaapi.St return } +func (c *KusciaAPIHttpClient) SuspendJob(ctx context.Context, request *kusciaapi.SuspendJobRequest) (response *kusciaapi.SuspendJobResponse, err error) { + response = &kusciaapi.SuspendJobResponse{} + err = c.Send(ctx, request, response, SuspendJobPath) + return +} + +func (c *KusciaAPIHttpClient) RestartJob(ctx context.Context, request *kusciaapi.RestartJobRequest) (response *kusciaapi.RestartJobResponse, err error) { + response = &kusciaapi.RestartJobResponse{} + err = c.Send(ctx, request, response, RestartJobPath) + return +} + +func (c *KusciaAPIHttpClient) CancelJob(ctx context.Context, request *kusciaapi.CancelJobRequest) (response *kusciaapi.CancelJobResponse, err error) { + response = &kusciaapi.CancelJobResponse{} + err = c.Send(ctx, request, response, CancelJobPath) + return +} + func (c *KusciaAPIHttpClient) ApproveJob(ctx context.Context, request *kusciaapi.ApproveJobRequest) (response *kusciaapi.ApproveJobResponse, err error) { response = &kusciaapi.ApproveJobResponse{} err = c.Send(ctx, request, response, ApproveJobPath) diff --git a/pkg/kusciaapi/service/domain_route_service.go b/pkg/kusciaapi/service/domain_route_service.go index 890486a8..28472d66 100644 --- a/pkg/kusciaapi/service/domain_route_service.go +++ b/pkg/kusciaapi/service/domain_route_service.go @@ -94,6 +94,7 @@ func (s domainRouteService) CreateDomainRoute(ctx context.Context, request *kusc } cdrEndpoint.Ports = make([]v1alpha1.DomainPort, len(endpoint.Ports)) for i, port := range endpoint.Ports { + // TODO: Converted `isTLS` is about to be removed drProtocol, isTLS, err := convert2DomainRouteProtocol(port.Protocol) if err != nil { return &kusciaapi.CreateDomainRouteResponse{ @@ -104,7 +105,7 @@ func (s domainRouteService) CreateDomainRoute(ctx context.Context, request *kusc Name: port.Name, Port: int(port.Port), Protocol: drProtocol, - IsTLS: isTLS, + IsTLS: isTLS || port.IsTLS, } } // build cdr token config or mtls config diff --git a/pkg/kusciaapi/service/domaindata_grant.go b/pkg/kusciaapi/service/domaindata_grant.go index 233ca593..8d8e07f7 100644 --- a/pkg/kusciaapi/service/domaindata_grant.go +++ b/pkg/kusciaapi/service/domaindata_grant.go @@ -69,13 +69,6 @@ func (s *domainDataGrantService) CreateDomainDataGrant(ctx context.Context, requ } } - // check domain exists - if errorCode, errMsg := CheckDomainExists(s.conf.KusciaClient, request.GetDomainId()); utils.ResponseCodeSuccess != errorCode { - return &kusciaapi.CreateDomainDataGrantResponse{ - Status: utils.BuildErrorResponseStatus(errorCode, errMsg), - } - } - dd, err := s.conf.KusciaClient.KusciaV1alpha1().DomainDatas(request.DomainId).Get(ctx, request.DomaindataId, metav1.GetOptions{}) if err != nil { return &kusciaapi.CreateDomainDataGrantResponse{ @@ -109,7 +102,7 @@ func (s *domainDataGrantService) CreateDomainDataGrant(ctx context.Context, requ if err != nil { nlog.Errorf("CreateDomainDataGrant failed, error:%s", err.Error()) return &kusciaapi.CreateDomainDataGrantResponse{ - Status: utils.BuildErrorResponseStatus(errorcode.GetDomainDataGrantErrorCode(err, errorcode.ErrCreateDomainDataGrant), err.Error()), + Status: utils.BuildErrorResponseStatus(errorcode.CreateDomainDataGrantErrorCode(err, errorcode.ErrCreateDomainDataGrant), err.Error()), } } return &kusciaapi.CreateDomainDataGrantResponse{ @@ -121,6 +114,7 @@ func (s *domainDataGrantService) CreateDomainDataGrant(ctx context.Context, requ } func (s *domainDataGrantService) QueryDomainDataGrant(ctx context.Context, request *kusciaapi.QueryDomainDataGrantRequest) *kusciaapi.QueryDomainDataGrantResponse { + dg, err := s.conf.KusciaClient.KusciaV1alpha1().DomainDataGrants(request.DomainId).Get(ctx, request.DomaindatagrantId, metav1.GetOptions{}) if err != nil { nlog.Errorf("Query DomainDataGrant failed, error:%s", err.Error()) @@ -154,6 +148,7 @@ func (s *domainDataGrantService) BatchQueryDomainDataGrant(ctx context.Context, } func (s *domainDataGrantService) UpdateDomainDataGrant(ctx context.Context, request *kusciaapi.UpdateDomainDataGrantRequest) *kusciaapi.UpdateDomainDataGrantResponse { + if request.DomaindataId == "" { return &kusciaapi.UpdateDomainDataGrantResponse{ Status: utils.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "domaindataid cant be null"), diff --git a/pkg/kusciaapi/service/domaindata_service.go b/pkg/kusciaapi/service/domaindata_service.go index 76aac23e..6a0838b8 100644 --- a/pkg/kusciaapi/service/domaindata_service.go +++ b/pkg/kusciaapi/service/domaindata_service.go @@ -76,13 +76,6 @@ func (s domainDataService) CreateDomainData(ctx context.Context, request *kuscia } } - // check domain exists - if errorCode, errMsg := CheckDomainExists(s.conf.KusciaClient, request.GetDomainId()); utils.ResponseCodeSuccess != errorCode { - return &kusciaapi.CreateDomainDataResponse{ - Status: utils.BuildErrorResponseStatus(errorCode, errMsg), - } - } - // check whether domainData is existed if request.DomaindataId != "" { // do k8s validate @@ -104,6 +97,7 @@ func (s domainDataService) CreateDomainData(ctx context.Context, request *kuscia Status: utils.BuildErrorResponseStatus(errorcode.ErrAuthFailed, err.Error()), } } + // normalization request s.normalizationCreateRequest(request) // verdor priority using incoming @@ -144,7 +138,7 @@ func (s domainDataService) CreateDomainData(ctx context.Context, request *kuscia if err != nil { nlog.Errorf("CreateDomainData failed, error: %s", err.Error()) return &kusciaapi.CreateDomainDataResponse{ - Status: utils.BuildErrorResponseStatus(errorcode.GetDomainDataErrorCode(err, errorcode.ErrCreateDomainDataFailed), err.Error()), + Status: utils.BuildErrorResponseStatus(errorcode.CreateDomainDataErrorCode(err, errorcode.ErrCreateDomainDataFailed), err.Error()), } } return &kusciaapi.CreateDomainDataResponse{ @@ -162,6 +156,7 @@ func (s domainDataService) UpdateDomainData(ctx context.Context, request *kuscia Status: utils.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "domain id and domaindata id can not be empty"), } } + if err := s.validateRequestWhenLite(request); err != nil { return &kusciaapi.UpdateDomainDataResponse{ Status: utils.BuildErrorResponseStatus(errorcode.ErrRequestValidate, err.Error()), diff --git a/pkg/kusciaapi/service/domaindata_source.go b/pkg/kusciaapi/service/domaindata_source.go index 60b5de00..cb789dd0 100644 --- a/pkg/kusciaapi/service/domaindata_source.go +++ b/pkg/kusciaapi/service/domaindata_source.go @@ -79,13 +79,6 @@ func (s domainDataSourceService) CreateDomainDataSource(ctx context.Context, req } } - // check domain exists - if errorCode, errMsg := CheckDomainExists(s.conf.KusciaClient, request.GetDomainId()); utils.ResponseCodeSuccess != errorCode { - return &kusciaapi.CreateDomainDataSourceResponse{ - Status: utils.BuildErrorResponseStatus(errorCode, errMsg), - } - } - if request.DatasourceId == "" { name := "" if request.Name != nil { @@ -168,7 +161,7 @@ func (s domainDataSourceService) CreateDomainDataSource(ctx context.Context, req if err != nil { nlog.Errorf(errCreateDomainDataSource, err.Error()) return &kusciaapi.CreateDomainDataSourceResponse{ - Status: utils.BuildErrorResponseStatus(errorcode.GetDomainDataSourceErrorCode(err, errorcode.ErrCreateDomainDataSource), err.Error()), + Status: utils.BuildErrorResponseStatus(errorcode.CreateDomainDataSourceErrorCode(err, errorcode.ErrCreateDomainDataSource), err.Error()), } } diff --git a/pkg/kusciaapi/service/job_service.go b/pkg/kusciaapi/service/job_service.go index 85134726..647edf2d 100644 --- a/pkg/kusciaapi/service/job_service.go +++ b/pkg/kusciaapi/service/job_service.go @@ -21,6 +21,7 @@ import ( "fmt" "math" "reflect" + "strconv" "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -50,6 +51,9 @@ type IJobService interface { DeleteJob(ctx context.Context, request *kusciaapi.DeleteJobRequest) *kusciaapi.DeleteJobResponse WatchJob(ctx context.Context, request *kusciaapi.WatchJobRequest, event chan<- *kusciaapi.WatchJobEventResponse) error ApproveJob(ctx context.Context, request *kusciaapi.ApproveJobRequest) *kusciaapi.ApproveJobResponse + SuspendJob(ctx context.Context, request *kusciaapi.SuspendJobRequest) *kusciaapi.SuspendJobResponse + RestartJob(ctx context.Context, request *kusciaapi.RestartJobRequest) *kusciaapi.RestartJobResponse + CancelJob(ctx context.Context, request *kusciaapi.CancelJobRequest) *kusciaapi.CancelJobResponse } type jobService struct { @@ -269,11 +273,7 @@ func (h *jobService) StopJob(ctx context.Context, request *kusciaapi.StopJobRequ } } // stop kuscia job - if job.Labels == nil { - job.Labels = make(map[string]string) - } - job.Labels[common.LabelJobStage] = string(v1alpha1.JobStopStage) - job.Labels[common.LabelJobStageTrigger] = domainId + h.setJobStage(job, v1alpha1.JobStopStage, h.Initiator) _, err = h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Update(ctx, job, metav1.UpdateOptions{}) if err != nil { return &kusciaapi.StopJobResponse{ @@ -316,7 +316,7 @@ func (h *jobService) ApproveJob(ctx context.Context, request *kusciaapi.ApproveJ } } // auth handler - if err := h.authHandlerJobApprove(ctx, job); err != nil { + if err := h.authHandlerJob(ctx, job); err != nil { return &kusciaapi.ApproveJobResponse{ Status: utils2.BuildErrorResponseStatus(errorcode.ErrAuthFailed, err.Error()), } @@ -356,6 +356,154 @@ func (h *jobService) ApproveJob(ctx context.Context, request *kusciaapi.ApproveJ } } +func (h *jobService) SuspendJob(ctx context.Context, request *kusciaapi.SuspendJobRequest) *kusciaapi.SuspendJobResponse { + // do validate + jobID := request.JobId + if jobID == "" { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "job id can not be empty"), + } + } + // get domain from context + _, domainId := GetRoleAndDomainFromCtx(ctx) + if len(domainId) == 0 { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "source domain header must be set"), + } + } + job, err := h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Get(ctx, jobID, metav1.GetOptions{}) + if err != nil { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrSuspendJob, err.Error()), + } + } + // auth handler + if err := h.authHandlerJob(ctx, job); err != nil { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrAuthFailed, err.Error()), + } + } + nlog.Infof("Suspend job: %s, reason: %s", jobID, request.Reason) + // suspend kuscia job + h.setJobStage(job, v1alpha1.JobSuspendStage, h.Initiator) + + _, err = h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Update(ctx, job, metav1.UpdateOptions{}) + if err != nil { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrSuspendJob, err.Error()), + } + } + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildSuccessResponseStatus(), + Data: &kusciaapi.SuspendJobResponseData{ + JobId: jobID, + }, + } +} + +func (h *jobService) RestartJob(ctx context.Context, request *kusciaapi.RestartJobRequest) *kusciaapi.RestartJobResponse { + // do validate + jobID := request.JobId + if jobID == "" { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "job id can not be empty"), + } + } + // get domain from context + _, domainId := GetRoleAndDomainFromCtx(ctx) + if len(domainId) == 0 { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "source domain header must be set"), + } + } + job, err := h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Get(ctx, jobID, metav1.GetOptions{}) + if err != nil { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRestartJob, err.Error()), + } + } + // auth handler + if err := h.authHandlerJob(ctx, job); err != nil { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrAuthFailed, err.Error()), + } + } + nlog.Infof("Restart job: %s, reason: %s", jobID, request.Reason) + // restart kuscia job + h.setJobStage(job, v1alpha1.JobRestartStage, h.Initiator) + _, err = h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Update(ctx, job, metav1.UpdateOptions{}) + if err != nil { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrSuspendJob, err.Error()), + } + } + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildSuccessResponseStatus(), + Data: &kusciaapi.RestartJobResponseData{ + JobId: jobID, + }, + } +} + +func (h *jobService) CancelJob(ctx context.Context, request *kusciaapi.CancelJobRequest) *kusciaapi.CancelJobResponse { + // do validate + jobID := request.JobId + if jobID == "" { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "job id can not be empty"), + } + } + // get domain from context + _, domainId := GetRoleAndDomainFromCtx(ctx) + if len(domainId) == 0 { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "source domain header must be set"), + } + } + job, err := h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Get(ctx, jobID, metav1.GetOptions{}) + if err != nil { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrCancelJob, err.Error()), + } + } + // auth handler + if err := h.authHandlerJob(ctx, job); err != nil { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrAuthFailed, err.Error()), + } + } + nlog.Infof("Cancel job: %s, reason: %s", jobID, request.Reason) + // cancel kuscia job + h.setJobStage(job, v1alpha1.JobCancelStage, h.Initiator) + _, err = h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Update(ctx, job, metav1.UpdateOptions{}) + if err != nil { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrCancelJob, err.Error()), + } + } + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildSuccessResponseStatus(), + Data: &kusciaapi.CancelJobResponseData{ + JobId: jobID, + }, + } +} + +func (h *jobService) setJobStage(job *v1alpha1.KusciaJob, stage v1alpha1.JobStage, domain string) { + if job.Labels == nil { + job.Labels = make(map[string]string) + } + job.Labels[common.LabelJobStage] = string(stage) + job.Labels[common.LabelJobStageTrigger] = domain + jobVersion := "1" + if v, ok := job.Labels[common.LabelJobStageVersion]; ok { + if iV, err := strconv.Atoi(v); err == nil { + jobVersion = strconv.Itoa(iV + 1) + } + } + job.Labels[common.LabelJobStageVersion] = jobVersion +} + func annotationToDomainList(value string) (domains []string) { domains = strings.Split(value, "_") return @@ -395,20 +543,16 @@ func (h *jobService) BatchQueryJobStatus(ctx context.Context, request *kusciaapi func (h *jobService) WatchJob(ctx context.Context, request *kusciaapi.WatchJobRequest, eventCh chan<- *kusciaapi.WatchJobEventResponse) error { timeout := request.TimeoutSeconds - if timeout < 0 { - return fmt.Errorf("timeout seconds must be greater than or equal to 0") + if timeout < 0 || timeout > math.MaxInt32 { + return fmt.Errorf("timeout seconds must be in [0,2^31-1]") } - var timeoutSeconds *int64 + timeoutSeconds := int64(math.MaxInt32) if request.TimeoutSeconds > 0 { - timeoutSeconds = &request.TimeoutSeconds - } - - if *timeoutSeconds == 0 { - *timeoutSeconds = math.MaxInt64 + timeoutSeconds = request.TimeoutSeconds } w, err := h.kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).Watch(ctx, metav1.ListOptions{ - TimeoutSeconds: timeoutSeconds, + TimeoutSeconds: &timeoutSeconds, }) if err != nil { return err @@ -529,12 +673,13 @@ func (h *jobService) buildJobStatus(ctx context.Context, kusciaJob *v1alpha1.Kus taskID := kt.TaskID ts := &kusciaapi.TaskStatus{ TaskId: taskID, + State: getTaskState(v1alpha1.TaskPending), } if phase, ok := kusciaJobStatus.TaskStatus[taskID]; ok { ts.State = getTaskState(phase) task, err := h.kusciaClient.KusciaV1alpha1().KusciaTasks(common.KusciaCrossDomain).Get(ctx, taskID, metav1.GetOptions{}) if err != nil { - nlog.Warnf("found task [%s] occurs error: %v", taskID, err.Error()) + nlog.Warnf("Failed to get task [%s], %v", taskID, err.Error()) } else { taskStatus := task.Status ts.ErrMsg = taskStatus.Message @@ -660,7 +805,7 @@ func (h *jobService) authHandlerJobWatch(ctx context.Context, kusciaJob *v1alpha return true } -func (h *jobService) authHandlerJobApprove(ctx context.Context, kusciaJob *v1alpha1.KusciaJob) error { +func (h *jobService) authHandlerJob(ctx context.Context, kusciaJob *v1alpha1.KusciaJob) error { role, domainId := GetRoleAndDomainFromCtx(ctx) if role == consts.AuthRoleDomain { for _, task := range kusciaJob.Spec.Tasks { @@ -749,6 +894,8 @@ func getJobState(jobPhase v1alpha1.KusciaJobPhase) string { return kusciaapi.State_ApprovalReject.String() case v1alpha1.KusciaJobCancelled: return kusciaapi.State_Cancelled.String() + case v1alpha1.KusciaJobSuspended: + return kusciaapi.State_Suspended.String() default: return kusciaapi.State_Unknown.String() } diff --git a/pkg/kusciaapi/service/job_service_lite.go b/pkg/kusciaapi/service/job_service_lite.go index 046104d1..e048c09b 100644 --- a/pkg/kusciaapi/service/job_service_lite.go +++ b/pkg/kusciaapi/service/job_service_lite.go @@ -100,6 +100,60 @@ func (h *jobServiceLite) StopJob(ctx context.Context, request *kusciaapi.StopJob return resp } +func (h *jobServiceLite) SuspendJob(ctx context.Context, request *kusciaapi.SuspendJobRequest) *kusciaapi.SuspendJobResponse { + // do validate + jobID := request.JobId + if jobID == "" { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "job id can not be empty"), + } + } + // request the master api + resp, err := h.kusciaAPIClient.SuspendJob(ctx, request) + if err != nil { + return &kusciaapi.SuspendJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestMasterFailed, err.Error()), + } + } + return resp +} + +func (h *jobServiceLite) RestartJob(ctx context.Context, request *kusciaapi.RestartJobRequest) *kusciaapi.RestartJobResponse { + // do validate + jobID := request.JobId + if jobID == "" { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "job id can not be empty"), + } + } + // request the master api + resp, err := h.kusciaAPIClient.RestartJob(ctx, request) + if err != nil { + return &kusciaapi.RestartJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestMasterFailed, err.Error()), + } + } + return resp +} + +func (h *jobServiceLite) CancelJob(ctx context.Context, request *kusciaapi.CancelJobRequest) *kusciaapi.CancelJobResponse { + // do validate + jobID := request.JobId + if jobID == "" { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestValidate, "job id can not be empty"), + } + } + // request the master api + resp, err := h.kusciaAPIClient.CancelJob(ctx, request) + if err != nil { + return &kusciaapi.CancelJobResponse{ + Status: utils2.BuildErrorResponseStatus(errorcode.ErrRequestMasterFailed, err.Error()), + } + } + return resp +} + func (h *jobServiceLite) ApproveJob(ctx context.Context, request *kusciaapi.ApproveJobRequest) *kusciaapi.ApproveJobResponse { // do validate jobID := request.JobId diff --git a/pkg/kusciaapi/service/job_service_test.go b/pkg/kusciaapi/service/job_service_test.go index 1ec00224..587265e7 100644 --- a/pkg/kusciaapi/service/job_service_test.go +++ b/pkg/kusciaapi/service/job_service_test.go @@ -17,11 +17,13 @@ package service import ( "context" + "fmt" "testing" "gotest.tools/v3/assert" "github.com/secretflow/kuscia/pkg/kusciaapi/errorcode" + consts "github.com/secretflow/kuscia/pkg/web/constants" "github.com/secretflow/kuscia/proto/api/v1alpha1/kusciaapi" ) @@ -42,6 +44,52 @@ func TestQueryJob(t *testing.T) { assert.Equal(t, len(queryJobResponse.Data.Tasks), len(kusciaAPIJS.tasks)) } +func TestSuspendJob(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, consts.AuthRole, consts.AuthRoleMaster) + ctx = context.WithValue(ctx, consts.SourceDomainKey, "alice") + kusciaAPIJS.CreateJob(ctx, &kusciaapi.CreateJobRequest{ + JobId: kusciaAPIJS.jobID, + Initiator: "alice", + Tasks: kusciaAPIJS.tasks, + }) + res := kusciaAPIJS.SuspendJob(ctx, &kusciaapi.SuspendJobRequest{ + JobId: kusciaAPIJS.jobID, + }) + fmt.Printf("resp:%+v", res) + assert.Equal(t, res.Data.JobId, kusciaAPIJS.jobID) +} + +func TestRestartJob(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, consts.AuthRole, consts.AuthRoleMaster) + ctx = context.WithValue(ctx, consts.SourceDomainKey, "alice") + res := kusciaAPIJS.RestartJob(ctx, &kusciaapi.RestartJobRequest{ + JobId: kusciaAPIJS.jobID, + }) + assert.Equal(t, res.Data.JobId, kusciaAPIJS.jobID) +} + +func TestStopJob(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, consts.AuthRole, consts.AuthRoleMaster) + ctx = context.WithValue(ctx, consts.SourceDomainKey, "alice") + res := kusciaAPIJS.StopJob(ctx, &kusciaapi.StopJobRequest{ + JobId: kusciaAPIJS.jobID, + }) + assert.Equal(t, res.Data.JobId, kusciaAPIJS.jobID) +} + +func TestCancelJob(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, consts.AuthRole, consts.AuthRoleMaster) + ctx = context.WithValue(ctx, consts.SourceDomainKey, "alice") + res := kusciaAPIJS.CancelJob(ctx, &kusciaapi.CancelJobRequest{ + JobId: kusciaAPIJS.jobID, + }) + assert.Equal(t, res.Data.JobId, kusciaAPIJS.jobID) +} + func TestBatchQueryJob(t *testing.T) { batchResponse := kusciaAPIJS.BatchQueryJobStatus(context.Background(), &kusciaapi.BatchQueryJobStatusRequest{ JobIds: []string{kusciaAPIJS.jobID}, diff --git a/pkg/scheduler/kusciascheduling/core/core.go b/pkg/scheduler/kusciascheduling/core/core.go index 57fea038..15aac745 100644 --- a/pkg/scheduler/kusciascheduling/core/core.go +++ b/pkg/scheduler/kusciascheduling/core/core.go @@ -18,6 +18,7 @@ package core import ( "context" + "errors" "fmt" "strings" "sync" @@ -62,6 +63,8 @@ const ( Wait Status = "Wait" ) +var errWaitingForTaskResource = errors.New("waiting for task resource") + // Manager defines the interfaces for TaskResource management. type Manager interface { PreFilter(context.Context, *corev1.Pod) error @@ -145,7 +148,7 @@ func (trMgr *TaskResourceManager) PreFilter(ctx context.Context, pod *corev1.Pod } if tr == nil { - return fmt.Errorf("failed to get task resource %v/%v for pod", pod.Namespace, trName) + return errWaitingForTaskResource } if tr.Status.Phase == "" || tr.Status.Phase == kusciaapisv1alpha1.TaskResourcePhasePending { diff --git a/pkg/scheduler/kusciascheduling/kusciascheduling.go b/pkg/scheduler/kusciascheduling/kusciascheduling.go index d3228501..bec335e3 100644 --- a/pkg/scheduler/kusciascheduling/kusciascheduling.go +++ b/pkg/scheduler/kusciascheduling/kusciascheduling.go @@ -134,7 +134,6 @@ func (cs *KusciaScheduling) Name() string { // 2. Whether the total number of pods in a TaskResourceGroup is less than its `minReservedMember`. func (cs *KusciaScheduling) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) { if err := cs.trMgr.PreFilter(ctx, pod); err != nil { - nlog.Warnf("PreFilter failed for pod %v/%v, %v", pod.Namespace, pod.Name, err) return nil, framework.NewStatus(framework.UnschedulableAndUnresolvable, err.Error()) } return nil, framework.NewStatus(framework.Success, "") diff --git a/pkg/ssexporter/parse/domain.go b/pkg/ssexporter/parse/domain.go index 68b3bfbf..b53886fe 100644 --- a/pkg/ssexporter/parse/domain.go +++ b/pkg/ssexporter/parse/domain.go @@ -41,12 +41,13 @@ func GetIPFromDomain(localDomainName string) []string { } // GetClusterAddress get the address and port of a remote domain connected by a local domain -func GetClusterAddress(domainID string) map[string][]string { +func GetClusterAddress(domainID string) (map[string][]string, error) { endpointAddresses := make(map[string][]string) // get the results of config_dump resp, err := http.Get("http://localhost:10000/config_dump?resource=dynamic_active_clusters") if err != nil { - nlog.Warn("Fail to get the results of config_dump", err) + nlog.Error("Fail to get the results of config_dump", err) + return endpointAddresses, err } defer func(Body io.ReadCloser) { err := Body.Close() @@ -57,6 +58,7 @@ func GetClusterAddress(domainID string) map[string][]string { body, err := ioutil.ReadAll(resp.Body) if err != nil { nlog.Error("Fail to parse the results of config_dump", err) + return endpointAddresses, err } res := make(map[string]interface{}) err = jsoniter.Unmarshal(body, &res) @@ -93,5 +95,5 @@ func GetClusterAddress(domainID string) map[string][]string { } } } - return endpointAddresses + return endpointAddresses, nil } diff --git a/pkg/ssexporter/ssexporter.go b/pkg/ssexporter/ssexporter.go index 9a6508d3..a4b32958 100644 --- a/pkg/ssexporter/ssexporter.go +++ b/pkg/ssexporter/ssexporter.go @@ -36,7 +36,7 @@ var ( func SsExporter(ctx context.Context, runMode pkgcom.RunModeType, domainID string, exportPeriod uint, port string) error { // read the config _, AggregationMetrics := parse.LoadMetricConfig() - clusterAddresses := parse.GetClusterAddress(domainID) + clusterAddresses, _ := parse.GetClusterAddress(domainID) localDomainName := domainID var MetricTypes = promexporter.NewMetricTypes() @@ -52,7 +52,7 @@ func SsExporter(ctx context.Context, runMode pkgcom.RunModeType, domainID string go func(runMode pkgcom.RunModeType, reg *prometheus.Registry, MetricTypes map[string]string, exportPeriods uint, lastClusterMetricValues map[string]float64) { for range ticker.C { // get clusterName and clusterAddress - clusterAddresses = parse.GetClusterAddress(domainID) + clusterAddresses, _ = parse.GetClusterAddress(domainID) // get cluster metrics currentClusterMetricValues, err := ssmetrics.GetSsMetricResults(runMode, localDomainName, clusterAddresses, AggregationMetrics, exportPeriods) if err != nil { @@ -65,15 +65,14 @@ func SsExporter(ctx context.Context, runMode pkgcom.RunModeType, domainID string } }(runMode, reg, MetricTypes, exportPeriod, lastClusterMetricValues) // export to the prometheus - http.Handle( - "/ssmetrics", promhttp.HandlerFor( - reg, - promhttp.HandlerOpts{ - EnableOpenMetrics: true, - }), - ) + ssServer := http.NewServeMux() + ssServer.Handle("/ssmetrics", promhttp.HandlerFor( + reg, + promhttp.HandlerOpts{ + EnableOpenMetrics: true, + })) go func() { - if err := http.ListenAndServe("0.0.0.0:"+port, nil); err != nil { + if err := http.ListenAndServe("0.0.0.0:"+port, ssServer); err != nil { nlog.Error("Fail to start the metric exporterserver", err) } }() diff --git a/pkg/ssexporter/ssmetrics/ssmetrics.go b/pkg/ssexporter/ssmetrics/ssmetrics.go index 0eff5c77..f04c84e3 100644 --- a/pkg/ssexporter/ssmetrics/ssmetrics.go +++ b/pkg/ssexporter/ssmetrics/ssmetrics.go @@ -30,57 +30,63 @@ import ( // GetStatisticFromSs get the statistics of network flows from ss func GetStatisticFromSs() ([]map[string]string, error) { // execute ss command - cmd := exec.Command("ss", "-tio4nO") + // -H --no-header Suppress header line + cmd := exec.Command("ss", "-tio4nH") output, err := cmd.CombinedOutput() - var tcpStatisticList []map[string]string + if err != nil { nlog.Warnf("Cannot get metrics from ss, error: %v", err) - return tcpStatisticList, err + return nil, err } // parse statistics from ss lines := strings.Split(string(output), "\n") - var ssMetrics map[string]string - for idx, line := range lines { - if idx < 1 { - continue - } - fields := strings.Fields(line) - if len(fields) < 5 { - continue + var tcpStatisticList []map[string]string + // Combine every two lines and parse them + for i := 0; i+1 < len(lines); i += 2 { + combinedLine := lines[i] + lines[i+1] + // Parse the combined line + if ssMetrics := parseCombinedLine(combinedLine); ssMetrics != nil { + tcpStatisticList = append(tcpStatisticList, ssMetrics) } - ssMetrics = make(map[string]string) - ssMetrics[parse.MetricRto] = "0" - ssMetrics[parse.MetricRTT] = "0" - ssMetrics[parse.MetricByteSent] = "0" - ssMetrics[parse.MetricBytesReceived] = "0" - ssMetrics[parse.MetricTotalConnections] = "1" - ssMetrics[parse.MetricRetrans] = "0" - ssMetrics[parse.SsLocalAddr] = fields[3] - ssMetrics[parse.SsPeerAddr] = fields[4] - for idx, field := range fields { - if idx < 6 { - continue - } - kv := strings.Split(field, ":") - if len(kv) == 2 { - switch kv[0] { - case parse.MetricRto: - ssMetrics[parse.MetricRto] = kv[1] - case parse.MetricRTT: - ssMetrics[parse.MetricRTT] = strings.Split(kv[1], "/")[0] - case parse.MetricByteSent: - ssMetrics[parse.MetricByteSent] = kv[1] - case parse.MetricBytesReceived: - ssMetrics[parse.MetricBytesReceived] = kv[1] - case parse.MetricRetrans: - ssMetrics[parse.MetricRetrans] = strings.Split(kv[1], "/")[1] - } + } + return tcpStatisticList, nil +} + +func parseCombinedLine(line string) map[string]string { + fields := strings.Fields(line) + if len(fields) < 5 { + return nil + } + ssMetrics := make(map[string]string) + ssMetrics[parse.MetricRto] = "0" + ssMetrics[parse.MetricRTT] = "0" + ssMetrics[parse.MetricByteSent] = "0" + ssMetrics[parse.MetricBytesReceived] = "0" + ssMetrics[parse.MetricTotalConnections] = "1" + ssMetrics[parse.MetricRetrans] = "0" + ssMetrics[parse.SsLocalAddr] = fields[3] + ssMetrics[parse.SsPeerAddr] = fields[4] + + for idx := 6; idx < len(fields); idx++ { + field := fields[idx] + kv := strings.Split(field, ":") + if len(kv) == 2 { + switch kv[0] { + case parse.MetricRto: + ssMetrics[parse.MetricRto] = kv[1] + case parse.MetricRTT: + ssMetrics[parse.MetricRTT] = strings.Split(kv[1], "/")[0] + case parse.MetricByteSent: + ssMetrics[parse.MetricByteSent] = kv[1] + case parse.MetricBytesReceived: + ssMetrics[parse.MetricBytesReceived] = kv[1] + case parse.MetricRetrans: + ssMetrics[parse.MetricRetrans] = strings.Split(kv[1], "/")[1] } } - tcpStatisticList = append(tcpStatisticList, ssMetrics) } - return tcpStatisticList, nil + return ssMetrics } // Filter filter network flows according to given five-tuple rules diff --git a/pkg/utils/kusciaconfig/service_config.go b/pkg/utils/kusciaconfig/service_config.go index c18700c2..97065029 100644 --- a/pkg/utils/kusciaconfig/service_config.go +++ b/pkg/utils/kusciaconfig/service_config.go @@ -38,6 +38,10 @@ type MasterConfig struct { APIWhitelist []string `yaml:"apiWhitelist,omitempty"` } +func (m *MasterConfig) IsMaster() bool { + return m.APIServer != nil +} + func CheckServiceConfig(config *ServiceConfig, name string) error { if config.Endpoint == "" { return fmt.Errorf("serviceConfig(%s) need specify endpoint", name) diff --git a/pkg/utils/nlog/ljwriter/writer.go b/pkg/utils/nlog/ljwriter/writer.go index 8cacff6c..1bfc7904 100644 --- a/pkg/utils/nlog/ljwriter/writer.go +++ b/pkg/utils/nlog/ljwriter/writer.go @@ -19,7 +19,8 @@ import ( "os" "strings" - "github.com/natefinch/lumberjack" + "gopkg.in/natefinch/lumberjack.v2" + "github.com/secretflow/kuscia/pkg/utils/nlog" ) diff --git a/pkg/utils/queue/queue.go b/pkg/utils/queue/queue.go index 882040e5..21c3e508 100644 --- a/pkg/utils/queue/queue.go +++ b/pkg/utils/queue/queue.go @@ -106,13 +106,13 @@ func HandleQueueItem(ctx context.Context, queueID string, q workqueue.RateLimiti if err := handler(ctx, key); err != nil { if q.NumRequeues(key) < maxRetries { // Put the item back on the work queue to handle any transient errors. - nlog.Warnf("Error syncing: queue id[%v], retry:[%d] key[%v]: %q, re-queuing (%v)", queueID, q.NumRequeues(key), key, err.Error(), time.Since(startTime)) + nlog.Infof("Re-syncing: queue id[%v], retry:[%d] key[%v]: %q, re-queuing (%v)", queueID, q.NumRequeues(key), key, err.Error(), time.Since(startTime)) q.AddRateLimited(key) return } // We've exceeded the maximum retries, so we must forget the key. q.Forget(key) - nlog.Warnf("Forgetting: queue id[%v], key[%v] (%v), due to maximum retries[%v] reached, last error: %q", + nlog.Errorf("Forgetting: queue id[%v], key[%v] (%v), due to maximum retries[%v] reached, last error: %q", queueID, key, time.Since(startTime), maxRetries, err.Error()) return } @@ -162,11 +162,10 @@ func HandleQueueItemWithAlwaysRetry(ctx context.Context, queueID string, q workq } nlog.Debugf("Start processing item: queue id[%v], key[%v]", queueID, key) - // Run the handler, passing it the namespace/name string of the Pod resource to be synced. if err := handler(ctx, key); err != nil { // Put the item back on the work queue to handle any transient errors. - nlog.Warnf("Processing item failed: queue id[%v], key[%v]: %q, re-queuing (%v)", queueID, key, err.Error(), time.Since(startTime)) + nlog.Infof("Re-syncing: queue id[%v], key[%v]: %q, re-queuing (%v)", queueID, key, err.Error(), time.Since(startTime)) q.AddRateLimited(key) return } @@ -174,7 +173,6 @@ func HandleQueueItemWithAlwaysRetry(ctx context.Context, queueID string, q workq // Finally, if no error occurs we Forget this item so it does not get queued again until // another change happens. q.Forget(obj) - nlog.Infof("Finish processing item: queue id[%v], key[%v] (%v)", queueID, key, time.Since(startTime)) } select { @@ -213,7 +211,7 @@ func HandleQueueItemWithoutRetry(ctx context.Context, queueID string, q workqueu nlog.Debugf("Start processing item: queue id[%v], key[%v]", queueID, key) // Run the handler, passing it the namespace/name string of the Pod resource to be synced. if err := handler(ctx, key); err != nil { - nlog.Warnf("Handle queue id[%v] key[%v] (%v) failed: %v", queueID, key, time.Since(startTime), err.Error()) + nlog.Errorf("Handle queue id[%v] key[%v] (%v) failed: %v", queueID, key, time.Since(startTime), err.Error()) } else { nlog.Infof("Finish processing item: queue id[%v], key[%v] (%v)", queueID, key, time.Since(startTime)) } diff --git a/pkg/utils/resources/kusciajob.go b/pkg/utils/resources/kusciajob.go index e5f86255..77dc7446 100644 --- a/pkg/utils/resources/kusciajob.go +++ b/pkg/utils/resources/kusciajob.go @@ -20,6 +20,7 @@ import ( "reflect" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/secretflow/kuscia/pkg/common" @@ -118,7 +119,7 @@ func UpdateKusciaJobStatus(kusciaClient kusciaclientset.Interface, rawKj, curKj return nil } nlog.Infof("Start updating kuscia job %q status", rawKj.Name) - if _, err = kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).UpdateStatus(context.Background(), curKj, metav1.UpdateOptions{}); err != nil { + if _, err = kusciaClient.KusciaV1alpha1().KusciaJobs(common.KusciaCrossDomain).UpdateStatus(context.Background(), curKj, metav1.UpdateOptions{}); err != nil && !k8serrors.IsConflict(err) { return err } nlog.Infof("Finish updating kuscia job %q status", rawKj.Name) diff --git a/proto/api/v1alpha1/datamesh/domaindata.pb.go b/proto/api/v1alpha1/datamesh/domaindata.pb.go index 2016d3b8..07decd85 100644 --- a/proto/api/v1alpha1/datamesh/domaindata.pb.go +++ b/proto/api/v1alpha1/datamesh/domaindata.pb.go @@ -1047,14 +1047,14 @@ var file_kuscia_proto_api_v1alpha1_datamesh_domaindata_proto_rawDesc = []byte{ 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5c, 0x0a, 0x20, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5f, 0x0a, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x31, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x80, 0x01, 0x01, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/api/v1alpha1/datamesh/domaindatasource.pb.go b/proto/api/v1alpha1/datamesh/domaindatasource.pb.go index 87e1eb26..e5992baa 100644 --- a/proto/api/v1alpha1/datamesh/domaindatasource.pb.go +++ b/proto/api/v1alpha1/datamesh/domaindatasource.pb.go @@ -659,14 +659,14 @@ var file_kuscia_proto_api_v1alpha1_datamesh_domaindatasource_proto_rawDesc = []b 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5c, 0x0a, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5f, 0x0a, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x61, 0x31, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x6d, 0x65, 0x73, 0x68, 0x80, 0x01, 0x01, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/api/v1alpha1/kusciaapi/domain_route.pb.go b/proto/api/v1alpha1/kusciaapi/domain_route.pb.go index 0c647c3a..4fae2ba3 100644 --- a/proto/api/v1alpha1/kusciaapi/domain_route.pb.go +++ b/proto/api/v1alpha1/kusciaapi/domain_route.pb.go @@ -239,6 +239,7 @@ type EndpointPort struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` + IsTLS bool `protobuf:"varint,4,opt,name=isTLS,proto3" json:"isTLS,omitempty"` } func (x *EndpointPort) Reset() { @@ -294,6 +295,13 @@ func (x *EndpointPort) GetProtocol() string { return "" } +func (x *EndpointPort) GetIsTLS() bool { + if x != nil { + return x.IsTLS + } + return false +} + type TokenConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1245,215 +1253,216 @@ var file_kuscia_proto_api_v1alpha1_kusciaapi_domain_route_proto_rawDesc = []byte 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x22, 0x52, 0x0a, 0x0c, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x6f, 0x72, 0x74, 0x73, 0x22, 0x68, 0x0a, 0x0c, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0xcd, 0x01, 0x0a, 0x0b, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x16, 0x64, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, - 0x0a, 0x15, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, - 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x69, - 0x6f, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x28, - 0x0a, 0x10, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x65, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x47, - 0x65, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x0a, 0x4d, 0x54, 0x4c, - 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x6c, 0x73, 0x5f, 0x63, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6c, 0x73, 0x43, 0x61, 0x12, 0x39, - 0x0a, 0x19, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x16, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x22, 0xae, 0x01, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x56, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x33, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x96, 0x01, - 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, - 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x56, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x95, - 0x01, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, - 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xac, 0x01, 0x0a, 0x18, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x55, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, - 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xde, 0x03, 0x0a, 0x1c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x61, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, - 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x32, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x75, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x73, 0x54, 0x4c, + 0x53, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x54, 0x4c, 0x53, 0x22, 0xcd, + 0x01, 0x0a, 0x0b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, + 0x0a, 0x16, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, + 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x65, + 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x47, 0x65, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x8c, + 0x01, 0x0a, 0x0a, 0x4d, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x15, 0x0a, + 0x06, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x6c, 0x73, 0x43, 0x61, 0x12, 0x39, 0x0a, 0x19, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x2c, 0x0a, 0x12, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x22, 0xae, 0x01, + 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, - 0x69, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x0b, 0x6d, 0x74, - 0x6c, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x0a, 0x6d, 0x74, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x48, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, - 0x70, 0x69, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3d, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xba, 0x01, 0x0a, 0x22, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x52, - 0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x33, + 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x56, 0x0a, 0x19, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xac, 0x01, 0x0a, + 0x18, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x55, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x4b, 0x65, - 0x79, 0x73, 0x22, 0x4a, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc2, - 0x01, 0x0a, 0x23, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x60, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x4c, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x22, 0x79, 0x0a, 0x27, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x4e, - 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0xab, - 0x01, 0x0a, 0x11, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xde, 0x03, 0x0a, 0x1c, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x2f, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x61, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x0b, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x50, 0x0a, 0x0b, 0x6d, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x54, 0x4c, 0x53, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x6d, 0x74, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x29, 0x0a, 0x12, - 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, - 0x04, 0x4d, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x32, 0x83, 0x05, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x92, - 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x12, 0x3d, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x3d, 0x2e, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3d, 0x0a, 0x0b, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xba, 0x01, 0x0a, 0x22, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x10, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x3c, 0x2e, - 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, - 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x6b, 0x75, - 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, - 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xb0, 0x01, 0x0a, 0x1b, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x47, 0x2e, 0x6b, 0x75, 0x73, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x4a, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc2, 0x01, 0x0a, 0x23, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x60, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4c, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, + 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x79, 0x0a, 0x27, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x4e, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x22, 0xab, 0x01, 0x0a, 0x11, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x2a, 0x29, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x32, 0x83, 0x05, + 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x3d, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, - 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x48, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x11, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, + 0x3d, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, + 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8f, + 0x01, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x12, 0x3c, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5e, 0x0a, - 0x21, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, - 0x70, 0x69, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x3d, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0xb0, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x47, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, + 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x48, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x5e, 0x0a, 0x21, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2f, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/api/v1alpha1/kusciaapi/domain_route.proto b/proto/api/v1alpha1/kusciaapi/domain_route.proto index e7589d2c..35bc815f 100644 --- a/proto/api/v1alpha1/kusciaapi/domain_route.proto +++ b/proto/api/v1alpha1/kusciaapi/domain_route.proto @@ -52,6 +52,7 @@ message EndpointPort { string name = 1; int32 port = 2; string protocol = 3; + bool isTLS = 4; } message TokenConfig { diff --git a/proto/api/v1alpha1/kusciaapi/domaindatasource.pb.go b/proto/api/v1alpha1/kusciaapi/domaindatasource.pb.go index b4ae486e..5b753188 100644 --- a/proto/api/v1alpha1/kusciaapi/domaindatasource.pb.go +++ b/proto/api/v1alpha1/kusciaapi/domaindatasource.pb.go @@ -1069,7 +1069,7 @@ type OssDataSourceInfo struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // endpoint oss.xxx.cn-xxx.com or 127.0.0.1:9000 + // endpoint https://oss.xxx.cn-xxx.com or http://127.0.0.1:9000 Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` // the bucket name of the oss datasource Bucket string `protobuf:"bytes,2,opt,name=bucket,proto3" json:"bucket,omitempty"` diff --git a/proto/api/v1alpha1/kusciaapi/job.pb.go b/proto/api/v1alpha1/kusciaapi/job.pb.go index 846646df..3dde8bf8 100644 --- a/proto/api/v1alpha1/kusciaapi/job.pb.go +++ b/proto/api/v1alpha1/kusciaapi/job.pb.go @@ -46,6 +46,7 @@ const ( State_AwaitingApproval State = 5 // await the partner to approval the job State_ApprovalReject State = 6 // partner reject the job State_Cancelled State = 7 // the job cannot start again when it was cancelled + State_Suspended State = 8 // the job was suspended ) // Enum value maps for State. @@ -59,6 +60,7 @@ var ( 5: "AwaitingApproval", 6: "ApprovalReject", 7: "Cancelled", + 8: "Suspended", } State_value = map[string]int32{ "Unknown": 0, @@ -69,6 +71,7 @@ var ( "AwaitingApproval": 5, "ApprovalReject": 6, "Cancelled": 7, + "Suspended": 8, } ) @@ -706,6 +709,7 @@ type StopJobRequest struct { Header *v1alpha1.RequestHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` } func (x *StopJobRequest) Reset() { @@ -754,6 +758,13 @@ func (x *StopJobRequest) GetJobId() string { return "" } +func (x *StopJobRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + type StopJobResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -856,6 +867,501 @@ func (x *StopJobResponseData) GetJobId() string { return "" } +type SuspendJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *v1alpha1.RequestHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *SuspendJobRequest) Reset() { + *x = SuspendJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SuspendJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SuspendJobRequest) ProtoMessage() {} + +func (x *SuspendJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SuspendJobRequest.ProtoReflect.Descriptor instead. +func (*SuspendJobRequest) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{11} +} + +func (x *SuspendJobRequest) GetHeader() *v1alpha1.RequestHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *SuspendJobRequest) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *SuspendJobRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type SuspendJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status *v1alpha1.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Data *SuspendJobResponseData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *SuspendJobResponse) Reset() { + *x = SuspendJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SuspendJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SuspendJobResponse) ProtoMessage() {} + +func (x *SuspendJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SuspendJobResponse.ProtoReflect.Descriptor instead. +func (*SuspendJobResponse) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{12} +} + +func (x *SuspendJobResponse) GetStatus() *v1alpha1.Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *SuspendJobResponse) GetData() *SuspendJobResponseData { + if x != nil { + return x.Data + } + return nil +} + +type SuspendJobResponseData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` +} + +func (x *SuspendJobResponseData) Reset() { + *x = SuspendJobResponseData{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SuspendJobResponseData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SuspendJobResponseData) ProtoMessage() {} + +func (x *SuspendJobResponseData) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SuspendJobResponseData.ProtoReflect.Descriptor instead. +func (*SuspendJobResponseData) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{13} +} + +func (x *SuspendJobResponseData) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +type RestartJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *v1alpha1.RequestHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *RestartJobRequest) Reset() { + *x = RestartJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartJobRequest) ProtoMessage() {} + +func (x *RestartJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartJobRequest.ProtoReflect.Descriptor instead. +func (*RestartJobRequest) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{14} +} + +func (x *RestartJobRequest) GetHeader() *v1alpha1.RequestHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *RestartJobRequest) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *RestartJobRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type RestartJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status *v1alpha1.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Data *RestartJobResponseData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *RestartJobResponse) Reset() { + *x = RestartJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartJobResponse) ProtoMessage() {} + +func (x *RestartJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartJobResponse.ProtoReflect.Descriptor instead. +func (*RestartJobResponse) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{15} +} + +func (x *RestartJobResponse) GetStatus() *v1alpha1.Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *RestartJobResponse) GetData() *RestartJobResponseData { + if x != nil { + return x.Data + } + return nil +} + +type RestartJobResponseData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` +} + +func (x *RestartJobResponseData) Reset() { + *x = RestartJobResponseData{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartJobResponseData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartJobResponseData) ProtoMessage() {} + +func (x *RestartJobResponseData) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartJobResponseData.ProtoReflect.Descriptor instead. +func (*RestartJobResponseData) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{16} +} + +func (x *RestartJobResponseData) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +type CancelJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *v1alpha1.RequestHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *CancelJobRequest) Reset() { + *x = CancelJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CancelJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelJobRequest) ProtoMessage() {} + +func (x *CancelJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelJobRequest.ProtoReflect.Descriptor instead. +func (*CancelJobRequest) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{17} +} + +func (x *CancelJobRequest) GetHeader() *v1alpha1.RequestHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *CancelJobRequest) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *CancelJobRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type CancelJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status *v1alpha1.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Data *CancelJobResponseData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *CancelJobResponse) Reset() { + *x = CancelJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CancelJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelJobResponse) ProtoMessage() {} + +func (x *CancelJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelJobResponse.ProtoReflect.Descriptor instead. +func (*CancelJobResponse) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{18} +} + +func (x *CancelJobResponse) GetStatus() *v1alpha1.Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *CancelJobResponse) GetData() *CancelJobResponseData { + if x != nil { + return x.Data + } + return nil +} + +type CancelJobResponseData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` +} + +func (x *CancelJobResponseData) Reset() { + *x = CancelJobResponseData{} + if protoimpl.UnsafeEnabled { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CancelJobResponseData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelJobResponseData) ProtoMessage() {} + +func (x *CancelJobResponseData) ProtoReflect() protoreflect.Message { + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelJobResponseData.ProtoReflect.Descriptor instead. +func (*CancelJobResponseData) Descriptor() ([]byte, []int) { + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{19} +} + +func (x *CancelJobResponseData) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + type QueryJobRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -868,7 +1374,7 @@ type QueryJobRequest struct { func (x *QueryJobRequest) Reset() { *x = QueryJobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[11] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -881,7 +1387,7 @@ func (x *QueryJobRequest) String() string { func (*QueryJobRequest) ProtoMessage() {} func (x *QueryJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[11] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -894,7 +1400,7 @@ func (x *QueryJobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryJobRequest.ProtoReflect.Descriptor instead. func (*QueryJobRequest) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{11} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{20} } func (x *QueryJobRequest) GetHeader() *v1alpha1.RequestHeader { @@ -923,7 +1429,7 @@ type QueryJobResponse struct { func (x *QueryJobResponse) Reset() { *x = QueryJobResponse{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[12] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -936,7 +1442,7 @@ func (x *QueryJobResponse) String() string { func (*QueryJobResponse) ProtoMessage() {} func (x *QueryJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[12] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -949,7 +1455,7 @@ func (x *QueryJobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryJobResponse.ProtoReflect.Descriptor instead. func (*QueryJobResponse) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{12} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{21} } func (x *QueryJobResponse) GetStatus() *v1alpha1.Status { @@ -982,7 +1488,7 @@ type QueryJobResponseData struct { func (x *QueryJobResponseData) Reset() { *x = QueryJobResponseData{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[13] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -995,7 +1501,7 @@ func (x *QueryJobResponseData) String() string { func (*QueryJobResponseData) ProtoMessage() {} func (x *QueryJobResponseData) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[13] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1008,7 +1514,7 @@ func (x *QueryJobResponseData) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryJobResponseData.ProtoReflect.Descriptor instead. func (*QueryJobResponseData) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{13} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{22} } func (x *QueryJobResponseData) GetJobId() string { @@ -1066,7 +1572,7 @@ type ApproveJobRequest struct { func (x *ApproveJobRequest) Reset() { *x = ApproveJobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[14] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1079,7 +1585,7 @@ func (x *ApproveJobRequest) String() string { func (*ApproveJobRequest) ProtoMessage() {} func (x *ApproveJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[14] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1092,7 +1598,7 @@ func (x *ApproveJobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveJobRequest.ProtoReflect.Descriptor instead. func (*ApproveJobRequest) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{14} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{23} } func (x *ApproveJobRequest) GetJobId() string { @@ -1128,7 +1634,7 @@ type ApproveJobResponse struct { func (x *ApproveJobResponse) Reset() { *x = ApproveJobResponse{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[15] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1141,7 +1647,7 @@ func (x *ApproveJobResponse) String() string { func (*ApproveJobResponse) ProtoMessage() {} func (x *ApproveJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[15] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1154,7 +1660,7 @@ func (x *ApproveJobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveJobResponse.ProtoReflect.Descriptor instead. func (*ApproveJobResponse) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{15} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{24} } func (x *ApproveJobResponse) GetStatus() *v1alpha1.Status { @@ -1182,7 +1688,7 @@ type ApproveJobResponseData struct { func (x *ApproveJobResponseData) Reset() { *x = ApproveJobResponseData{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[16] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1195,7 +1701,7 @@ func (x *ApproveJobResponseData) String() string { func (*ApproveJobResponseData) ProtoMessage() {} func (x *ApproveJobResponseData) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[16] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1208,7 +1714,7 @@ func (x *ApproveJobResponseData) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveJobResponseData.ProtoReflect.Descriptor instead. func (*ApproveJobResponseData) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{16} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{25} } func (x *ApproveJobResponseData) GetJobId() string { @@ -1236,7 +1742,7 @@ type JobStatusDetail struct { func (x *JobStatusDetail) Reset() { *x = JobStatusDetail{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[17] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1249,7 +1755,7 @@ func (x *JobStatusDetail) String() string { func (*JobStatusDetail) ProtoMessage() {} func (x *JobStatusDetail) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[17] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1262,7 +1768,7 @@ func (x *JobStatusDetail) ProtoReflect() protoreflect.Message { // Deprecated: Use JobStatusDetail.ProtoReflect.Descriptor instead. func (*JobStatusDetail) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{17} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{26} } func (x *JobStatusDetail) GetState() string { @@ -1338,7 +1844,7 @@ type TaskConfig struct { func (x *TaskConfig) Reset() { *x = TaskConfig{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[18] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1351,7 +1857,7 @@ func (x *TaskConfig) String() string { func (*TaskConfig) ProtoMessage() {} func (x *TaskConfig) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[18] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1364,7 +1870,7 @@ func (x *TaskConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskConfig.ProtoReflect.Descriptor instead. func (*TaskConfig) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{18} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{27} } func (x *TaskConfig) GetAppImage() string { @@ -1428,7 +1934,7 @@ type PartyStageStatus struct { func (x *PartyStageStatus) Reset() { *x = PartyStageStatus{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[19] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1441,7 +1947,7 @@ func (x *PartyStageStatus) String() string { func (*PartyStageStatus) ProtoMessage() {} func (x *PartyStageStatus) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[19] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1454,7 +1960,7 @@ func (x *PartyStageStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PartyStageStatus.ProtoReflect.Descriptor instead. func (*PartyStageStatus) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{19} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{28} } func (x *PartyStageStatus) GetDomainId() string { @@ -1483,7 +1989,7 @@ type PartyApproveStatus struct { func (x *PartyApproveStatus) Reset() { *x = PartyApproveStatus{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[20] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1496,7 +2002,7 @@ func (x *PartyApproveStatus) String() string { func (*PartyApproveStatus) ProtoMessage() {} func (x *PartyApproveStatus) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[20] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1509,7 +2015,7 @@ func (x *PartyApproveStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PartyApproveStatus.ProtoReflect.Descriptor instead. func (*PartyApproveStatus) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{20} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{29} } func (x *PartyApproveStatus) GetDomainId() string { @@ -1543,7 +2049,7 @@ type TaskStatus struct { func (x *TaskStatus) Reset() { *x = TaskStatus{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[21] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1556,7 +2062,7 @@ func (x *TaskStatus) String() string { func (*TaskStatus) ProtoMessage() {} func (x *TaskStatus) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[21] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1569,7 +2075,7 @@ func (x *TaskStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskStatus.ProtoReflect.Descriptor instead. func (*TaskStatus) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{21} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{30} } func (x *TaskStatus) GetTaskId() string { @@ -1635,7 +2141,7 @@ type PartyStatus struct { func (x *PartyStatus) Reset() { *x = PartyStatus{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[22] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1648,7 +2154,7 @@ func (x *PartyStatus) String() string { func (*PartyStatus) ProtoMessage() {} func (x *PartyStatus) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[22] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1661,7 +2167,7 @@ func (x *PartyStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PartyStatus.ProtoReflect.Descriptor instead. func (*PartyStatus) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{22} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{31} } func (x *PartyStatus) GetDomainId() string { @@ -1704,7 +2210,7 @@ type BatchQueryJobStatusRequest struct { func (x *BatchQueryJobStatusRequest) Reset() { *x = BatchQueryJobStatusRequest{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[23] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1717,7 +2223,7 @@ func (x *BatchQueryJobStatusRequest) String() string { func (*BatchQueryJobStatusRequest) ProtoMessage() {} func (x *BatchQueryJobStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[23] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1730,7 +2236,7 @@ func (x *BatchQueryJobStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchQueryJobStatusRequest.ProtoReflect.Descriptor instead. func (*BatchQueryJobStatusRequest) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{23} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{32} } func (x *BatchQueryJobStatusRequest) GetHeader() *v1alpha1.RequestHeader { @@ -1759,7 +2265,7 @@ type BatchQueryJobStatusResponse struct { func (x *BatchQueryJobStatusResponse) Reset() { *x = BatchQueryJobStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[24] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1772,7 +2278,7 @@ func (x *BatchQueryJobStatusResponse) String() string { func (*BatchQueryJobStatusResponse) ProtoMessage() {} func (x *BatchQueryJobStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[24] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1785,7 +2291,7 @@ func (x *BatchQueryJobStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchQueryJobStatusResponse.ProtoReflect.Descriptor instead. func (*BatchQueryJobStatusResponse) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{24} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{33} } func (x *BatchQueryJobStatusResponse) GetStatus() *v1alpha1.Status { @@ -1813,7 +2319,7 @@ type BatchQueryJobStatusResponseData struct { func (x *BatchQueryJobStatusResponseData) Reset() { *x = BatchQueryJobStatusResponseData{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[25] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1826,7 +2332,7 @@ func (x *BatchQueryJobStatusResponseData) String() string { func (*BatchQueryJobStatusResponseData) ProtoMessage() {} func (x *BatchQueryJobStatusResponseData) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[25] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1839,7 +2345,7 @@ func (x *BatchQueryJobStatusResponseData) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchQueryJobStatusResponseData.ProtoReflect.Descriptor instead. func (*BatchQueryJobStatusResponseData) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{25} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{34} } func (x *BatchQueryJobStatusResponseData) GetJobs() []*JobStatus { @@ -1861,7 +2367,7 @@ type JobStatusResponse struct { func (x *JobStatusResponse) Reset() { *x = JobStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[26] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1874,7 +2380,7 @@ func (x *JobStatusResponse) String() string { func (*JobStatusResponse) ProtoMessage() {} func (x *JobStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[26] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1887,7 +2393,7 @@ func (x *JobStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use JobStatusResponse.ProtoReflect.Descriptor instead. func (*JobStatusResponse) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{26} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{35} } func (x *JobStatusResponse) GetStatus() *v1alpha1.Status { @@ -1916,7 +2422,7 @@ type JobStatusResponseData struct { func (x *JobStatusResponseData) Reset() { *x = JobStatusResponseData{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[27] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1929,7 +2435,7 @@ func (x *JobStatusResponseData) String() string { func (*JobStatusResponseData) ProtoMessage() {} func (x *JobStatusResponseData) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[27] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1942,7 +2448,7 @@ func (x *JobStatusResponseData) ProtoReflect() protoreflect.Message { // Deprecated: Use JobStatusResponseData.ProtoReflect.Descriptor instead. func (*JobStatusResponseData) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{27} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{36} } func (x *JobStatusResponseData) GetJobId() string { @@ -1971,7 +2477,7 @@ type JobStatus struct { func (x *JobStatus) Reset() { *x = JobStatus{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[28] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1984,7 +2490,7 @@ func (x *JobStatus) String() string { func (*JobStatus) ProtoMessage() {} func (x *JobStatus) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[28] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1997,7 +2503,7 @@ func (x *JobStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use JobStatus.ProtoReflect.Descriptor instead. func (*JobStatus) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{28} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{37} } func (x *JobStatus) GetJobId() string { @@ -2026,7 +2532,7 @@ type WatchJobRequest struct { func (x *WatchJobRequest) Reset() { *x = WatchJobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[29] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2039,7 +2545,7 @@ func (x *WatchJobRequest) String() string { func (*WatchJobRequest) ProtoMessage() {} func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[29] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2052,7 +2558,7 @@ func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchJobRequest.ProtoReflect.Descriptor instead. func (*WatchJobRequest) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{29} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{38} } func (x *WatchJobRequest) GetHeader() *v1alpha1.RequestHeader { @@ -2081,7 +2587,7 @@ type WatchJobEventResponse struct { func (x *WatchJobEventResponse) Reset() { *x = WatchJobEventResponse{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[30] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2094,7 +2600,7 @@ func (x *WatchJobEventResponse) String() string { func (*WatchJobEventResponse) ProtoMessage() {} func (x *WatchJobEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[30] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2107,7 +2613,7 @@ func (x *WatchJobEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchJobEventResponse.ProtoReflect.Descriptor instead. func (*WatchJobEventResponse) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{30} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{39} } func (x *WatchJobEventResponse) GetType() EventType { @@ -2140,7 +2646,7 @@ type JobPartyEndpoint struct { func (x *JobPartyEndpoint) Reset() { *x = JobPartyEndpoint{} if protoimpl.UnsafeEnabled { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[31] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2153,7 +2659,7 @@ func (x *JobPartyEndpoint) String() string { func (*JobPartyEndpoint) ProtoMessage() {} func (x *JobPartyEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[31] + mi := &file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2166,7 +2672,7 @@ func (x *JobPartyEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use JobPartyEndpoint.ProtoReflect.Descriptor instead. func (*JobPartyEndpoint) Descriptor() ([]byte, []int) { - return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{31} + return file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP(), []int{40} } func (x *JobPartyEndpoint) GetPortName() string { @@ -2280,80 +2786,58 @@ var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDesc = []byte{ 0x61, 0x22, 0x2e, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, - 0x64, 0x22, 0x69, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x9a, 0x01, 0x0a, - 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4c, 0x0a, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, + 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, - 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x74, 0x6f, - 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, - 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x6a, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, - 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x06, - 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, - 0x62, 0x49, 0x64, 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x4d, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x39, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, - 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x4c, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x22, 0xbc, 0x03, 0x0a, 0x14, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, - 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, - 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, - 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, - 0x69, 0x73, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x50, 0x61, - 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x12, 0x45, 0x0a, 0x05, 0x74, 0x61, 0x73, - 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x54, - 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, - 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, - 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x70, - 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, - 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x8e, 0x01, 0x0a, 0x11, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x4a, - 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, + 0x74, 0x61, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, + 0x22, 0x84, 0x01, 0x0a, 0x11, 0x53, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xa0, 0x01, 0x0a, 0x12, 0x53, 0x75, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4f, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x75, + 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2f, 0x0a, 0x16, 0x53, 0x75, + 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x84, 0x01, 0x0a, 0x11, + 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x22, 0xa0, 0x01, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, + 0x6f, 0x6e, 0x22, 0xa0, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, @@ -2361,249 +2845,362 @@ var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDesc = []byte{ 0x61, 0x74, 0x75, 0x73, 0x12, 0x4f, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2f, 0x0a, 0x16, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2f, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0xae, 0x03, 0x0a, 0x0f, 0x4a, 0x6f, 0x62, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x61, 0x0a, 0x11, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, - 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x72, - 0x74, 0x79, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0f, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x67, - 0x0a, 0x13, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x6b, 0x75, + 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x43, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x15, 0x0a, + 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, + 0x6f, 0x62, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x9e, 0x01, 0x0a, + 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, - 0x69, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x79, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x11, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x8a, 0x02, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, 0x70, 0x49, 0x6d, - 0x61, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x79, - 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, - 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, - 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, - 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, - 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, - 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x6e, 0x70, - 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x22, 0x45, 0x0a, 0x10, 0x50, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x61, - 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x47, 0x0a, 0x12, 0x50, - 0x61, 0x72, 0x74, 0x79, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 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, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, - 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, - 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x65, - 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, - 0x72, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, - 0x65, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x53, - 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, - 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x50, 0x61, 0x72, 0x74, 0x79, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x22, 0x77, 0x0a, 0x1a, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x73, 0x22, 0xb2, 0x01, 0x0a, - 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x58, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, - 0x61, 0x22, 0x65, 0x0a, 0x1f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, - 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x44, 0x61, 0x74, 0x61, 0x12, 0x42, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x4a, 0x6f, 0x62, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, + 0x69, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2e, 0x0a, + 0x15, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x6a, 0x0a, + 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x04, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4d, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, - 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x7c, 0x0a, 0x15, 0x4a, 0x6f, 0x62, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, - 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xbc, 0x03, 0x0a, 0x14, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, + 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0e, 0x6d, 0x61, 0x78, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x12, + 0x45, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, + 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x70, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, + 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8e, 0x01, 0x0a, 0x11, 0x41, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, + 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, + 0x6f, 0x62, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, + 0x76, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xa0, 0x01, 0x0a, 0x12, 0x41, 0x70, 0x70, + 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4f, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x41, + 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2f, 0x0a, 0x16, 0x41, + 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0xae, 0x03, 0x0a, + 0x0f, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, + 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x74, 0x61, + 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, - 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x70, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x4c, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6b, 0x75, + 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x12, 0x61, 0x0a, 0x11, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, + 0x70, 0x69, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x67, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x08, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x37, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x79, 0x41, 0x70, 0x70, + 0x72, 0x6f, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x11, 0x61, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x8a, 0x02, + 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, + 0x61, 0x70, 0x70, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x61, 0x70, 0x70, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6b, 0x75, 0x73, + 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, + 0x2e, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, + 0x61, 0x73, 0x6b, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x45, 0x0a, 0x10, 0x50, 0x61, + 0x72, 0x74, 0x79, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, + 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x22, 0x47, 0x0a, 0x12, 0x50, 0x61, 0x72, 0x74, 0x79, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x0a, 0x54, + 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, + 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, + 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x07, + 0x70, 0x61, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x72, + 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x65, + 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, + 0x72, 0x4d, 0x73, 0x67, 0x12, 0x53, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, + 0x62, 0x50, 0x61, 0x72, 0x74, 0x79, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x77, 0x0a, 0x1a, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x6a, 0x6f, 0x62, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x6f, 0x62, 0x49, + 0x64, 0x73, 0x22, 0xb2, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x58, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, - 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x7c, 0x0a, 0x0f, 0x57, 0x61, 0x74, - 0x63, 0x68, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x27, - 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x57, 0x61, 0x74, 0x63, - 0x68, 0x4a, 0x6f, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x42, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x2e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, + 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, + 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x65, 0x0a, 0x1f, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x42, 0x0a, 0x04, 0x6a, 0x6f, + 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, + 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x9e, + 0x01, 0x0a, 0x11, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x4e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, + 0x7c, 0x0a, 0x15, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, + 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, + 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x70, 0x0a, + 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, + 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, + 0x64, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x7c, 0x0a, 0x0f, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xa3, 0x01, + 0x0a, 0x15, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x6f, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x61, 0x0a, - 0x10, 0x4a, 0x6f, 0x62, 0x50, 0x61, 0x72, 0x74, 0x79, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x2a, 0x82, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, - 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, - 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x10, 0x03, - 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, - 0x41, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, - 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x52, 0x65, - 0x6a, 0x65, 0x63, 0x74, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x6c, 0x65, 0x64, 0x10, 0x07, 0x2a, 0x61, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x52, 0x4f, 0x56, - 0x45, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x50, 0x50, 0x52, 0x4f, 0x56, 0x45, 0x5f, 0x52, 0x45, - 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x01, 0x12, 0x19, 0x0a, - 0x15, 0x41, 0x50, 0x50, 0x52, 0x4f, 0x56, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, - 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x02, 0x2a, 0x4b, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, - 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52, 0x54, 0x42, - 0x45, 0x41, 0x54, 0x10, 0x04, 0x32, 0x8d, 0x07, 0x0a, 0x0a, 0x4a, 0x6f, 0x62, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, - 0x62, 0x12, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, + 0x69, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x22, 0x61, 0x0a, 0x10, 0x4a, 0x6f, 0x62, 0x50, 0x61, 0x72, 0x74, 0x79, 0x45, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2a, 0x91, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, + 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x63, 0x63, 0x65, + 0x65, 0x64, 0x65, 0x64, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x41, 0x70, + 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x53, + 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x10, 0x08, 0x2a, 0x61, 0x0a, 0x0d, 0x41, 0x70, + 0x70, 0x72, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1a, 0x0a, 0x16, 0x41, + 0x50, 0x50, 0x52, 0x4f, 0x56, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x50, 0x50, 0x52, 0x4f, + 0x56, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, + 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x50, 0x50, 0x52, 0x4f, 0x56, 0x45, 0x5f, 0x52, 0x45, + 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x02, 0x2a, 0x4b, 0x0a, + 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, + 0x44, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x48, + 0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x04, 0x32, 0x87, 0x0a, 0x0a, 0x0a, 0x4a, + 0x6f, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7a, 0x0a, 0x09, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x08, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, + 0x62, 0x12, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, - 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, - 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x77, 0x0a, 0x08, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x12, 0x34, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, - 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, - 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x13, 0x42, 0x61, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x98, + 0x01, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x40, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x3f, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, - 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x40, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x12, - 0x33, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x07, 0x53, 0x74, 0x6f, + 0x70, 0x4a, 0x6f, 0x62, 0x12, 0x33, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x4a, - 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x09, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x08, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4a, - 0x6f, 0x62, 0x12, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, - 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x6f, - 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, - 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x4a, 0x6f, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x7d, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, - 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x36, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, - 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6b, + 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, + 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, + 0x53, 0x74, 0x6f, 0x70, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x7d, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x12, 0x36, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7d, + 0x0a, 0x0a, 0x53, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x36, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, - 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5e, 0x0a, 0x21, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, - 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6b, 0x75, 0x73, 0x63, - 0x69, 0x61, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x69, 0x2e, 0x53, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x75, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, + 0x09, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x35, 0x2e, 0x6b, 0x75, 0x73, + 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, + 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x36, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, + 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x09, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x35, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x08, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x6f, + 0x62, 0x12, 0x34, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x6f, 0x62, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x4a, 0x6f, 0x62, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x7d, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, + 0x4a, 0x6f, 0x62, 0x12, 0x36, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, + 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, + 0x69, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5e, 0x0a, 0x21, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x61, 0x70, 0x69, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, 0x77, + 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, + 0x61, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2619,7 +3216,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDescGZIP() []byte { } var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes = make([]protoimpl.MessageInfo, 34) +var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes = make([]protoimpl.MessageInfo, 43) var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_goTypes = []interface{}{ (State)(0), // 0: kuscia.proto.api.v1alpha1.kusciaapi.State (ApproveResult)(0), // 1: kuscia.proto.api.v1alpha1.kusciaapi.ApproveResult @@ -2635,90 +3232,114 @@ var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_goTypes = []interface{}{ (*StopJobRequest)(nil), // 11: kuscia.proto.api.v1alpha1.kusciaapi.StopJobRequest (*StopJobResponse)(nil), // 12: kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponse (*StopJobResponseData)(nil), // 13: kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponseData - (*QueryJobRequest)(nil), // 14: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobRequest - (*QueryJobResponse)(nil), // 15: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse - (*QueryJobResponseData)(nil), // 16: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData - (*ApproveJobRequest)(nil), // 17: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobRequest - (*ApproveJobResponse)(nil), // 18: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse - (*ApproveJobResponseData)(nil), // 19: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponseData - (*JobStatusDetail)(nil), // 20: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail - (*TaskConfig)(nil), // 21: kuscia.proto.api.v1alpha1.kusciaapi.TaskConfig - (*PartyStageStatus)(nil), // 22: kuscia.proto.api.v1alpha1.kusciaapi.PartyStageStatus - (*PartyApproveStatus)(nil), // 23: kuscia.proto.api.v1alpha1.kusciaapi.PartyApproveStatus - (*TaskStatus)(nil), // 24: kuscia.proto.api.v1alpha1.kusciaapi.TaskStatus - (*PartyStatus)(nil), // 25: kuscia.proto.api.v1alpha1.kusciaapi.PartyStatus - (*BatchQueryJobStatusRequest)(nil), // 26: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusRequest - (*BatchQueryJobStatusResponse)(nil), // 27: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse - (*BatchQueryJobStatusResponseData)(nil), // 28: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponseData - (*JobStatusResponse)(nil), // 29: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponse - (*JobStatusResponseData)(nil), // 30: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponseData - (*JobStatus)(nil), // 31: kuscia.proto.api.v1alpha1.kusciaapi.JobStatus - (*WatchJobRequest)(nil), // 32: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobRequest - (*WatchJobEventResponse)(nil), // 33: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse - (*JobPartyEndpoint)(nil), // 34: kuscia.proto.api.v1alpha1.kusciaapi.JobPartyEndpoint - nil, // 35: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.CustomFieldsEntry - nil, // 36: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.CustomFieldsEntry - (*v1alpha1.RequestHeader)(nil), // 37: kuscia.proto.api.v1alpha1.RequestHeader - (*v1alpha1.Status)(nil), // 38: kuscia.proto.api.v1alpha1.Status + (*SuspendJobRequest)(nil), // 14: kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobRequest + (*SuspendJobResponse)(nil), // 15: kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobResponse + (*SuspendJobResponseData)(nil), // 16: kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobResponseData + (*RestartJobRequest)(nil), // 17: kuscia.proto.api.v1alpha1.kusciaapi.RestartJobRequest + (*RestartJobResponse)(nil), // 18: kuscia.proto.api.v1alpha1.kusciaapi.RestartJobResponse + (*RestartJobResponseData)(nil), // 19: kuscia.proto.api.v1alpha1.kusciaapi.RestartJobResponseData + (*CancelJobRequest)(nil), // 20: kuscia.proto.api.v1alpha1.kusciaapi.CancelJobRequest + (*CancelJobResponse)(nil), // 21: kuscia.proto.api.v1alpha1.kusciaapi.CancelJobResponse + (*CancelJobResponseData)(nil), // 22: kuscia.proto.api.v1alpha1.kusciaapi.CancelJobResponseData + (*QueryJobRequest)(nil), // 23: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobRequest + (*QueryJobResponse)(nil), // 24: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse + (*QueryJobResponseData)(nil), // 25: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData + (*ApproveJobRequest)(nil), // 26: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobRequest + (*ApproveJobResponse)(nil), // 27: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse + (*ApproveJobResponseData)(nil), // 28: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponseData + (*JobStatusDetail)(nil), // 29: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail + (*TaskConfig)(nil), // 30: kuscia.proto.api.v1alpha1.kusciaapi.TaskConfig + (*PartyStageStatus)(nil), // 31: kuscia.proto.api.v1alpha1.kusciaapi.PartyStageStatus + (*PartyApproveStatus)(nil), // 32: kuscia.proto.api.v1alpha1.kusciaapi.PartyApproveStatus + (*TaskStatus)(nil), // 33: kuscia.proto.api.v1alpha1.kusciaapi.TaskStatus + (*PartyStatus)(nil), // 34: kuscia.proto.api.v1alpha1.kusciaapi.PartyStatus + (*BatchQueryJobStatusRequest)(nil), // 35: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusRequest + (*BatchQueryJobStatusResponse)(nil), // 36: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse + (*BatchQueryJobStatusResponseData)(nil), // 37: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponseData + (*JobStatusResponse)(nil), // 38: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponse + (*JobStatusResponseData)(nil), // 39: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponseData + (*JobStatus)(nil), // 40: kuscia.proto.api.v1alpha1.kusciaapi.JobStatus + (*WatchJobRequest)(nil), // 41: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobRequest + (*WatchJobEventResponse)(nil), // 42: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse + (*JobPartyEndpoint)(nil), // 43: kuscia.proto.api.v1alpha1.kusciaapi.JobPartyEndpoint + nil, // 44: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.CustomFieldsEntry + nil, // 45: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.CustomFieldsEntry + (*v1alpha1.RequestHeader)(nil), // 46: kuscia.proto.api.v1alpha1.RequestHeader + (*v1alpha1.Status)(nil), // 47: kuscia.proto.api.v1alpha1.Status } var file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_depIdxs = []int32{ - 37, // 0: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 46, // 0: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader 6, // 1: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.tasks:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.Task - 35, // 2: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.custom_fields:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.CustomFieldsEntry - 38, // 3: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 44, // 2: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.custom_fields:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest.CustomFieldsEntry + 47, // 3: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status 5, // 4: kuscia.proto.api.v1alpha1.kusciaapi.CreateJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobResponseData 7, // 5: kuscia.proto.api.v1alpha1.kusciaapi.Task.parties:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.Party - 37, // 6: kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader - 38, // 7: kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 46, // 6: kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 7: kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status 10, // 8: kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobResponseData - 37, // 9: kuscia.proto.api.v1alpha1.kusciaapi.StopJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader - 38, // 10: kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 46, // 9: kuscia.proto.api.v1alpha1.kusciaapi.StopJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 10: kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status 13, // 11: kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponseData - 37, // 12: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader - 38, // 13: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status - 16, // 14: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData - 21, // 15: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.tasks:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.TaskConfig - 20, // 16: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.status:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail - 36, // 17: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.custom_fields:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.CustomFieldsEntry - 1, // 18: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobRequest.result:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveResult - 38, // 19: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status - 19, // 20: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponseData - 24, // 21: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail.tasks:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.TaskStatus - 22, // 22: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail.stage_status_list:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.PartyStageStatus - 23, // 23: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail.approve_status_list:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.PartyApproveStatus - 7, // 24: kuscia.proto.api.v1alpha1.kusciaapi.TaskConfig.parties:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.Party - 25, // 25: kuscia.proto.api.v1alpha1.kusciaapi.TaskStatus.parties:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.PartyStatus - 34, // 26: kuscia.proto.api.v1alpha1.kusciaapi.PartyStatus.endpoints:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobPartyEndpoint - 37, // 27: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader - 38, // 28: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status - 28, // 29: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponseData - 31, // 30: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponseData.jobs:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatus - 38, // 31: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status - 30, // 32: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponseData - 20, // 33: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponseData.status:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail - 20, // 34: kuscia.proto.api.v1alpha1.kusciaapi.JobStatus.status:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail - 37, // 35: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader - 2, // 36: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse.type:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.EventType - 31, // 37: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse.object:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatus - 3, // 38: kuscia.proto.api.v1alpha1.kusciaapi.JobService.CreateJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest - 14, // 39: kuscia.proto.api.v1alpha1.kusciaapi.JobService.QueryJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobRequest - 26, // 40: kuscia.proto.api.v1alpha1.kusciaapi.JobService.BatchQueryJobStatus:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusRequest - 11, // 41: kuscia.proto.api.v1alpha1.kusciaapi.JobService.StopJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.StopJobRequest - 8, // 42: kuscia.proto.api.v1alpha1.kusciaapi.JobService.DeleteJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobRequest - 32, // 43: kuscia.proto.api.v1alpha1.kusciaapi.JobService.WatchJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.WatchJobRequest - 17, // 44: kuscia.proto.api.v1alpha1.kusciaapi.JobService.ApproveJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobRequest - 4, // 45: kuscia.proto.api.v1alpha1.kusciaapi.JobService.CreateJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobResponse - 15, // 46: kuscia.proto.api.v1alpha1.kusciaapi.JobService.QueryJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse - 27, // 47: kuscia.proto.api.v1alpha1.kusciaapi.JobService.BatchQueryJobStatus:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse - 12, // 48: kuscia.proto.api.v1alpha1.kusciaapi.JobService.StopJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponse - 9, // 49: kuscia.proto.api.v1alpha1.kusciaapi.JobService.DeleteJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobResponse - 33, // 50: kuscia.proto.api.v1alpha1.kusciaapi.JobService.WatchJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse - 18, // 51: kuscia.proto.api.v1alpha1.kusciaapi.JobService.ApproveJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse - 45, // [45:52] is the sub-list for method output_type - 38, // [38:45] is the sub-list for method input_type - 38, // [38:38] is the sub-list for extension type_name - 38, // [38:38] is the sub-list for extension extendee - 0, // [0:38] is the sub-list for field type_name + 46, // 12: kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 13: kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 16, // 14: kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobResponseData + 46, // 15: kuscia.proto.api.v1alpha1.kusciaapi.RestartJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 16: kuscia.proto.api.v1alpha1.kusciaapi.RestartJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 19, // 17: kuscia.proto.api.v1alpha1.kusciaapi.RestartJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.RestartJobResponseData + 46, // 18: kuscia.proto.api.v1alpha1.kusciaapi.CancelJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 19: kuscia.proto.api.v1alpha1.kusciaapi.CancelJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 22, // 20: kuscia.proto.api.v1alpha1.kusciaapi.CancelJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.CancelJobResponseData + 46, // 21: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 22: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 25, // 23: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData + 30, // 24: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.tasks:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.TaskConfig + 29, // 25: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.status:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail + 45, // 26: kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.custom_fields:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponseData.CustomFieldsEntry + 1, // 27: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobRequest.result:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveResult + 47, // 28: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 28, // 29: kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponseData + 33, // 30: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail.tasks:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.TaskStatus + 31, // 31: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail.stage_status_list:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.PartyStageStatus + 32, // 32: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail.approve_status_list:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.PartyApproveStatus + 7, // 33: kuscia.proto.api.v1alpha1.kusciaapi.TaskConfig.parties:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.Party + 34, // 34: kuscia.proto.api.v1alpha1.kusciaapi.TaskStatus.parties:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.PartyStatus + 43, // 35: kuscia.proto.api.v1alpha1.kusciaapi.PartyStatus.endpoints:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobPartyEndpoint + 46, // 36: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 47, // 37: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 37, // 38: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponseData + 40, // 39: kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponseData.jobs:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatus + 47, // 40: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponse.status:type_name -> kuscia.proto.api.v1alpha1.Status + 39, // 41: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponse.data:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponseData + 29, // 42: kuscia.proto.api.v1alpha1.kusciaapi.JobStatusResponseData.status:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail + 29, // 43: kuscia.proto.api.v1alpha1.kusciaapi.JobStatus.status:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatusDetail + 46, // 44: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobRequest.header:type_name -> kuscia.proto.api.v1alpha1.RequestHeader + 2, // 45: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse.type:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.EventType + 40, // 46: kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse.object:type_name -> kuscia.proto.api.v1alpha1.kusciaapi.JobStatus + 3, // 47: kuscia.proto.api.v1alpha1.kusciaapi.JobService.CreateJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobRequest + 23, // 48: kuscia.proto.api.v1alpha1.kusciaapi.JobService.QueryJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobRequest + 35, // 49: kuscia.proto.api.v1alpha1.kusciaapi.JobService.BatchQueryJobStatus:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusRequest + 11, // 50: kuscia.proto.api.v1alpha1.kusciaapi.JobService.StopJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.StopJobRequest + 17, // 51: kuscia.proto.api.v1alpha1.kusciaapi.JobService.RestartJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.RestartJobRequest + 14, // 52: kuscia.proto.api.v1alpha1.kusciaapi.JobService.SuspendJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobRequest + 20, // 53: kuscia.proto.api.v1alpha1.kusciaapi.JobService.CancelJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.CancelJobRequest + 8, // 54: kuscia.proto.api.v1alpha1.kusciaapi.JobService.DeleteJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobRequest + 41, // 55: kuscia.proto.api.v1alpha1.kusciaapi.JobService.WatchJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.WatchJobRequest + 26, // 56: kuscia.proto.api.v1alpha1.kusciaapi.JobService.ApproveJob:input_type -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobRequest + 4, // 57: kuscia.proto.api.v1alpha1.kusciaapi.JobService.CreateJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.CreateJobResponse + 24, // 58: kuscia.proto.api.v1alpha1.kusciaapi.JobService.QueryJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.QueryJobResponse + 36, // 59: kuscia.proto.api.v1alpha1.kusciaapi.JobService.BatchQueryJobStatus:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.BatchQueryJobStatusResponse + 12, // 60: kuscia.proto.api.v1alpha1.kusciaapi.JobService.StopJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.StopJobResponse + 18, // 61: kuscia.proto.api.v1alpha1.kusciaapi.JobService.RestartJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.RestartJobResponse + 15, // 62: kuscia.proto.api.v1alpha1.kusciaapi.JobService.SuspendJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.SuspendJobResponse + 21, // 63: kuscia.proto.api.v1alpha1.kusciaapi.JobService.CancelJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.CancelJobResponse + 9, // 64: kuscia.proto.api.v1alpha1.kusciaapi.JobService.DeleteJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.DeleteJobResponse + 42, // 65: kuscia.proto.api.v1alpha1.kusciaapi.JobService.WatchJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.WatchJobEventResponse + 27, // 66: kuscia.proto.api.v1alpha1.kusciaapi.JobService.ApproveJob:output_type -> kuscia.proto.api.v1alpha1.kusciaapi.ApproveJobResponse + 57, // [57:67] is the sub-list for method output_type + 47, // [47:57] is the sub-list for method input_type + 47, // [47:47] is the sub-list for extension type_name + 47, // [47:47] is the sub-list for extension extendee + 0, // [0:47] is the sub-list for field type_name } func init() { file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() } @@ -2860,7 +3481,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryJobRequest); i { + switch v := v.(*SuspendJobRequest); i { case 0: return &v.state case 1: @@ -2872,7 +3493,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryJobResponse); i { + switch v := v.(*SuspendJobResponse); i { case 0: return &v.state case 1: @@ -2884,7 +3505,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryJobResponseData); i { + switch v := v.(*SuspendJobResponseData); i { case 0: return &v.state case 1: @@ -2896,7 +3517,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApproveJobRequest); i { + switch v := v.(*RestartJobRequest); i { case 0: return &v.state case 1: @@ -2908,7 +3529,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApproveJobResponse); i { + switch v := v.(*RestartJobResponse); i { case 0: return &v.state case 1: @@ -2920,7 +3541,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApproveJobResponseData); i { + switch v := v.(*RestartJobResponseData); i { case 0: return &v.state case 1: @@ -2932,7 +3553,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JobStatusDetail); i { + switch v := v.(*CancelJobRequest); i { case 0: return &v.state case 1: @@ -2944,7 +3565,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TaskConfig); i { + switch v := v.(*CancelJobResponse); i { case 0: return &v.state case 1: @@ -2956,7 +3577,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PartyStageStatus); i { + switch v := v.(*CancelJobResponseData); i { case 0: return &v.state case 1: @@ -2968,7 +3589,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PartyApproveStatus); i { + switch v := v.(*QueryJobRequest); i { case 0: return &v.state case 1: @@ -2980,7 +3601,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TaskStatus); i { + switch v := v.(*QueryJobResponse); i { case 0: return &v.state case 1: @@ -2992,7 +3613,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PartyStatus); i { + switch v := v.(*QueryJobResponseData); i { case 0: return &v.state case 1: @@ -3004,7 +3625,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchQueryJobStatusRequest); i { + switch v := v.(*ApproveJobRequest); i { case 0: return &v.state case 1: @@ -3016,7 +3637,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchQueryJobStatusResponse); i { + switch v := v.(*ApproveJobResponse); i { case 0: return &v.state case 1: @@ -3028,7 +3649,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchQueryJobStatusResponseData); i { + switch v := v.(*ApproveJobResponseData); i { case 0: return &v.state case 1: @@ -3040,7 +3661,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JobStatusResponse); i { + switch v := v.(*JobStatusDetail); i { case 0: return &v.state case 1: @@ -3052,7 +3673,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JobStatusResponseData); i { + switch v := v.(*TaskConfig); i { case 0: return &v.state case 1: @@ -3064,7 +3685,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JobStatus); i { + switch v := v.(*PartyStageStatus); i { case 0: return &v.state case 1: @@ -3076,7 +3697,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WatchJobRequest); i { + switch v := v.(*PartyApproveStatus); i { case 0: return &v.state case 1: @@ -3088,7 +3709,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WatchJobEventResponse); i { + switch v := v.(*TaskStatus); i { case 0: return &v.state case 1: @@ -3100,6 +3721,114 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { } } file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PartyStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchQueryJobStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchQueryJobStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchQueryJobStatusResponseData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*JobStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*JobStatusResponseData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*JobStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchJobRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchJobEventResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*JobPartyEndpoint); i { case 0: return &v.state @@ -3118,7 +3847,7 @@ func file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_kuscia_proto_api_v1alpha1_kusciaapi_job_proto_rawDesc, NumEnums: 3, - NumMessages: 34, + NumMessages: 43, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/api/v1alpha1/kusciaapi/job.proto b/proto/api/v1alpha1/kusciaapi/job.proto index 5bdc721c..66dcaef1 100644 --- a/proto/api/v1alpha1/kusciaapi/job.proto +++ b/proto/api/v1alpha1/kusciaapi/job.proto @@ -29,6 +29,12 @@ service JobService { rpc BatchQueryJobStatus(BatchQueryJobStatusRequest) returns (BatchQueryJobStatusResponse); rpc StopJob(StopJobRequest)returns (StopJobResponse); + + rpc RestartJob(RestartJobRequest)returns (RestartJobResponse); + + rpc SuspendJob(SuspendJobRequest)returns (SuspendJobResponse); + + rpc CancelJob(CancelJobRequest)returns (CancelJobResponse); rpc DeleteJob(DeleteJobRequest) returns (DeleteJobResponse); @@ -87,6 +93,7 @@ message DeleteJobResponseData { message StopJobRequest { RequestHeader header = 1; string job_id = 2; + string reason = 3; } message StopJobResponse { @@ -98,6 +105,51 @@ message StopJobResponseData { string job_id = 1; } +message SuspendJobRequest { + RequestHeader header = 1; + string job_id = 2; + string reason = 3; +} + +message SuspendJobResponse { + Status status = 1; + SuspendJobResponseData data = 2; +} + +message SuspendJobResponseData { + string job_id = 1; +} + +message RestartJobRequest { + RequestHeader header = 1; + string job_id = 2; + string reason = 3; +} + +message RestartJobResponse { + Status status = 1; + RestartJobResponseData data = 2; +} + +message RestartJobResponseData { + string job_id = 1; +} + +message CancelJobRequest { + RequestHeader header = 1; + string job_id = 2; + string reason = 3; +} + +message CancelJobResponse { + Status status = 1; + CancelJobResponseData data = 2; +} + +message CancelJobResponseData { + string job_id = 1; +} + message QueryJobRequest { RequestHeader header = 1; string job_id = 2; @@ -190,6 +242,7 @@ enum State { AwaitingApproval = 5; // await the partner to approval the job ApprovalReject = 6; // partner reject the job Cancelled = 7; // the job cannot start again when it was cancelled + Suspended = 8; // the job was suspended } enum ApproveResult { diff --git a/proto/api/v1alpha1/kusciaapi/job_grpc.pb.go b/proto/api/v1alpha1/kusciaapi/job_grpc.pb.go index 83239cbf..9ac38636 100644 --- a/proto/api/v1alpha1/kusciaapi/job_grpc.pb.go +++ b/proto/api/v1alpha1/kusciaapi/job_grpc.pb.go @@ -37,6 +37,9 @@ const ( JobService_QueryJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/QueryJob" JobService_BatchQueryJobStatus_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/BatchQueryJobStatus" JobService_StopJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/StopJob" + JobService_RestartJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/RestartJob" + JobService_SuspendJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/SuspendJob" + JobService_CancelJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/CancelJob" JobService_DeleteJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/DeleteJob" JobService_WatchJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/WatchJob" JobService_ApproveJob_FullMethodName = "/kuscia.proto.api.v1alpha1.kusciaapi.JobService/ApproveJob" @@ -50,6 +53,9 @@ type JobServiceClient interface { QueryJob(ctx context.Context, in *QueryJobRequest, opts ...grpc.CallOption) (*QueryJobResponse, error) BatchQueryJobStatus(ctx context.Context, in *BatchQueryJobStatusRequest, opts ...grpc.CallOption) (*BatchQueryJobStatusResponse, error) StopJob(ctx context.Context, in *StopJobRequest, opts ...grpc.CallOption) (*StopJobResponse, error) + RestartJob(ctx context.Context, in *RestartJobRequest, opts ...grpc.CallOption) (*RestartJobResponse, error) + SuspendJob(ctx context.Context, in *SuspendJobRequest, opts ...grpc.CallOption) (*SuspendJobResponse, error) + CancelJob(ctx context.Context, in *CancelJobRequest, opts ...grpc.CallOption) (*CancelJobResponse, error) DeleteJob(ctx context.Context, in *DeleteJobRequest, opts ...grpc.CallOption) (*DeleteJobResponse, error) WatchJob(ctx context.Context, in *WatchJobRequest, opts ...grpc.CallOption) (JobService_WatchJobClient, error) ApproveJob(ctx context.Context, in *ApproveJobRequest, opts ...grpc.CallOption) (*ApproveJobResponse, error) @@ -99,6 +105,33 @@ func (c *jobServiceClient) StopJob(ctx context.Context, in *StopJobRequest, opts return out, nil } +func (c *jobServiceClient) RestartJob(ctx context.Context, in *RestartJobRequest, opts ...grpc.CallOption) (*RestartJobResponse, error) { + out := new(RestartJobResponse) + err := c.cc.Invoke(ctx, JobService_RestartJob_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *jobServiceClient) SuspendJob(ctx context.Context, in *SuspendJobRequest, opts ...grpc.CallOption) (*SuspendJobResponse, error) { + out := new(SuspendJobResponse) + err := c.cc.Invoke(ctx, JobService_SuspendJob_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *jobServiceClient) CancelJob(ctx context.Context, in *CancelJobRequest, opts ...grpc.CallOption) (*CancelJobResponse, error) { + out := new(CancelJobResponse) + err := c.cc.Invoke(ctx, JobService_CancelJob_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *jobServiceClient) DeleteJob(ctx context.Context, in *DeleteJobRequest, opts ...grpc.CallOption) (*DeleteJobResponse, error) { out := new(DeleteJobResponse) err := c.cc.Invoke(ctx, JobService_DeleteJob_FullMethodName, in, out, opts...) @@ -157,6 +190,9 @@ type JobServiceServer interface { QueryJob(context.Context, *QueryJobRequest) (*QueryJobResponse, error) BatchQueryJobStatus(context.Context, *BatchQueryJobStatusRequest) (*BatchQueryJobStatusResponse, error) StopJob(context.Context, *StopJobRequest) (*StopJobResponse, error) + RestartJob(context.Context, *RestartJobRequest) (*RestartJobResponse, error) + SuspendJob(context.Context, *SuspendJobRequest) (*SuspendJobResponse, error) + CancelJob(context.Context, *CancelJobRequest) (*CancelJobResponse, error) DeleteJob(context.Context, *DeleteJobRequest) (*DeleteJobResponse, error) WatchJob(*WatchJobRequest, JobService_WatchJobServer) error ApproveJob(context.Context, *ApproveJobRequest) (*ApproveJobResponse, error) @@ -179,6 +215,15 @@ func (UnimplementedJobServiceServer) BatchQueryJobStatus(context.Context, *Batch func (UnimplementedJobServiceServer) StopJob(context.Context, *StopJobRequest) (*StopJobResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method StopJob not implemented") } +func (UnimplementedJobServiceServer) RestartJob(context.Context, *RestartJobRequest) (*RestartJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RestartJob not implemented") +} +func (UnimplementedJobServiceServer) SuspendJob(context.Context, *SuspendJobRequest) (*SuspendJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SuspendJob not implemented") +} +func (UnimplementedJobServiceServer) CancelJob(context.Context, *CancelJobRequest) (*CancelJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelJob not implemented") +} func (UnimplementedJobServiceServer) DeleteJob(context.Context, *DeleteJobRequest) (*DeleteJobResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteJob not implemented") } @@ -273,6 +318,60 @@ func _JobService_StopJob_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _JobService_RestartJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RestartJobRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobServiceServer).RestartJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: JobService_RestartJob_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(JobServiceServer).RestartJob(ctx, req.(*RestartJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _JobService_SuspendJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SuspendJobRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobServiceServer).SuspendJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: JobService_SuspendJob_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(JobServiceServer).SuspendJob(ctx, req.(*SuspendJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _JobService_CancelJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CancelJobRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobServiceServer).CancelJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: JobService_CancelJob_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(JobServiceServer).CancelJob(ctx, req.(*CancelJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _JobService_DeleteJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteJobRequest) if err := dec(in); err != nil { @@ -353,6 +452,18 @@ var JobService_ServiceDesc = grpc.ServiceDesc{ MethodName: "StopJob", Handler: _JobService_StopJob_Handler, }, + { + MethodName: "RestartJob", + Handler: _JobService_RestartJob_Handler, + }, + { + MethodName: "SuspendJob", + Handler: _JobService_SuspendJob_Handler, + }, + { + MethodName: "CancelJob", + Handler: _JobService_CancelJob_Handler, + }, { MethodName: "DeleteJob", Handler: _JobService_DeleteJob_Handler, diff --git a/scripts/deploy/deploy.sh b/scripts/deploy/deploy.sh index a3bc0909..fe3f8f06 100755 --- a/scripts/deploy/deploy.sh +++ b/scripts/deploy/deploy.sh @@ -112,28 +112,16 @@ function do_http_probe() { local ctr=$1 local endpoint=$2 local max_retry=$3 - local retry=0 - while [ $retry -lt "$max_retry" ]; do - local status_code - status_code=$(docker exec -it $ctr curl -k --write-out '%{http_code}' --silent --output /dev/null "${endpoint}") - if [[ $status_code -eq 200 || $status_code -eq 404 || $status_code -eq 401 ]]; then - return 0 - fi - sleep 1 - retry=$((retry + 1)) - done - - return 1 -} + local enable_mtls=$4 + local cert_config + if [[ "$enable_mtls" == "true" ]]; then + cert_config="--cacert ${CTR_CERT_ROOT}/ca.crt --cert ${CTR_CERT_ROOT}/ca.crt --key ${CTR_CERT_ROOT}/ca.key" + fi -function do_https_probe() { - local ctr=$1 - local endpoint=$2 - local max_retry=$3 local retry=0 - while [ $retry -lt "$max_retry" ]; do + while [[ "$retry" -lt "$max_retry" ]]; do local status_code - status_code=$(docker exec -it $ctr curl -k --write-out '%{http_code}' --silent --output /dev/null "${endpoint}" --cacert ${CTR_CERT_ROOT}/ca.crt --cert ${CTR_CERT_ROOT}/ca.crt --key ${CTR_CERT_ROOT}/ca.key) + status_code=$(docker exec -it $ctr curl -k --write-out '%{http_code}' --silent --output /dev/null ${endpoint} ${cert_config}) if [[ $status_code -eq 200 || $status_code -eq 404 || $status_code -eq 401 ]]; then return 0 fi @@ -245,7 +233,7 @@ function create_secretflow_app_image() { fi app_type=$(echo "${image_repo}" | awk -F'/' '{print $NF}' | awk -F'-' '{print $1}') - if [[ ${app_type} == "" ]]; then + if [[ ${app_type} != "psi" ]]; then app_type="secretflow" fi @@ -255,7 +243,7 @@ function create_secretflow_app_image() { function probe_datamesh() { local domain_ctr=$1 - if ! do_https_probe "$domain_ctr" "https://127.0.0.1:8070/healthZ" 30; then + if ! do_http_probe "$domain_ctr" "https://127.0.0.1:8070/healthZ" 30 true; then echo "[Error] Probe datamesh in container '$domain_ctr' failed." >&2 echo "You cloud run command that 'docker logs $domain_ctr' to check the log" >&2 fi @@ -290,7 +278,7 @@ function generate_mount_flag() { function get_runtime() { local conf_file=$1 local runtime - runtime=$(grep '^runtime:' ${conf_file} 2>/dev/null | cut -d':' -f2 | awk '{$1=$1};1') + runtime=$(grep '^runtime:' ${conf_file} 2>/dev/null | cut -d':' -f2 | awk '{$1=$1};1' | tr -d '\r\n') if [[ $runtime == "" ]]; then runtime=runc fi @@ -476,7 +464,10 @@ agent: } function get_absolute_path() { - echo "$(cd "$(dirname -- "$1")" >/dev/null; pwd -P)/$(basename -- "$1")" + echo "$( + cd "$(dirname -- "$1")" >/dev/null + pwd -P + )/$(basename -- "$1")" } usage() { @@ -507,7 +498,7 @@ OPTIONS: deploy_mode= case "$1" in -autonomy | lite | master ) +autonomy | lite | master) deploy_mode=$1 shift ;; @@ -596,7 +587,7 @@ fi function init() { local deploy_mode=$1 local domain_ctr=${USER}-kuscia-${deploy_mode}-${DOMAIN_ID} - if [[ ${deploy_mode} == "master" ]]; then + if [[ ${deploy_mode} == "master" ]]; then local domain_ctr=${USER}-kuscia-master fi [[ ${ROOT} == "" ]] && ROOT=${PWD} diff --git a/scripts/deploy/init_example_data.sh b/scripts/deploy/init_example_data.sh new file mode 100755 index 00000000..d9648f20 --- /dev/null +++ b/scripts/deploy/init_example_data.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +function create_alice2bob_example_data() { + curl -X POST 'https://127.0.0.1:8082/api/v1/domaindata/create' --header "Token: $(cat /home/kuscia/var/certs/token)" --header 'Content-Type: application/json' -d '{"domaindata_id":"alice-table","name":"alice.csv","type":"table","relative_uri":"alice.csv","domain_id":"alice","datasource_id":"default-data-source","attributes":{"description":"alice demo data"},"columns":[{"comment":"","name":"id1","type":"str"},{"comment":"","name":"age","type":"float"},{"comment":"","name":"education","type":"float"},{"comment":"","name":"default","type":"float"},{"comment":"","name":"balance","type":"float"},{"comment":"","name":"housing","type":"float"},{"comment":"","name":"loan","type":"float"},{"comment":"","name":"day","type":"float"},{"comment":"","name":"duration","type":"float"},{"comment":"","name":"campaign","type":"float"},{"comment":"","name":"pdays","type":"float"},{"comment":"","name":"previous","type":"float"},{"comment":"","name":"job_blue-collar","type":"float"},{"comment":"","name":"job_entrepreneur","type":"float"},{"comment":"","name":"job_housemaid","type":"float"},{"comment":"","name":"job_management","type":"float"},{"comment":"","name":"job_retired","type":"float"},{"comment":"","name":"job_self-employed","type":"float"},{"comment":"","name":"job_services","type":"float"},{"comment":"","name":"job_student","type":"float"},{"comment":"","name":"job_technician","type":"float"},{"comment":"","name":"job_unemployed","type":"float"},{"comment":"","name":"marital_divorced","type":"float"},{"comment":"","name":"marital_married","type":"float"},{"comment":"","name":"marital_single","type":"float"}],"vendor":"manual","author":"alice"}' --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo + curl -X POST 'https://127.0.0.1:8082/api/v1/domaindata/create' --header "Token: $(cat /home/kuscia/var/certs/token)" --header 'Content-Type: application/json' -d '{"domaindata_id":"alice-dp-table","name":"alice.csv","type":"table","relative_uri":"alice.csv","domain_id":"alice","datasource_id":"default-dp-data-source","attributes":{"description":"alice demo data"},"columns":[{"comment":"","name":"id1","type":"str"},{"comment":"","name":"age","type":"float"},{"comment":"","name":"education","type":"float"},{"comment":"","name":"default","type":"float"},{"comment":"","name":"balance","type":"float"},{"comment":"","name":"housing","type":"float"},{"comment":"","name":"loan","type":"float"},{"comment":"","name":"day","type":"float"},{"comment":"","name":"duration","type":"float"},{"comment":"","name":"campaign","type":"float"},{"comment":"","name":"pdays","type":"float"},{"comment":"","name":"previous","type":"float"},{"comment":"","name":"job_blue-collar","type":"float"},{"comment":"","name":"job_entrepreneur","type":"float"},{"comment":"","name":"job_housemaid","type":"float"},{"comment":"","name":"job_management","type":"float"},{"comment":"","name":"job_retired","type":"float"},{"comment":"","name":"job_self-employed","type":"float"},{"comment":"","name":"job_services","type":"float"},{"comment":"","name":"job_student","type":"float"},{"comment":"","name":"job_technician","type":"float"},{"comment":"","name":"job_unemployed","type":"float"},{"comment":"","name":"marital_divorced","type":"float"},{"comment":"","name":"marital_married","type":"float"},{"comment":"","name":"marital_single","type":"float"}],"vendor":"manual","author":"alice"}' --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo + curl https://127.0.0.1:8070/api/v1/datamesh/domaindatagrant/create -X POST -H 'content-type: application/json' -d '{"author":"alice","domaindata_id":"alice-table","grant_domain":"bob"}' \ + --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo + curl https://127.0.0.1:8070/api/v1/datamesh/domaindatagrant/create -X POST -H 'content-type: application/json' -d '{"author":"alice","domaindata_id":"alice-dp-table","grant_domain":"bob"}' \ + --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo +} + +function create_bob2alice_example_data() { + curl -X POST 'https://127.0.0.1:8082/api/v1/domaindata/create' --header "Token: $(cat /home/kuscia/var/certs/token)" --header 'Content-Type: application/json' -d '{"domaindata_id":"bob-table","name":"bob.csv","type":"table","relative_uri":"bob.csv","domain_id":"bob","datasource_id":"default-data-source","attributes":{"description":"bob demo data"},"columns":[{"comment":"","name":"id2","type":"str"},{"comment":"","name":"contact_cellular","type":"float"},{"comment":"","name":"contact_telephone","type":"float"},{"comment":"","name":"contact_unknown","type":"float"},{"comment":"","name":"month_apr","type":"float"},{"comment":"","name":"month_aug","type":"float"},{"comment":"","name":"month_dec","type":"float"},{"comment":"","name":"month_feb","type":"float"},{"comment":"","name":"month_jan","type":"float"},{"comment":"","name":"month_jul","type":"float"},{"comment":"","name":"month_jun","type":"float"},{"comment":"","name":"month_mar","type":"float"},{"comment":"","name":"month_may","type":"float"},{"comment":"","name":"month_nov","type":"float"},{"comment":"","name":"month_oct","type":"float"},{"comment":"","name":"month_sep","type":"float"},{"comment":"","name":"poutcome_failure","type":"float"},{"comment":"","name":"poutcome_other","type":"float"},{"comment":"","name":"poutcome_success","type":"float"},{"comment":"","name":"poutcome_unknown","type":"float"},{"comment":"","name":"y","type":"int"}],"vendor":"manual","author":"bob"}' --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo + curl -X POST 'https://127.0.0.1:8082/api/v1/domaindata/create' --header "Token: $(cat /home/kuscia/var/certs/token)" --header 'Content-Type: application/json' -d '{"domaindata_id":"bob-dp-table","name":"bob.csv","type":"table","relative_uri":"bob.csv","domain_id":"bob","datasource_id":"default-dp-data-source","attributes":{"description":"bob demo data"},"columns":[{"comment":"","name":"id2","type":"str"},{"comment":"","name":"contact_cellular","type":"float"},{"comment":"","name":"contact_telephone","type":"float"},{"comment":"","name":"contact_unknown","type":"float"},{"comment":"","name":"month_apr","type":"float"},{"comment":"","name":"month_aug","type":"float"},{"comment":"","name":"month_dec","type":"float"},{"comment":"","name":"month_feb","type":"float"},{"comment":"","name":"month_jan","type":"float"},{"comment":"","name":"month_jul","type":"float"},{"comment":"","name":"month_jun","type":"float"},{"comment":"","name":"month_mar","type":"float"},{"comment":"","name":"month_may","type":"float"},{"comment":"","name":"month_nov","type":"float"},{"comment":"","name":"month_oct","type":"float"},{"comment":"","name":"month_sep","type":"float"},{"comment":"","name":"poutcome_failure","type":"float"},{"comment":"","name":"poutcome_other","type":"float"},{"comment":"","name":"poutcome_success","type":"float"},{"comment":"","name":"poutcome_unknown","type":"float"},{"comment":"","name":"y","type":"int"}],"vendor":"manual","author":"bob"}' --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo + curl https://127.0.0.1:8070/api/v1/datamesh/domaindatagrant/create -X POST -H 'content-type: application/json' -d '{"author":"bob","domaindata_id":"bob-table","grant_domain":"alice"}' --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo + curl https://127.0.0.1:8070/api/v1/datamesh/domaindatagrant/create -X POST -H 'content-type: application/json' -d '{"author":"bob","domaindata_id":"bob-dp-table","grant_domain":"alice"}' --cacert /home/kuscia/var/certs/ca.crt --cert /home/kuscia/var/certs/ca.crt --key /home/kuscia/var/certs/ca.key + echo +} + +domain_id=$1 +if [[ ${domain_id} = "alice" ]]; then + create_alice2bob_example_data +elif [[ ${domain_id} = "bob" ]]; then + create_bob2alice_example_data +fi \ No newline at end of file diff --git a/scripts/deploy/join_to_host.sh b/scripts/deploy/join_to_host.sh index 8a7f3eeb..36baab43 100755 --- a/scripts/deploy/join_to_host.sh +++ b/scripts/deploy/join_to_host.sh @@ -82,13 +82,6 @@ function join_to_host() { <"${ROOT}/scripts/templates/cluster_domain_route.token.transit.yaml") fi echo "${domain_route_template}" | kubectl apply -f - - - if [[ ${interconn_protocol} == "kuscia" && ${need_interop} == "true" ]]; then - INTEROP_CONFIG_TEMPLATE=$(sed "s/{{.MEMBER_DOMAIN_ID}}/${self_domain_id}/g; - s/{{.HOST_DOMAIN_ID}}/${host_domain_id}/g" \ - <"${ROOT}/scripts/templates/interop_config.yaml") - echo "${INTEROP_CONFIG_TEMPLATE}" | kubectl apply -f - - fi } usage() { diff --git a/scripts/deploy/kuscia.sh b/scripts/deploy/kuscia.sh new file mode 100644 index 00000000..6dd129e4 --- /dev/null +++ b/scripts/deploy/kuscia.sh @@ -0,0 +1,706 @@ +#!/bin/bash +# +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +ROOT_DIR=$HOME/kuscia + +GREEN='\033[0;32m' +NC='\033[0m' +RED='\033[31m' + +if [[ ${KUSCIA_IMAGE} == "" ]]; then + KUSCIA_IMAGE=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia:latest +fi + +if [ "$SECRETFLOW_IMAGE" != "" ]; then + echo -e "SECRETFLOW_IMAGE=${SECRETFLOW_IMAGE}" +fi + +CTR_PREFIX=${USER}-kuscia +CTR_ROOT=/home/kuscia +CTR_CERT_ROOT=${CTR_ROOT}/var/certs +MASTER_DOMAIN="kuscia-system" +ALICE_DOMAIN="alice" +BOB_DOMAIN="bob" +MASTER_CTR=${CTR_PREFIX}-master +FORCE_START=false +MASTER_MEMORY_LIMIT=2G +LITE_MEMORY_LIMIT=4G +AUTONOMY_MEMORY_LIMIT=6G +SF_IMAGE_NAME="secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/secretflow-lite-anolis8" +SF_IMAGE_TAG="1.3.0b0" +SF_IMAGE_REGISTRY="" +NETWORK_NAME="kuscia-exchange" +VOLUME_PATH="${ROOT_DIR}" + +function log() { + local log_content=$1 + echo -e "${GREEN}${log_content}${NC}" +} + +function arch_check() { + local arch=$(uname -a) + if [[ $arch == *"ARM"* ]] || [[ $arch == *"aarch64"* ]]; then + echo -e "${RED}ARM architecture is not supported by kuscia currently${NC}" + exit 1 + elif [[ $arch == *"x86_64"* ]]; then + echo -e "${GREEN}x86_64 architecture. Continuing...${NC}" + elif [[ $arch == *"amd64"* ]]; then + echo "Warning: amd64 architecture. Continuing..." + else + echo -e "${RED}$arch architecture is not supported by kuscia currently${NC}" + exit 1 + fi +} + +function pre_check() { + if ! mkdir -p "$1" 2>/dev/null; then + echo -e "${RED}User does not have access to create the directory: $1${NC}" + exit 1 + fi +} + +function init_sf_image_info() { + if [ "$SECRETFLOW_IMAGE" != "" ]; then + SF_IMAGE_TAG=${SECRETFLOW_IMAGE##*:} + path_separator_count="$(echo "$SECRETFLOW_IMAGE" | tr -cd "/" | wc -c)" + if [ ${path_separator_count} == 1 ]; then + SF_IMAGE_NAME=$(echo "$SECRETFLOW_IMAGE" | sed "s/:${SF_IMAGE_TAG}//") + elif [ $path_separator_count == 2 ]; then + registry=$(echo $SECRETFLOW_IMAGE | cut -d "/" -f 1) + bucket=$(echo $SECRETFLOW_IMAGE | cut -d "/" -f 2) + name_and_tag=$(echo $SECRETFLOW_IMAGE | cut -d "/" -f 3) + name=$(echo "$name_and_tag" | sed "s/:${SF_IMAGE_TAG}//") + SF_IMAGE_REGISTRY="$registry/$bucket" + SF_IMAGE_NAME="$name" + fi + fi +} + +init_sf_image_info + +function wrap_kuscia_config_file() { + local kuscia_config_file=$1 + local p2p_protocol=$2 + + PRIVILEGED_CONFIG=" +agent: + allowPrivileged: true + " + + BFIA_CONFIG=" +agent: + allowPrivileged: ${ALLOW_PRIVILEGED} + plugins: + - name: cert-issuance + - name: config-render + - name: env-import + config: + usePodLabels: false + envList: + - envs: + - name: system.transport + value: transport.${DOMAIN}.svc + - name: system.storage + value: file:///home/kuscia/var/storage + selectors: + - key: maintainer + value: secretflow-contact@service.alipay.com + " + + if [[ $p2p_protocol == "bfia" ]]; then + echo -e "$BFIA_CONFIG" >>"$kuscia_config_file" + else + if [[ $ALLOW_PRIVILEGED == "true" ]]; then + echo -e "$PRIVILEGED_CONFIG" >>"$kuscia_config_file" + fi + fi +} + +function need_start_docker_container() { + ctr=$1 + + if [[ ! "$(docker ps -a -q -f name=^/${ctr}$)" ]]; then + # need start your container + return 0 + fi + + if $FORCE_START; then + log "Remove container '${ctr}' ..." + docker rm -f $ctr >/dev/null 2>&1 + # need start your container + return 0 + fi + + read -rp "$(echo -e ${GREEN}The container \'${ctr}\' already exists. Do you need to recreate it? [y/n]: ${NC})" yn + case $yn in + [Yy]*) + echo -e "${GREEN}Remove container ${ctr} ...${NC}" + docker rm -f $ctr + # need start your container + return 0 + ;; + *) + echo -e "${RED}installation exit.${NC}" + exit 1 + ;; + esac + + return 1 +} + +function do_http_probe() { + local ctr=$1 + local endpoint=$2 + local max_retry=$3 + local enable_mtls=$4 + local cert_config + if [[ "$enable_mtls" == "true" ]]; then + cert_config="--cacert ${CTR_CERT_ROOT}/ca.crt --cert ${CTR_CERT_ROOT}/ca.crt --key ${CTR_CERT_ROOT}/ca.key" + fi + + local retry=0 + while [[ "$retry" -lt "$max_retry" ]]; do + local status_code + status_code=$(docker exec -it $ctr curl -k --write-out '%{http_code}' --silent --output /dev/null ${endpoint} ${cert_config}) + if [[ $status_code -eq 200 || $status_code -eq 404 || $status_code -eq 401 ]]; then + return 0 + fi + sleep 1 + retry=$((retry + 1)) + done + + return 1 +} + +function probe_k3s() { + local domain_ctr=$1 + + if ! do_http_probe $domain_ctr "https://127.0.0.1:6443" 60; then + echo "[Error] Probe k3s in container '$domain_ctr' failed. Please check k3s log in container, path: /home/kuscia/var/logs/k3s.log" >&2 + exit 1 + fi +} + +function probe_gateway_crd() { + local master=$1 + local domain=$2 + local gw_name=$3 + local max_retry=$4 + probe_k3s $master + + local retry=0 + while [ $retry -lt $max_retry ]; do + local line_num=$(docker exec -it $master kubectl get gateways -n $domain | grep -i $gw_name | wc -l | xargs) + if [[ $line_num == "1" ]]; then + return + fi + sleep 1 + retry=$((retry + 1)) + done + echo "[Error] Probe gateway in namespace '$domain' failed. Please check envoy log in container, path: /home/kuscia/var/logs/envoy" >&2 + exit 1 +} + +function generate_env_flag() { + local env_flag + local env_file=${ROOT}/env.list + if [ -e $env_file ]; then + env_flag="--env-file $env_file" + else + env_flag="--env REGISTRY_ENDPOINT=${SF_IMAGE_REGISTRY}" + fi + echo $env_flag +} + +function copy_between_containers() { + local src_file=$1 + local dest_file=$2 + local dest_volume=$3 + local temp_file + temp_file=$(basename $dest_file) + docker cp $src_file /tmp/${temp_file} >/dev/null + docker cp /tmp/${temp_file} $dest_file >/dev/null + rm /tmp/${temp_file} + echo "Copy file successfully src_file:'$src_file' to dest_file:'$dest_file'" +} + +function create_secretflow_app_image() { + local ctr=$1 + docker exec -it ${ctr} scripts/deploy/create_sf_app_image.sh "${SF_IMAGE_NAME}" "${SF_IMAGE_TAG}" + log "create secretflow app image done" +} + +function probe_datamesh() { + local domain_ctr=$1 + if ! do_http_probe "$domain_ctr" "https://127.0.0.1:8070/healthZ" 30 true; then + echo "[Error] Probe datamesh in container '$domain_ctr' failed." >&2 + echo "You cloud run command that 'docker logs $domain_ctr' to check the log" >&2 + fi + log "Probe datamesh successfully" +} + +function get_runtime() { + local conf_file=$1 + local runtime + runtime=$(grep '^runtime:' ${conf_file} | cut -d':' -f2 | awk '{$1=$1};1' | tr -d '\r\n') + if [[ $runtime == "" ]]; then + runtime=runc + fi + echo "$runtime" +} + +function generate_mount_flag() { + local mount_flag="-v ${DOMAIN_DATA_DIR}:/home/kuscia/var/storage/data -v ${DOMAIN_LOG_DIR}:/home/kuscia/var/stdout" + echo "$mount_flag" +} + +function create_cluster_domain_route() { + local src_domain=$1 + local dest_domain=$2 + log "Starting create cluster domain route from '${src_domain}' to '${dest_domain}'" + + docker exec -it ${MASTER_CTR} scripts/deploy/create_cluster_domain_route.sh ${src_domain} ${dest_domain} http://${CTR_PREFIX}-lite-${dest_domain}:1080 + log "Cluster domain route from '${src_domain}' to '${dest_domain}' created successfully dest_endpoint: '${CTR_PREFIX}'-lite-'${dest_domain}':1080" +} + +function check_sf_image() { + local domain_id=$1 + local domain_ctr=$2 + local volume_path=$3 + local env_file=${ROOT}/env.list + local default_repo=${SF_IMAGE_REGISTRY} + local repo + if [ -e $env_file ]; then + repo=$(awk -F "=" '/REGISTRY_ENDPOINT/ {print $2}' $env_file) + fi + local sf_image="${SF_IMAGE_NAME}:${SF_IMAGE_TAG}" + if [ "$repo" != "" ]; then + sf_image="${repo}/${SF_IMAGE_NAME##*/}:${SF_IMAGE_TAG}" + elif [ "$default_repo" != "" ]; then + sf_image="${default_repo}/${SF_IMAGE_NAME##*/}:${SF_IMAGE_TAG}" + fi + if [ "$SECRETFLOW_IMAGE" != "" ]; then + sf_image=$SECRETFLOW_IMAGE + fi + + if docker exec -it $domain_ctr crictl inspecti $sf_image >/dev/null 2>&1; then + log "Image '${sf_image}' already exists in domain '${domain_id}'" + return + fi + + local has_sf_image=false + if docker image inspect ${sf_image} >/dev/null 2>&1; then + has_sf_image=true + fi + + if [ "$has_sf_image" == true ]; then + log "Found the secretflow image '${sf_image}' on host" + else + log "Not found the secretflow image '${sf_image}' on host" + if [ "$repo" != "" ]; then + docker login $repo + fi + log "Start pulling image '${sf_image}' ..." + docker pull ${sf_image} + fi + + log "Start importing image '${sf_image}' Please be patient..." + local image_id + image_id=$(docker images --filter="reference=${sf_image}" --format "{{.ID}}") + local image_tar + image_tar=/tmp/$(echo ${sf_image} | sed 's/\//_/g').${image_id}.tar + if [ ! -e $image_tar ]; then + docker save $sf_image -o $image_tar + fi + docker exec -it $domain_ctr ctr -a=${CTR_ROOT}/containerd/run/containerd.sock -n=k8s.io images import $image_tar + log "Successfully imported image '${sf_image}' to container '${domain_ctr}' ..." +} + +function build_interconn() { + local host_ctr=$1 + local member_ctr=$2 + local member_domain=$3 + local host_domain=$4 + local interconn_protocol=$5 + + log "Starting build internet connect from '${member_domain}' to '${host_domain}'" + copy_between_containers ${member_ctr}:${CTR_CERT_ROOT}/domain.crt ${host_ctr}:${CTR_CERT_ROOT}/${member_domain}.domain.crt + docker exec -it ${host_ctr} scripts/deploy/add_domain.sh $member_domain p2p ${interconn_protocol} ${master_domain} + + docker exec -it ${member_ctr} scripts/deploy/join_to_host.sh $member_domain $host_domain https://${host_ctr}:1080 + log "Build internet connect from '${member_domain}' to '${host_domain}' successfully protocol: '${interconn_protocol}' dest host: '${host_ctr}':1080" +} + +function init_kuscia_conf_file() { + local domain_type=$1 + local domain_id=$2 + local domain_ctr=$3 + local kuscia_conf_file=$4 + local master_endpoint=$5 + local master_ctr=$(echo "${master_endpoint}" | cut -d'/' -f3 | cut -d':' -f1) + pre_check "${PWD}/${domain_ctr}" + if [[ "${domain_type}" = "lite" ]]; then + token=$(docker exec -it "${master_ctr}" scripts/deploy/add_domain_lite.sh "${domain_id}" | tr -d '\r\n') + docker run -it --rm ${KUSCIA_IMAGE} kuscia init --mode "${domain_type}" --domain "${domain_id}" --master-endpoint ${master_endpoint} --lite-deploy-token ${token} > "${kuscia_conf_file}" + else + docker run -it --rm ${KUSCIA_IMAGE} kuscia init --mode "${domain_type}" --domain "${domain_id}" > "${kuscia_conf_file}" + fi + wrap_kuscia_config_file ${kuscia_conf_file} +} + +function init() { + local domain_ctr=$1 + [[ ${ROOT} == "" ]] && ROOT=${PWD} + [[ ${DOMAIN_DATA_DIR} == "" ]] && DOMAIN_DATA_DIR="${ROOT}/${domain_ctr}/data" + [[ ${DOMAIN_LOG_DIR} == "" ]] && DOMAIN_LOG_DIR="${ROOT}/${domain_ctr}/logs" + + pre_check "${DOMAIN_DATA_DIR}" + pre_check "${DOMAIN_LOG_DIR}" + + log "ROOT=${ROOT}" + log "DOMAIN_ID=${domain_id}" + log "DOMAIN_HOST_PORT=${DOMAIN_HOST_PORT}" + log "DOMAIN_HOST_INTERNAL_PORT=${DOMAIN_HOST_INTERNAL_PORT}" + log "DOMAIN_DATA_DIR=${DOMAIN_DATA_DIR}" + log "DOMAIN_LOG_DIR=${DOMAIN_LOG_DIR}" + log "KUSCIA_IMAGE=${KUSCIA_IMAGE}" + log "KUSCIAAPI_HTTP_PORT=${KUSCIAAPI_HTTP_PORT}" + log "KUSCIAAPI_GRPC_PORT=${KUSCIAAPI_GRPC_PORT}" + + build_kuscia_network +} + +function start_container() { + docker run -dit${privileged_flag} --name="${domain_ctr}" --hostname="${domain_ctr}" --restart=always --network=${NETWORK_NAME} -m $LITE_MEMORY_LIMIT \ + -p "${domain_host_internal_port}":80 \ + -p "${domain_host_port}":1080 \ + -p "${kusciaapi_http_port}":8082 \ + -p "${kusciaapi_grpc_port}":8083 \ + --mount source=${domain_ctr}-containerd,target=${CTR_ROOT}/containerd \ + -v /tmp:/tmp \ + -v ${kuscia_conf_file}:/home/kuscia/etc/conf/kuscia.yaml \ + ${env_flag} ${mount_flag} \ + --env NAMESPACE=${domain_id} \ + "${KUSCIA_IMAGE}" bin/kuscia start -c etc/conf/kuscia.yaml +} + +function start_kuscia_container() { + local domain_type=$1 + local domain_id=${2:-$DOMAIN_ID} + local runtime=$3 + local master_endpoint=$4 + local domain_host_port=${5:-$DOMAIN_HOST_PORT} + local kusciaapi_http_port=${6:-$KUSCIAAPI_HTTP_PORT} + local kusciaapi_grpc_port=${7:-$KUSCIAAPI_GRPC_PORT} + local domain_host_internal_port=${8:-$DOMAIN_HOST_INTERNAL_PORT} + local domain_ctr=$9 + local init_kuscia_conf_file_true_or_false=${10} + local mount_flag=${11} + local env_flag=$(generate_env_flag) + + if [[ ${init_kuscia_conf_file_true_or_false} = "true" ]]; then + local kuscia_conf_file="${PWD}/${domain_ctr}/kuscia.yaml" + init_kuscia_conf_file ${domain_type} ${domain_id} ${domain_ctr} ${kuscia_conf_file} ${master_endpoint} + fi + + if need_start_docker_container "$domain_ctr"; then + log "Starting container $domain_ctr ..." + start_container "${domain_ctr}" "${domain_id}" "${domain_host_port}" "${kusciaapi_http_port}" "${kusciaapi_grpc_port}" "${domain_host_internal_port}" "${env_flag}" "${kuscia_conf_file}" "${mount_flag}" + if [[ "$domain_type" != "lite" ]]; then + probe_gateway_crd "${domain_ctr}" "${domain_id}" "${domain_ctr}" 60 + else + probe_datamesh "${domain_ctr}" + fi + fi + + if [[ "$domain_type" != "master" ]] && [[ ${runtime} == "runc" ]]; then + check_sf_image "${domain_id}" "${domain_ctr}" + fi + + if [[ "$domain_type" != "lite" ]]; then + create_secretflow_app_image "${domain_ctr}" + fi + log "$domain_type domain '${domain_id}' deployed successfully" +} + +function get_config_value() { + local config_file=$1 + local key=$2 + grep "$key:" "$config_file" | awk '{ print $2 }' | tr -d '\r\n' +} + +function start_kuscia() { + local kuscia_conf_file=${KUSCIA_CONFIG_FILE} + local domain_id=$(get_config_value "$kuscia_conf_file" "domainID") + local deploy_mode=$(get_config_value "$kuscia_conf_file" "mode") + local master_endpoint=$(get_config_value "$kuscia_conf_file" "masterEndpoint") + local runtime=$(get_runtime "$kuscia_conf_file") + local privileged_flag + + wrap_kuscia_config_file ${kuscia_conf_file} + local domain_ctr="${CTR_PREFIX}-${deploy_mode}-${domain_id}" + if [[ "${deploy_mode}" == "master" ]]; then + domain_ctr="${MASTER_CTR}" + fi + + [[ ${runtime} == "runc" ]] && privileged_flag=" --privileged" + [[ ${DOMAIN_HOST_PORT} == "" ]] && { printf "empty domain host port\n" >&2; exit 1; } + [[ ${DOMAIN_HOST_INTERNAL_PORT} == "" ]] && DOMAIN_HOST_INTERNAL_PORT=13081 + [[ ${KUSCIAAPI_HTTP_PORT} == "" ]] && KUSCIAAPI_HTTP_PORT=13082 + [[ ${KUSCIAAPI_GRPC_PORT} == "" ]] && KUSCIAAPI_GRPC_PORT=13083 + + init ${domain_ctr} + local mount_flag=$(generate_mount_flag) + start_kuscia_container "${deploy_mode}" "${domain_id}" "$runtime" "$master_endpoint" "${DOMAIN_HOST_PORT}" "${KUSCIAAPI_HTTP_PORT}" "${KUSCIAAPI_GRPC_PORT}" "${DOMAIN_HOST_INTERNAL_PORT}" "${domain_ctr}" "false" "${mount_flag}" +} + +function start_center_cluster() { + local runtime="runc" + local privileged_flag=" --privileged" + local alice_ctr=${CTR_PREFIX}-lite-${ALICE_DOMAIN} + local bob_ctr=${CTR_PREFIX}-lite-${BOB_DOMAIN} + start_kuscia_container "master" "${MASTER_DOMAIN}" "" "" "18081" "18082" "18083" "18084" "${MASTER_CTR}" "true" + start_kuscia_container "lite" "${ALICE_DOMAIN}" "${runtime}" "https://${MASTER_CTR}:1080" "28081" "28082" "28083" "28084" "${alice_ctr}" "true" + start_kuscia_container "lite" "${BOB_DOMAIN}" "${runtime}" "https://${MASTER_CTR}:1080" "38081" "38082" "38083" "38084" "${bob_ctr}" "true" + create_cluster_domain_route ${ALICE_DOMAIN} ${BOB_DOMAIN} + create_cluster_domain_route ${BOB_DOMAIN} ${ALICE_DOMAIN} + docker exec -it ${alice_ctr} scripts/deploy/init_example_data.sh ${ALICE_DOMAIN} + docker exec -it ${bob_ctr} scripts/deploy/init_example_data.sh ${BOB_DOMAIN} + log "Kuscia ${mode} cluster started successfully" +} + +function start_p2p_cluster() { + local runtime="runc" + local p2p_protocol=$1 + local privileged_flag=" --privileged" + local alice_ctr=${CTR_PREFIX}-autonomy-${ALICE_DOMAIN} + local bob_ctr=${CTR_PREFIX}-autonomy-${BOB_DOMAIN} + start_kuscia_container "autonomy" "${ALICE_DOMAIN}" "${runtime}" " " "11081" "11082" "11083" "11084" "${alice_ctr}" "true" + start_kuscia_container "autonomy" "${BOB_DOMAIN}" "${runtime}" " " "12081" "12082" "12083" "12084" "${bob_ctr}" "true" + build_interconn ${bob_ctr} ${alice_ctr} ${ALICE_DOMAIN} ${BOB_DOMAIN} ${p2p_protocol} + build_interconn ${alice_ctr} ${bob_ctr} ${BOB_DOMAIN} ${ALICE_DOMAIN} ${p2p_protocol} + docker exec -it ${alice_ctr} scripts/deploy/init_example_data.sh ${ALICE_DOMAIN} + docker exec -it ${bob_ctr} scripts/deploy/init_example_data.sh ${BOB_DOMAIN} + log "Kuscia ${mode} cluster started successfully" +} + +function start_cxc_cluster() { + local runtime="runc" + local privileged_flag=" --privileged" + local alice_ctr=${CTR_PREFIX}-lite-${ALICE_DOMAIN} + local bob_ctr=${CTR_PREFIX}-lite-${BOB_DOMAIN} + local alice_master_domain="master-alice" + local bob_master_domain="master-bob" + local alice_master_ctr=${CTR_PREFIX}-${alice_master_domain} + local bob_master_ctr=${CTR_PREFIX}-${bob_master_domain} + local p2p_protocol="kuscia" + local transit=$1 + + start_kuscia_container "master" "${alice_master_domain}" "" "" "18081" "18082" "18083" "18084" "${alice_master_ctr}" "true" + start_kuscia_container "master" "${bob_master_domain}" "" "" "28081" "28082" "28083" "28084" "${bob_master_ctr}" "true" + start_kuscia_container "lite" "${ALICE_DOMAIN}" "${runtime}" "https://${alice_master_ctr}:1080" "38081" "38082" "38083" "38084" "${alice_ctr}" "true" + start_kuscia_container "lite" "${BOB_DOMAIN}" "${runtime}" "https://${bob_master_ctr}:1080" "48081" "48082" "48083" "48084" "${bob_ctr}" "true" + + build_interconn ${bob_master_ctr} ${alice_master_ctr} ${alice_master_domain} ${bob_master_domain} ${p2p_protocol} + build_interconn ${alice_master_ctr} ${bob_master_ctr} ${bob_master_domain} ${alice_master_domain} ${p2p_protocol} + copy_between_containers ${alice_ctr}:${CTR_CERT_ROOT}/domain.crt ${bob_master_ctr}:${CTR_CERT_ROOT}/${ALICE_DOMAIN}.domain.crt + copy_between_containers ${bob_ctr}:${CTR_CERT_ROOT}/domain.crt ${alice_master_ctr}:${CTR_CERT_ROOT}/${BOB_DOMAIN}.domain.crt + docker exec -it ${alice_master_ctr} scripts/deploy/add_domain.sh ${BOB_DOMAIN} p2p ${p2p_protocol} ${bob_master_domain} + docker exec -it ${bob_master_ctr} scripts/deploy/add_domain.sh ${ALICE_DOMAIN} p2p ${p2p_protocol} ${alice_master_domain} + if [[ $transit == false ]]; then + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${ALICE_DOMAIN} ${BOB_DOMAIN} http://${bob_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${bob_master_ctr} scripts/deploy/join_to_host.sh ${BOB_DOMAIN} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${bob_master_ctr} scripts/deploy/join_to_host.sh ${ALICE_DOMAIN} ${BOB_DOMAIN} http://${bob_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${BOB_DOMAIN} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -p ${p2p_protocol} + else + docker exec -it ${bob_master_ctr} scripts/deploy/join_to_host.sh ${bob_master_domain} ${BOB_DOMAIN} http://${bob_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${alice_master_domain} ${BOB_DOMAIN} http://${bob_ctr}:1080 -i false -x ${bob_master_domain} -p ${p2p_protocol} + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${ALICE_DOMAIN} ${BOB_DOMAIN} http://${bob_ctr}:1080 -i false -x ${alice_master_domain} -p ${p2p_protocol} + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${alice_master_domain} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${bob_master_ctr} scripts/deploy/join_to_host.sh ${bob_master_domain} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -x ${alice_master_domain} -p ${p2p_protocol} + docker exec -it ${bob_master_ctr} scripts/deploy/join_to_host.sh ${BOB_DOMAIN} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -x ${bob_master_domain} -p ${p2p_protocol} + fi + docker exec -it ${alice_ctr} scripts/deploy/init_example_data.sh ${ALICE_DOMAIN} + docker exec -it ${bob_ctr} scripts/deploy/init_example_data.sh ${BOB_DOMAIN} + log "Kuscia ${mode} cluster started successfully" +} + +function start_cxp_cluster() { + local runtime="runc" + local privileged_flag=" --privileged" + local alice_ctr=${CTR_PREFIX}-lite-${ALICE_DOMAIN} + local bob_ctr=${CTR_PREFIX}-autonomy-${BOB_DOMAIN} + local alice_master_domain="master-alice" + local alice_master_ctr=${CTR_PREFIX}-${alice_master_domain} + local p2p_protocol="kuscia" + local transit=$1 + + start_kuscia_container "master" "${alice_master_domain}" "" "" "18081" "18082" "18083" "18084" "${alice_master_ctr}" "true" + start_kuscia_container "lite" "${ALICE_DOMAIN}" "${runtime}" "https://${alice_master_ctr}:1080" "28081" "28082" "28083" "28084" "${alice_ctr}" "true" + start_kuscia_container "autonomy" "${BOB_DOMAIN}" "${runtime}" "https://${alice_master_ctr}:1080" "12081" "12082" "12083" "12084" "${bob_ctr}" "true" + + build_interconn ${bob_ctr} ${alice_master_ctr} ${alice_master_domain} ${BOB_DOMAIN} ${p2p_protocol} + build_interconn ${alice_master_ctr} ${bob_ctr} ${BOB_DOMAIN} ${alice_master_domain} ${p2p_protocol} + copy_between_containers ${alice_ctr}:${CTR_CERT_ROOT}/domain.crt ${bob_ctr}:${CTR_CERT_ROOT}/${ALICE_DOMAIN}.domain.crt + docker exec -it ${bob_ctr} scripts/deploy/add_domain.sh ${ALICE_DOMAIN} p2p ${p2p_protocol} ${alice_master_domain} + + if [[ $transit == false ]]; then + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${ALICE_DOMAIN} ${BOB_DOMAIN} https://${bob_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${bob_ctr} scripts/deploy/join_to_host.sh ${BOB_DOMAIN} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${bob_ctr} scripts/deploy/join_to_host.sh ${ALICE_DOMAIN} ${BOB_DOMAIN} http://${bob_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${BOB_DOMAIN} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -p ${p2p_protocol} + else + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh ${ALICE_DOMAIN} ${BOB_DOMAIN} https://${bob_ctr}:1080 -i false -x ${alice_master_domain} -p ${p2p_protocol} + docker exec -it ${alice_master_ctr} scripts/deploy/join_to_host.sh $alice_master_domain ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -p ${p2p_protocol} + docker exec -it ${bob_ctr} scripts/deploy/join_to_host.sh ${BOB_DOMAIN} ${ALICE_DOMAIN} http://${alice_ctr}:1080 -i false -x ${alice_master_domain} -p ${p2p_protocol} + fi + + docker exec -it ${alice_ctr} scripts/deploy/init_example_data.sh ${ALICE_DOMAIN} + docker exec -it ${bob_ctr} scripts/deploy/init_example_data.sh ${BOB_DOMAIN} + log "Kuscia ${mode} cluster started successfully" +} + +function build_kuscia_network() { + if [[ ! "$(docker network ls -q -f name=${NETWORK_NAME})" ]]; then + docker network create ${NETWORK_NAME} + fi +} + +function get_absolute_path() { + echo "$( + cd "$(dirname -- "$1")" >/dev/null + pwd -P + )/$(basename -- "$1")" +} + +usage() { + echo "$(basename "$0") DEPLOY_MODE [OPTIONS] +DEPLOY_MODE: + center centralized network mode + p2p p2p network mode + cxc center and center mode + cxp center and p2p mode + start Multi-machine mode + +Common Options: + -h Show this help text. + -c The host path of kuscia configure file. It will be mounted into the domain container. + -d The data directory used to store domain data. It will be mounted into the domain container. + You can set Env 'DOMAIN_DATA_DIR' instead. Default is '{{ROOT}}/{{DOMAIN_CONTAINER_NAME}}/data'. + -l The data directory used to store domain logs. It will be mounted into the domain container. + You can set Env 'DOMAIN_LOG_DIR' instead. Default is '{{ROOT}}/{{DOMAIN_CONTAINER_NAME}}/logs'. + -p The port exposed by domain. You can set Env 'DOMAIN_HOST_PORT' instead. + -q (Only used in autonomy or lite mode)The port exposed for internal use by domain. You can set Env 'DOMAIN_HOST_INTERNAL_PORT' instead. + -r The install directory. You can set Env 'ROOT' instead. Default is $(pwd). + -t (Only used in lite mode) The deploy token. You can set Env 'DOMAIN_TOKEN' instead. + -k (Only used in autonomy or master mode)The http port exposed by KusciaAPI , default is 13082. You can set Env 'KUSCIAAPI_HTTP_PORT' instead. + -g (Only used in autonomy or master mode)The grpc port exposed by KusciaAPI, default is 13083. You can set Env 'KUSCIAAPI_GRPC_PORT' instead." +} + +mode= +case "$1" in +center | p2p | cxc | cxp | start) + mode=$1 + shift + ;; +esac + +interconn_protocol= +transit=false +while getopts 'P:c:d:l:p:q:t:r:k:g:h' option; do + case "$option" in + P) + interconn_protocol=$OPTARG + [ "$interconn_protocol" == "bfia" -o "$interconn_protocol" == "kuscia" ] && continue + printf "illegal value for -%s\n" "$option" >&2 + usage + exit + ;; + c) + KUSCIA_CONFIG_FILE=$(get_absolute_path $OPTARG) + ;; + d) + DOMAIN_DATA_DIR=$OPTARG + ;; + l) + DOMAIN_LOG_DIR=$OPTARG + ;; + p) + DOMAIN_HOST_PORT=$OPTARG + ;; + q) + DOMAIN_HOST_INTERNAL_PORT=$OPTARG + ;; + t) + transit=true + ;; + r) + ROOT_DIR=$OPTARG + ;; + k) + KUSCIAAPI_HTTP_PORT=$OPTARG + ;; + g) + KUSCIAAPI_GRPC_PORT=$OPTARG + ;; + h) + usage + exit + ;; + :) + printf "missing argument for -%s\n" "$OPTARG" >&2 + exit 1 + ;; + \?) + printf "illegal option: -%s\n" "$OPTARG" >&2 + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +[ "$interconn_protocol" == "bfia" ] || interconn_protocol="kuscia" +[ "$mode" == "" ] && mode=$1 +[ "$mode" == "" -o "$mode" == "centralized" ] && mode="center" +if [ "$mode" == "center" -a "$interconn_protocol" != "kuscia" ]; then + printf "In current quickstart script, center mode just support 'kuscia'\n" >&2 + exit 1 +fi + +case "$mode" in +center) + start_center_cluster + ;; +p2p) + start_p2p_cluster $interconn_protocol + ;; +cxc) + start_cxc_cluster $transit + ;; +cxp) + start_cxp_cluster $transit + ;; +start) + start_kuscia + ;; +*) + printf "unsupported network mode: %s\n" "$mode" >&2 + exit 1 + ;; +esac \ No newline at end of file diff --git a/scripts/deploy/start_standalone.sh b/scripts/deploy/start_standalone.sh index 5a4061fd..fb76c14c 100755 --- a/scripts/deploy/start_standalone.sh +++ b/scripts/deploy/start_standalone.sh @@ -52,6 +52,13 @@ function log() { echo -e "${GREEN}${log_content}${NC}" } +function pre_check() { + if ! mkdir -p "$1" 2>/dev/null; then + echo -e "${RED}User does not have access to create the directory: $1${NC}" + exit 1 + fi +} + function arch_check() { local arch=$(uname -a) if [[ $arch == *"ARM"* ]] || [[ $arch == *"aarch64"* ]]; then @@ -121,11 +128,16 @@ function do_http_probe() { local ctr=$1 local endpoint=$2 local max_retry=$3 + local enable_mtls=$4 + local cert_config + if [[ "$enable_mtls" == "true" ]]; then + cert_config="--cacert ${CTR_CERT_ROOT}/ca.crt --cert ${CTR_CERT_ROOT}/ca.crt --key ${CTR_CERT_ROOT}/ca.key" + fi + local retry=0 - while [ $retry -lt $max_retry ]; do + while [[ "$retry" -lt "$max_retry" ]]; do local status_code - # TODO support MTLS - status_code=$(docker exec -it $ctr curl -k --write-out '%{http_code}' --silent --output /dev/null ${endpoint}) + status_code=$(docker exec -it $ctr curl -k --write-out '%{http_code}' --silent --output /dev/null ${endpoint} ${cert_config}) if [[ $status_code -eq 200 || $status_code -eq 404 || $status_code -eq 401 ]]; then return 0 fi @@ -271,7 +283,7 @@ function create_domaindata_bob_table() { function probe_datamesh() { local domain_ctr=$1 - if ! do_http_probe "$domain_ctr" "https://127.0.0.1:8070/healthZ" 30; then + if ! do_http_probe "$domain_ctr" "https://127.0.0.1:8070/healthZ" 30 true; then echo "[Error] Probe datamesh in container '$domain_ctr' failed." >&2 echo "You cloud run command that 'docker logs $domain_ctr' to check the log" >&2 fi @@ -302,7 +314,7 @@ function start_lite() { fi # init kuscia.yaml - mkdir -p "${conf_dir}" + pre_check ${data_path} csr_token=$(docker exec -it "${master_ctr}" scripts/deploy/add_domain_lite.sh "${domain_id}" "${master_domain}" | tr -d '\r\n') docker run -it --rm ${IMAGE} kuscia init --mode Lite --domain "${domain_id}" --master-endpoint "${master_endpoint}" --lite-deploy-token "${csr_token}" >"${kuscia_config_file}" wrap_kuscia_config_file "${kuscia_config_file}" @@ -593,7 +605,7 @@ function start_autonomy() { env_flag=$(generate_env_flag $domain_id) # init kuscia.yaml - mkdir -p "${conf_dir}" + pre_check ${data_path} docker run -it --rm ${IMAGE} kuscia init --mode Autonomy --domain "${domain_id}" >"${kuscia_config_file}" wrap_kuscia_config_file "${kuscia_config_file}" "${p2p_protocol}" diff --git a/scripts/templates/grafana-dashboard-machine.json b/scripts/templates/grafana-dashboard-machine.json index 44963b53..de44b5bd 100644 --- a/scripts/templates/grafana-dashboard-machine.json +++ b/scripts/templates/grafana-dashboard-machine.json @@ -3353,4 +3353,3 @@ "version": 1, "weekStart": "" } - diff --git a/scripts/test/integration_test.sh b/scripts/test/integration_test.sh index f757216e..cbb074a0 100755 --- a/scripts/test/integration_test.sh +++ b/scripts/test/integration_test.sh @@ -130,7 +130,7 @@ fi installRequires copyTestScripts -docker run --rm ${KUSCIA_IMAGE} cat /home/kuscia/scripts/deploy/start_standalone.sh > start_standalone.sh && chmod u+x start_standalone.sh +docker run --rm ${KUSCIA_IMAGE} cat /home/kuscia/scripts/deploy/kuscia.sh > kuscia.sh && chmod u+x kuscia.sh if [ "${SELECTED_TEST_SUITE}" == "all" ]; then for suite in ${TEST_SUITES}; do diff --git a/scripts/test/suite/core/functions.sh b/scripts/test/suite/core/functions.sh index 49c3be80..eb990b64 100644 --- a/scripts/test/suite/core/functions.sh +++ b/scripts/test/suite/core/functions.sh @@ -154,7 +154,7 @@ function start_center_mode() { mkdir -p "${test_suite_run_kuscia_dir}" # Run as Center - ./start_standalone.sh center + ./kuscia.sh center # Check centralized container Up local master_container_state=$(get_container_state "${MASTER_CONTAINER}") @@ -191,7 +191,7 @@ function start_p2p_mode() { mkdir -p "${test_suite_run_kuscia_dir}" # Run as P2P - ./start_standalone.sh p2p + ./kuscia.sh p2p # Check p2p container Up local autonomy_alice_container_state=$(get_container_state "${AUTONOMY_ALICE_CONTAINER}") diff --git a/scripts/test/suite/p2p/base.sh b/scripts/test/suite/p2p/base.sh index 950e9fbf..cc984680 100755 --- a/scripts/test/suite/p2p/base.sh +++ b/scripts/test/suite/p2p/base.sh @@ -121,22 +121,31 @@ function test_p2p_token_rolling_party_offline() { local cdr_name="alice-bob" local dr_name="alice-bob" local src_domain="alice" - local period=30 local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) assertEquals "true" "$ready" # party offline docker stop $bob_ctr - sleep $period - local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + for i in {1..30}; do + local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + if [[ $ready == "false" ]]; then + break + fi + sleep 2 + done assertEquals "false" "$ready" # back online docker start $bob_ctr - sleep $period - local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + for i in {1..30}; do + local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + if [[ $ready == "true" ]]; then + break + fi + sleep 2 + done assertEquals "true" "$ready" # run task @@ -150,7 +159,6 @@ function test_p2p_token_rolling_auth_removal() { local dr_name="alice-bob" local dst_domain="bob" local src_domain="alice" - local period=30 local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) assertEquals "true" "$ready" @@ -160,16 +168,26 @@ function test_p2p_token_rolling_auth_removal() { # dr removal docker exec "$bob_ctr" kubectl delete cdr $cdr_name - sleep $period - local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + for i in {1..30}; do + local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + if [[ $ready == "false" ]]; then + break + fi + sleep 2 + done assertEquals "false" "$ready" # dr restore docker cp tmp.json $bob_ctr:/home/kuscia/ docker exec "$bob_ctr" kubectl create -f /home/kuscia/tmp.json - sleep $period - local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + for i in {1..30}; do + local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + if [[ $ready == "true" ]]; then + break + fi + sleep 2 + done assertEquals "true" "$ready" # run task @@ -183,21 +201,31 @@ function test_p2p_token_rolling_cert_misconfig() { local cdr_name="alice-bob" local dst_domain="bob" local src_domain="alice" - local period=30 + local dst_cert=$(docker exec "$alice_ctr" kubectl get domain $dst_domain -o jsonpath='{.spec.cert}') local mis_cert=$(docker exec "$alice_ctr" kubectl get domain $src_domain -o jsonpath='{.spec.cert}') # use alice domain cert as misconfigured cert # cert mis config docker exec "$alice_ctr" kubectl patch domain $dst_domain --type json -p="[{\"op\": \"replace\", \"path\": \"/spec/cert\", \"value\": ${mis_cert}}]" - sleep $period - local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + for i in {1..30}; do + local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + if [[ $ready == "false" ]]; then + break + fi + sleep 2 + done assertEquals "false" "$ready" # cert restore docker exec "$alice_ctr" kubectl patch domain $dst_domain --type json -p="[{\"op\": \"replace\", \"path\": \"/spec/cert\", \"value\": ${dst_cert}}]" - sleep $period - local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + for i in {1..30}; do + local ready=$(get_dr_revision_token_ready $alice_ctr $dr_name $src_domain) + if [[ $ready == "true" ]]; then + break + fi + sleep 2 + done assertEquals "true" "$ready" # run task diff --git a/thirdparty/fate/scripts/templates/fate/app_image.fate.yaml b/thirdparty/fate/scripts/templates/fate/app_image.fate.yaml index f01a6b6f..9ba417da 100644 --- a/thirdparty/fate/scripts/templates/fate/app_image.fate.yaml +++ b/thirdparty/fate/scripts/templates/fate/app_image.fate.yaml @@ -17,6 +17,7 @@ spec: spec: containers: - name: fate-adapter + workingDir: /home/kuscia command: - sh - -c diff --git a/thirdparty/fate/scripts/templates/fate/fate_job.yaml b/thirdparty/fate/scripts/templates/fate/fate_job.yaml index 1966ac8f..37d873a8 100644 --- a/thirdparty/fate/scripts/templates/fate/fate_job.yaml +++ b/thirdparty/fate/scripts/templates/fate/fate_job.yaml @@ -1,6 +1,7 @@ apiVersion: kuscia.secretflow/v1alpha1 kind: KusciaJob metadata: + namespace: cross-domain name: {{.JOB_NAME}} spec: initiator: {{.Initiator}}