diff --git a/.github/actions/detect-docker-image-tags/action.yaml b/.github/actions/detect-docker-image-tags/action.yaml index 9ffe91135d..c9f8c5c819 100644 --- a/.github/actions/detect-docker-image-tags/action.yaml +++ b/.github/actions/detect-docker-image-tags/action.yaml @@ -48,6 +48,7 @@ runs: ["vdaas/vald-agent-sidecar"]="agent.sidecar.image.tag" ["vdaas/vald-discoverer-k8s"]="discoverer.image.tag" ["vdaas/vald-lb-gateway"]="gateway.lb.image.tag" + ["vdaas/vald-mirror-gateway"]="gateway.mirror.image.tag" ["vdaas/vald-manager-index"]="manager.index.image.tag" ["vdaas/vald-index-creation"]="manager.index.creator.image.tag" ["vdaas/vald-index-save"]="manager.index.saver.image.tag" diff --git a/.github/actions/e2e-deploy-vald-helm-operator/action.yaml b/.github/actions/e2e-deploy-vald-helm-operator/action.yaml index 30bc2d0feb..8b49d45082 100644 --- a/.github/actions/e2e-deploy-vald-helm-operator/action.yaml +++ b/.github/actions/e2e-deploy-vald-helm-operator/action.yaml @@ -75,7 +75,7 @@ runs: ${HELM_EXTRA_OPTIONS} \ charts/vald-helm-operator/. - sleep 3 + sleep 6 env: DEFAULT_IMAGE_TAG: ${{ inputs.default_image_tag }} HELM_EXTRA_OPTIONS: ${{ inputs.helm_extra_options }} @@ -97,7 +97,7 @@ runs: run: | kubectl apply -f ${VALDRELEASE} - sleep 3 + sleep 6 kubectl wait --for=condition=ready pod -l ${WAIT_FOR_SELECTOR} --timeout=${WAIT_FOR_TIMEOUT} diff --git a/.github/actions/e2e-deploy-vald/action.yaml b/.github/actions/e2e-deploy-vald/action.yaml index 6ba3b275e6..e442119c4c 100644 --- a/.github/actions/e2e-deploy-vald/action.yaml +++ b/.github/actions/e2e-deploy-vald/action.yaml @@ -29,6 +29,9 @@ inputs: description: "Path to the values.yaml that passed to Helm command." required: false default: "false" + namespace: + description: "Namespace to deploy." + required: false wait_for_selector: description: "Label selector used for specifying a pod waited for" required: false @@ -65,6 +68,14 @@ runs: run: | cat ${{ inputs.values }} + - name: Change namespace + if: ${{ inputs.namespace != '' }} + shell: bash + run: | + kubectl create ns ${NAMESPACE} ; kubectl config set-context --current --namespace=${NAMESPACE} + env: + NAMESPACE: ${{ inputs.namespace }} + - name: Deploy vald from remote charts shell: bash id: deploy_vald_remote diff --git a/.github/helm/values/vald-mirror-target.yaml b/.github/helm/values/vald-mirror-target.yaml new file mode 100644 index 0000000000..48f24eaaac --- /dev/null +++ b/.github/helm/values/vald-mirror-target.yaml @@ -0,0 +1,24 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +apiVersion: vald.vdaas.org/v1 +kind: ValdMirrorTarget +metadata: + name: mirror-target-02 +spec: + colocation: dc1 + target: + host: vald-mirror-gateway.vald-02.svc.cluster.local + port: 8081 diff --git a/.github/helm/values/values-mirror-01.yaml b/.github/helm/values/values-mirror-01.yaml new file mode 100644 index 0000000000..69a336cd58 --- /dev/null +++ b/.github/helm/values/values-mirror-01.yaml @@ -0,0 +1,81 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# + +defaults: + logging: + level: info + networkPolicy: + enabled: false + +gateway: + mirror: + enabled: true + clusterRoleBinding: + name: vald-mirror-01 + serviceAccount: + name: vald-mirror-01 + lb: + enabled: true + minReplicas: 1 + hpa: + enabled: false + resources: + requests: + cpu: 100m + memory: 50Mi + gateway_config: + index_replica: 3 + +agent: + minReplicas: 3 + maxReplicas: 10 + podManagementPolicy: Parallel + hpa: + enabled: false + resources: + requests: + cpu: 100m + memory: 50Mi + ngt: + auto_index_duration_limit: 2m + auto_index_check_duration: 30s + auto_index_length: 1000 + dimension: 784 + +discoverer: + minReplicas: 1 + hpa: + enabled: false + resources: + requests: + cpu: 100m + memory: 50Mi + clusterRoleBinding: + name: vald-01 + serviceAccount: + name: vald-01 + +manager: + index: + replicas: 1 + resources: + requests: + cpu: 100m + memory: 30Mi + indexer: + auto_index_duration_limit: 2m + auto_index_check_duration: 30s + auto_index_length: 1000 diff --git a/.github/helm/values/values-mirror-02.yaml b/.github/helm/values/values-mirror-02.yaml new file mode 100644 index 0000000000..29b0990b09 --- /dev/null +++ b/.github/helm/values/values-mirror-02.yaml @@ -0,0 +1,85 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# + +defaults: + logging: + level: info + networkPolicy: + enabled: false + +gateway: + mirror: + enabled: true + clusterRole: + enabled: false + clusterRoleBinding: + name: vald-mirror-02 + serviceAccount: + name: vald-mirror-02 + lb: + enabled: true + minReplicas: 1 + hpa: + enabled: false + resources: + requests: + cpu: 100m + memory: 50Mi + gateway_config: + index_replica: 3 + +agent: + minReplicas: 3 + maxReplicas: 10 + podManagementPolicy: Parallel + hpa: + enabled: false + resources: + requests: + cpu: 100m + memory: 50Mi + ngt: + auto_index_duration_limit: 2m + auto_index_check_duration: 30s + auto_index_length: 1000 + dimension: 784 + +discoverer: + minReplicas: 1 + hpa: + enabled: false + resources: + requests: + cpu: 100m + memory: 50Mi + clusterRole: + enabled: false + clusterRoleBinding: + name: vald-02 + serviceAccount: + name: vald-02 + +manager: + index: + replicas: 1 + resources: + requests: + cpu: 100m + memory: 30Mi + indexer: + auto_index_duration_limit: 2m + auto_index_check_duration: 30s + auto_index_length: 1000 diff --git a/.github/workflows/dockers-gateway-mirror-image.yaml b/.github/workflows/dockers-gateway-mirror-image.yaml new file mode 100644 index 0000000000..e3e137e3e8 --- /dev/null +++ b/.github/workflows/dockers-gateway-mirror-image.yaml @@ -0,0 +1,82 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +name: "Build docker image: gateway-mirror" +on: + push: + branches: + - main + tags: + - "*.*.*" + - "v*.*.*" + - "*.*.*-*" + - "v*.*.*-*" + paths: + - ".github/actions/docker-build/actions.yaml" + - ".github/workflows/dockers-gateway-mirror-image.yml" + - "go.mod" + - "go.sum" + - "internal/**" + - "!internal/**/*_test.go" + - "!internal/**/*_mock.go" + - "!internal/db/**" + - "!internal/k8s/**" + - "apis/grpc/**" + - "pkg/gateway/mirror/**" + - "cmd/gateway/mirror/**" + - "pkg/gateway/internal/**" + - "dockers/gateway/mirror/Dockerfile" + - "versions/GO_VERSION" + pull_request: + paths: + - ".github/actions/docker-build/actions.yaml" + - ".github/workflows/dockers-gateway-mirror-image.yml" + - "go.mod" + - "go.sum" + - "internal/**" + - "!internal/**/*_test.go" + - "!internal/**/*_mock.go" + - "!internal/db/**" + - "!internal/k8s/**" + - "apis/grpc/**" + - "pkg/gateway/mirror/**" + - "cmd/gateway/mirror/**" + - "pkg/gateway/internal/**" + - "dockers/gateway/mirror/Dockerfile" + - "versions/GO_VERSION" + pull_request_target: + paths: + - ".github/actions/docker-build/actions.yaml" + - ".github/workflows/dockers-gateway-mirror-image.yml" + - "go.mod" + - "go.sum" + - "internal/**" + - "!internal/**/*_test.go" + - "!internal/**/*_mock.go" + - "!internal/db/**" + - "!internal/k8s/**" + - "apis/grpc/**" + - "pkg/gateway/mirror/**" + - "cmd/gateway/nirror/**" + - "pkg/gateway/internal/**" + - "dockers/gateway/mirror/Dockerfile" + - "versions/GO_VERSION" + +jobs: + build: + uses: ./.github/workflows/_docker-image.yaml + with: + target: gateway-mirror + secrets: inherit diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 28f0722a11..48a8eb11e5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -319,8 +319,8 @@ jobs: env: POD_NAME: ${{ steps.deploy_vald.outputs.POD_NAME }} - e2e-agent-and-sidecar: - name: "E2E Agent & Sidecar test" + e2e-stream-crud-with-mirror: + name: "E2E test (Stream CRUD) with mirror" needs: [dump-contexts-to-log] runs-on: ubuntu-latest timeout-minutes: 60 @@ -335,44 +335,62 @@ jobs: id: setup_e2e uses: ./.github/actions/setup-e2e with: - target_images: vdaas/vald-agent-ngt vdaas/vald-agent-sidecar + target_images: "vdaas/vald-agent-ngt vdaas/vald-discoverer-k8s vdaas/vald-lb-gateway vdaas/vald-manager-index vdaas/vald-mirror-gateway" - - name: Deploy Vald - id: deploy_vald + - name: Deploy Vald-01 + id: deploy_vald_01 uses: ./.github/actions/e2e-deploy-vald with: - default_image_tag: ${{ steps.setup_e2e.outputs.DEFAULT_IMAGE_TAG }} - require_minio: "true" + namespace: vald-01 helm_extra_options: ${{ steps.setup_e2e.outputs.HELM_EXTRA_OPTIONS }} - values: .github/helm/values/values-agent-sidecar.yaml - wait_for_selector: app=vald-agent-ngt - wait_for_timeout: 29m + values: .github/helm/values/values-mirror-01.yaml + wait_for_selector: app=vald-mirror-gateway - - name: Run E2E Agent & Sidecar + - name: Deploy Vald-02 + id: deploy_vald_02 + uses: ./.github/actions/e2e-deploy-vald + with: + namespace: vald-02 + helm_extra_options: ${{ steps.setup_e2e.outputs.HELM_EXTRA_OPTIONS }} + values: .github/helm/values/values-mirror-02.yaml + wait_for_selector: app=vald-mirror-gateway + + - name: Deploy Mirror Target + run: | + kubectl apply -f .github/helm/values/vald-mirror-target.yaml -n vald-01 + sleep 5s + kubectl get pods -A && kubectl get vmt -o wide -A + + - name: Run E2E CRUD run: | make hack/benchmark/assets/dataset/${{ env.DATASET }} make E2E_BIND_PORT=8081 \ E2E_DATASET_NAME=${{ env.DATASET }} \ - E2E_INSERT_COUNT=10000 \ - E2E_SEARCH_COUNT=4000 \ - E2E_WAIT_FOR_CREATE_INDEX_DURATION=8m \ + E2E_INSERT_COUNT=10000\ + E2E_SEARCH_COUNT=10000 \ + E2E_SEARCH_BY_ID_COUNT=10000 \ + E2E_GET_OBJECT_COUNT=100 \ + E2E_UPDATE_COUNT=100 \ + E2E_UPSERT_COUNT=100 \ + E2E_REMOVE_COUNT=100 \ + E2E_WAIT_FOR_CREATE_INDEX_DURATION=3m \ E2E_TARGET_POD_NAME=${POD_NAME} \ - E2E_TARGET_NAMESPACE=default \ - e2e/sidecar + E2E_TARGET_NAMESPACE=vald-01 \ + e2e env: - POD_NAME: ${{ steps.deploy_vald.outputs.POD_NAME }} + POD_NAME: ${{ steps.deploy_vald_01.outputs.POD_NAME }} slack-notification: name: "Slack notification" if: startsWith( github.ref, 'refs/tags/') needs: - - e2e-agent-and-sidecar - e2e-jobs - e2e-multiapis-crud - e2e-stream-crud - e2e-stream-crud-for-operator - e2e-stream-crud-skip-exist-check - e2e-stream-crud-under-index-management-jobs + - e2e-stream-crud-with-mirror runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index b072ea28a7..7d6227297c 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ CI_CONTAINER_IMAGE = $(NAME)-ci-container DEV_CONTAINER_IMAGE = $(NAME)-dev-container DISCOVERER_IMAGE = $(NAME)-discoverer-k8s FILTER_GATEWAY_IMAGE = $(NAME)-filter-gateway +MIRROR_GATEWAY_IMAGE = $(NAME)-mirror-gateway HELM_OPERATOR_IMAGE = $(NAME)-helm-operator LB_GATEWAY_IMAGE = $(NAME)-lb-gateway LOADTEST_IMAGE = $(NAME)-loadtest @@ -305,7 +306,7 @@ E2E_UPSERT_COUNT ?= 10 E2E_REMOVE_COUNT ?= 3 E2E_WAIT_FOR_CREATE_INDEX_DURATION ?= 8m E2E_TARGET_NAME ?= vald-lb-gateway -E2E_TARGET_POD_NAME ?= $(eval E2E_TARGET_POD_NAME := $(shell kubectl get pods --selector=app=$(E2E_TARGET_NAME) | tail -1 | cut -f1 -d " "))$(E2E_TARGET_POD_NAME) +E2E_TARGET_POD_NAME ?= $(eval E2E_TARGET_POD_NAME := $(shell kubectl get pods --selector=app=$(E2E_TARGET_NAME) -n $(E2E_TARGET_NAMESPACE) | tail -1 | cut -f1 -d " "))$(E2E_TARGET_POD_NAME) E2E_TARGET_NAMESPACE ?= default E2E_TARGET_PORT ?= 8081 E2E_PORTFORWARD_ENABLED ?= true diff --git a/Makefile.d/build.mk b/Makefile.d/build.mk index 4cbd8e842b..e12219a943 100644 --- a/Makefile.d/build.mk +++ b/Makefile.d/build.mk @@ -179,6 +179,34 @@ cmd/gateway/filter/filter: \ $(dir $@)main.go $@ -version +cmd/gateway/mirror/mirror: \ + $(GO_SOURCES_INTERNAL) \ + $(PBGOS) \ + $(shell find ./cmd/gateway/mirror -type f -name '*.go' -not -name '*_test.go' -not -name 'doc.go') \ + $(shell find ./pkg/gateway/mirror -type f -name '*.go' -not -name '*_test.go' -not -name 'doc.go') + CGO_ENABLED=0 \ + GO111MODULE=on \ + GOPRIVATE=$(GOPRIVATE) \ + go build \ + --ldflags "-w -extldflags=-static \ + -X '$(GOPKG)/internal/info.Version=$(VERSION)' \ + -X '$(GOPKG)/internal/info.GitCommit=$(GIT_COMMIT)' \ + -X '$(GOPKG)/internal/info.BuildTime=$(DATETIME)' \ + -X '$(GOPKG)/internal/info.GoVersion=$(GO_VERSION)' \ + -X '$(GOPKG)/internal/info.GoOS=$(GOOS)' \ + -X '$(GOPKG)/internal/info.GoArch=$(GOARCH)' \ + -X '$(GOPKG)/internal/info.CGOEnabled=$${CGO_ENABLED}' \ + -X '$(GOPKG)/internal/info.BuildCPUInfoFlags=$(CPU_INFO_FLAGS)' \ + -buildid=" \ + -mod=readonly \ + -modcacherw \ + -a \ + -tags "osusergo netgo static_build" \ + -trimpath \ + -o $@ \ + $(dir $@)main.go + $@ -version + cmd/manager/index/index: \ $(GO_SOURCES_INTERNAL) \ $(PBGOS) \ diff --git a/Makefile.d/docker.mk b/Makefile.d/docker.mk index cc45bfbd6b..922275438f 100644 --- a/Makefile.d/docker.mk +++ b/Makefile.d/docker.mk @@ -129,6 +129,17 @@ docker/build/gateway-filter: IMAGE=$(FILTER_GATEWAY_IMAGE) \ docker/build/image +.PHONY: docker/name/gateway-mirror +docker/name/gateway-mirror: + @echo "$(ORG)/$(MIRROR_GATEWAY_IMAGE)" + +.PHONY: docker/build/gateway-mirror +## build gateway-mirror image +docker/build/gateway-mirror: + @make DOCKERFILE="$(ROOTDIR)/dockers/gateway/mirror/Dockerfile" \ + IMAGE=$(MIRROR_GATEWAY_IMAGE) \ + docker/build/image + .PHONY: docker/name/manager-index docker/name/manager-index: @echo "$(ORG)/$(MANAGER_INDEX_IMAGE)" diff --git a/Makefile.d/helm.mk b/Makefile.d/helm.mk index 4f781debb9..29159bcdc9 100644 --- a/Makefile.d/helm.mk +++ b/Makefile.d/helm.mk @@ -163,6 +163,17 @@ helm/schema/crd/vald-helm-operator: \ $(BINDIR)/yq eval-all 'select(fileIndex==0).spec.versions[0].schema.openAPIV3Schema.properties.spec = select(fileIndex==1).spec | select(fileIndex==0)' \ $(TEMP_DIR)/valdhelmoperatorrelease.yaml $(TEMP_DIR)/valdhelmoperatorrelease-spec.yaml > charts/vald-helm-operator/crds/valdhelmoperatorrelease.yaml +.PHONY: helm/schema/crd/vald/mirror-target +## generate OpenAPI v3 schema for ValdMirrorTarget +helm/schema/crd/vald/mirror-target: \ + yq/install + mv charts/vald/crds/valdmirrortarget.yaml $(TEMP_DIR)/valdmirrortarget.yaml + GOPRIVATE=$(GOPRIVATE) \ + go run -mod=readonly hack/helm/schema/crd/main.go \ + charts/vald/schemas/mirror-target-values.yaml > $(TEMP_DIR)/valdmirrortarget-spec.yaml + $(BINDIR)/yq eval-all 'select(fileIndex==0).spec.versions[0].schema.openAPIV3Schema.properties.spec = select(fileIndex==1).spec | select(fileIndex==0)' \ + $(TEMP_DIR)/valdmirrortarget.yaml $(TEMP_DIR)/valdmirrortarget-spec.yaml > charts/vald/crds/valdmirrortarget.yaml + .PHONY: helm/schema/crd/vald-benchmark-job ## generate OpenAPI v3 schema for ValdBenchmarkJobRelease helm/schema/crd/vald-benchmark-job: \ diff --git a/Makefile.d/k8s.mk b/Makefile.d/k8s.mk index 2a1b419c2d..f7ca5d4a6d 100644 --- a/Makefile.d/k8s.mk +++ b/Makefile.d/k8s.mk @@ -15,6 +15,10 @@ # JAEGER_OPERATOR_WAIT_DURATION := 0 +MIRROR01_NAMESPACE = vald-01 +MIRROR02_NAMESPACE = vald-02 +MIRROR03_NAMESPACE = vald-03 +MIRROR_APP_NAME = vald-mirror-gateway .PHONY: k8s/manifest/clean ## clean k8s manifests @@ -41,7 +45,7 @@ k8s/manifest/update: \ mkdir -p k8s/index/job/readreplica mv $(TEMP_DIR)/vald/templates/agent k8s/agent mv $(TEMP_DIR)/vald/templates/discoverer k8s/discoverer - mv $(TEMP_DIR)/vald/templates/gateway/lb k8s/gateway/lb + mv $(TEMP_DIR)/vald/templates/gateway k8s/gateway mv $(TEMP_DIR)/vald/templates/manager/index k8s/manager/index mv $(TEMP_DIR)/vald/templates/index/job/correction k8s/index/job/correction mv $(TEMP_DIR)/vald/templates/index/job/creation k8s/index/job/creation @@ -96,10 +100,12 @@ k8s/vald/deploy: --set discoverer.image.repository=$(CRORG)/$(DISCOVERER_IMAGE) \ --set gateway.filter.image.repository=$(CRORG)/$(FILTER_GATEWAY_IMAGE) \ --set gateway.lb.image.repository=$(CRORG)/$(LB_GATEWAY_IMAGE) \ + --set gateway.mirror.image.repository=$(CRORG)/$(MIRROR_GATEWAY_IMAGE) \ --set manager.index.image.repository=$(CRORG)/$(MANAGER_INDEX_IMAGE) \ --set manager.index.creator.image.repository=$(CRORG)/$(INDEX_CREATION_IMAGE) \ --set manager.index.saver.image.repository=$(CRORG)/$(INDEX_SAVE_IMAGE) \ $(HELM_EXTRA_OPTIONS) \ + --include-crds \ --output-dir $(TEMP_DIR) \ charts/vald @echo "Permitting error because there's some cases nothing to apply" @@ -107,7 +113,10 @@ k8s/vald/deploy: kubectl apply -f $(TEMP_DIR)/vald/templates/agent || true kubectl apply -f $(TEMP_DIR)/vald/templates/agent/readreplica || true kubectl apply -f $(TEMP_DIR)/vald/templates/discoverer || true + kubectl apply -f $(TEMP_DIR)/vald/templates/gateway || true kubectl apply -f $(TEMP_DIR)/vald/templates/gateway/lb || true + kubectl apply -f $(TEMP_DIR)/vald/crds || true + kubectl apply -f $(TEMP_DIR)/vald/templates/gateway/mirror || true kubectl apply -f $(TEMP_DIR)/vald/templates/index/job/correction || true kubectl apply -f $(TEMP_DIR)/vald/templates/index/job/creation || true kubectl apply -f $(TEMP_DIR)/vald/templates/index/job/save || true @@ -126,22 +135,59 @@ k8s/vald/delete: --set discoverer.image.repository=$(CRORG)/$(DISCOVERER_IMAGE) \ --set gateway.filter.image.repository=$(CRORG)/$(FILTER_GATEWAY_IMAGE) \ --set gateway.lb.image.repository=$(CRORG)/$(LB_GATEWAY_IMAGE) \ + --set gateway.mirror.image.repository=$(CRORG)/$(MIRROR_GATEWAY_IMAGE) \ --set manager.index.image.repository=$(CRORG)/$(MANAGER_INDEX_IMAGE) \ + --include-crds \ --output-dir $(TEMP_DIR) \ charts/vald + kubectl delete -f $(TEMP_DIR)/vald/templates/gateway/mirror kubectl delete -f $(TEMP_DIR)/vald/templates/index/job/readreplica/rotate kubectl delete -f $(TEMP_DIR)/vald/templates/index/job/save kubectl delete -f $(TEMP_DIR)/vald/templates/index/job/creation kubectl delete -f $(TEMP_DIR)/vald/templates/index/job/correction kubectl delete -f $(TEMP_DIR)/vald/templates/index/job/creation kubectl delete -f $(TEMP_DIR)/vald/templates/index/job/save + kubectl delete -f $(TEMP_DIR)/vald/templates/gateway kubectl delete -f $(TEMP_DIR)/vald/templates/gateway/lb kubectl delete -f $(TEMP_DIR)/vald/templates/manager/index kubectl delete -f $(TEMP_DIR)/vald/templates/discoverer kubectl delete -f $(TEMP_DIR)/vald/templates/agent/readreplica || true kubectl delete -f $(TEMP_DIR)/vald/templates/agent + kubectl delete -f $(TEMP_DIR)/vald/crds rm -rf $(TEMP_DIR) +.PHONY: k8s/multi/vald/deploy +## deploy multiple vald sample clusters to k8s +k8s/multi/vald/deploy: + -@kubectl create ns $(MIRROR01_NAMESPACE) + -@kubectl create ns $(MIRROR02_NAMESPACE) + -@kubectl create ns $(MIRROR03_NAMESPACE) + helm install vald-cluster-01 charts/vald \ + -f ./charts/vald/values/multi-vald/dev-vald-with-mirror.yaml \ + -f ./charts/vald/values/multi-vald/dev-vald-01.yaml \ + -n $(MIRROR01_NAMESPACE) + helm install vald-cluster-02 charts/vald \ + -f ./charts/vald/values/multi-vald/dev-vald-with-mirror.yaml \ + -f ./charts/vald/values/multi-vald/dev-vald-02.yaml \ + -n $(MIRROR02_NAMESPACE) + helm install vald-cluster-03 charts/vald \ + -f ./charts/vald/values/multi-vald/dev-vald-with-mirror.yaml \ + -f ./charts/vald/values/multi-vald/dev-vald-03.yaml \ + -n $(MIRROR03_NAMESPACE) + kubectl wait --for=condition=ready pod -l app=$(MIRROR_APP_NAME) --timeout=120s -n $(MIRROR01_NAMESPACE) + kubectl wait --for=condition=ready pod -l app=$(MIRROR_APP_NAME) --timeout=120s -n $(MIRROR02_NAMESPACE) + kubectl wait --for=condition=ready pod -l app=$(MIRROR_APP_NAME) --timeout=120s -n $(MIRROR03_NAMESPACE) + kubectl apply -f ./charts/vald/values/multi-vald/mirror-target.yaml \ + -n $(MIRROR03_NAMESPACE) + +.PHONY: k8s/multi/vald/delete +## delete multiple vald sample clusters to k8s +k8s/multi/vald/delete: + helm uninstall vald-cluster-01 -n vald-01 + helm uninstall vald-cluster-02 -n vald-02 + helm uninstall vald-cluster-03 -n vald-03 + -@kubectl delete ns vald-01 vald-02 vald-03 + .PHONY: k8s/vald-helm-operator/deploy ## deploy vald-helm-operator to k8s k8s/vald-helm-operator/deploy: diff --git a/apis/docs/v1/docs.md b/apis/docs/v1/docs.md index 80245dd00c..3cdd7ab520 100644 --- a/apis/docs/v1/docs.md +++ b/apis/docs/v1/docs.md @@ -40,6 +40,9 @@ - [Insert.MultiRequest](#payload-v1-Insert-MultiRequest) - [Insert.ObjectRequest](#payload-v1-Insert-ObjectRequest) - [Insert.Request](#payload-v1-Insert-Request) + - [Mirror](#payload-v1-Mirror) + - [Mirror.Target](#payload-v1-Mirror-Target) + - [Mirror.Targets](#payload-v1-Mirror-Targets) - [Object](#payload-v1-Object) - [Object.Blob](#payload-v1-Object-Blob) - [Object.Distance](#payload-v1-Object-Distance) @@ -104,6 +107,8 @@ - [Filter](#filter-ingress-v1-Filter) - [v1/manager/index/index_manager.proto](#v1_manager_index_index_manager-proto) - [Index](#manager-index-v1-Index) +- [v1/mirror/mirror.proto](#v1_mirror_mirror-proto) + - [Mirror](#mirror-v1-Mirror) - [v1/rpc/errdetails/error_details.proto](#v1_rpc_errdetails_error_details-proto) - [BadRequest](#rpc-v1-BadRequest) - [BadRequest.FieldViolation](#rpc-v1-BadRequest-FieldViolation) @@ -480,6 +485,33 @@ Represent the insert request. | vector | [Object.Vector](#payload-v1-Object-Vector) | | The vector to be inserted. | | config | [Insert.Config](#payload-v1-Insert-Config) | | The configuration of the insert request. | + + +### Mirror + +Mirror related messages. + + + +### Mirror.Target + +Represent server information. + +| Field | Type | Label | Description | +| ----- | ----------------- | ----- | -------------------- | +| host | [string](#string) | | The target hostname. | +| port | [uint32](#uint32) | | The target port. | + + + +### Mirror.Targets + +Represent the multiple Target message. + +| Field | Type | Label | Description | +| ------- | ------------------------------------------ | -------- | -------------------------------- | +| targets | [Mirror.Target](#payload-v1-Mirror-Target) | repeated | The multiple target information. | + ### Object @@ -1113,6 +1145,36 @@ Represent the index manager service. | ----------- | -------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------- | | IndexInfo | [.payload.v1.Empty](#payload-v1-Empty) | [.payload.v1.Info.Index.Count](#payload-v1-Info-Index-Count) | Represent the RPC to get the index information. | + + +

Top

+ +## v1/mirror/mirror.proto + +Copyright (C) 2019-2024 vdaas.org vald team <vald@vdaas.org> + +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 + +https://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. + + + +### Mirror + +Represent the mirror service. + +| Method Name | Request Type | Response Type | Description | +| ----------- | -------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------- | +| Register | [.payload.v1.Mirror.Targets](#payload-v1-Mirror-Targets) | [.payload.v1.Mirror.Targets](#payload-v1-Mirror-Targets) | Register is the RPC to register other mirror servers. | +

Top

diff --git a/apis/grpc/v1/agent/core/agent.pb.go b/apis/grpc/v1/agent/core/agent.pb.go index ce90f86ffa..3541c8b8f0 100644 --- a/apis/grpc/v1/agent/core/agent.pb.go +++ b/apis/grpc/v1/agent/core/agent.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/agent/core/agent.proto diff --git a/apis/grpc/v1/agent/sidecar/sidecar.pb.go b/apis/grpc/v1/agent/sidecar/sidecar.pb.go index 421f6a8a55..b6b3ad1053 100644 --- a/apis/grpc/v1/agent/sidecar/sidecar.pb.go +++ b/apis/grpc/v1/agent/sidecar/sidecar.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/agent/sidecar/sidecar.proto diff --git a/apis/grpc/v1/discoverer/discoverer.pb.go b/apis/grpc/v1/discoverer/discoverer.pb.go index 851f665368..fe6cc80593 100644 --- a/apis/grpc/v1/discoverer/discoverer.pb.go +++ b/apis/grpc/v1/discoverer/discoverer.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/discoverer/discoverer.proto diff --git a/apis/grpc/v1/filter/egress/egress_filter.pb.go b/apis/grpc/v1/filter/egress/egress_filter.pb.go index 607517e3e1..fa0e9725a4 100644 --- a/apis/grpc/v1/filter/egress/egress_filter.pb.go +++ b/apis/grpc/v1/filter/egress/egress_filter.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/filter/egress/egress_filter.proto diff --git a/apis/grpc/v1/filter/ingress/ingress_filter.pb.go b/apis/grpc/v1/filter/ingress/ingress_filter.pb.go index d223b6de6c..ee4a3a302b 100644 --- a/apis/grpc/v1/filter/ingress/ingress_filter.pb.go +++ b/apis/grpc/v1/filter/ingress/ingress_filter.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/filter/ingress/ingress_filter.proto diff --git a/apis/grpc/v1/manager/index/index_manager.pb.go b/apis/grpc/v1/manager/index/index_manager.pb.go index 5c239f8a28..2ddc40a16b 100644 --- a/apis/grpc/v1/manager/index/index_manager.pb.go +++ b/apis/grpc/v1/manager/index/index_manager.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/manager/index/index_manager.proto diff --git a/apis/grpc/v1/mirror/mirror.pb.go b/apis/grpc/v1/mirror/mirror.pb.go new file mode 100644 index 0000000000..69f1516db7 --- /dev/null +++ b/apis/grpc/v1/mirror/mirror.pb.go @@ -0,0 +1,100 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc (unknown) +// source: v1/mirror/mirror.proto + +package mirror + +import ( + reflect "reflect" + + payload "github.com/vdaas/vald/apis/grpc/v1/payload" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var File_v1_mirror_mirror_proto protoreflect.FileDescriptor + +var file_v1_mirror_mirror_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x76, 0x31, 0x2f, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x2f, 0x6d, 0x69, 0x72, 0x72, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x18, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2f, 0x70, 0x61, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x69, 0x0a, 0x06, 0x4d, + 0x69, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x5f, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x12, 0x1a, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x69, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x1a, 0x1a, 0x2e, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x72, 0x72, 0x6f, + 0x72, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x2f, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x57, 0x0a, 0x1c, 0x6f, 0x72, 0x67, 0x2e, 0x76, 0x64, + 0x61, 0x61, 0x73, 0x2e, 0x76, 0x61, 0x6c, 0x64, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x0a, 0x56, 0x61, 0x6c, 0x64, 0x4d, 0x69, 0x72, 0x72, + 0x6f, 0x72, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x76, 0x64, 0x61, 0x61, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x73, + 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_v1_mirror_mirror_proto_goTypes = []interface{}{ + (*payload.Mirror_Targets)(nil), // 0: payload.v1.Mirror.Targets +} +var file_v1_mirror_mirror_proto_depIdxs = []int32{ + 0, // 0: mirror.v1.Mirror.Register:input_type -> payload.v1.Mirror.Targets + 0, // 1: mirror.v1.Mirror.Register:output_type -> payload.v1.Mirror.Targets + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_v1_mirror_mirror_proto_init() } +func file_v1_mirror_mirror_proto_init() { + if File_v1_mirror_mirror_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_mirror_mirror_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_v1_mirror_mirror_proto_goTypes, + DependencyIndexes: file_v1_mirror_mirror_proto_depIdxs, + }.Build() + File_v1_mirror_mirror_proto = out.File + file_v1_mirror_mirror_proto_rawDesc = nil + file_v1_mirror_mirror_proto_goTypes = nil + file_v1_mirror_mirror_proto_depIdxs = nil +} diff --git a/apis/grpc/v1/mirror/mirror_vtproto.pb.go b/apis/grpc/v1/mirror/mirror_vtproto.pb.go new file mode 100644 index 0000000000..399c385d4b --- /dev/null +++ b/apis/grpc/v1/mirror/mirror_vtproto.pb.go @@ -0,0 +1,127 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 mirror + +import ( + context "context" + + payload "github.com/vdaas/vald/apis/grpc/v1/payload" + codes "github.com/vdaas/vald/internal/net/grpc/codes" + status "github.com/vdaas/vald/internal/net/grpc/status" + grpc "google.golang.org/grpc" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// MirrorClient is the client API for Mirror service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type MirrorClient interface { + // Register is the RPC to register other mirror servers. + Register(ctx context.Context, in *payload.Mirror_Targets, opts ...grpc.CallOption) (*payload.Mirror_Targets, error) +} + +type mirrorClient struct { + cc grpc.ClientConnInterface +} + +func NewMirrorClient(cc grpc.ClientConnInterface) MirrorClient { + return &mirrorClient{cc} +} + +func (c *mirrorClient) Register(ctx context.Context, in *payload.Mirror_Targets, opts ...grpc.CallOption) (*payload.Mirror_Targets, error) { + out := new(payload.Mirror_Targets) + err := c.cc.Invoke(ctx, "/mirror.v1.Mirror/Register", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MirrorServer is the server API for Mirror service. +// All implementations must embed UnimplementedMirrorServer +// for forward compatibility +type MirrorServer interface { + // Register is the RPC to register other mirror servers. + Register(context.Context, *payload.Mirror_Targets) (*payload.Mirror_Targets, error) + mustEmbedUnimplementedMirrorServer() +} + +// UnimplementedMirrorServer must be embedded to have forward compatible implementations. +type UnimplementedMirrorServer struct { +} + +func (UnimplementedMirrorServer) Register(context.Context, *payload.Mirror_Targets) (*payload.Mirror_Targets, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedMirrorServer) mustEmbedUnimplementedMirrorServer() {} + +// UnsafeMirrorServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MirrorServer will +// result in compilation errors. +type UnsafeMirrorServer interface { + mustEmbedUnimplementedMirrorServer() +} + +func RegisterMirrorServer(s grpc.ServiceRegistrar, srv MirrorServer) { + s.RegisterService(&Mirror_ServiceDesc, srv) +} + +func _Mirror_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(payload.Mirror_Targets) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MirrorServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/mirror.v1.Mirror/Register", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MirrorServer).Register(ctx, req.(*payload.Mirror_Targets)) + } + return interceptor(ctx, in, info, handler) +} + +// Mirror_ServiceDesc is the grpc.ServiceDesc for Mirror service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Mirror_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "mirror.v1.Mirror", + HandlerType: (*MirrorServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Register", + Handler: _Mirror_Register_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "v1/mirror/mirror.proto", +} diff --git a/apis/grpc/v1/payload/payload.pb.go b/apis/grpc/v1/payload/payload.pb.go index db70fd3d64..5e94a4f000 100644 --- a/apis/grpc/v1/payload/payload.pb.go +++ b/apis/grpc/v1/payload/payload.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/payload/payload.proto @@ -550,6 +550,45 @@ func (*Info) Descriptor() ([]byte, []int) { return file_v1_payload_payload_proto_rawDescGZIP(), []int{9} } +// Mirror related messages. +type Mirror struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Mirror) Reset() { + *x = Mirror{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_payload_payload_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Mirror) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mirror) ProtoMessage() {} + +func (x *Mirror) ProtoReflect() protoreflect.Message { + mi := &file_v1_payload_payload_proto_msgTypes[10] + 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 Mirror.ProtoReflect.Descriptor instead. +func (*Mirror) Descriptor() ([]byte, []int) { + return file_v1_payload_payload_proto_rawDescGZIP(), []int{10} +} + // Represent an empty message. type Empty struct { state protoimpl.MessageState @@ -560,7 +599,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[10] + mi := &file_v1_payload_payload_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -573,7 +612,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[10] + mi := &file_v1_payload_payload_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -586,7 +625,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_v1_payload_payload_proto_rawDescGZIP(), []int{10} + return file_v1_payload_payload_proto_rawDescGZIP(), []int{11} } // Represent a search request. @@ -604,7 +643,7 @@ type Search_Request struct { func (x *Search_Request) Reset() { *x = Search_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[11] + mi := &file_v1_payload_payload_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -617,7 +656,7 @@ func (x *Search_Request) String() string { func (*Search_Request) ProtoMessage() {} func (x *Search_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[11] + mi := &file_v1_payload_payload_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -660,7 +699,7 @@ type Search_MultiRequest struct { func (x *Search_MultiRequest) Reset() { *x = Search_MultiRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[12] + mi := &file_v1_payload_payload_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -673,7 +712,7 @@ func (x *Search_MultiRequest) String() string { func (*Search_MultiRequest) ProtoMessage() {} func (x *Search_MultiRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[12] + mi := &file_v1_payload_payload_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -711,7 +750,7 @@ type Search_IDRequest struct { func (x *Search_IDRequest) Reset() { *x = Search_IDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[13] + mi := &file_v1_payload_payload_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -724,7 +763,7 @@ func (x *Search_IDRequest) String() string { func (*Search_IDRequest) ProtoMessage() {} func (x *Search_IDRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[13] + mi := &file_v1_payload_payload_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -767,7 +806,7 @@ type Search_MultiIDRequest struct { func (x *Search_MultiIDRequest) Reset() { *x = Search_MultiIDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[14] + mi := &file_v1_payload_payload_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -780,7 +819,7 @@ func (x *Search_MultiIDRequest) String() string { func (*Search_MultiIDRequest) ProtoMessage() {} func (x *Search_MultiIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[14] + mi := &file_v1_payload_payload_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -820,7 +859,7 @@ type Search_ObjectRequest struct { func (x *Search_ObjectRequest) Reset() { *x = Search_ObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[15] + mi := &file_v1_payload_payload_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -833,7 +872,7 @@ func (x *Search_ObjectRequest) String() string { func (*Search_ObjectRequest) ProtoMessage() {} func (x *Search_ObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[15] + mi := &file_v1_payload_payload_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -883,7 +922,7 @@ type Search_MultiObjectRequest struct { func (x *Search_MultiObjectRequest) Reset() { *x = Search_MultiObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[16] + mi := &file_v1_payload_payload_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -896,7 +935,7 @@ func (x *Search_MultiObjectRequest) String() string { func (*Search_MultiObjectRequest) ProtoMessage() {} func (x *Search_MultiObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[16] + mi := &file_v1_payload_payload_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -948,7 +987,7 @@ type Search_Config struct { func (x *Search_Config) Reset() { *x = Search_Config{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[17] + mi := &file_v1_payload_payload_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -961,7 +1000,7 @@ func (x *Search_Config) String() string { func (*Search_Config) ProtoMessage() {} func (x *Search_Config) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[17] + mi := &file_v1_payload_payload_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1055,7 +1094,7 @@ type Search_Response struct { func (x *Search_Response) Reset() { *x = Search_Response{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[18] + mi := &file_v1_payload_payload_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1068,7 +1107,7 @@ func (x *Search_Response) String() string { func (*Search_Response) ProtoMessage() {} func (x *Search_Response) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[18] + mi := &file_v1_payload_payload_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1111,7 +1150,7 @@ type Search_Responses struct { func (x *Search_Responses) Reset() { *x = Search_Responses{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[19] + mi := &file_v1_payload_payload_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1124,7 +1163,7 @@ func (x *Search_Responses) String() string { func (*Search_Responses) ProtoMessage() {} func (x *Search_Responses) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[19] + mi := &file_v1_payload_payload_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1163,7 +1202,7 @@ type Search_StreamResponse struct { func (x *Search_StreamResponse) Reset() { *x = Search_StreamResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[20] + mi := &file_v1_payload_payload_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1176,7 +1215,7 @@ func (x *Search_StreamResponse) String() string { func (*Search_StreamResponse) ProtoMessage() {} func (x *Search_StreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[20] + mi := &file_v1_payload_payload_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1246,7 +1285,7 @@ type Filter_Target struct { func (x *Filter_Target) Reset() { *x = Filter_Target{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[21] + mi := &file_v1_payload_payload_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1259,7 +1298,7 @@ func (x *Filter_Target) String() string { func (*Filter_Target) ProtoMessage() {} func (x *Filter_Target) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[21] + mi := &file_v1_payload_payload_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1302,7 +1341,7 @@ type Filter_Config struct { func (x *Filter_Config) Reset() { *x = Filter_Config{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[22] + mi := &file_v1_payload_payload_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1315,7 +1354,7 @@ func (x *Filter_Config) String() string { func (*Filter_Config) ProtoMessage() {} func (x *Filter_Config) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[22] + mi := &file_v1_payload_payload_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1353,7 +1392,7 @@ type Insert_Request struct { func (x *Insert_Request) Reset() { *x = Insert_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[23] + mi := &file_v1_payload_payload_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1366,7 +1405,7 @@ func (x *Insert_Request) String() string { func (*Insert_Request) ProtoMessage() {} func (x *Insert_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[23] + mi := &file_v1_payload_payload_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1409,7 +1448,7 @@ type Insert_MultiRequest struct { func (x *Insert_MultiRequest) Reset() { *x = Insert_MultiRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[24] + mi := &file_v1_payload_payload_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1422,7 +1461,7 @@ func (x *Insert_MultiRequest) String() string { func (*Insert_MultiRequest) ProtoMessage() {} func (x *Insert_MultiRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[24] + mi := &file_v1_payload_payload_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1462,7 +1501,7 @@ type Insert_ObjectRequest struct { func (x *Insert_ObjectRequest) Reset() { *x = Insert_ObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[25] + mi := &file_v1_payload_payload_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1475,7 +1514,7 @@ func (x *Insert_ObjectRequest) String() string { func (*Insert_ObjectRequest) ProtoMessage() {} func (x *Insert_ObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[25] + mi := &file_v1_payload_payload_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1525,7 +1564,7 @@ type Insert_MultiObjectRequest struct { func (x *Insert_MultiObjectRequest) Reset() { *x = Insert_MultiObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[26] + mi := &file_v1_payload_payload_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1538,7 +1577,7 @@ func (x *Insert_MultiObjectRequest) String() string { func (*Insert_MultiObjectRequest) ProtoMessage() {} func (x *Insert_MultiObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[26] + mi := &file_v1_payload_payload_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1578,7 +1617,7 @@ type Insert_Config struct { func (x *Insert_Config) Reset() { *x = Insert_Config{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[27] + mi := &file_v1_payload_payload_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1591,7 +1630,7 @@ func (x *Insert_Config) String() string { func (*Insert_Config) ProtoMessage() {} func (x *Insert_Config) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[27] + mi := &file_v1_payload_payload_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1643,7 +1682,7 @@ type Update_Request struct { func (x *Update_Request) Reset() { *x = Update_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[28] + mi := &file_v1_payload_payload_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1656,7 +1695,7 @@ func (x *Update_Request) String() string { func (*Update_Request) ProtoMessage() {} func (x *Update_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[28] + mi := &file_v1_payload_payload_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1699,7 +1738,7 @@ type Update_MultiRequest struct { func (x *Update_MultiRequest) Reset() { *x = Update_MultiRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[29] + mi := &file_v1_payload_payload_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1712,7 +1751,7 @@ func (x *Update_MultiRequest) String() string { func (*Update_MultiRequest) ProtoMessage() {} func (x *Update_MultiRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[29] + mi := &file_v1_payload_payload_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1752,7 +1791,7 @@ type Update_ObjectRequest struct { func (x *Update_ObjectRequest) Reset() { *x = Update_ObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[30] + mi := &file_v1_payload_payload_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1765,7 +1804,7 @@ func (x *Update_ObjectRequest) String() string { func (*Update_ObjectRequest) ProtoMessage() {} func (x *Update_ObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[30] + mi := &file_v1_payload_payload_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1815,7 +1854,7 @@ type Update_MultiObjectRequest struct { func (x *Update_MultiObjectRequest) Reset() { *x = Update_MultiObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[31] + mi := &file_v1_payload_payload_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1828,7 +1867,7 @@ func (x *Update_MultiObjectRequest) String() string { func (*Update_MultiObjectRequest) ProtoMessage() {} func (x *Update_MultiObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[31] + mi := &file_v1_payload_payload_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1871,7 +1910,7 @@ type Update_Config struct { func (x *Update_Config) Reset() { *x = Update_Config{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[32] + mi := &file_v1_payload_payload_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1884,7 +1923,7 @@ func (x *Update_Config) String() string { func (*Update_Config) ProtoMessage() {} func (x *Update_Config) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[32] + mi := &file_v1_payload_payload_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1943,7 +1982,7 @@ type Upsert_Request struct { func (x *Upsert_Request) Reset() { *x = Upsert_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[33] + mi := &file_v1_payload_payload_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1956,7 +1995,7 @@ func (x *Upsert_Request) String() string { func (*Upsert_Request) ProtoMessage() {} func (x *Upsert_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[33] + mi := &file_v1_payload_payload_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1999,7 +2038,7 @@ type Upsert_MultiRequest struct { func (x *Upsert_MultiRequest) Reset() { *x = Upsert_MultiRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[34] + mi := &file_v1_payload_payload_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2012,7 +2051,7 @@ func (x *Upsert_MultiRequest) String() string { func (*Upsert_MultiRequest) ProtoMessage() {} func (x *Upsert_MultiRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[34] + mi := &file_v1_payload_payload_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2052,7 +2091,7 @@ type Upsert_ObjectRequest struct { func (x *Upsert_ObjectRequest) Reset() { *x = Upsert_ObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[35] + mi := &file_v1_payload_payload_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2065,7 +2104,7 @@ func (x *Upsert_ObjectRequest) String() string { func (*Upsert_ObjectRequest) ProtoMessage() {} func (x *Upsert_ObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[35] + mi := &file_v1_payload_payload_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2115,7 +2154,7 @@ type Upsert_MultiObjectRequest struct { func (x *Upsert_MultiObjectRequest) Reset() { *x = Upsert_MultiObjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[36] + mi := &file_v1_payload_payload_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2128,7 +2167,7 @@ func (x *Upsert_MultiObjectRequest) String() string { func (*Upsert_MultiObjectRequest) ProtoMessage() {} func (x *Upsert_MultiObjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[36] + mi := &file_v1_payload_payload_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2171,7 +2210,7 @@ type Upsert_Config struct { func (x *Upsert_Config) Reset() { *x = Upsert_Config{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[37] + mi := &file_v1_payload_payload_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2184,7 +2223,7 @@ func (x *Upsert_Config) String() string { func (*Upsert_Config) ProtoMessage() {} func (x *Upsert_Config) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[37] + mi := &file_v1_payload_payload_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2243,7 +2282,7 @@ type Remove_Request struct { func (x *Remove_Request) Reset() { *x = Remove_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[38] + mi := &file_v1_payload_payload_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2256,7 +2295,7 @@ func (x *Remove_Request) String() string { func (*Remove_Request) ProtoMessage() {} func (x *Remove_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[38] + mi := &file_v1_payload_payload_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2299,7 +2338,7 @@ type Remove_MultiRequest struct { func (x *Remove_MultiRequest) Reset() { *x = Remove_MultiRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[39] + mi := &file_v1_payload_payload_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2312,7 +2351,7 @@ func (x *Remove_MultiRequest) String() string { func (*Remove_MultiRequest) ProtoMessage() {} func (x *Remove_MultiRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[39] + mi := &file_v1_payload_payload_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2348,7 +2387,7 @@ type Remove_TimestampRequest struct { func (x *Remove_TimestampRequest) Reset() { *x = Remove_TimestampRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[40] + mi := &file_v1_payload_payload_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2361,7 +2400,7 @@ func (x *Remove_TimestampRequest) String() string { func (*Remove_TimestampRequest) ProtoMessage() {} func (x *Remove_TimestampRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[40] + mi := &file_v1_payload_payload_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2399,7 +2438,7 @@ type Remove_Timestamp struct { func (x *Remove_Timestamp) Reset() { *x = Remove_Timestamp{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[41] + mi := &file_v1_payload_payload_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2412,7 +2451,7 @@ func (x *Remove_Timestamp) String() string { func (*Remove_Timestamp) ProtoMessage() {} func (x *Remove_Timestamp) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[41] + mi := &file_v1_payload_payload_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2457,7 +2496,7 @@ type Remove_Config struct { func (x *Remove_Config) Reset() { *x = Remove_Config{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[42] + mi := &file_v1_payload_payload_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2470,7 +2509,7 @@ func (x *Remove_Config) String() string { func (*Remove_Config) ProtoMessage() {} func (x *Remove_Config) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[42] + mi := &file_v1_payload_payload_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2515,7 +2554,7 @@ type Object_VectorRequest struct { func (x *Object_VectorRequest) Reset() { *x = Object_VectorRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[43] + mi := &file_v1_payload_payload_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2528,7 +2567,7 @@ func (x *Object_VectorRequest) String() string { func (*Object_VectorRequest) ProtoMessage() {} func (x *Object_VectorRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[43] + mi := &file_v1_payload_payload_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2573,7 +2612,7 @@ type Object_Distance struct { func (x *Object_Distance) Reset() { *x = Object_Distance{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[44] + mi := &file_v1_payload_payload_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2586,7 +2625,7 @@ func (x *Object_Distance) String() string { func (*Object_Distance) ProtoMessage() {} func (x *Object_Distance) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[44] + mi := &file_v1_payload_payload_proto_msgTypes[45] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2632,7 +2671,7 @@ type Object_StreamDistance struct { func (x *Object_StreamDistance) Reset() { *x = Object_StreamDistance{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[45] + mi := &file_v1_payload_payload_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2645,7 +2684,7 @@ func (x *Object_StreamDistance) String() string { func (*Object_StreamDistance) ProtoMessage() {} func (x *Object_StreamDistance) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[45] + mi := &file_v1_payload_payload_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2712,7 +2751,7 @@ type Object_ID struct { func (x *Object_ID) Reset() { *x = Object_ID{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[46] + mi := &file_v1_payload_payload_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2725,7 +2764,7 @@ func (x *Object_ID) String() string { func (*Object_ID) ProtoMessage() {} func (x *Object_ID) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[46] + mi := &file_v1_payload_payload_proto_msgTypes[47] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2760,7 +2799,7 @@ type Object_IDs struct { func (x *Object_IDs) Reset() { *x = Object_IDs{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[47] + mi := &file_v1_payload_payload_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2773,7 +2812,7 @@ func (x *Object_IDs) String() string { func (*Object_IDs) ProtoMessage() {} func (x *Object_IDs) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[47] + mi := &file_v1_payload_payload_proto_msgTypes[48] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2813,7 +2852,7 @@ type Object_Vector struct { func (x *Object_Vector) Reset() { *x = Object_Vector{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[48] + mi := &file_v1_payload_payload_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2826,7 +2865,7 @@ func (x *Object_Vector) String() string { func (*Object_Vector) ProtoMessage() {} func (x *Object_Vector) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[48] + mi := &file_v1_payload_payload_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2876,7 +2915,7 @@ type Object_GetTimestampRequest struct { func (x *Object_GetTimestampRequest) Reset() { *x = Object_GetTimestampRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[49] + mi := &file_v1_payload_payload_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2889,7 +2928,7 @@ func (x *Object_GetTimestampRequest) String() string { func (*Object_GetTimestampRequest) ProtoMessage() {} func (x *Object_GetTimestampRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[49] + mi := &file_v1_payload_payload_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2927,7 +2966,7 @@ type Object_Timestamp struct { func (x *Object_Timestamp) Reset() { *x = Object_Timestamp{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[50] + mi := &file_v1_payload_payload_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2940,7 +2979,7 @@ func (x *Object_Timestamp) String() string { func (*Object_Timestamp) ProtoMessage() {} func (x *Object_Timestamp) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[50] + mi := &file_v1_payload_payload_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2982,7 +3021,7 @@ type Object_Vectors struct { func (x *Object_Vectors) Reset() { *x = Object_Vectors{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[51] + mi := &file_v1_payload_payload_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2995,7 +3034,7 @@ func (x *Object_Vectors) String() string { func (*Object_Vectors) ProtoMessage() {} func (x *Object_Vectors) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[51] + mi := &file_v1_payload_payload_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3034,7 +3073,7 @@ type Object_StreamVector struct { func (x *Object_StreamVector) Reset() { *x = Object_StreamVector{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[52] + mi := &file_v1_payload_payload_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3047,7 +3086,7 @@ func (x *Object_StreamVector) String() string { func (*Object_StreamVector) ProtoMessage() {} func (x *Object_StreamVector) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[52] + mi := &file_v1_payload_payload_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3117,7 +3156,7 @@ type Object_ReshapeVector struct { func (x *Object_ReshapeVector) Reset() { *x = Object_ReshapeVector{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[53] + mi := &file_v1_payload_payload_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3130,7 +3169,7 @@ func (x *Object_ReshapeVector) String() string { func (*Object_ReshapeVector) ProtoMessage() {} func (x *Object_ReshapeVector) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[53] + mi := &file_v1_payload_payload_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3175,7 +3214,7 @@ type Object_Blob struct { func (x *Object_Blob) Reset() { *x = Object_Blob{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[54] + mi := &file_v1_payload_payload_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3188,7 +3227,7 @@ func (x *Object_Blob) String() string { func (*Object_Blob) ProtoMessage() {} func (x *Object_Blob) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[54] + mi := &file_v1_payload_payload_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3234,7 +3273,7 @@ type Object_StreamBlob struct { func (x *Object_StreamBlob) Reset() { *x = Object_StreamBlob{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[55] + mi := &file_v1_payload_payload_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3247,7 +3286,7 @@ func (x *Object_StreamBlob) String() string { func (*Object_StreamBlob) ProtoMessage() {} func (x *Object_StreamBlob) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[55] + mi := &file_v1_payload_payload_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3319,7 +3358,7 @@ type Object_Location struct { func (x *Object_Location) Reset() { *x = Object_Location{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[56] + mi := &file_v1_payload_payload_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3332,7 +3371,7 @@ func (x *Object_Location) String() string { func (*Object_Location) ProtoMessage() {} func (x *Object_Location) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[56] + mi := &file_v1_payload_payload_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3385,7 +3424,7 @@ type Object_StreamLocation struct { func (x *Object_StreamLocation) Reset() { *x = Object_StreamLocation{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[57] + mi := &file_v1_payload_payload_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3398,7 +3437,7 @@ func (x *Object_StreamLocation) String() string { func (*Object_StreamLocation) ProtoMessage() {} func (x *Object_StreamLocation) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[57] + mi := &file_v1_payload_payload_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3465,7 +3504,7 @@ type Object_Locations struct { func (x *Object_Locations) Reset() { *x = Object_Locations{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[58] + mi := &file_v1_payload_payload_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3478,7 +3517,7 @@ func (x *Object_Locations) String() string { func (*Object_Locations) ProtoMessage() {} func (x *Object_Locations) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[58] + mi := &file_v1_payload_payload_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3511,7 +3550,7 @@ type Object_List struct { func (x *Object_List) Reset() { *x = Object_List{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[59] + mi := &file_v1_payload_payload_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3524,7 +3563,7 @@ func (x *Object_List) String() string { func (*Object_List) ProtoMessage() {} func (x *Object_List) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[59] + mi := &file_v1_payload_payload_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3549,7 +3588,7 @@ type Object_List_Request struct { func (x *Object_List_Request) Reset() { *x = Object_List_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[60] + mi := &file_v1_payload_payload_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3562,7 +3601,7 @@ func (x *Object_List_Request) String() string { func (*Object_List_Request) ProtoMessage() {} func (x *Object_List_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[60] + mi := &file_v1_payload_payload_proto_msgTypes[61] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3593,7 +3632,7 @@ type Object_List_Response struct { func (x *Object_List_Response) Reset() { *x = Object_List_Response{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[61] + mi := &file_v1_payload_payload_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3606,7 +3645,7 @@ func (x *Object_List_Response) String() string { func (*Object_List_Response) ProtoMessage() {} func (x *Object_List_Response) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[61] + mi := &file_v1_payload_payload_proto_msgTypes[62] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3674,7 +3713,7 @@ type Control_CreateIndexRequest struct { func (x *Control_CreateIndexRequest) Reset() { *x = Control_CreateIndexRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[62] + mi := &file_v1_payload_payload_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3687,7 +3726,7 @@ func (x *Control_CreateIndexRequest) String() string { func (*Control_CreateIndexRequest) ProtoMessage() {} func (x *Control_CreateIndexRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[62] + mi := &file_v1_payload_payload_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3727,7 +3766,7 @@ type Discoverer_Request struct { func (x *Discoverer_Request) Reset() { *x = Discoverer_Request{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[63] + mi := &file_v1_payload_payload_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3740,7 +3779,7 @@ func (x *Discoverer_Request) String() string { func (*Discoverer_Request) ProtoMessage() {} func (x *Discoverer_Request) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[63] + mi := &file_v1_payload_payload_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3787,7 +3826,7 @@ type Info_Index struct { func (x *Info_Index) Reset() { *x = Info_Index{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[64] + mi := &file_v1_payload_payload_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3800,7 +3839,7 @@ func (x *Info_Index) String() string { func (*Info_Index) ProtoMessage() {} func (x *Info_Index) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[64] + mi := &file_v1_payload_payload_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3841,7 +3880,7 @@ type Info_Pod struct { func (x *Info_Pod) Reset() { *x = Info_Pod{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[65] + mi := &file_v1_payload_payload_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3854,7 +3893,7 @@ func (x *Info_Pod) String() string { func (*Info_Pod) ProtoMessage() {} func (x *Info_Pod) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[65] + mi := &file_v1_payload_payload_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3942,7 +3981,7 @@ type Info_Node struct { func (x *Info_Node) Reset() { *x = Info_Node{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[66] + mi := &file_v1_payload_payload_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3955,7 +3994,7 @@ func (x *Info_Node) String() string { func (*Info_Node) ProtoMessage() {} func (x *Info_Node) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[66] + mi := &file_v1_payload_payload_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4036,7 +4075,7 @@ type Info_Service struct { func (x *Info_Service) Reset() { *x = Info_Service{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[67] + mi := &file_v1_payload_payload_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4049,7 +4088,7 @@ func (x *Info_Service) String() string { func (*Info_Service) ProtoMessage() {} func (x *Info_Service) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[67] + mi := &file_v1_payload_payload_proto_msgTypes[68] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4122,7 +4161,7 @@ type Info_ServicePort struct { func (x *Info_ServicePort) Reset() { *x = Info_ServicePort{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[68] + mi := &file_v1_payload_payload_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4135,7 +4174,7 @@ func (x *Info_ServicePort) String() string { func (*Info_ServicePort) ProtoMessage() {} func (x *Info_ServicePort) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[68] + mi := &file_v1_payload_payload_proto_msgTypes[69] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4177,7 +4216,7 @@ type Info_Labels struct { func (x *Info_Labels) Reset() { *x = Info_Labels{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[69] + mi := &file_v1_payload_payload_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4190,7 +4229,7 @@ func (x *Info_Labels) String() string { func (*Info_Labels) ProtoMessage() {} func (x *Info_Labels) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[69] + mi := &file_v1_payload_payload_proto_msgTypes[70] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4225,7 +4264,7 @@ type Info_Annotations struct { func (x *Info_Annotations) Reset() { *x = Info_Annotations{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[70] + mi := &file_v1_payload_payload_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4238,7 +4277,7 @@ func (x *Info_Annotations) String() string { func (*Info_Annotations) ProtoMessage() {} func (x *Info_Annotations) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[70] + mi := &file_v1_payload_payload_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4278,7 +4317,7 @@ type Info_CPU struct { func (x *Info_CPU) Reset() { *x = Info_CPU{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[71] + mi := &file_v1_payload_payload_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4291,7 +4330,7 @@ func (x *Info_CPU) String() string { func (*Info_CPU) ProtoMessage() {} func (x *Info_CPU) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[71] + mi := &file_v1_payload_payload_proto_msgTypes[72] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4345,7 +4384,7 @@ type Info_Memory struct { func (x *Info_Memory) Reset() { *x = Info_Memory{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[72] + mi := &file_v1_payload_payload_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4358,7 +4397,7 @@ func (x *Info_Memory) String() string { func (*Info_Memory) ProtoMessage() {} func (x *Info_Memory) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[72] + mi := &file_v1_payload_payload_proto_msgTypes[73] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4408,7 +4447,7 @@ type Info_Pods struct { func (x *Info_Pods) Reset() { *x = Info_Pods{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[73] + mi := &file_v1_payload_payload_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4421,7 +4460,7 @@ func (x *Info_Pods) String() string { func (*Info_Pods) ProtoMessage() {} func (x *Info_Pods) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[73] + mi := &file_v1_payload_payload_proto_msgTypes[74] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4457,7 +4496,7 @@ type Info_Nodes struct { func (x *Info_Nodes) Reset() { *x = Info_Nodes{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[74] + mi := &file_v1_payload_payload_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4470,7 +4509,7 @@ func (x *Info_Nodes) String() string { func (*Info_Nodes) ProtoMessage() {} func (x *Info_Nodes) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[74] + mi := &file_v1_payload_payload_proto_msgTypes[75] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4506,7 +4545,7 @@ type Info_Services struct { func (x *Info_Services) Reset() { *x = Info_Services{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[75] + mi := &file_v1_payload_payload_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4519,7 +4558,7 @@ func (x *Info_Services) String() string { func (*Info_Services) ProtoMessage() {} func (x *Info_Services) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[75] + mi := &file_v1_payload_payload_proto_msgTypes[76] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4554,7 +4593,7 @@ type Info_IPs struct { func (x *Info_IPs) Reset() { *x = Info_IPs{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[76] + mi := &file_v1_payload_payload_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4567,7 +4606,7 @@ func (x *Info_IPs) String() string { func (*Info_IPs) ProtoMessage() {} func (x *Info_IPs) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[76] + mi := &file_v1_payload_payload_proto_msgTypes[77] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4609,7 +4648,7 @@ type Info_Index_Count struct { func (x *Info_Index_Count) Reset() { *x = Info_Index_Count{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[77] + mi := &file_v1_payload_payload_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4622,7 +4661,7 @@ func (x *Info_Index_Count) String() string { func (*Info_Index_Count) ProtoMessage() {} func (x *Info_Index_Count) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[77] + mi := &file_v1_payload_payload_proto_msgTypes[78] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4676,7 +4715,7 @@ type Info_Index_UUID struct { func (x *Info_Index_UUID) Reset() { *x = Info_Index_UUID{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[78] + mi := &file_v1_payload_payload_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4689,7 +4728,7 @@ func (x *Info_Index_UUID) String() string { func (*Info_Index_UUID) ProtoMessage() {} func (x *Info_Index_UUID) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[78] + mi := &file_v1_payload_payload_proto_msgTypes[79] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4717,7 +4756,7 @@ type Info_Index_UUID_Committed struct { func (x *Info_Index_UUID_Committed) Reset() { *x = Info_Index_UUID_Committed{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[79] + mi := &file_v1_payload_payload_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4730,7 +4769,7 @@ func (x *Info_Index_UUID_Committed) String() string { func (*Info_Index_UUID_Committed) ProtoMessage() {} func (x *Info_Index_UUID_Committed) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[79] + mi := &file_v1_payload_payload_proto_msgTypes[80] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4765,7 +4804,7 @@ type Info_Index_UUID_Uncommitted struct { func (x *Info_Index_UUID_Uncommitted) Reset() { *x = Info_Index_UUID_Uncommitted{} if protoimpl.UnsafeEnabled { - mi := &file_v1_payload_payload_proto_msgTypes[80] + mi := &file_v1_payload_payload_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4778,7 +4817,7 @@ func (x *Info_Index_UUID_Uncommitted) String() string { func (*Info_Index_UUID_Uncommitted) ProtoMessage() {} func (x *Info_Index_UUID_Uncommitted) ProtoReflect() protoreflect.Message { - mi := &file_v1_payload_payload_proto_msgTypes[80] + mi := &file_v1_payload_payload_proto_msgTypes[81] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4801,6 +4840,113 @@ func (x *Info_Index_UUID_Uncommitted) GetUuid() string { return "" } +// Represent server information. +type Mirror_Target struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The target hostname. + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + // The target port. + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` +} + +func (x *Mirror_Target) Reset() { + *x = Mirror_Target{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_payload_payload_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Mirror_Target) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mirror_Target) ProtoMessage() {} + +func (x *Mirror_Target) ProtoReflect() protoreflect.Message { + mi := &file_v1_payload_payload_proto_msgTypes[84] + 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 Mirror_Target.ProtoReflect.Descriptor instead. +func (*Mirror_Target) Descriptor() ([]byte, []int) { + return file_v1_payload_payload_proto_rawDescGZIP(), []int{10, 0} +} + +func (x *Mirror_Target) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *Mirror_Target) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +// Represent the multiple Target message. +type Mirror_Targets struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The multiple target information. + Targets []*Mirror_Target `protobuf:"bytes,1,rep,name=targets,proto3" json:"targets,omitempty"` +} + +func (x *Mirror_Targets) Reset() { + *x = Mirror_Targets{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_payload_payload_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Mirror_Targets) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mirror_Targets) ProtoMessage() {} + +func (x *Mirror_Targets) ProtoReflect() protoreflect.Message { + mi := &file_v1_payload_payload_proto_msgTypes[85] + 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 Mirror_Targets.ProtoReflect.Descriptor instead. +func (*Mirror_Targets) Descriptor() ([]byte, []int) { + return file_v1_payload_payload_proto_rawDescGZIP(), []int{10, 1} +} + +func (x *Mirror_Targets) GetTargets() []*Mirror_Target { + if x != nil { + return x.Targets + } + return nil +} + var File_v1_payload_payload_proto protoreflect.FileDescriptor var file_v1_payload_payload_proto_rawDesc = []byte{ @@ -5267,14 +5413,22 @@ var file_v1_payload_payload_proto_rawDesc = []byte{ 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x08, 0xba, 0x48, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x1a, 0x15, 0x0a, 0x03, 0x49, 0x50, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x42, 0x64, 0x0a, 0x1d, 0x6f, 0x72, 0x67, 0x2e, 0x76, 0x64, 0x61, 0x61, 0x73, 0x2e, 0x76, - 0x61, 0x6c, 0x64, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x42, 0x0b, 0x56, 0x61, 0x6c, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x50, - 0x01, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x64, - 0x61, 0x61, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x67, 0x72, - 0x70, 0x63, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0xa2, 0x02, 0x07, - 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0x7a, 0x0a, 0x06, 0x4d, 0x69, 0x72, 0x72, + 0x6f, 0x72, 0x1a, 0x30, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, + 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x3e, 0x0a, 0x07, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, + 0x33, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, + 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x64, 0x0a, + 0x1d, 0x6f, 0x72, 0x67, 0x2e, 0x76, 0x64, 0x61, 0x61, 0x73, 0x2e, 0x76, 0x61, 0x6c, 0x64, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x0b, + 0x56, 0x61, 0x6c, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x01, 0x5a, 0x2a, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x64, 0x61, 0x61, 0x73, 0x2f, + 0x76, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x76, + 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0xa2, 0x02, 0x07, 0x50, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5290,7 +5444,7 @@ func file_v1_payload_payload_proto_rawDescGZIP() []byte { } var file_v1_payload_payload_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_v1_payload_payload_proto_msgTypes = make([]protoimpl.MessageInfo, 83) +var file_v1_payload_payload_proto_msgTypes = make([]protoimpl.MessageInfo, 86) var file_v1_payload_payload_proto_goTypes = []interface{}{ (Search_AggregationAlgorithm)(0), // 0: payload.v1.Search.AggregationAlgorithm (Remove_Timestamp_Operator)(0), // 1: payload.v1.Remove.Timestamp.Operator @@ -5304,160 +5458,164 @@ var file_v1_payload_payload_proto_goTypes = []interface{}{ (*Control)(nil), // 9: payload.v1.Control (*Discoverer)(nil), // 10: payload.v1.Discoverer (*Info)(nil), // 11: payload.v1.Info - (*Empty)(nil), // 12: payload.v1.Empty - (*Search_Request)(nil), // 13: payload.v1.Search.Request - (*Search_MultiRequest)(nil), // 14: payload.v1.Search.MultiRequest - (*Search_IDRequest)(nil), // 15: payload.v1.Search.IDRequest - (*Search_MultiIDRequest)(nil), // 16: payload.v1.Search.MultiIDRequest - (*Search_ObjectRequest)(nil), // 17: payload.v1.Search.ObjectRequest - (*Search_MultiObjectRequest)(nil), // 18: payload.v1.Search.MultiObjectRequest - (*Search_Config)(nil), // 19: payload.v1.Search.Config - (*Search_Response)(nil), // 20: payload.v1.Search.Response - (*Search_Responses)(nil), // 21: payload.v1.Search.Responses - (*Search_StreamResponse)(nil), // 22: payload.v1.Search.StreamResponse - (*Filter_Target)(nil), // 23: payload.v1.Filter.Target - (*Filter_Config)(nil), // 24: payload.v1.Filter.Config - (*Insert_Request)(nil), // 25: payload.v1.Insert.Request - (*Insert_MultiRequest)(nil), // 26: payload.v1.Insert.MultiRequest - (*Insert_ObjectRequest)(nil), // 27: payload.v1.Insert.ObjectRequest - (*Insert_MultiObjectRequest)(nil), // 28: payload.v1.Insert.MultiObjectRequest - (*Insert_Config)(nil), // 29: payload.v1.Insert.Config - (*Update_Request)(nil), // 30: payload.v1.Update.Request - (*Update_MultiRequest)(nil), // 31: payload.v1.Update.MultiRequest - (*Update_ObjectRequest)(nil), // 32: payload.v1.Update.ObjectRequest - (*Update_MultiObjectRequest)(nil), // 33: payload.v1.Update.MultiObjectRequest - (*Update_Config)(nil), // 34: payload.v1.Update.Config - (*Upsert_Request)(nil), // 35: payload.v1.Upsert.Request - (*Upsert_MultiRequest)(nil), // 36: payload.v1.Upsert.MultiRequest - (*Upsert_ObjectRequest)(nil), // 37: payload.v1.Upsert.ObjectRequest - (*Upsert_MultiObjectRequest)(nil), // 38: payload.v1.Upsert.MultiObjectRequest - (*Upsert_Config)(nil), // 39: payload.v1.Upsert.Config - (*Remove_Request)(nil), // 40: payload.v1.Remove.Request - (*Remove_MultiRequest)(nil), // 41: payload.v1.Remove.MultiRequest - (*Remove_TimestampRequest)(nil), // 42: payload.v1.Remove.TimestampRequest - (*Remove_Timestamp)(nil), // 43: payload.v1.Remove.Timestamp - (*Remove_Config)(nil), // 44: payload.v1.Remove.Config - (*Object_VectorRequest)(nil), // 45: payload.v1.Object.VectorRequest - (*Object_Distance)(nil), // 46: payload.v1.Object.Distance - (*Object_StreamDistance)(nil), // 47: payload.v1.Object.StreamDistance - (*Object_ID)(nil), // 48: payload.v1.Object.ID - (*Object_IDs)(nil), // 49: payload.v1.Object.IDs - (*Object_Vector)(nil), // 50: payload.v1.Object.Vector - (*Object_GetTimestampRequest)(nil), // 51: payload.v1.Object.GetTimestampRequest - (*Object_Timestamp)(nil), // 52: payload.v1.Object.Timestamp - (*Object_Vectors)(nil), // 53: payload.v1.Object.Vectors - (*Object_StreamVector)(nil), // 54: payload.v1.Object.StreamVector - (*Object_ReshapeVector)(nil), // 55: payload.v1.Object.ReshapeVector - (*Object_Blob)(nil), // 56: payload.v1.Object.Blob - (*Object_StreamBlob)(nil), // 57: payload.v1.Object.StreamBlob - (*Object_Location)(nil), // 58: payload.v1.Object.Location - (*Object_StreamLocation)(nil), // 59: payload.v1.Object.StreamLocation - (*Object_Locations)(nil), // 60: payload.v1.Object.Locations - (*Object_List)(nil), // 61: payload.v1.Object.List - (*Object_List_Request)(nil), // 62: payload.v1.Object.List.Request - (*Object_List_Response)(nil), // 63: payload.v1.Object.List.Response - (*Control_CreateIndexRequest)(nil), // 64: payload.v1.Control.CreateIndexRequest - (*Discoverer_Request)(nil), // 65: payload.v1.Discoverer.Request - (*Info_Index)(nil), // 66: payload.v1.Info.Index - (*Info_Pod)(nil), // 67: payload.v1.Info.Pod - (*Info_Node)(nil), // 68: payload.v1.Info.Node - (*Info_Service)(nil), // 69: payload.v1.Info.Service - (*Info_ServicePort)(nil), // 70: payload.v1.Info.ServicePort - (*Info_Labels)(nil), // 71: payload.v1.Info.Labels - (*Info_Annotations)(nil), // 72: payload.v1.Info.Annotations - (*Info_CPU)(nil), // 73: payload.v1.Info.CPU - (*Info_Memory)(nil), // 74: payload.v1.Info.Memory - (*Info_Pods)(nil), // 75: payload.v1.Info.Pods - (*Info_Nodes)(nil), // 76: payload.v1.Info.Nodes - (*Info_Services)(nil), // 77: payload.v1.Info.Services - (*Info_IPs)(nil), // 78: payload.v1.Info.IPs - (*Info_Index_Count)(nil), // 79: payload.v1.Info.Index.Count - (*Info_Index_UUID)(nil), // 80: payload.v1.Info.Index.UUID - (*Info_Index_UUID_Committed)(nil), // 81: payload.v1.Info.Index.UUID.Committed - (*Info_Index_UUID_Uncommitted)(nil), // 82: payload.v1.Info.Index.UUID.Uncommitted - nil, // 83: payload.v1.Info.Labels.LabelsEntry - nil, // 84: payload.v1.Info.Annotations.AnnotationsEntry - (*status.Status)(nil), // 85: google.rpc.Status + (*Mirror)(nil), // 12: payload.v1.Mirror + (*Empty)(nil), // 13: payload.v1.Empty + (*Search_Request)(nil), // 14: payload.v1.Search.Request + (*Search_MultiRequest)(nil), // 15: payload.v1.Search.MultiRequest + (*Search_IDRequest)(nil), // 16: payload.v1.Search.IDRequest + (*Search_MultiIDRequest)(nil), // 17: payload.v1.Search.MultiIDRequest + (*Search_ObjectRequest)(nil), // 18: payload.v1.Search.ObjectRequest + (*Search_MultiObjectRequest)(nil), // 19: payload.v1.Search.MultiObjectRequest + (*Search_Config)(nil), // 20: payload.v1.Search.Config + (*Search_Response)(nil), // 21: payload.v1.Search.Response + (*Search_Responses)(nil), // 22: payload.v1.Search.Responses + (*Search_StreamResponse)(nil), // 23: payload.v1.Search.StreamResponse + (*Filter_Target)(nil), // 24: payload.v1.Filter.Target + (*Filter_Config)(nil), // 25: payload.v1.Filter.Config + (*Insert_Request)(nil), // 26: payload.v1.Insert.Request + (*Insert_MultiRequest)(nil), // 27: payload.v1.Insert.MultiRequest + (*Insert_ObjectRequest)(nil), // 28: payload.v1.Insert.ObjectRequest + (*Insert_MultiObjectRequest)(nil), // 29: payload.v1.Insert.MultiObjectRequest + (*Insert_Config)(nil), // 30: payload.v1.Insert.Config + (*Update_Request)(nil), // 31: payload.v1.Update.Request + (*Update_MultiRequest)(nil), // 32: payload.v1.Update.MultiRequest + (*Update_ObjectRequest)(nil), // 33: payload.v1.Update.ObjectRequest + (*Update_MultiObjectRequest)(nil), // 34: payload.v1.Update.MultiObjectRequest + (*Update_Config)(nil), // 35: payload.v1.Update.Config + (*Upsert_Request)(nil), // 36: payload.v1.Upsert.Request + (*Upsert_MultiRequest)(nil), // 37: payload.v1.Upsert.MultiRequest + (*Upsert_ObjectRequest)(nil), // 38: payload.v1.Upsert.ObjectRequest + (*Upsert_MultiObjectRequest)(nil), // 39: payload.v1.Upsert.MultiObjectRequest + (*Upsert_Config)(nil), // 40: payload.v1.Upsert.Config + (*Remove_Request)(nil), // 41: payload.v1.Remove.Request + (*Remove_MultiRequest)(nil), // 42: payload.v1.Remove.MultiRequest + (*Remove_TimestampRequest)(nil), // 43: payload.v1.Remove.TimestampRequest + (*Remove_Timestamp)(nil), // 44: payload.v1.Remove.Timestamp + (*Remove_Config)(nil), // 45: payload.v1.Remove.Config + (*Object_VectorRequest)(nil), // 46: payload.v1.Object.VectorRequest + (*Object_Distance)(nil), // 47: payload.v1.Object.Distance + (*Object_StreamDistance)(nil), // 48: payload.v1.Object.StreamDistance + (*Object_ID)(nil), // 49: payload.v1.Object.ID + (*Object_IDs)(nil), // 50: payload.v1.Object.IDs + (*Object_Vector)(nil), // 51: payload.v1.Object.Vector + (*Object_GetTimestampRequest)(nil), // 52: payload.v1.Object.GetTimestampRequest + (*Object_Timestamp)(nil), // 53: payload.v1.Object.Timestamp + (*Object_Vectors)(nil), // 54: payload.v1.Object.Vectors + (*Object_StreamVector)(nil), // 55: payload.v1.Object.StreamVector + (*Object_ReshapeVector)(nil), // 56: payload.v1.Object.ReshapeVector + (*Object_Blob)(nil), // 57: payload.v1.Object.Blob + (*Object_StreamBlob)(nil), // 58: payload.v1.Object.StreamBlob + (*Object_Location)(nil), // 59: payload.v1.Object.Location + (*Object_StreamLocation)(nil), // 60: payload.v1.Object.StreamLocation + (*Object_Locations)(nil), // 61: payload.v1.Object.Locations + (*Object_List)(nil), // 62: payload.v1.Object.List + (*Object_List_Request)(nil), // 63: payload.v1.Object.List.Request + (*Object_List_Response)(nil), // 64: payload.v1.Object.List.Response + (*Control_CreateIndexRequest)(nil), // 65: payload.v1.Control.CreateIndexRequest + (*Discoverer_Request)(nil), // 66: payload.v1.Discoverer.Request + (*Info_Index)(nil), // 67: payload.v1.Info.Index + (*Info_Pod)(nil), // 68: payload.v1.Info.Pod + (*Info_Node)(nil), // 69: payload.v1.Info.Node + (*Info_Service)(nil), // 70: payload.v1.Info.Service + (*Info_ServicePort)(nil), // 71: payload.v1.Info.ServicePort + (*Info_Labels)(nil), // 72: payload.v1.Info.Labels + (*Info_Annotations)(nil), // 73: payload.v1.Info.Annotations + (*Info_CPU)(nil), // 74: payload.v1.Info.CPU + (*Info_Memory)(nil), // 75: payload.v1.Info.Memory + (*Info_Pods)(nil), // 76: payload.v1.Info.Pods + (*Info_Nodes)(nil), // 77: payload.v1.Info.Nodes + (*Info_Services)(nil), // 78: payload.v1.Info.Services + (*Info_IPs)(nil), // 79: payload.v1.Info.IPs + (*Info_Index_Count)(nil), // 80: payload.v1.Info.Index.Count + (*Info_Index_UUID)(nil), // 81: payload.v1.Info.Index.UUID + (*Info_Index_UUID_Committed)(nil), // 82: payload.v1.Info.Index.UUID.Committed + (*Info_Index_UUID_Uncommitted)(nil), // 83: payload.v1.Info.Index.UUID.Uncommitted + nil, // 84: payload.v1.Info.Labels.LabelsEntry + nil, // 85: payload.v1.Info.Annotations.AnnotationsEntry + (*Mirror_Target)(nil), // 86: payload.v1.Mirror.Target + (*Mirror_Targets)(nil), // 87: payload.v1.Mirror.Targets + (*status.Status)(nil), // 88: google.rpc.Status } var file_v1_payload_payload_proto_depIdxs = []int32{ - 19, // 0: payload.v1.Search.Request.config:type_name -> payload.v1.Search.Config - 13, // 1: payload.v1.Search.MultiRequest.requests:type_name -> payload.v1.Search.Request - 19, // 2: payload.v1.Search.IDRequest.config:type_name -> payload.v1.Search.Config - 15, // 3: payload.v1.Search.MultiIDRequest.requests:type_name -> payload.v1.Search.IDRequest - 19, // 4: payload.v1.Search.ObjectRequest.config:type_name -> payload.v1.Search.Config - 23, // 5: payload.v1.Search.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target - 17, // 6: payload.v1.Search.MultiObjectRequest.requests:type_name -> payload.v1.Search.ObjectRequest - 24, // 7: payload.v1.Search.Config.ingress_filters:type_name -> payload.v1.Filter.Config - 24, // 8: payload.v1.Search.Config.egress_filters:type_name -> payload.v1.Filter.Config + 20, // 0: payload.v1.Search.Request.config:type_name -> payload.v1.Search.Config + 14, // 1: payload.v1.Search.MultiRequest.requests:type_name -> payload.v1.Search.Request + 20, // 2: payload.v1.Search.IDRequest.config:type_name -> payload.v1.Search.Config + 16, // 3: payload.v1.Search.MultiIDRequest.requests:type_name -> payload.v1.Search.IDRequest + 20, // 4: payload.v1.Search.ObjectRequest.config:type_name -> payload.v1.Search.Config + 24, // 5: payload.v1.Search.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target + 18, // 6: payload.v1.Search.MultiObjectRequest.requests:type_name -> payload.v1.Search.ObjectRequest + 25, // 7: payload.v1.Search.Config.ingress_filters:type_name -> payload.v1.Filter.Config + 25, // 8: payload.v1.Search.Config.egress_filters:type_name -> payload.v1.Filter.Config 0, // 9: payload.v1.Search.Config.aggregation_algorithm:type_name -> payload.v1.Search.AggregationAlgorithm - 46, // 10: payload.v1.Search.Response.results:type_name -> payload.v1.Object.Distance - 20, // 11: payload.v1.Search.Responses.responses:type_name -> payload.v1.Search.Response - 20, // 12: payload.v1.Search.StreamResponse.response:type_name -> payload.v1.Search.Response - 85, // 13: payload.v1.Search.StreamResponse.status:type_name -> google.rpc.Status - 23, // 14: payload.v1.Filter.Config.targets:type_name -> payload.v1.Filter.Target - 50, // 15: payload.v1.Insert.Request.vector:type_name -> payload.v1.Object.Vector - 29, // 16: payload.v1.Insert.Request.config:type_name -> payload.v1.Insert.Config - 25, // 17: payload.v1.Insert.MultiRequest.requests:type_name -> payload.v1.Insert.Request - 56, // 18: payload.v1.Insert.ObjectRequest.object:type_name -> payload.v1.Object.Blob - 29, // 19: payload.v1.Insert.ObjectRequest.config:type_name -> payload.v1.Insert.Config - 23, // 20: payload.v1.Insert.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target - 27, // 21: payload.v1.Insert.MultiObjectRequest.requests:type_name -> payload.v1.Insert.ObjectRequest - 24, // 22: payload.v1.Insert.Config.filters:type_name -> payload.v1.Filter.Config - 50, // 23: payload.v1.Update.Request.vector:type_name -> payload.v1.Object.Vector - 34, // 24: payload.v1.Update.Request.config:type_name -> payload.v1.Update.Config - 30, // 25: payload.v1.Update.MultiRequest.requests:type_name -> payload.v1.Update.Request - 56, // 26: payload.v1.Update.ObjectRequest.object:type_name -> payload.v1.Object.Blob - 34, // 27: payload.v1.Update.ObjectRequest.config:type_name -> payload.v1.Update.Config - 23, // 28: payload.v1.Update.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target - 32, // 29: payload.v1.Update.MultiObjectRequest.requests:type_name -> payload.v1.Update.ObjectRequest - 24, // 30: payload.v1.Update.Config.filters:type_name -> payload.v1.Filter.Config - 50, // 31: payload.v1.Upsert.Request.vector:type_name -> payload.v1.Object.Vector - 39, // 32: payload.v1.Upsert.Request.config:type_name -> payload.v1.Upsert.Config - 35, // 33: payload.v1.Upsert.MultiRequest.requests:type_name -> payload.v1.Upsert.Request - 56, // 34: payload.v1.Upsert.ObjectRequest.object:type_name -> payload.v1.Object.Blob - 39, // 35: payload.v1.Upsert.ObjectRequest.config:type_name -> payload.v1.Upsert.Config - 23, // 36: payload.v1.Upsert.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target - 37, // 37: payload.v1.Upsert.MultiObjectRequest.requests:type_name -> payload.v1.Upsert.ObjectRequest - 24, // 38: payload.v1.Upsert.Config.filters:type_name -> payload.v1.Filter.Config - 48, // 39: payload.v1.Remove.Request.id:type_name -> payload.v1.Object.ID - 44, // 40: payload.v1.Remove.Request.config:type_name -> payload.v1.Remove.Config - 40, // 41: payload.v1.Remove.MultiRequest.requests:type_name -> payload.v1.Remove.Request - 43, // 42: payload.v1.Remove.TimestampRequest.timestamps:type_name -> payload.v1.Remove.Timestamp + 47, // 10: payload.v1.Search.Response.results:type_name -> payload.v1.Object.Distance + 21, // 11: payload.v1.Search.Responses.responses:type_name -> payload.v1.Search.Response + 21, // 12: payload.v1.Search.StreamResponse.response:type_name -> payload.v1.Search.Response + 88, // 13: payload.v1.Search.StreamResponse.status:type_name -> google.rpc.Status + 24, // 14: payload.v1.Filter.Config.targets:type_name -> payload.v1.Filter.Target + 51, // 15: payload.v1.Insert.Request.vector:type_name -> payload.v1.Object.Vector + 30, // 16: payload.v1.Insert.Request.config:type_name -> payload.v1.Insert.Config + 26, // 17: payload.v1.Insert.MultiRequest.requests:type_name -> payload.v1.Insert.Request + 57, // 18: payload.v1.Insert.ObjectRequest.object:type_name -> payload.v1.Object.Blob + 30, // 19: payload.v1.Insert.ObjectRequest.config:type_name -> payload.v1.Insert.Config + 24, // 20: payload.v1.Insert.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target + 28, // 21: payload.v1.Insert.MultiObjectRequest.requests:type_name -> payload.v1.Insert.ObjectRequest + 25, // 22: payload.v1.Insert.Config.filters:type_name -> payload.v1.Filter.Config + 51, // 23: payload.v1.Update.Request.vector:type_name -> payload.v1.Object.Vector + 35, // 24: payload.v1.Update.Request.config:type_name -> payload.v1.Update.Config + 31, // 25: payload.v1.Update.MultiRequest.requests:type_name -> payload.v1.Update.Request + 57, // 26: payload.v1.Update.ObjectRequest.object:type_name -> payload.v1.Object.Blob + 35, // 27: payload.v1.Update.ObjectRequest.config:type_name -> payload.v1.Update.Config + 24, // 28: payload.v1.Update.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target + 33, // 29: payload.v1.Update.MultiObjectRequest.requests:type_name -> payload.v1.Update.ObjectRequest + 25, // 30: payload.v1.Update.Config.filters:type_name -> payload.v1.Filter.Config + 51, // 31: payload.v1.Upsert.Request.vector:type_name -> payload.v1.Object.Vector + 40, // 32: payload.v1.Upsert.Request.config:type_name -> payload.v1.Upsert.Config + 36, // 33: payload.v1.Upsert.MultiRequest.requests:type_name -> payload.v1.Upsert.Request + 57, // 34: payload.v1.Upsert.ObjectRequest.object:type_name -> payload.v1.Object.Blob + 40, // 35: payload.v1.Upsert.ObjectRequest.config:type_name -> payload.v1.Upsert.Config + 24, // 36: payload.v1.Upsert.ObjectRequest.vectorizer:type_name -> payload.v1.Filter.Target + 38, // 37: payload.v1.Upsert.MultiObjectRequest.requests:type_name -> payload.v1.Upsert.ObjectRequest + 25, // 38: payload.v1.Upsert.Config.filters:type_name -> payload.v1.Filter.Config + 49, // 39: payload.v1.Remove.Request.id:type_name -> payload.v1.Object.ID + 45, // 40: payload.v1.Remove.Request.config:type_name -> payload.v1.Remove.Config + 41, // 41: payload.v1.Remove.MultiRequest.requests:type_name -> payload.v1.Remove.Request + 44, // 42: payload.v1.Remove.TimestampRequest.timestamps:type_name -> payload.v1.Remove.Timestamp 1, // 43: payload.v1.Remove.Timestamp.operator:type_name -> payload.v1.Remove.Timestamp.Operator - 48, // 44: payload.v1.Object.VectorRequest.id:type_name -> payload.v1.Object.ID - 24, // 45: payload.v1.Object.VectorRequest.filters:type_name -> payload.v1.Filter.Config - 46, // 46: payload.v1.Object.StreamDistance.distance:type_name -> payload.v1.Object.Distance - 85, // 47: payload.v1.Object.StreamDistance.status:type_name -> google.rpc.Status - 48, // 48: payload.v1.Object.GetTimestampRequest.id:type_name -> payload.v1.Object.ID - 50, // 49: payload.v1.Object.Vectors.vectors:type_name -> payload.v1.Object.Vector - 50, // 50: payload.v1.Object.StreamVector.vector:type_name -> payload.v1.Object.Vector - 85, // 51: payload.v1.Object.StreamVector.status:type_name -> google.rpc.Status - 56, // 52: payload.v1.Object.StreamBlob.blob:type_name -> payload.v1.Object.Blob - 85, // 53: payload.v1.Object.StreamBlob.status:type_name -> google.rpc.Status - 58, // 54: payload.v1.Object.StreamLocation.location:type_name -> payload.v1.Object.Location - 85, // 55: payload.v1.Object.StreamLocation.status:type_name -> google.rpc.Status - 58, // 56: payload.v1.Object.Locations.locations:type_name -> payload.v1.Object.Location - 50, // 57: payload.v1.Object.List.Response.vector:type_name -> payload.v1.Object.Vector - 85, // 58: payload.v1.Object.List.Response.status:type_name -> google.rpc.Status - 73, // 59: payload.v1.Info.Pod.cpu:type_name -> payload.v1.Info.CPU - 74, // 60: payload.v1.Info.Pod.memory:type_name -> payload.v1.Info.Memory - 68, // 61: payload.v1.Info.Pod.node:type_name -> payload.v1.Info.Node - 73, // 62: payload.v1.Info.Node.cpu:type_name -> payload.v1.Info.CPU - 74, // 63: payload.v1.Info.Node.memory:type_name -> payload.v1.Info.Memory - 75, // 64: payload.v1.Info.Node.Pods:type_name -> payload.v1.Info.Pods - 70, // 65: payload.v1.Info.Service.ports:type_name -> payload.v1.Info.ServicePort - 71, // 66: payload.v1.Info.Service.labels:type_name -> payload.v1.Info.Labels - 72, // 67: payload.v1.Info.Service.annotations:type_name -> payload.v1.Info.Annotations - 83, // 68: payload.v1.Info.Labels.labels:type_name -> payload.v1.Info.Labels.LabelsEntry - 84, // 69: payload.v1.Info.Annotations.annotations:type_name -> payload.v1.Info.Annotations.AnnotationsEntry - 67, // 70: payload.v1.Info.Pods.pods:type_name -> payload.v1.Info.Pod - 68, // 71: payload.v1.Info.Nodes.nodes:type_name -> payload.v1.Info.Node - 69, // 72: payload.v1.Info.Services.services:type_name -> payload.v1.Info.Service - 73, // [73:73] is the sub-list for method output_type - 73, // [73:73] is the sub-list for method input_type - 73, // [73:73] is the sub-list for extension type_name - 73, // [73:73] is the sub-list for extension extendee - 0, // [0:73] is the sub-list for field type_name + 49, // 44: payload.v1.Object.VectorRequest.id:type_name -> payload.v1.Object.ID + 25, // 45: payload.v1.Object.VectorRequest.filters:type_name -> payload.v1.Filter.Config + 47, // 46: payload.v1.Object.StreamDistance.distance:type_name -> payload.v1.Object.Distance + 88, // 47: payload.v1.Object.StreamDistance.status:type_name -> google.rpc.Status + 49, // 48: payload.v1.Object.GetTimestampRequest.id:type_name -> payload.v1.Object.ID + 51, // 49: payload.v1.Object.Vectors.vectors:type_name -> payload.v1.Object.Vector + 51, // 50: payload.v1.Object.StreamVector.vector:type_name -> payload.v1.Object.Vector + 88, // 51: payload.v1.Object.StreamVector.status:type_name -> google.rpc.Status + 57, // 52: payload.v1.Object.StreamBlob.blob:type_name -> payload.v1.Object.Blob + 88, // 53: payload.v1.Object.StreamBlob.status:type_name -> google.rpc.Status + 59, // 54: payload.v1.Object.StreamLocation.location:type_name -> payload.v1.Object.Location + 88, // 55: payload.v1.Object.StreamLocation.status:type_name -> google.rpc.Status + 59, // 56: payload.v1.Object.Locations.locations:type_name -> payload.v1.Object.Location + 51, // 57: payload.v1.Object.List.Response.vector:type_name -> payload.v1.Object.Vector + 88, // 58: payload.v1.Object.List.Response.status:type_name -> google.rpc.Status + 74, // 59: payload.v1.Info.Pod.cpu:type_name -> payload.v1.Info.CPU + 75, // 60: payload.v1.Info.Pod.memory:type_name -> payload.v1.Info.Memory + 69, // 61: payload.v1.Info.Pod.node:type_name -> payload.v1.Info.Node + 74, // 62: payload.v1.Info.Node.cpu:type_name -> payload.v1.Info.CPU + 75, // 63: payload.v1.Info.Node.memory:type_name -> payload.v1.Info.Memory + 76, // 64: payload.v1.Info.Node.Pods:type_name -> payload.v1.Info.Pods + 71, // 65: payload.v1.Info.Service.ports:type_name -> payload.v1.Info.ServicePort + 72, // 66: payload.v1.Info.Service.labels:type_name -> payload.v1.Info.Labels + 73, // 67: payload.v1.Info.Service.annotations:type_name -> payload.v1.Info.Annotations + 84, // 68: payload.v1.Info.Labels.labels:type_name -> payload.v1.Info.Labels.LabelsEntry + 85, // 69: payload.v1.Info.Annotations.annotations:type_name -> payload.v1.Info.Annotations.AnnotationsEntry + 68, // 70: payload.v1.Info.Pods.pods:type_name -> payload.v1.Info.Pod + 69, // 71: payload.v1.Info.Nodes.nodes:type_name -> payload.v1.Info.Node + 70, // 72: payload.v1.Info.Services.services:type_name -> payload.v1.Info.Service + 86, // 73: payload.v1.Mirror.Targets.targets:type_name -> payload.v1.Mirror.Target + 74, // [74:74] is the sub-list for method output_type + 74, // [74:74] is the sub-list for method input_type + 74, // [74:74] is the sub-list for extension type_name + 74, // [74:74] is the sub-list for extension extendee + 0, // [0:74] is the sub-list for field type_name } func init() { file_v1_payload_payload_proto_init() } @@ -5587,7 +5745,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { + switch v := v.(*Mirror); i { case 0: return &v.state case 1: @@ -5599,7 +5757,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_Request); i { + switch v := v.(*Empty); i { case 0: return &v.state case 1: @@ -5611,7 +5769,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_MultiRequest); i { + switch v := v.(*Search_Request); i { case 0: return &v.state case 1: @@ -5623,7 +5781,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_IDRequest); i { + switch v := v.(*Search_MultiRequest); i { case 0: return &v.state case 1: @@ -5635,7 +5793,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_MultiIDRequest); i { + switch v := v.(*Search_IDRequest); i { case 0: return &v.state case 1: @@ -5647,7 +5805,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_ObjectRequest); i { + switch v := v.(*Search_MultiIDRequest); i { case 0: return &v.state case 1: @@ -5659,7 +5817,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_MultiObjectRequest); i { + switch v := v.(*Search_ObjectRequest); i { case 0: return &v.state case 1: @@ -5671,7 +5829,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_Config); i { + switch v := v.(*Search_MultiObjectRequest); i { case 0: return &v.state case 1: @@ -5683,7 +5841,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_Response); i { + switch v := v.(*Search_Config); i { case 0: return &v.state case 1: @@ -5695,7 +5853,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_Responses); i { + switch v := v.(*Search_Response); i { case 0: return &v.state case 1: @@ -5707,7 +5865,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Search_StreamResponse); i { + switch v := v.(*Search_Responses); i { case 0: return &v.state case 1: @@ -5719,7 +5877,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Filter_Target); i { + switch v := v.(*Search_StreamResponse); i { case 0: return &v.state case 1: @@ -5731,7 +5889,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Filter_Config); i { + switch v := v.(*Filter_Target); i { case 0: return &v.state case 1: @@ -5743,7 +5901,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Insert_Request); i { + switch v := v.(*Filter_Config); i { case 0: return &v.state case 1: @@ -5755,7 +5913,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Insert_MultiRequest); i { + switch v := v.(*Insert_Request); i { case 0: return &v.state case 1: @@ -5767,7 +5925,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Insert_ObjectRequest); i { + switch v := v.(*Insert_MultiRequest); i { case 0: return &v.state case 1: @@ -5779,7 +5937,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Insert_MultiObjectRequest); i { + switch v := v.(*Insert_ObjectRequest); i { case 0: return &v.state case 1: @@ -5791,7 +5949,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Insert_Config); i { + switch v := v.(*Insert_MultiObjectRequest); i { case 0: return &v.state case 1: @@ -5803,7 +5961,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Update_Request); i { + switch v := v.(*Insert_Config); i { case 0: return &v.state case 1: @@ -5815,7 +5973,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Update_MultiRequest); i { + switch v := v.(*Update_Request); i { case 0: return &v.state case 1: @@ -5827,7 +5985,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Update_ObjectRequest); i { + switch v := v.(*Update_MultiRequest); i { case 0: return &v.state case 1: @@ -5839,7 +5997,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Update_MultiObjectRequest); i { + switch v := v.(*Update_ObjectRequest); i { case 0: return &v.state case 1: @@ -5851,7 +6009,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Update_Config); i { + switch v := v.(*Update_MultiObjectRequest); i { case 0: return &v.state case 1: @@ -5863,7 +6021,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Upsert_Request); i { + switch v := v.(*Update_Config); i { case 0: return &v.state case 1: @@ -5875,7 +6033,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Upsert_MultiRequest); i { + switch v := v.(*Upsert_Request); i { case 0: return &v.state case 1: @@ -5887,7 +6045,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Upsert_ObjectRequest); i { + switch v := v.(*Upsert_MultiRequest); i { case 0: return &v.state case 1: @@ -5899,7 +6057,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Upsert_MultiObjectRequest); i { + switch v := v.(*Upsert_ObjectRequest); i { case 0: return &v.state case 1: @@ -5911,7 +6069,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Upsert_Config); i { + switch v := v.(*Upsert_MultiObjectRequest); i { case 0: return &v.state case 1: @@ -5923,7 +6081,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Remove_Request); i { + switch v := v.(*Upsert_Config); i { case 0: return &v.state case 1: @@ -5935,7 +6093,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Remove_MultiRequest); i { + switch v := v.(*Remove_Request); i { case 0: return &v.state case 1: @@ -5947,7 +6105,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Remove_TimestampRequest); i { + switch v := v.(*Remove_MultiRequest); i { case 0: return &v.state case 1: @@ -5959,7 +6117,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Remove_Timestamp); i { + switch v := v.(*Remove_TimestampRequest); i { case 0: return &v.state case 1: @@ -5971,7 +6129,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Remove_Config); i { + switch v := v.(*Remove_Timestamp); i { case 0: return &v.state case 1: @@ -5983,7 +6141,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_VectorRequest); i { + switch v := v.(*Remove_Config); i { case 0: return &v.state case 1: @@ -5995,7 +6153,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Distance); i { + switch v := v.(*Object_VectorRequest); i { case 0: return &v.state case 1: @@ -6007,7 +6165,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_StreamDistance); i { + switch v := v.(*Object_Distance); i { case 0: return &v.state case 1: @@ -6019,7 +6177,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_ID); i { + switch v := v.(*Object_StreamDistance); i { case 0: return &v.state case 1: @@ -6031,7 +6189,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_IDs); i { + switch v := v.(*Object_ID); i { case 0: return &v.state case 1: @@ -6043,7 +6201,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Vector); i { + switch v := v.(*Object_IDs); i { case 0: return &v.state case 1: @@ -6055,7 +6213,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_GetTimestampRequest); i { + switch v := v.(*Object_Vector); i { case 0: return &v.state case 1: @@ -6067,7 +6225,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Timestamp); i { + switch v := v.(*Object_GetTimestampRequest); i { case 0: return &v.state case 1: @@ -6079,7 +6237,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Vectors); i { + switch v := v.(*Object_Timestamp); i { case 0: return &v.state case 1: @@ -6091,7 +6249,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_StreamVector); i { + switch v := v.(*Object_Vectors); i { case 0: return &v.state case 1: @@ -6103,7 +6261,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_ReshapeVector); i { + switch v := v.(*Object_StreamVector); i { case 0: return &v.state case 1: @@ -6115,7 +6273,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Blob); i { + switch v := v.(*Object_ReshapeVector); i { case 0: return &v.state case 1: @@ -6127,7 +6285,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_StreamBlob); i { + switch v := v.(*Object_Blob); i { case 0: return &v.state case 1: @@ -6139,7 +6297,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Location); i { + switch v := v.(*Object_StreamBlob); i { case 0: return &v.state case 1: @@ -6151,7 +6309,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_StreamLocation); i { + switch v := v.(*Object_Location); i { case 0: return &v.state case 1: @@ -6163,7 +6321,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_Locations); i { + switch v := v.(*Object_StreamLocation); i { case 0: return &v.state case 1: @@ -6175,7 +6333,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_List); i { + switch v := v.(*Object_Locations); i { case 0: return &v.state case 1: @@ -6187,7 +6345,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_List_Request); i { + switch v := v.(*Object_List); i { case 0: return &v.state case 1: @@ -6199,7 +6357,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object_List_Response); i { + switch v := v.(*Object_List_Request); i { case 0: return &v.state case 1: @@ -6211,7 +6369,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Control_CreateIndexRequest); i { + switch v := v.(*Object_List_Response); i { case 0: return &v.state case 1: @@ -6223,7 +6381,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Discoverer_Request); i { + switch v := v.(*Control_CreateIndexRequest); i { case 0: return &v.state case 1: @@ -6235,7 +6393,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Index); i { + switch v := v.(*Discoverer_Request); i { case 0: return &v.state case 1: @@ -6247,7 +6405,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Pod); i { + switch v := v.(*Info_Index); i { case 0: return &v.state case 1: @@ -6259,7 +6417,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Node); i { + switch v := v.(*Info_Pod); i { case 0: return &v.state case 1: @@ -6271,7 +6429,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Service); i { + switch v := v.(*Info_Node); i { case 0: return &v.state case 1: @@ -6283,7 +6441,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_ServicePort); i { + switch v := v.(*Info_Service); i { case 0: return &v.state case 1: @@ -6295,7 +6453,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Labels); i { + switch v := v.(*Info_ServicePort); i { case 0: return &v.state case 1: @@ -6307,7 +6465,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Annotations); i { + switch v := v.(*Info_Labels); i { case 0: return &v.state case 1: @@ -6319,7 +6477,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_CPU); i { + switch v := v.(*Info_Annotations); i { case 0: return &v.state case 1: @@ -6331,7 +6489,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Memory); i { + switch v := v.(*Info_CPU); i { case 0: return &v.state case 1: @@ -6343,7 +6501,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Pods); i { + switch v := v.(*Info_Memory); i { case 0: return &v.state case 1: @@ -6355,7 +6513,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Nodes); i { + switch v := v.(*Info_Pods); i { case 0: return &v.state case 1: @@ -6367,7 +6525,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Services); i { + switch v := v.(*Info_Nodes); i { case 0: return &v.state case 1: @@ -6379,7 +6537,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_IPs); i { + switch v := v.(*Info_Services); i { case 0: return &v.state case 1: @@ -6391,7 +6549,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Index_Count); i { + switch v := v.(*Info_IPs); i { case 0: return &v.state case 1: @@ -6403,7 +6561,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Index_UUID); i { + switch v := v.(*Info_Index_Count); i { case 0: return &v.state case 1: @@ -6415,7 +6573,7 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info_Index_UUID_Committed); i { + switch v := v.(*Info_Index_UUID); i { case 0: return &v.state case 1: @@ -6427,6 +6585,18 @@ func file_v1_payload_payload_proto_init() { } } file_v1_payload_payload_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Info_Index_UUID_Committed); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_payload_payload_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Info_Index_UUID_Uncommitted); i { case 0: return &v.state @@ -6438,28 +6608,52 @@ func file_v1_payload_payload_proto_init() { return nil } } + file_v1_payload_payload_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Mirror_Target); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_payload_payload_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Mirror_Targets); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } - file_v1_payload_payload_proto_msgTypes[20].OneofWrappers = []interface{}{ + file_v1_payload_payload_proto_msgTypes[21].OneofWrappers = []interface{}{ (*Search_StreamResponse_Response)(nil), (*Search_StreamResponse_Status)(nil), } - file_v1_payload_payload_proto_msgTypes[45].OneofWrappers = []interface{}{ + file_v1_payload_payload_proto_msgTypes[46].OneofWrappers = []interface{}{ (*Object_StreamDistance_Distance)(nil), (*Object_StreamDistance_Status)(nil), } - file_v1_payload_payload_proto_msgTypes[52].OneofWrappers = []interface{}{ + file_v1_payload_payload_proto_msgTypes[53].OneofWrappers = []interface{}{ (*Object_StreamVector_Vector)(nil), (*Object_StreamVector_Status)(nil), } - file_v1_payload_payload_proto_msgTypes[55].OneofWrappers = []interface{}{ + file_v1_payload_payload_proto_msgTypes[56].OneofWrappers = []interface{}{ (*Object_StreamBlob_Blob)(nil), (*Object_StreamBlob_Status)(nil), } - file_v1_payload_payload_proto_msgTypes[57].OneofWrappers = []interface{}{ + file_v1_payload_payload_proto_msgTypes[58].OneofWrappers = []interface{}{ (*Object_StreamLocation_Location)(nil), (*Object_StreamLocation_Status)(nil), } - file_v1_payload_payload_proto_msgTypes[61].OneofWrappers = []interface{}{ + file_v1_payload_payload_proto_msgTypes[62].OneofWrappers = []interface{}{ (*Object_List_Response_Vector)(nil), (*Object_List_Response_Status)(nil), } @@ -6469,7 +6663,7 @@ func file_v1_payload_payload_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_payload_payload_proto_rawDesc, NumEnums: 2, - NumMessages: 83, + NumMessages: 86, NumExtensions: 0, NumServices: 0, }, diff --git a/apis/grpc/v1/payload/payload_vtproto.pb.go b/apis/grpc/v1/payload/payload_vtproto.pb.go index 861c080ffa..97c00f6a48 100644 --- a/apis/grpc/v1/payload/payload_vtproto.pb.go +++ b/apis/grpc/v1/payload/payload_vtproto.pb.go @@ -1833,6 +1833,64 @@ func (m *Info) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *Mirror_Target) CloneVT() *Mirror_Target { + if m == nil { + return (*Mirror_Target)(nil) + } + r := &Mirror_Target{ + Host: m.Host, + Port: m.Port, + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Mirror_Target) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *Mirror_Targets) CloneVT() *Mirror_Targets { + if m == nil { + return (*Mirror_Targets)(nil) + } + r := &Mirror_Targets{} + if rhs := m.Targets; rhs != nil { + tmpContainer := make([]*Mirror_Target, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.Targets = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Mirror_Targets) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *Mirror) CloneVT() *Mirror { + if m == nil { + return (*Mirror)(nil) + } + r := &Mirror{} + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Mirror) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *Empty) CloneVT() *Empty { if m == nil { return (*Empty)(nil) @@ -4252,6 +4310,77 @@ func (this *Info) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *Mirror_Target) EqualVT(that *Mirror_Target) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Host != that.Host { + return false + } + if this.Port != that.Port { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Mirror_Target) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*Mirror_Target) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *Mirror_Targets) EqualVT(that *Mirror_Targets) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if len(this.Targets) != len(that.Targets) { + return false + } + for i, vx := range this.Targets { + vy := that.Targets[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &Mirror_Target{} + } + if q == nil { + q = &Mirror_Target{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Mirror_Targets) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*Mirror_Targets) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *Mirror) EqualVT(that *Mirror) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Mirror) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*Mirror) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *Empty) EqualVT(that *Empty) bool { if this == that { return true @@ -8409,6 +8538,129 @@ func (m *Info) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Mirror_Target) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Mirror_Target) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Mirror_Target) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Port != 0 { + i = encodeVarint(dAtA, i, uint64(m.Port)) + i-- + dAtA[i] = 0x10 + } + if len(m.Host) > 0 { + i -= len(m.Host) + copy(dAtA[i:], m.Host) + i = encodeVarint(dAtA, i, uint64(len(m.Host))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Mirror_Targets) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Mirror_Targets) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Mirror_Targets) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Targets) > 0 { + for iNdEx := len(m.Targets) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Targets[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Mirror) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Mirror) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Mirror) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + return len(dAtA) - i, nil +} + func (m *Empty) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -9990,6 +10242,49 @@ func (m *Info) SizeVT() (n int) { return n } +func (m *Mirror_Target) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Host) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + if m.Port != 0 { + n += 1 + sov(uint64(m.Port)) + } + n += len(m.unknownFields) + return n +} + +func (m *Mirror_Targets) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Targets) > 0 { + for _, e := range m.Targets { + l = e.SizeVT() + n += 1 + l + sov(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *Mirror) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += len(m.unknownFields) + return n +} + func (m *Empty) SizeVT() (n int) { if m == nil { return 0 @@ -18768,6 +19063,244 @@ func (m *Info) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *Mirror_Target) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Mirror_Target: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Mirror_Target: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Host", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Host = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Port |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Mirror_Targets) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Mirror_Targets: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Mirror_Targets: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Targets", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Targets = append(m.Targets, &Mirror_Target{}) + if err := m.Targets[len(m.Targets)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Mirror) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Mirror: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Mirror: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Empty) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/apis/grpc/v1/rpc/errdetails/error_details.pb.go b/apis/grpc/v1/rpc/errdetails/error_details.pb.go index 2d91f3a121..c1b22e2cbc 100644 --- a/apis/grpc/v1/rpc/errdetails/error_details.pb.go +++ b/apis/grpc/v1/rpc/errdetails/error_details.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/rpc/errdetails/error_details.proto diff --git a/apis/grpc/v1/vald/filter.pb.go b/apis/grpc/v1/vald/filter.pb.go index 21e3f2a553..51baa71dbd 100644 --- a/apis/grpc/v1/vald/filter.pb.go +++ b/apis/grpc/v1/vald/filter.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/filter.proto diff --git a/apis/grpc/v1/vald/insert.pb.go b/apis/grpc/v1/vald/insert.pb.go index c650fcb0bb..1523003a54 100644 --- a/apis/grpc/v1/vald/insert.pb.go +++ b/apis/grpc/v1/vald/insert.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/insert.proto diff --git a/apis/grpc/v1/vald/object.pb.go b/apis/grpc/v1/vald/object.pb.go index 3479429db1..16ed2f94cf 100644 --- a/apis/grpc/v1/vald/object.pb.go +++ b/apis/grpc/v1/vald/object.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/object.proto diff --git a/apis/grpc/v1/vald/remove.pb.go b/apis/grpc/v1/vald/remove.pb.go index d09508c71f..21a057dc8a 100644 --- a/apis/grpc/v1/vald/remove.pb.go +++ b/apis/grpc/v1/vald/remove.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/remove.proto diff --git a/apis/grpc/v1/vald/search.pb.go b/apis/grpc/v1/vald/search.pb.go index a26a97b8bb..9ffb9435ce 100644 --- a/apis/grpc/v1/vald/search.pb.go +++ b/apis/grpc/v1/vald/search.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/search.proto diff --git a/apis/grpc/v1/vald/update.pb.go b/apis/grpc/v1/vald/update.pb.go index a2346cff03..673a078d24 100644 --- a/apis/grpc/v1/vald/update.pb.go +++ b/apis/grpc/v1/vald/update.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/update.proto diff --git a/apis/grpc/v1/vald/upsert.pb.go b/apis/grpc/v1/vald/upsert.pb.go index 7eaf492f15..8ad5eb4345 100644 --- a/apis/grpc/v1/vald/upsert.pb.go +++ b/apis/grpc/v1/vald/upsert.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc (unknown) // source: v1/vald/upsert.proto diff --git a/apis/grpc/v1/vald/vald.go b/apis/grpc/v1/vald/vald.go index e3d2a8c46d..14477c37b6 100644 --- a/apis/grpc/v1/vald/vald.go +++ b/apis/grpc/v1/vald/vald.go @@ -18,6 +18,7 @@ package vald import ( + "github.com/vdaas/vald/apis/grpc/v1/mirror" grpc "google.golang.org/grpc" ) @@ -35,6 +36,11 @@ type ServerWithFilter interface { FilterServer } +type ServerWithMirror interface { + Server + mirror.MirrorServer +} + type UnimplementedValdServer struct { UnimplementedInsertServer UnimplementedUpdateServer @@ -49,6 +55,11 @@ type UnimplementedValdServerWithFilter struct { UnimplementedFilterServer } +type UnimplementedValdServerWithMirror struct { + UnimplementedValdServer + mirror.UnimplementedMirrorServer +} + type Client interface { InsertClient UpdateClient @@ -63,6 +74,11 @@ type ClientWithFilter interface { FilterClient } +type ClientWithMirror interface { + Client + mirror.MirrorClient +} + const PackageName = "vald.v1" const ( @@ -73,6 +89,7 @@ const ( RemoveRPCServiceName = "Remove" ObjectRPCServiceName = "Object" FilterRPCServiceName = "Filter" + MirrorRPCServiceName = "Mirror" ) const ( @@ -126,6 +143,8 @@ const ( GetTimestampRPCName = "GetTimestamp" StreamGetObjectRPCName = "StreamGetObject" StreamListObjectRPCName = "StreamListObject" + + RegisterRPCName = "Register" ) type client struct { @@ -137,6 +156,11 @@ type client struct { ObjectClient } +type clientWithMirror struct { + Client + mirror.MirrorClient +} + func RegisterValdServer(s *grpc.Server, srv Server) { RegisterInsertServer(s, srv) RegisterUpdateServer(s, srv) @@ -151,6 +175,11 @@ func RegisterValdServerWithFilter(s *grpc.Server, srv ServerWithFilter) { RegisterFilterServer(s, srv) } +func RegisterValdServerWithMirror(s *grpc.Server, srv ServerWithMirror) { + RegisterValdServer(s, srv) + mirror.RegisterMirrorServer(s, srv) +} + func NewValdClient(conn *grpc.ClientConn) Client { return &client{ NewInsertClient(conn), @@ -161,3 +190,10 @@ func NewValdClient(conn *grpc.ClientConn) Client { NewObjectClient(conn), } } + +func NewValdClientWithMirror(conn *grpc.ClientConn) ClientWithMirror { + return &clientWithMirror{ + Client: NewValdClient(conn), + MirrorClient: mirror.NewMirrorClient(conn), + } +} diff --git a/apis/proto/v1/mirror/mirror.proto b/apis/proto/v1/mirror/mirror.proto new file mode 100644 index 0000000000..5275ceb2eb --- /dev/null +++ b/apis/proto/v1/mirror/mirror.proto @@ -0,0 +1,37 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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. +// +syntax = "proto3"; + +package mirror.v1; + +import "google/api/annotations.proto"; +import "v1/payload/payload.proto"; + +option go_package = "github.com/vdaas/vald/apis/grpc/v1/mirror"; +option java_multiple_files = true; +option java_outer_classname = "ValdMirror"; +option java_package = "org.vdaas.vald.api.v1.mirror"; + +// Represent the mirror service. +service Mirror { + // Register is the RPC to register other mirror servers. + rpc Register(payload.v1.Mirror.Targets) returns (payload.v1.Mirror.Targets) { + option (google.api.http) = { + post: "/mirror/register" + body: "*" + }; + } +} diff --git a/apis/proto/v1/payload/payload.proto b/apis/proto/v1/payload/payload.proto index 38bec33d1e..1a21234d6f 100644 --- a/apis/proto/v1/payload/payload.proto +++ b/apis/proto/v1/payload/payload.proto @@ -641,5 +641,22 @@ message Info { } } +// Mirror related messages. +message Mirror { + // Represent server information. + message Target { + // The target hostname. + string host = 1; + // The target port. + uint32 port = 2; + } + + // Represent the multiple Target message. + message Targets { + // The multiple target information. + repeated Target targets = 1; + } +} + // Represent an empty message. message Empty {} diff --git a/apis/swagger/v1/discoverer/discoverer.swagger.json b/apis/swagger/v1/discoverer/discoverer.swagger.json index b6df1227c3..915d82ab71 100644 --- a/apis/swagger/v1/discoverer/discoverer.swagger.json +++ b/apis/swagger/v1/discoverer/discoverer.swagger.json @@ -20,7 +20,7 @@ "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/InfoNodes" + "$ref": "#/definitions/v1InfoNodes" } }, "default": { @@ -52,7 +52,7 @@ "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/InfoPods" + "$ref": "#/definitions/v1InfoPods" } }, "default": { @@ -84,7 +84,7 @@ "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/InfoServices" + "$ref": "#/definitions/v1InfoServices" } }, "default": { @@ -200,26 +200,12 @@ "description": "The memory information of the node." }, "Pods": { - "$ref": "#/definitions/InfoPods", + "$ref": "#/definitions/v1InfoPods", "description": "The pod information of the node." } }, "description": "Represent the node information message." }, - "InfoNodes": { - "type": "object", - "properties": { - "nodes": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/InfoNode" - }, - "description": "The multiple node information." - } - }, - "description": "Represent the multiple node information message." - }, "InfoPod": { "type": "object", "properties": { @@ -254,20 +240,6 @@ }, "description": "Represent the pod information message." }, - "InfoPods": { - "type": "object", - "properties": { - "pods": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/InfoPod" - }, - "description": "The multiple pod information." - } - }, - "description": "Represent the multiple pod information message." - }, "InfoService": { "type": "object", "properties": { @@ -320,20 +292,6 @@ }, "description": "Represets the service port information message." }, - "InfoServices": { - "type": "object", - "properties": { - "services": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/InfoService" - }, - "description": "The multiple service information." - } - }, - "description": "Represent the multiple service information message." - }, "protobufAny": { "type": "object", "properties": { @@ -385,6 +343,48 @@ } }, "description": "Represent the dicoverer request." + }, + "v1InfoNodes": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/InfoNode" + }, + "description": "The multiple node information." + } + }, + "description": "Represent the multiple node information message." + }, + "v1InfoPods": { + "type": "object", + "properties": { + "pods": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/InfoPod" + }, + "description": "The multiple pod information." + } + }, + "description": "Represent the multiple pod information message." + }, + "v1InfoServices": { + "type": "object", + "properties": { + "services": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/InfoService" + }, + "description": "The multiple service information." + } + }, + "description": "Represent the multiple service information message." } } } diff --git a/apis/swagger/v1/mirror/apis/proto/v1/mirror/mirror.swagger.json b/apis/swagger/v1/mirror/apis/proto/v1/mirror/mirror.swagger.json new file mode 100644 index 0000000000..ea66b1c572 --- /dev/null +++ b/apis/swagger/v1/mirror/apis/proto/v1/mirror/mirror.swagger.json @@ -0,0 +1,108 @@ +{ + "swagger": "2.0", + "info": { + "title": "apis/proto/v1/mirror/mirror.proto", + "version": "version not set" + }, + "consumes": ["application/json"], + "produces": ["application/json"], + "paths": { + "/mirror/register": { + "post": { + "summary": "Register is the RPC to register other mirror servers.", + "operationId": "Mirror_Register", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/MirrorTargets" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/MirrorTargets" + } + } + ], + "tags": ["Mirror"] + } + } + }, + "definitions": { + "MirrorTargets": { + "type": "object", + "properties": { + "targets": { + "type": "array", + "items": { + "$ref": "#/definitions/v1MirrorTarget" + }, + "description": "The multiple target information." + } + }, + "description": "Represent the multiple Target message." + }, + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string", + "description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics." + }, + "value": { + "type": "string", + "format": "byte", + "description": "Must be a valid serialized protocol buffer of the above specified type." + } + }, + "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }" + }, + "runtimeError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1MirrorTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent server information." + } + } +} diff --git a/apis/swagger/v1/mirror/mirror.swagger.json b/apis/swagger/v1/mirror/mirror.swagger.json new file mode 100644 index 0000000000..8c2fea2bb4 --- /dev/null +++ b/apis/swagger/v1/mirror/mirror.swagger.json @@ -0,0 +1,113 @@ +{ + "swagger": "2.0", + "info": { + "title": "v1/mirror/mirror.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "Mirror" + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "paths": { + "/mirror/register": { + "post": { + "summary": "Register is the RPC to register other mirror servers.", + "operationId": "Mirror_Register", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/MirrorTargets" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "Represent the multiple Target message.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/MirrorTargets" + } + } + ], + "tags": ["Mirror"] + } + } + }, + "definitions": { + "MirrorTargets": { + "type": "object", + "properties": { + "targets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1MirrorTarget" + }, + "description": "The multiple target information." + } + }, + "description": "Represent the multiple Target message." + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics." + } + }, + "additionalProperties": {}, + "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }" + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "description": "The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code]." + }, + "message": { + "type": "string", + "description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client." + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + }, + "description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use." + } + }, + "description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)." + }, + "v1MirrorTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent server information." + } + } +} diff --git a/apis/swagger/v1/vald/filter.swagger.json b/apis/swagger/v1/vald/filter.swagger.json index d4993d784e..0e060ee5d3 100644 --- a/apis/swagger/v1/vald/filter.swagger.json +++ b/apis/swagger/v1/vald/filter.swagger.json @@ -270,21 +270,6 @@ } }, "definitions": { - "FilterTarget": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "The target hostname." - }, - "port": { - "type": "integer", - "format": "int64", - "description": "The target port." - } - }, - "description": "Represent the target filter server." - }, "ObjectBlob": { "type": "object", "properties": { @@ -423,13 +408,28 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/FilterTarget" + "$ref": "#/definitions/v1FilterTarget" }, "description": "Represent the filter target configuration." } }, "description": "Represent filter configuration." }, + "v1FilterTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent the target filter server." + }, "v1InsertConfig": { "type": "object", "properties": { @@ -475,7 +475,7 @@ "description": "The configuration of the insert request." }, "vectorizer": { - "$ref": "#/definitions/FilterTarget", + "$ref": "#/definitions/v1FilterTarget", "description": "Filter configurations." } }, @@ -576,7 +576,7 @@ "description": "The configuration of the search request." }, "vectorizer": { - "$ref": "#/definitions/FilterTarget", + "$ref": "#/definitions/v1FilterTarget", "description": "Filter configuration." } }, @@ -649,7 +649,7 @@ "description": "The configuration of the update request." }, "vectorizer": { - "$ref": "#/definitions/FilterTarget", + "$ref": "#/definitions/v1FilterTarget", "description": "Filter target." } }, @@ -704,7 +704,7 @@ "description": "The configuration of the upsert request." }, "vectorizer": { - "$ref": "#/definitions/FilterTarget", + "$ref": "#/definitions/v1FilterTarget", "description": "Filter target." } }, diff --git a/apis/swagger/v1/vald/insert.swagger.json b/apis/swagger/v1/vald/insert.swagger.json index 8b47bbcdde..6b76719382 100644 --- a/apis/swagger/v1/vald/insert.swagger.json +++ b/apis/swagger/v1/vald/insert.swagger.json @@ -78,21 +78,6 @@ } }, "definitions": { - "FilterTarget": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "The target hostname." - }, - "port": { - "type": "integer", - "format": "int64", - "description": "The target port." - } - }, - "description": "Represent the target filter server." - }, "ObjectLocations": { "type": "object", "properties": { @@ -184,13 +169,28 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/FilterTarget" + "$ref": "#/definitions/v1FilterTarget" }, "description": "Represent the filter target configuration." } }, "description": "Represent filter configuration." }, + "v1FilterTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent the target filter server." + }, "v1InsertConfig": { "type": "object", "properties": { diff --git a/apis/swagger/v1/vald/object.swagger.json b/apis/swagger/v1/vald/object.swagger.json index b0ca410f12..de87f3948e 100644 --- a/apis/swagger/v1/vald/object.swagger.json +++ b/apis/swagger/v1/vald/object.swagger.json @@ -102,21 +102,6 @@ } }, "definitions": { - "FilterTarget": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "The target hostname." - }, - "port": { - "type": "integer", - "format": "int64", - "description": "The target port." - } - }, - "description": "Represent the target filter server." - }, "ObjectID": { "type": "object", "properties": { @@ -217,12 +202,27 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/FilterTarget" + "$ref": "#/definitions/v1FilterTarget" }, "description": "Represent the filter target configuration." } }, "description": "Represent filter configuration." + }, + "v1FilterTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent the target filter server." } } } diff --git a/apis/swagger/v1/vald/search.swagger.json b/apis/swagger/v1/vald/search.swagger.json index 3a0dafd252..b131377f51 100644 --- a/apis/swagger/v1/vald/search.swagger.json +++ b/apis/swagger/v1/vald/search.swagger.json @@ -270,21 +270,6 @@ } }, "definitions": { - "FilterTarget": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "The target hostname." - }, - "port": { - "type": "integer", - "format": "int64", - "description": "The target port." - } - }, - "description": "Represent the target filter server." - }, "ObjectDistance": { "type": "object", "properties": { @@ -409,13 +394,28 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/FilterTarget" + "$ref": "#/definitions/v1FilterTarget" }, "description": "Represent the filter target configuration." } }, "description": "Represent filter configuration." }, + "v1FilterTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent the target filter server." + }, "v1SearchConfig": { "type": "object", "properties": { diff --git a/apis/swagger/v1/vald/update.swagger.json b/apis/swagger/v1/vald/update.swagger.json index 1b256710d6..d295febc19 100644 --- a/apis/swagger/v1/vald/update.swagger.json +++ b/apis/swagger/v1/vald/update.swagger.json @@ -78,21 +78,6 @@ } }, "definitions": { - "FilterTarget": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "The target hostname." - }, - "port": { - "type": "integer", - "format": "int64", - "description": "The target port." - } - }, - "description": "Represent the target filter server." - }, "ObjectLocations": { "type": "object", "properties": { @@ -184,13 +169,28 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/FilterTarget" + "$ref": "#/definitions/v1FilterTarget" }, "description": "Represent the filter target configuration." } }, "description": "Represent filter configuration." }, + "v1FilterTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent the target filter server." + }, "v1ObjectLocation": { "type": "object", "properties": { diff --git a/apis/swagger/v1/vald/upsert.swagger.json b/apis/swagger/v1/vald/upsert.swagger.json index d4f429b6f3..b36801bc74 100644 --- a/apis/swagger/v1/vald/upsert.swagger.json +++ b/apis/swagger/v1/vald/upsert.swagger.json @@ -78,21 +78,6 @@ } }, "definitions": { - "FilterTarget": { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "The target hostname." - }, - "port": { - "type": "integer", - "format": "int64", - "description": "The target port." - } - }, - "description": "Represent the target filter server." - }, "ObjectLocations": { "type": "object", "properties": { @@ -184,13 +169,28 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/FilterTarget" + "$ref": "#/definitions/v1FilterTarget" }, "description": "Represent the filter target configuration." } }, "description": "Represent filter configuration." }, + "v1FilterTarget": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The target hostname." + }, + "port": { + "type": "integer", + "format": "int64", + "description": "The target port." + } + }, + "description": "Represent the target filter server." + }, "v1ObjectLocation": { "type": "object", "properties": { diff --git a/assets/docs/overview/component/mirror-gateway/full-mesh-connection.png b/assets/docs/overview/component/mirror-gateway/full-mesh-connection.png new file mode 100644 index 0000000000..66ae6b4d40 Binary files /dev/null and b/assets/docs/overview/component/mirror-gateway/full-mesh-connection.png differ diff --git a/assets/docs/overview/component/mirror-gateway/mirror-gateway.png b/assets/docs/overview/component/mirror-gateway/mirror-gateway.png new file mode 100644 index 0000000000..84e44e7bd2 Binary files /dev/null and b/assets/docs/overview/component/mirror-gateway/mirror-gateway.png differ diff --git a/assets/docs/overview/component/mirror-gateway/request-forwarding.png b/assets/docs/overview/component/mirror-gateway/request-forwarding.png new file mode 100644 index 0000000000..3e80c7202e Binary files /dev/null and b/assets/docs/overview/component/mirror-gateway/request-forwarding.png differ diff --git a/assets/docs/tutorial/vald-multicluster-on-k8s.png b/assets/docs/tutorial/vald-multicluster-on-k8s.png new file mode 100644 index 0000000000..7355ef1561 Binary files /dev/null and b/assets/docs/tutorial/vald-multicluster-on-k8s.png differ diff --git a/charts/vald-helm-operator/crds/valdrelease.yaml b/charts/vald-helm-operator/crds/valdrelease.yaml index 543411bdbd..214ee2b95d 100644 --- a/charts/vald-helm-operator/crds/valdrelease.yaml +++ b/charts/vald-helm-operator/crds/valdrelease.yaml @@ -6053,6 +6053,1067 @@ spec: items: type: object x-kubernetes-preserve-unknown-fields: true + mirror: + type: object + properties: + affinity: + type: object + properties: + nodeAffinity: + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + requiredDuringSchedulingIgnoredDuringExecution: + type: object + properties: + nodeSelectorTerms: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + podAffinity: + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + requiredDuringSchedulingIgnoredDuringExecution: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + podAntiAffinity: + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + requiredDuringSchedulingIgnoredDuringExecution: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + annotations: + type: object + x-kubernetes-preserve-unknown-fields: true + clusterRole: + type: object + properties: + enabled: + type: boolean + name: + type: string + clusterRoleBinding: + type: object + properties: + enabled: + type: boolean + name: + type: string + enabled: + type: boolean + env: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + externalTrafficPolicy: + type: string + gateway_config: + type: object + properties: + client: + type: object + properties: + addrs: + type: array + items: + type: string + backoff: + type: object + properties: + backoff_factor: + type: number + backoff_time_limit: + type: string + enable_error_log: + type: boolean + initial_duration: + type: string + jitter_limit: + type: string + maximum_duration: + type: string + retry_count: + type: integer + call_option: + type: object + x-kubernetes-preserve-unknown-fields: true + circuit_breaker: + type: object + properties: + closed_error_rate: + type: number + closed_refresh_timeout: + type: string + half_open_error_rate: + type: number + min_samples: + type: integer + open_timeout: + type: string + connection_pool: + type: object + properties: + enable_dns_resolver: + type: boolean + enable_rebalance: + type: boolean + old_conn_close_duration: + type: string + rebalance_duration: + type: string + size: + type: integer + dial_option: + type: object + properties: + backoff_base_delay: + type: string + backoff_jitter: + type: number + backoff_max_delay: + type: string + backoff_multiplier: + type: number + enable_backoff: + type: boolean + initial_connection_window_size: + type: integer + initial_window_size: + type: integer + insecure: + type: boolean + interceptors: + type: array + items: + type: string + enum: + - TraceInterceptor + keepalive: + type: object + properties: + permit_without_stream: + type: boolean + time: + type: string + timeout: + type: string + max_msg_size: + type: integer + min_connection_timeout: + type: string + net: + type: object + properties: + dialer: + type: object + properties: + dual_stack_enabled: + type: boolean + keepalive: + type: string + timeout: + type: string + dns: + type: object + properties: + cache_enabled: + type: boolean + cache_expiration: + type: string + refresh_duration: + type: string + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + tls: + type: object + properties: + ca: + type: string + cert: + type: string + enabled: + type: boolean + insecure_skip_verify: + type: boolean + key: + type: string + read_buffer_size: + type: integer + timeout: + type: string + write_buffer_size: + type: integer + health_check_duration: + type: string + max_recv_msg_size: + type: integer + max_retry_rpc_buffer_size: + type: integer + max_send_msg_size: + type: integer + tls: + type: object + properties: + ca: + type: string + cert: + type: string + enabled: + type: boolean + insecure_skip_verify: + type: boolean + key: + type: string + wait_for_ready: + type: boolean + colocation: + type: string + discovery_duration: + type: string + gateway_addr: + type: string + group: + type: string + namespace: + type: string + net: + type: object + properties: + dialer: + type: object + properties: + dual_stack_enabled: + type: boolean + keepalive: + type: string + timeout: + type: string + dns: + type: object + properties: + cache_enabled: + type: boolean + cache_expiration: + type: string + refresh_duration: + type: string + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + tls: + type: object + properties: + ca: + type: string + cert: + type: string + enabled: + type: boolean + insecure_skip_verify: + type: boolean + key: + type: string + pod_name: + type: string + register_duration: + type: string + self_mirror_addr: + type: string + hpa: + type: object + properties: + enabled: + type: boolean + targetCPUUtilizationPercentage: + type: integer + image: + type: object + properties: + pullPolicy: + type: string + enum: + - Always + - Never + - IfNotPresent + repository: + type: string + tag: + type: string + ingress: + type: object + properties: + annotations: + type: object + x-kubernetes-preserve-unknown-fields: true + defaultBackend: + type: object + properties: + enabled: + type: boolean + enabled: + type: boolean + host: + type: string + pathType: + type: string + servicePort: + type: string + initContainers: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + internalTrafficPolicy: + type: string + kind: + type: string + enum: + - Deployment + - DaemonSet + logging: + type: object + properties: + format: + type: string + enum: + - raw + - json + level: + type: string + enum: + - debug + - info + - warn + - error + - fatal + logger: + type: string + enum: + - glg + - zap + maxReplicas: + type: integer + minimum: 0 + maxUnavailable: + type: string + minReplicas: + type: integer + minimum: 0 + name: + type: string + nodeName: + type: string + nodeSelector: + type: object + x-kubernetes-preserve-unknown-fields: true + observability: + type: object + properties: + enabled: + type: boolean + metrics: + type: object + properties: + enable_cgo: + type: boolean + enable_goroutine: + type: boolean + enable_memory: + type: boolean + enable_version_info: + type: boolean + version_info_labels: + type: array + items: + type: string + enum: + - vald_version + - server_name + - git_commit + - build_time + - go_version + - go_os + - go_arch + - cgo_enabled + - ngt_version + - build_cpu_info_flags + otlp: + type: object + properties: + attribute: + type: object + properties: + namespace: + type: string + node_name: + type: string + pod_name: + type: string + service_name: + type: string + collector_endpoint: + type: string + metrics_export_interval: + type: string + metrics_export_timeout: + type: string + trace_batch_timeout: + type: string + trace_export_timeout: + type: string + trace_max_export_batch_size: + type: integer + trace_max_queue_size: + type: integer + trace: + type: object + properties: + enabled: + type: boolean + podAnnotations: + type: object + x-kubernetes-preserve-unknown-fields: true + podPriority: + type: object + properties: + enabled: + type: boolean + value: + type: integer + podSecurityContext: + type: object + x-kubernetes-preserve-unknown-fields: true + progressDeadlineSeconds: + type: integer + resources: + type: object + properties: + limits: + type: object + x-kubernetes-preserve-unknown-fields: true + requests: + type: object + x-kubernetes-preserve-unknown-fields: true + revisionHistoryLimit: + type: integer + minimum: 0 + rollingUpdate: + type: object + properties: + maxSurge: + type: string + maxUnavailable: + type: string + securityContext: + type: object + x-kubernetes-preserve-unknown-fields: true + server_config: + type: object + properties: + full_shutdown_duration: + type: string + healths: + type: object + properties: + liveness: + type: object + properties: + enabled: + type: boolean + host: + type: string + livenessProbe: + type: object + properties: + failureThreshold: + type: integer + httpGet: + type: object + properties: + path: + type: string + port: + type: string + scheme: + type: string + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + timeoutSeconds: + type: integer + port: + type: integer + maximum: 65535 + minimum: 0 + server: + type: object + properties: + http: + type: object + properties: + handler_timeout: + type: string + idle_timeout: + type: string + read_header_timeout: + type: string + read_timeout: + type: string + shutdown_duration: + type: string + write_timeout: + type: string + mode: + type: string + network: + type: string + enum: + - tcp + - tcp4 + - tcp6 + - udp + - udp4 + - udp6 + - unix + - unixgram + - unixpacket + probe_wait_time: + type: string + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + socket_path: + type: string + servicePort: + type: integer + maximum: 65535 + minimum: 0 + readiness: + type: object + properties: + enabled: + type: boolean + host: + type: string + port: + type: integer + maximum: 65535 + minimum: 0 + readinessProbe: + type: object + properties: + failureThreshold: + type: integer + httpGet: + type: object + properties: + path: + type: string + port: + type: string + scheme: + type: string + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + timeoutSeconds: + type: integer + server: + type: object + properties: + http: + type: object + properties: + handler_timeout: + type: string + idle_timeout: + type: string + read_header_timeout: + type: string + read_timeout: + type: string + shutdown_duration: + type: string + write_timeout: + type: string + mode: + type: string + network: + type: string + enum: + - tcp + - tcp4 + - tcp6 + - udp + - udp4 + - udp6 + - unix + - unixgram + - unixpacket + probe_wait_time: + type: string + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + socket_path: + type: string + servicePort: + type: integer + maximum: 65535 + minimum: 0 + startup: + type: object + properties: + enabled: + type: boolean + port: + type: integer + maximum: 65535 + minimum: 0 + startupProbe: + type: object + properties: + failureThreshold: + type: integer + httpGet: + type: object + properties: + path: + type: string + port: + type: string + scheme: + type: string + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + timeoutSeconds: + type: integer + metrics: + type: object + properties: + pprof: + type: object + properties: + enabled: + type: boolean + host: + type: string + port: + type: integer + maximum: 65535 + minimum: 0 + server: + type: object + properties: + http: + type: object + properties: + handler_timeout: + type: string + idle_timeout: + type: string + read_header_timeout: + type: string + read_timeout: + type: string + shutdown_duration: + type: string + write_timeout: + type: string + mode: + type: string + network: + type: string + enum: + - tcp + - tcp4 + - tcp6 + - udp + - udp4 + - udp6 + - unix + - unixgram + - unixpacket + probe_wait_time: + type: string + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + socket_path: + type: string + servicePort: + type: integer + maximum: 65535 + minimum: 0 + servers: + type: object + properties: + grpc: + type: object + properties: + enabled: + type: boolean + host: + type: string + port: + type: integer + maximum: 65535 + minimum: 0 + server: + type: object + properties: + grpc: + type: object + properties: + bidirectional_stream_concurrency: + type: integer + connection_timeout: + type: string + enable_reflection: + type: boolean + header_table_size: + type: integer + initial_conn_window_size: + type: integer + initial_window_size: + type: integer + interceptors: + type: array + items: + type: string + enum: + - RecoverInterceptor + - AccessLogInterceptor + - TraceInterceptor + - MetricInterceptor + keepalive: + type: object + properties: + max_conn_age: + type: string + max_conn_age_grace: + type: string + max_conn_idle: + type: string + min_time: + type: string + permit_without_stream: + type: boolean + time: + type: string + timeout: + type: string + max_header_list_size: + type: integer + max_receive_message_size: + type: integer + max_send_message_size: + type: integer + read_buffer_size: + type: integer + write_buffer_size: + type: integer + mode: + type: string + network: + type: string + enum: + - tcp + - tcp4 + - tcp6 + - udp + - udp4 + - udp6 + - unix + - unixgram + - unixpacket + probe_wait_time: + type: string + restart: + type: boolean + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + socket_path: + type: string + servicePort: + type: integer + maximum: 65535 + minimum: 0 + rest: + type: object + properties: + enabled: + type: boolean + host: + type: string + port: + type: integer + maximum: 65535 + minimum: 0 + server: + type: object + properties: + http: + type: object + properties: + handler_timeout: + type: string + idle_timeout: + type: string + read_header_timeout: + type: string + read_timeout: + type: string + shutdown_duration: + type: string + write_timeout: + type: string + mode: + type: string + network: + type: string + enum: + - tcp + - tcp4 + - tcp6 + - udp + - udp4 + - udp6 + - unix + - unixgram + - unixpacket + probe_wait_time: + type: string + socket_option: + type: object + properties: + ip_recover_destination_addr: + type: boolean + ip_transparent: + type: boolean + reuse_addr: + type: boolean + reuse_port: + type: boolean + tcp_cork: + type: boolean + tcp_defer_accept: + type: boolean + tcp_fast_open: + type: boolean + tcp_no_delay: + type: boolean + tcp_quick_ack: + type: boolean + socket_path: + type: string + servicePort: + type: integer + maximum: 65535 + minimum: 0 + tls: + type: object + properties: + ca: + type: string + cert: + type: string + enabled: + type: boolean + insecure_skip_verify: + type: boolean + key: + type: string + service: + type: object + properties: + annotations: + type: object + x-kubernetes-preserve-unknown-fields: true + labels: + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccount: + type: object + properties: + enabled: + type: boolean + name: + type: string + serviceType: + type: string + enum: + - ClusterIP + - LoadBalancer + - NodePort + terminationGracePeriodSeconds: + type: integer + minimum: 0 + time_zone: + type: string + tolerations: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + version: + type: string + pattern: ^v[0-9]+\.[0-9]+\.[0-9]$ + volumeMounts: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true manager: type: object properties: diff --git a/charts/vald-helm-operator/templates/clusterrole.yaml b/charts/vald-helm-operator/templates/clusterrole.yaml index 249d1d5f86..b9727f4f6d 100644 --- a/charts/vald-helm-operator/templates/clusterrole.yaml +++ b/charts/vald-helm-operator/templates/clusterrole.yaml @@ -35,6 +35,18 @@ rules: verbs: - create - patch + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/charts/vald/crds/valdmirrortarget.yaml b/charts/vald/crds/valdmirrortarget.yaml new file mode 100644 index 0000000000..39d0b385ef --- /dev/null +++ b/charts/vald/crds/valdmirrortarget.yaml @@ -0,0 +1,100 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: valdmirrortargets.vald.vdaas.org +spec: + group: vald.vdaas.org + names: + kind: ValdMirrorTarget + shortNames: + - vmt + - vmts + listKind: ValdMirrorTargetList + plural: valdmirrortargets + singular: valdmirrortarget + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: Host + type: string + jsonPath: .spec.target.host + priority: 1 + - name: Port + type: integer + jsonPath: .spec.target.port + priority: 1 + - name: Status + type: string + jsonPath: .status.phase + - name: "Last Transition Time" + type: string + jsonPath: .status.lastTransitionTime + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + schema: + openAPIV3Schema: + description: ValdMirrorTarget is the Schema for the valdmirrortargets API + type: object + properties: + apiVersion: + description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + status: + description: ValdMirrorTargetStatus defines the observed state of ValdMirrorTarget + type: object + default: {} + properties: + phase: + type: string + enum: + - Pending + - Connected + - Disconnected + - Unknown + default: Pending + lastTransitionTime: + type: string + spec: + type: object + properties: + colocation: + type: string + target: + type: object + properties: + host: + type: string + minLength: 1 + port: + type: integer + maximum: 65535 + minimum: 0 + required: + - host + - port diff --git a/charts/vald/schemas/mirror-target-values.yaml b/charts/vald/schemas/mirror-target-values.yaml new file mode 100644 index 0000000000..ded00b6136 --- /dev/null +++ b/charts/vald/schemas/mirror-target-values.yaml @@ -0,0 +1,28 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# + +# @schema {"name": "colocation", "type": "string"} +# colocation -- The colocation name +colocation: dc1 +# @schema {"name": "target", "type": "object", "required": ["host", "port"]} +# target -- Mirror target information +target: + # @schema {"name": "target.host", "type": "string", "minLength": 1} + # host -- Mirror target host name + host: "vald-mirror-gateway.vald.svc.cluster.local" + # @schema {"name": "target.port", "type": "integer", "minimum": 0, "maximum": 65535} + # target.port -- Mirror target port + port: 8081 diff --git a/charts/vald/templates/gateway/filter/ing.yaml b/charts/vald/templates/gateway/filter/ing.yaml deleted file mode 100644 index b1f6eade81..0000000000 --- a/charts/vald/templates/gateway/filter/ing.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright (C) 2019-2024 vdaas.org vald team -# -# 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 -# -# https://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. -# -{{- $gateway := .Values.gateway.filter -}} -{{- if and $gateway.enabled $gateway.ingress.enabled }} -{{- if (.Capabilities.APIVersions.Has "networking.k8s.io/v1") }} -apiVersion: networking.k8s.io/v1 -{{- else }} -apiVersion: networking.k8s.io/v1alpha1 -{{- end }} -kind: Ingress -metadata: - annotations: - {{- toYaml $gateway.ingress.annotations | nindent 4 }} - labels: - name: {{ $gateway.name }}-ingress - app: {{ $gateway.name }}-ingress - app.kubernetes.io/name: {{ include "vald.name" . }} - helm.sh/chart: {{ include "vald.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.Version }} - app.kubernetes.io/component: gateway-filter - name: {{ $gateway.name }}-ingress -spec: - {{- if $gateway.ingress.defaultBackend.enabled }} - defaultBackend: - service: - name: {{ $gateway.name }} - {{- include "vald.ingressPort" (dict "Values" $gateway.ingress) | nindent 6 }} - {{- end }} - rules: - - host: {{ $gateway.ingress.host }} - http: - paths: - - backend: - service: - name: {{ $gateway.name }} - {{- include "vald.ingressPort" (dict "Values" $gateway.ingress) | nindent 12 }} - pathType: {{ $gateway.ingress.pathType }} -{{- end }} diff --git a/charts/vald/templates/gateway/ing.yaml b/charts/vald/templates/gateway/ing.yaml new file mode 100644 index 0000000000..d9dff5060c --- /dev/null +++ b/charts/vald/templates/gateway/ing.yaml @@ -0,0 +1,96 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $filter := .Values.gateway.filter -}} +{{- $filterIngEnabled := and $filter.enabled $filter.ingress.enabled -}} +{{- $mirror := .Values.gateway.mirror -}} +{{- $mirrorIngEnabled := and $mirror.enabled $mirror.ingress.enabled -}} +{{- $lb := .Values.gateway.lb -}} +{{- $lbIngEnabled := and $lb.enabled $lb.ingress.enabled -}} +{{- $gateway := "" -}} +{{- $gatewayName := "" -}} +{{- if or $filterIngEnabled $mirrorIngEnabled $lbIngEnabled }} +{{- if $filterIngEnabled }} +{{- $gateway = $filter -}} +{{- $gatewayName = "gateway-filter" }} +{{- else if $mirrorIngEnabled }} +{{- $gateway = $mirror -}} +{{- $gatewayName = "gateway-mirror" }} +{{- else }} +{{- $gateway = $lb -}} +{{- $gatewayName = "gateway-lb" }} +{{- end }} +{{- if (.Capabilities.APIVersions.Has "networking.k8s.io/v1") }} +apiVersion: networking.k8s.io/v1 +{{- else }} +apiVersion: networking.k8s.io/v1alpha1 +{{- end }} +kind: Ingress +metadata: + annotations: + {{- toYaml $gateway.ingress.annotations | nindent 4 }} + labels: + name: {{ $gateway.name }}-ingress + app: {{ $gateway.name }}-ingress + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: {{ $gatewayName }} + name: {{ .Release.Name }}-ingress +spec: + {{- if $gateway.ingress.defaultBackend.enabled }} + defaultBackend: + service: + name: {{ $gateway.name }} + {{- include "vald.ingressPort" (dict "Values" $gateway.ingress) | nindent 6 }} + {{- end }} + rules: + - host: {{ $gateway.ingress.host }} + http: + paths: + {{- if $filterIngEnabled }} + - backend: + service: + name: {{ $filter.name }} + {{- include "vald.ingressPort" (dict "Values" $filter.ingress) | nindent 12 }} + pathType: {{ $filter.ingress.pathType }} + {{- else if and $mirrorIngEnabled $lb.enabled }} + - path: "/vald.v1.Search" + backend: + service: + name: {{ $lb.name }} + {{- include "vald.ingressPort" (dict "Values" $lb.ingress) | nindent 12 }} + pathType: {{ $mirror.ingress.pathType }} + - path: "/vald.v1.Object" + backend: + service: + name: {{ $lb.name }} + {{- include "vald.ingressPort" (dict "Values" $lb.ingress) | nindent 12 }} + pathType: {{ $mirror.ingress.pathType }} + - backend: + service: + name: {{ $mirror.name }} + {{- include "vald.ingressPort" (dict "Values" $mirror.ingress) | nindent 12 }} + pathType: {{ $mirror.ingress.pathType }} + {{- else if $lbIngEnabled }} + - backend: + service: + name: {{ $lb.name }} + {{- include "vald.ingressPort" (dict "Values" $lb.ingress) | nindent 12 }} + pathType: {{ $lb.ingress.pathType }} + {{- end }} +{{- end }} diff --git a/charts/vald/templates/gateway/lb/ing.yaml b/charts/vald/templates/gateway/lb/ing.yaml deleted file mode 100644 index 668aa212e9..0000000000 --- a/charts/vald/templates/gateway/lb/ing.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright (C) 2019-2024 vdaas.org vald team -# -# 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 -# -# https://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. -# -{{- $gateway := .Values.gateway.lb -}} -{{- if and $gateway.enabled $gateway.ingress.enabled }} -{{- if (.Capabilities.APIVersions.Has "networking.k8s.io/v1") }} -apiVersion: networking.k8s.io/v1 -{{- else }} -apiVersion: networking.k8s.io/v1alpha1 -{{- end }} -kind: Ingress -metadata: - annotations: - {{- toYaml $gateway.ingress.annotations | nindent 4 }} - labels: - name: {{ $gateway.name }}-ingress - app: {{ $gateway.name }}-ingress - app.kubernetes.io/name: {{ include "vald.name" . }} - helm.sh/chart: {{ include "vald.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.Version }} - app.kubernetes.io/component: gateway-lb - name: {{ $gateway.name }}-ingress -spec: - {{- if $gateway.ingress.defaultBackend.enabled }} - defaultBackend: - service: - name: {{ $gateway.name }} - {{- include "vald.ingressPort" (dict "Values" $gateway.ingress) | nindent 6 }} - {{- end }} - rules: - - host: {{ $gateway.ingress.host }} - http: - paths: - - backend: - service: - name: {{ $gateway.name }} - {{- include "vald.ingressPort" (dict "Values" $gateway.ingress) | nindent 12 }} - pathType: {{ $gateway.ingress.pathType }} -{{- end }} diff --git a/charts/vald/templates/gateway/lb/networkpolicy.yaml b/charts/vald/templates/gateway/lb/networkpolicy.yaml index 6d5885f269..0132a79a00 100644 --- a/charts/vald/templates/gateway/lb/networkpolicy.yaml +++ b/charts/vald/templates/gateway/lb/networkpolicy.yaml @@ -17,6 +17,7 @@ {{- $agent := .Values.agent -}} {{- $lb := .Values.gateway.lb -}} {{- $filter := .Values.gateway.filter -}} +{{- $mirror := .Values.gateway.mirror -}} {{- $discoverer := .Values.discoverer -}} {{- if .Values.defaults.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 @@ -41,6 +42,12 @@ spec: podSelector: matchLabels: app: {{ $filter.name }} + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: + app: {{ $mirror.name }} {{- if .Values.defaults.networkPolicy.custom.ingress }} {{- toYaml .Values.defaults.networkPolicy.custom.ingress | nindent 4 }} {{- end }} diff --git a/charts/vald/templates/gateway/mirror/clusterrole.yaml b/charts/vald/templates/gateway/mirror/clusterrole.yaml new file mode 100644 index 0000000000..e72ce4eaf1 --- /dev/null +++ b/charts/vald/templates/gateway/mirror/clusterrole.yaml @@ -0,0 +1,58 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled $gateway.clusterRole.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ $gateway.clusterRole.name }} + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +rules: + - apiGroups: + - vald.vdaas.org + resources: + - valdmirrortargets + verbs: + - create + - update + - delete + - get + - list + - watch + - patch + - apiGroups: + - vald.vdaas.org + resources: + - valdmirrortargets/status + verbs: + - create + - update + - get + - list + - patch + - apiGroups: + - vald.vdaas.org + resources: + - valdmirrortargets/finalizers + verbs: + - update +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/clusterrolebinding.yaml b/charts/vald/templates/gateway/mirror/clusterrolebinding.yaml new file mode 100644 index 0000000000..7495b799df --- /dev/null +++ b/charts/vald/templates/gateway/mirror/clusterrolebinding.yaml @@ -0,0 +1,37 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled $gateway.clusterRoleBinding.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ $gateway.clusterRoleBinding.name }} + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ $gateway.clusterRole.name }} +subjects: + - kind: ServiceAccount + name: {{ $gateway.serviceAccount.name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/configmap.yaml b/charts/vald/templates/gateway/mirror/configmap.yaml new file mode 100644 index 0000000000..0a5dc5fdee --- /dev/null +++ b/charts/vald/templates/gateway/mirror/configmap.yaml @@ -0,0 +1,84 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- $lb := .Values.gateway.lb -}} +{{- if $gateway.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $gateway.name }}-config + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +data: + config.yaml: | + --- + version: {{ $gateway.version }} + time_zone: {{ default .Values.defaults.time_zone $gateway.time_zone }} + logging: + {{- $logging := dict "Values" $gateway.logging "default" .Values.defaults.logging }} + {{- include "vald.logging" $logging | nindent 6 }} + server_config: + {{- $servers := dict "Values" $gateway.server_config "default" .Values.defaults.server_config }} + {{- include "vald.servers" $servers | nindent 6 }} + observability: + {{- $observability := dict "Values" $gateway.observability "default" .Values.defaults.observability }} + {{- include "vald.observability" $observability | nindent 6 }} + gateway: + pod_name: {{ $gateway.gateway_config.pod_name }} + register_duration: {{ $gateway.gateway_config.register_duration }} + namespace: {{ $gateway.gateway_config.namespace }} + discovery_duration: {{ $gateway.gateway_config.discovery_duration }} + colocation: {{ $gateway.gateway_config.colocation }} + group: {{ $gateway.gateway_config.group }} + net: + {{- toYaml $gateway.gateway_config.net | nindent 8 }} + client: + {{- $client := $gateway.gateway_config.client }} + {{- $addrs := default list $client.addrs }} + {{- if $lb.enabled -}} + {{- $defaultHost := printf "%s.%s.svc.cluster.local" $lb.name .Release.Namespace }} + {{- $defaultPort := default .Values.defaults.server_config.servers.grpc.port $lb.server_config.servers.grpc.port }} + {{- $defaultAddr := (list (printf "%s:%d" $defaultHost (int64 $defaultPort))) }} + {{- $addrs = (concat $addrs $defaultAddr) }} + {{- end -}} + {{- if $addrs }} + addrs: + {{- toYaml $addrs | nindent 10 }} + {{- else }} + addrs: [] + {{- end -}} + {{- $GRPCClient := dict "Values" $client "default" .Values.defaults.grpc.client }} + {{- include "vald.grpc.client" $GRPCClient | nindent 8 }} + self_mirror_addr: + {{- if $gateway.ingress.enabled -}} + {{- $gateway.gateway_config.self_mirror_addr | default (printf "%s:%d" $gateway.ingress.host 80) | indent 1 }} + {{- else -}} + {{- $defaultHost := printf "%s.%s.svc.cluster.local" $gateway.name .Release.Namespace }} + {{- $defaultPort := default .Values.defaults.server_config.servers.grpc.port $gateway.server_config.servers.grpc.port }} + {{- printf "%s:%d" $defaultHost (int64 $defaultPort) | indent 1 }} + {{- end }} + gateway_addr: + {{- if $lb.enabled -}} + {{- $defaultHost := printf "%s.%s.svc.cluster.local" $lb.name .Release.Namespace }} + {{- $defaultPort := default .Values.defaults.server_config.servers.grpc.port $lb.server_config.servers.grpc.port }} + {{- printf "%s:%d" $defaultHost (int64 $defaultPort) | indent 1 }} + {{- end }} +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/daemonset.yaml b/charts/vald/templates/gateway/mirror/daemonset.yaml new file mode 100644 index 0000000000..e9aa3de0c9 --- /dev/null +++ b/charts/vald/templates/gateway/mirror/daemonset.yaml @@ -0,0 +1,135 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled (eq $gateway.kind "DaemonSet") }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ $gateway.name }} + labels: + app: {{ $gateway.name }} + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror + {{- if $gateway.annotations }} + annotations: + {{- toYaml $gateway.annotations | nindent 4 }} + {{- end }} +spec: + revisionHistoryLimit: {{ $gateway.revisionHistoryLimit }} + selector: + matchLabels: + app: {{ $gateway.name }} + updateStrategy: + type: RollingUpdate + rollingUpdate: + maxSurge: {{ $gateway.rollingUpdate.maxSurge }} + maxUnavailable: {{ $gateway.rollingUpdate.maxUnavailable }} + template: + metadata: + creationTimestamp: null + labels: + app: {{ $gateway.name }} + app.kubernetes.io/name: {{ include "vald.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: gateway-mirror + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/gateway/mirror/configmap.yaml") . | sha256sum }} + {{- if $gateway.podAnnotations }} + {{- toYaml $gateway.podAnnotations | nindent 8 }} + {{- end }} + {{- $pprof := default .Values.defaults.server_config.metrics.pprof $gateway.server_config.metrics.pprof }} + {{- if $pprof.enabled }} + pyroscope.io/scrape: "true" + pyroscope.io/application-name: {{ $gateway.name }} + pyroscope.io/profile-cpu-enabled: "true" + pyroscope.io/profile-mem-enabled: "true" + pyroscope.io/port: "{{ $pprof.port }}" + {{- end }} + spec: + {{- if $gateway.initContainers }} + initContainers: + {{- $initContainers := dict "initContainers" $gateway.initContainers "Values" .Values "namespace" .Release.Namespace -}} + {{- include "vald.initContainers" $initContainers | trim | nindent 8 }} + {{- end }} + affinity: + {{- include "vald.affinity" $gateway.affinity | nindent 8 }} + {{- if $gateway.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml $gateway.topologySpreadConstraints | nindent 8 }} + {{- end }} + containers: + - name: {{ $gateway.name }} + image: "{{ $gateway.image.repository }}:{{ default .Values.defaults.image.tag $gateway.image.tag }}" + imagePullPolicy: {{ $gateway.image.pullPolicy }} + {{- $servers := dict "Values" $gateway.server_config "default" .Values.defaults.server_config -}} + {{- include "vald.containerPorts" $servers | trim | nindent 10 }} + resources: + {{- toYaml $gateway.resources | nindent 12 }} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + {{- if $gateway.securityContext }} + securityContext: + {{- toYaml $gateway.securityContext | nindent 12 }} + {{- end }} + {{- if $gateway.env }} + env: + {{- toYaml $gateway.env | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ $gateway.name }}-config + mountPath: /etc/server/ + {{- if $gateway.volumeMounts }} + {{- toYaml $gateway.volumeMounts | nindent 12 }} + {{- end }} + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + serviceAccountName: {{ $gateway.serviceAccount.name }} + {{- if $gateway.podSecurityContext }} + securityContext: + {{- toYaml $gateway.podSecurityContext | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ $gateway.terminationGracePeriodSeconds }} + volumes: + - name: {{ $gateway.name }}-config + configMap: + defaultMode: 420 + name: {{ $gateway.name }}-config + {{- if $gateway.volumes }} + {{- toYaml $gateway.volumes | nindent 8 }} + {{- end }} + {{- if $gateway.nodeName }} + nodeName: {{ $gateway.nodeName }} + {{- end }} + {{- if $gateway.nodeSelector }} + nodeSelector: + {{- toYaml $gateway.nodeSelector | nindent 8 }} + {{- end }} + {{- if $gateway.tolerations }} + tolerations: + {{- toYaml $gateway.tolerations | nindent 8 }} + {{- end }} + {{- if $gateway.podPriority }} + {{- if $gateway.podPriority.enabled }} + priorityClassName: {{ .Release.Namespace }}-{{ $gateway.name }}-priority + {{- end }} + {{- end }} +status: +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/deployment.yaml b/charts/vald/templates/gateway/mirror/deployment.yaml new file mode 100644 index 0000000000..524675d2a1 --- /dev/null +++ b/charts/vald/templates/gateway/mirror/deployment.yaml @@ -0,0 +1,139 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled (eq $gateway.kind "Deployment") }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $gateway.name }} + labels: + app: {{ $gateway.name }} + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror + {{- if $gateway.annotations }} + annotations: + {{- toYaml $gateway.annotations | nindent 4 }} + {{- end }} +spec: + progressDeadlineSeconds: {{ $gateway.progressDeadlineSeconds }} + {{- if not $gateway.hpa.enabled }} + replicas: {{ $gateway.minReplicas }} + {{- end }} + revisionHistoryLimit: {{ $gateway.revisionHistoryLimit }} + selector: + matchLabels: + app: {{ $gateway.name }} + strategy: + rollingUpdate: + maxSurge: {{ $gateway.rollingUpdate.maxSurge }} + maxUnavailable: {{ $gateway.rollingUpdate.maxUnavailable }} + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: {{ $gateway.name }} + app.kubernetes.io/name: {{ include "vald.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: gateway-mirror + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/gateway/mirror/configmap.yaml") . | sha256sum }} + {{- if $gateway.podAnnotations }} + {{- toYaml $gateway.podAnnotations | nindent 8 }} + {{- end }} + {{- $pprof := default .Values.defaults.server_config.metrics.pprof $gateway.server_config.metrics.pprof }} + {{- if $pprof.enabled }} + pyroscope.io/scrape: "true" + pyroscope.io/application-name: {{ $gateway.name }} + pyroscope.io/profile-cpu-enabled: "true" + pyroscope.io/profile-mem-enabled: "true" + pyroscope.io/port: "{{ $pprof.port }}" + {{- end }} + spec: + {{- if $gateway.initContainers }} + initContainers: + {{- $initContainers := dict "initContainers" $gateway.initContainers "Values" .Values "namespace" .Release.Namespace -}} + {{- include "vald.initContainers" $initContainers | trim | nindent 8 }} + {{- end }} + affinity: + {{- include "vald.affinity" $gateway.affinity | nindent 8 }} + {{- if $gateway.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml $gateway.topologySpreadConstraints | nindent 8 }} + {{- end }} + containers: + - name: {{ $gateway.name }} + image: "{{ $gateway.image.repository }}:{{ default .Values.defaults.image.tag $gateway.image.tag }}" + imagePullPolicy: {{ $gateway.image.pullPolicy }} + {{- $servers := dict "Values" $gateway.server_config "default" .Values.defaults.server_config -}} + {{- include "vald.containerPorts" $servers | trim | nindent 10 }} + resources: + {{- toYaml $gateway.resources | nindent 12 }} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + {{- if $gateway.securityContext }} + securityContext: + {{- toYaml $gateway.securityContext | nindent 12 }} + {{- end }} + {{- if $gateway.env }} + env: + {{- toYaml $gateway.env | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ $gateway.name }}-config + mountPath: /etc/server/ + {{- if $gateway.volumeMounts }} + {{- toYaml $gateway.volumeMounts | nindent 12 }} + {{- end }} + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + serviceAccountName: {{ $gateway.serviceAccount.name }} + {{- if $gateway.podSecurityContext }} + securityContext: + {{- toYaml $gateway.podSecurityContext | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ $gateway.terminationGracePeriodSeconds }} + volumes: + - name: {{ $gateway.name }}-config + configMap: + defaultMode: 420 + name: {{ $gateway.name }}-config + {{- if $gateway.volumes }} + {{- toYaml $gateway.volumes | nindent 8 }} + {{- end }} + {{- if $gateway.nodeName }} + nodeName: {{ $gateway.nodeName }} + {{- end }} + {{- if $gateway.nodeSelector }} + nodeSelector: + {{- toYaml $gateway.nodeSelector | nindent 8 }} + {{- end }} + {{- if $gateway.tolerations }} + tolerations: + {{- toYaml $gateway.tolerations | nindent 8 }} + {{- end }} + {{- if $gateway.podPriority }} + {{- if $gateway.podPriority.enabled }} + priorityClassName: {{ .Release.Namespace }}-{{ $gateway.name }}-priority + {{- end }} + {{- end }} +status: +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/hpa.yaml b/charts/vald/templates/gateway/mirror/hpa.yaml new file mode 100644 index 0000000000..d0094ead4b --- /dev/null +++ b/charts/vald/templates/gateway/mirror/hpa.yaml @@ -0,0 +1,38 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled $gateway.hpa.enabled }} +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $gateway.name }} + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +spec: + maxReplicas: {{ $gateway.maxReplicas }} + minReplicas: {{ $gateway.minReplicas }} + scaleTargetRef: + apiVersion: apps/v1 + kind: {{ $gateway.kind }} + name: {{ $gateway.name }} + targetCPUUtilizationPercentage: {{ $gateway.hpa.targetCPUUtilizationPercentage }} +status: +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/networkpolicy.yaml b/charts/vald/templates/gateway/mirror/networkpolicy.yaml new file mode 100644 index 0000000000..0c2d31b30a --- /dev/null +++ b/charts/vald/templates/gateway/mirror/networkpolicy.yaml @@ -0,0 +1,53 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# + +{{- $lb := .Values.gateway.lb -}} +{{- $mirror := .Values.gateway.mirror -}} +{{- if .Values.defaults.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: mirror-allow +spec: + podSelector: + matchLabels: + app: {{ $mirror.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + {{- if .Values.defaults.networkPolicy.custom.ingress }} + {{- toYaml .Values.defaults.networkPolicy.custom.ingress | nindent 4 }} + {{- end }} + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: + app: {{ $lb.name }} + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + {{- if .Values.defaults.networkPolicy.custom.egress }} + {{- toYaml .Values.defaults.networkPolicy.custom.egress | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/pdb.yaml b/charts/vald/templates/gateway/mirror/pdb.yaml new file mode 100644 index 0000000000..4776797849 --- /dev/null +++ b/charts/vald/templates/gateway/mirror/pdb.yaml @@ -0,0 +1,38 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if $gateway.enabled }} +{{- if (.Capabilities.APIVersions.Has "policy/v1") }} +apiVersion: policy/v1 +{{- else }} +apiVersion: policy/v1beta1 +{{- end }} +kind: PodDisruptionBudget +metadata: + name: {{ $gateway.name }} + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +spec: + maxUnavailable: {{ $gateway.maxUnavailable }} + selector: + matchLabels: + app: {{ $gateway.name }} +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/priorityclass.yaml b/charts/vald/templates/gateway/mirror/priorityclass.yaml new file mode 100644 index 0000000000..f22a1fe3a2 --- /dev/null +++ b/charts/vald/templates/gateway/mirror/priorityclass.yaml @@ -0,0 +1,32 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled $gateway.podPriority.enabled }} +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: {{ .Release.Namespace }}-{{ $gateway.name }}-priority + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +value: {{ $gateway.podPriority.value }} +globalDefault: false +description: "A priority class for Vald mirror gateway." +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/serviceaccount.yaml b/charts/vald/templates/gateway/mirror/serviceaccount.yaml new file mode 100644 index 0000000000..6aab125708 --- /dev/null +++ b/charts/vald/templates/gateway/mirror/serviceaccount.yaml @@ -0,0 +1,29 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if and $gateway.enabled $gateway.serviceAccount.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $gateway.serviceAccount.name }} + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror +{{- end }} diff --git a/charts/vald/templates/gateway/mirror/svc.yaml b/charts/vald/templates/gateway/mirror/svc.yaml new file mode 100644 index 0000000000..908d94845c --- /dev/null +++ b/charts/vald/templates/gateway/mirror/svc.yaml @@ -0,0 +1,52 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +{{- $gateway := .Values.gateway.mirror -}} +{{- if $gateway.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ $gateway.name }} + {{- if $gateway.service.annotations }} + annotations: + {{- toYaml $gateway.service.annotations | nindent 4 }} + {{- end }} + labels: + app.kubernetes.io/name: {{ include "vald.name" . }} + helm.sh/chart: {{ include "vald.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.Version }} + app.kubernetes.io/component: gateway-mirror + {{- if $gateway.service.labels }} + {{- toYaml $gateway.service.labels | nindent 4 }} + {{- end }} +spec: + {{- $servers := dict "Values" $gateway.server_config "default" .Values.defaults.server_config }} + {{- include "vald.servicePorts" $servers | nindent 2 }} + selector: + app.kubernetes.io/name: {{ include "vald.name" . }} + app.kubernetes.io/component: gateway-mirror + {{- if eq $gateway.serviceType "ClusterIP" }} + clusterIP: None + {{- end }} + type: {{ $gateway.serviceType }} + {{- if $gateway.externalTrafficPolicy }} + externalTrafficPolicy: {{ $gateway.externalTrafficPolicy }} + {{- end }} + {{- if $gateway.internalTrafficPolicy }} + internalTrafficPolicy: {{ $gateway.internalTrafficPolicy }} + {{- end }} +{{- end }} diff --git a/charts/vald/values.schema.json b/charts/vald/values.schema.json index f225cea11d..41cd419a73 100644 --- a/charts/vald/values.schema.json +++ b/charts/vald/values.schema.json @@ -10056,6 +10056,1795 @@ "items": { "type": "object" } } } + }, + "mirror": { + "type": "object", + "properties": { + "affinity": { + "type": "object", + "properties": { + "nodeAffinity": { + "type": "object", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array", + "description": "node affinity preferred scheduling terms", + "items": { "type": "object" } + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "object", + "properties": { + "nodeSelectorTerms": { + "type": "array", + "description": "node affinity required node selectors", + "items": { "type": "object" } + } + } + } + } + }, + "podAffinity": { + "type": "object", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array", + "description": "pod affinity preferred scheduling terms", + "items": { "type": "object" } + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array", + "description": "pod affinity required scheduling terms", + "items": { "type": "object" } + } + } + }, + "podAntiAffinity": { + "type": "object", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "type": "array", + "description": "pod anti-affinity preferred scheduling terms", + "items": { "type": "object" } + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "type": "array", + "description": "pod anti-affinity required scheduling terms", + "items": { "type": "object" } + } + } + } + } + }, + "annotations": { + "type": "object", + "description": "deployment annotations" + }, + "clusterRole": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "creates clusterRole resource" + }, + "name": { + "type": "string", + "description": "name of clusterRole" + } + } + }, + "clusterRoleBinding": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "creates clusterRoleBinding resource" + }, + "name": { + "type": "string", + "description": "name of clusterRoleBinding" + } + } + }, + "enabled": { "type": "boolean", "description": "gateway enabled" }, + "env": { + "type": "array", + "description": "environment variables", + "items": { "type": "object" } + }, + "externalTrafficPolicy": { + "type": "string", + "description": "external traffic policy (can be specified when service type is LoadBalancer or NodePort) : Cluster or Local" + }, + "gateway_config": { + "type": "object", + "properties": { + "client": { + "type": "object", + "properties": { + "addrs": { + "type": "array", + "description": "gRPC client addresses", + "items": { "type": "string" } + }, + "backoff": { + "type": "object", + "properties": { + "backoff_factor": { + "type": "number", + "description": "gRPC client backoff factor" + }, + "backoff_time_limit": { + "type": "string", + "description": "gRPC client backoff time limit" + }, + "enable_error_log": { + "type": "boolean", + "description": "gRPC client backoff log enabled" + }, + "initial_duration": { + "type": "string", + "description": "gRPC client backoff initial duration" + }, + "jitter_limit": { + "type": "string", + "description": "gRPC client backoff jitter limit" + }, + "maximum_duration": { + "type": "string", + "description": "gRPC client backoff maximum duration" + }, + "retry_count": { + "type": "integer", + "description": "gRPC client backoff retry count" + } + } + }, + "call_option": { "type": "object" }, + "circuit_breaker": { + "type": "object", + "properties": { + "closed_error_rate": { + "type": "number", + "description": "gRPC client circuitbreaker closed error rate" + }, + "closed_refresh_timeout": { + "type": "string", + "description": "gRPC client circuitbreaker closed refresh timeout" + }, + "half_open_error_rate": { + "type": "number", + "description": "gRPC client circuitbreaker half-open error rate" + }, + "min_samples": { + "type": "integer", + "description": "gRPC client circuitbreaker minimum sampling count" + }, + "open_timeout": { + "type": "string", + "description": "gRPC client circuitbreaker open timeout" + } + } + }, + "connection_pool": { + "type": "object", + "properties": { + "enable_dns_resolver": { + "type": "boolean", + "description": "enables gRPC client connection pool dns resolver, when enabled vald uses ip handshake exclude dns discovery which improves network performance" + }, + "enable_rebalance": { + "type": "boolean", + "description": "enables gRPC client connection pool rebalance" + }, + "old_conn_close_duration": { + "type": "string", + "description": "makes delay before gRPC client connection closing during connection pool rebalance" + }, + "rebalance_duration": { + "type": "string", + "description": "gRPC client connection pool rebalance duration" + }, + "size": { + "type": "integer", + "description": "gRPC client connection pool size" + } + } + }, + "dial_option": { + "type": "object", + "properties": { + "backoff_base_delay": { + "type": "string", + "description": "gRPC client dial option base backoff delay" + }, + "backoff_jitter": { + "type": "number", + "description": "gRPC client dial option base backoff delay" + }, + "backoff_max_delay": { + "type": "string", + "description": "gRPC client dial option max backoff delay" + }, + "backoff_multiplier": { + "type": "number", + "description": "gRPC client dial option base backoff delay" + }, + "enable_backoff": { + "type": "boolean", + "description": "gRPC client dial option backoff enabled" + }, + "initial_connection_window_size": { + "type": "integer", + "description": "gRPC client dial option initial connection window size" + }, + "initial_window_size": { + "type": "integer", + "description": "gRPC client dial option initial window size" + }, + "insecure": { + "type": "boolean", + "description": "gRPC client dial option insecure enabled" + }, + "interceptors": { + "type": "array", + "description": "gRPC client interceptors", + "items": { + "type": "string", + "enum": ["TraceInterceptor"] + } + }, + "keepalive": { + "type": "object", + "properties": { + "permit_without_stream": { + "type": "boolean", + "description": "gRPC client keep alive permit without stream" + }, + "time": { + "type": "string", + "description": "gRPC client keep alive time" + }, + "timeout": { + "type": "string", + "description": "gRPC client keep alive timeout" + } + } + }, + "max_msg_size": { + "type": "integer", + "description": "gRPC client dial option max message size" + }, + "min_connection_timeout": { + "type": "string", + "description": "gRPC client dial option minimum connection timeout" + }, + "net": { + "type": "object", + "properties": { + "dialer": { + "type": "object", + "properties": { + "dual_stack_enabled": { + "type": "boolean", + "description": "gRPC client TCP dialer dual stack enabled" + }, + "keepalive": { + "type": "string", + "description": "gRPC client TCP dialer keep alive" + }, + "timeout": { + "type": "string", + "description": "gRPC client TCP dialer timeout" + } + } + }, + "dns": { + "type": "object", + "properties": { + "cache_enabled": { + "type": "boolean", + "description": "gRPC client TCP DNS cache enabled" + }, + "cache_expiration": { + "type": "string", + "description": "gRPC client TCP DNS cache expiration" + }, + "refresh_duration": { + "type": "string", + "description": "gRPC client TCP DNS cache refresh duration" + } + } + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "tls": { + "type": "object", + "properties": { + "ca": { + "type": "string", + "description": "TLS ca path" + }, + "cert": { + "type": "string", + "description": "TLS cert path" + }, + "enabled": { + "type": "boolean", + "description": "TLS enabled" + }, + "insecure_skip_verify": { + "type": "boolean", + "description": "enable/disable skip SSL certificate verification" + }, + "key": { + "type": "string", + "description": "TLS key path" + } + } + } + } + }, + "read_buffer_size": { + "type": "integer", + "description": "gRPC client dial option read buffer size" + }, + "timeout": { + "type": "string", + "description": "gRPC client dial option timeout" + }, + "write_buffer_size": { + "type": "integer", + "description": "gRPC client dial option write buffer size" + } + } + }, + "health_check_duration": { + "type": "string", + "description": "gRPC client health check duration" + }, + "max_recv_msg_size": { "type": "integer" }, + "max_retry_rpc_buffer_size": { "type": "integer" }, + "max_send_msg_size": { "type": "integer" }, + "tls": { + "type": "object", + "properties": { + "ca": { + "type": "string", + "description": "TLS ca path" + }, + "cert": { + "type": "string", + "description": "TLS cert path" + }, + "enabled": { + "type": "boolean", + "description": "TLS enabled" + }, + "insecure_skip_verify": { + "type": "boolean", + "description": "enable/disable skip SSL certificate verification" + }, + "key": { + "type": "string", + "description": "TLS key path" + } + } + }, + "wait_for_ready": { "type": "boolean" } + } + }, + "colocation": { + "type": "string", + "description": "colocation name" + }, + "discovery_duration": { + "type": "string", + "description": "duration to discovery" + }, + "gateway_addr": { + "type": "string", + "description": "address for lb-gateway" + }, + "group": { + "type": "string", + "description": "mirror group name" + }, + "namespace": { + "type": "string", + "description": "namespace to discovery" + }, + "net": { + "type": "object", + "properties": { + "dialer": { + "type": "object", + "properties": { + "dual_stack_enabled": { + "type": "boolean", + "description": "gRPC client TCP dialer dual stack enabled" + }, + "keepalive": { + "type": "string", + "description": "gRPC client TCP dialer keep alive" + }, + "timeout": { + "type": "string", + "description": "gRPC client TCP dialer timeout" + } + } + }, + "dns": { + "type": "object", + "properties": { + "cache_enabled": { + "type": "boolean", + "description": "gRPC client TCP DNS cache enabled" + }, + "cache_expiration": { + "type": "string", + "description": "gRPC client TCP DNS cache expiration" + }, + "refresh_duration": { + "type": "string", + "description": "gRPC client TCP DNS cache refresh duration" + } + } + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "tls": { + "type": "object", + "properties": { + "ca": { + "type": "string", + "description": "TLS ca path" + }, + "cert": { + "type": "string", + "description": "TLS cert path" + }, + "enabled": { + "type": "boolean", + "description": "TLS enabled" + }, + "insecure_skip_verify": { + "type": "boolean", + "description": "enable/disable skip SSL certificate verification" + }, + "key": { + "type": "string", + "description": "TLS key path" + } + } + } + } + }, + "pod_name": { + "type": "string", + "description": "self mirror gateway pod name" + }, + "register_duration": { + "type": "string", + "description": "duration to register mirror-gateway." + }, + "self_mirror_addr": { + "type": "string", + "description": "address for self mirror-gateway" + } + } + }, + "hpa": { + "type": "object", + "properties": { + "enabled": { "type": "boolean", "description": "HPA enabled" }, + "targetCPUUtilizationPercentage": { + "type": "integer", + "description": "HPA CPU utilization percentage" + } + } + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string", + "description": "image pull policy", + "enum": ["Always", "Never", "IfNotPresent"] + }, + "repository": { + "type": "string", + "description": "image repository" + }, + "tag": { + "type": "string", + "description": "image tag (overrides defaults.image.tag)" + } + } + }, + "ingress": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "description": "annotations for ingress" + }, + "defaultBackend": { + "type": "object", + "description": "defaultBackend config", + "properties": { + "enabled": { + "type": "boolean", + "description": "gateway ingress defaultBackend enabled" + } + } + }, + "enabled": { + "type": "boolean", + "description": "gateway ingress enabled" + }, + "host": { "type": "string", "description": "ingress hostname" }, + "pathType": { + "type": "string", + "description": "gateway ingress pathType" + }, + "servicePort": { + "type": "string", + "description": "service port to be exposed by ingress" + } + } + }, + "initContainers": { + "type": "array", + "description": "init containers", + "items": { "type": "object" } + }, + "internalTrafficPolicy": { + "type": "string", + "description": "internal traffic policy (can be specified when service type is LoadBalancer or NodePort) : Cluster or Local" + }, + "kind": { + "type": "string", + "description": "deployment kind: Deployment or DaemonSet", + "enum": ["Deployment", "DaemonSet"] + }, + "logging": { + "type": "object", + "properties": { + "format": { + "type": "string", + "description": "logging format. logging format must be `raw` or `json`", + "enum": ["raw", "json"] + }, + "level": { + "type": "string", + "description": "logging level. logging level must be `debug`, `info`, `warn`, `error` or `fatal`.", + "enum": ["debug", "info", "warn", "error", "fatal"] + }, + "logger": { + "type": "string", + "description": "logger name. currently logger must be `glg` or `zap`.", + "enum": ["glg", "zap"] + } + } + }, + "maxReplicas": { + "type": "integer", + "description": "maximum number of replicas. if HPA is disabled, this value will be ignored.", + "minimum": 0 + }, + "maxUnavailable": { + "type": "string", + "description": "maximum number of unavailable replicas" + }, + "minReplicas": { + "type": "integer", + "description": "minimum number of replicas. if HPA is disabled, the replicas will be set to this value", + "minimum": 0 + }, + "name": { + "type": "string", + "description": "name of gateway deployment" + }, + "nodeName": { "type": "string", "description": "node name" }, + "nodeSelector": { + "type": "object", + "description": "node selector" + }, + "observability": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "observability features enabled" + }, + "metrics": { + "type": "object", + "properties": { + "enable_cgo": { + "type": "boolean", + "description": "CGO metrics enabled" + }, + "enable_goroutine": { + "type": "boolean", + "description": "goroutine metrics enabled" + }, + "enable_memory": { + "type": "boolean", + "description": "memory metrics enabled" + }, + "enable_version_info": { + "type": "boolean", + "description": "version info metrics enabled" + }, + "version_info_labels": { + "type": "array", + "description": "enabled label names of version info", + "items": { + "type": "string", + "enum": [ + "vald_version", + "server_name", + "git_commit", + "build_time", + "go_version", + "go_os", + "go_arch", + "cgo_enabled", + "ngt_version", + "build_cpu_info_flags" + ] + } + } + } + }, + "otlp": { + "type": "object", + "properties": { + "attribute": { + "type": "object", + "description": "default resource attribute", + "properties": { + "namespace": { + "type": "string", + "description": "namespace" + }, + "node_name": { + "type": "string", + "description": "node name" + }, + "pod_name": { + "type": "string", + "description": "pod name" + }, + "service_name": { + "type": "string", + "description": "service name" + } + } + }, + "collector_endpoint": { + "type": "string", + "description": "OpenTelemetry Collector endpoint" + }, + "metrics_export_interval": { + "type": "string", + "description": "metrics export interval" + }, + "metrics_export_timeout": { + "type": "string", + "description": "metrics export timeout" + }, + "trace_batch_timeout": { + "type": "string", + "description": "trace batch timeout" + }, + "trace_export_timeout": { + "type": "string", + "description": "trace export timeout" + }, + "trace_max_export_batch_size": { + "type": "integer", + "description": "trace maximum export batch size" + }, + "trace_max_queue_size": { + "type": "integer", + "description": "trace maximum queue size" + } + } + }, + "trace": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "trace enabled" + } + } + } + } + }, + "podAnnotations": { + "type": "object", + "description": "pod annotations" + }, + "podPriority": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "gateway pod PriorityClass enabled" + }, + "value": { + "type": "integer", + "description": "gateway pod PriorityClass value" + } + } + }, + "podSecurityContext": { + "type": "object", + "description": "security context for pod" + }, + "progressDeadlineSeconds": { + "type": "integer", + "description": "progress deadline seconds" + }, + "resources": { + "type": "object", + "description": "compute resources", + "properties": { + "limits": { "type": "object" }, + "requests": { "type": "object" } + } + }, + "revisionHistoryLimit": { + "type": "integer", + "description": "number of old history to retain to allow rollback", + "minimum": 0 + }, + "rollingUpdate": { + "type": "object", + "properties": { + "maxSurge": { + "type": "string", + "description": "max surge of rolling update" + }, + "maxUnavailable": { + "type": "string", + "description": "max unavailable of rolling update" + } + } + }, + "securityContext": { + "type": "object", + "description": "security context for container" + }, + "server_config": { + "type": "object", + "properties": { + "full_shutdown_duration": { + "type": "string", + "description": "server full shutdown duration" + }, + "healths": { + "type": "object", + "properties": { + "liveness": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "liveness server enabled" + }, + "host": { + "type": "string", + "description": "liveness server host" + }, + "livenessProbe": { + "type": "object", + "properties": { + "failureThreshold": { + "type": "integer", + "description": "liveness probe failure threshold" + }, + "httpGet": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "liveness probe path" + }, + "port": { + "type": "string", + "description": "liveness probe port" + }, + "scheme": { + "type": "string", + "description": "liveness probe scheme" + } + } + }, + "initialDelaySeconds": { + "type": "integer", + "description": "liveness probe initial delay seconds" + }, + "periodSeconds": { + "type": "integer", + "description": "liveness probe period seconds" + }, + "successThreshold": { + "type": "integer", + "description": "liveness probe success threshold" + }, + "timeoutSeconds": { + "type": "integer", + "description": "liveness probe timeout seconds" + } + } + }, + "port": { + "type": "integer", + "description": "liveness server port", + "maximum": 65535, + "minimum": 0 + }, + "server": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "handler_timeout": { + "type": "string", + "description": "REST server handler timeout" + }, + "idle_timeout": { + "type": "string", + "description": "REST server idle timeout" + }, + "read_header_timeout": { + "type": "string", + "description": "REST server read header timeout" + }, + "read_timeout": { + "type": "string", + "description": "REST server read timeout" + }, + "shutdown_duration": { + "type": "string", + "description": "REST server shutdown duration" + }, + "write_timeout": { + "type": "string", + "description": "REST server write timeout" + } + } + }, + "mode": { + "type": "string", + "description": "REST server server mode" + }, + "network": { + "type": "string", + "description": "mysql network", + "enum": [ + "tcp", + "tcp4", + "tcp6", + "udp", + "udp4", + "udp6", + "unix", + "unixgram", + "unixpacket" + ] + }, + "probe_wait_time": { + "type": "string", + "description": "REST server probe wait time" + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "socket_path": { + "type": "string", + "description": "mysql socket_path" + } + } + }, + "servicePort": { + "type": "integer", + "description": "liveness server service port", + "maximum": 65535, + "minimum": 0 + } + } + }, + "readiness": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "readiness server enabled" + }, + "host": { + "type": "string", + "description": "readiness server host" + }, + "port": { + "type": "integer", + "description": "readiness server port", + "maximum": 65535, + "minimum": 0 + }, + "readinessProbe": { + "type": "object", + "properties": { + "failureThreshold": { + "type": "integer", + "description": "readiness probe failure threshold" + }, + "httpGet": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "readiness probe path" + }, + "port": { + "type": "string", + "description": "readiness probe port" + }, + "scheme": { + "type": "string", + "description": "readiness probe scheme" + } + } + }, + "initialDelaySeconds": { + "type": "integer", + "description": "readiness probe initial delay seconds" + }, + "periodSeconds": { + "type": "integer", + "description": "readiness probe period seconds" + }, + "successThreshold": { + "type": "integer", + "description": "readiness probe success threshold" + }, + "timeoutSeconds": { + "type": "integer", + "description": "readiness probe timeout seconds" + } + } + }, + "server": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "handler_timeout": { + "type": "string", + "description": "REST server handler timeout" + }, + "idle_timeout": { + "type": "string", + "description": "REST server idle timeout" + }, + "read_header_timeout": { + "type": "string", + "description": "REST server read header timeout" + }, + "read_timeout": { + "type": "string", + "description": "REST server read timeout" + }, + "shutdown_duration": { + "type": "string", + "description": "REST server shutdown duration" + }, + "write_timeout": { + "type": "string", + "description": "REST server write timeout" + } + } + }, + "mode": { + "type": "string", + "description": "REST server server mode" + }, + "network": { + "type": "string", + "description": "mysql network", + "enum": [ + "tcp", + "tcp4", + "tcp6", + "udp", + "udp4", + "udp6", + "unix", + "unixgram", + "unixpacket" + ] + }, + "probe_wait_time": { + "type": "string", + "description": "REST server probe wait time" + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "socket_path": { + "type": "string", + "description": "mysql socket_path" + } + } + }, + "servicePort": { + "type": "integer", + "description": "readiness server service port", + "maximum": 65535, + "minimum": 0 + } + } + }, + "startup": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "startup server enabled" + }, + "port": { + "type": "integer", + "description": "startup server port", + "maximum": 65535, + "minimum": 0 + }, + "startupProbe": { + "type": "object", + "properties": { + "failureThreshold": { + "type": "integer", + "description": "startup probe failure threshold" + }, + "httpGet": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "startup probe path" + }, + "port": { + "type": "string", + "description": "startup probe port" + }, + "scheme": { + "type": "string", + "description": "startup probe scheme" + } + } + }, + "initialDelaySeconds": { + "type": "integer", + "description": "startup probe initial delay seconds" + }, + "periodSeconds": { + "type": "integer", + "description": "startup probe period seconds" + }, + "successThreshold": { + "type": "integer", + "description": "startup probe success threshold" + }, + "timeoutSeconds": { + "type": "integer", + "description": "startup probe timeout seconds" + } + } + } + } + } + } + }, + "metrics": { + "type": "object", + "properties": { + "pprof": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "pprof server enabled" + }, + "host": { + "type": "string", + "description": "pprof server host" + }, + "port": { + "type": "integer", + "description": "pprof server port", + "maximum": 65535, + "minimum": 0 + }, + "server": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "handler_timeout": { + "type": "string", + "description": "REST server handler timeout" + }, + "idle_timeout": { + "type": "string", + "description": "REST server idle timeout" + }, + "read_header_timeout": { + "type": "string", + "description": "REST server read header timeout" + }, + "read_timeout": { + "type": "string", + "description": "REST server read timeout" + }, + "shutdown_duration": { + "type": "string", + "description": "REST server shutdown duration" + }, + "write_timeout": { + "type": "string", + "description": "REST server write timeout" + } + } + }, + "mode": { + "type": "string", + "description": "REST server server mode" + }, + "network": { + "type": "string", + "description": "mysql network", + "enum": [ + "tcp", + "tcp4", + "tcp6", + "udp", + "udp4", + "udp6", + "unix", + "unixgram", + "unixpacket" + ] + }, + "probe_wait_time": { + "type": "string", + "description": "REST server probe wait time" + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "socket_path": { + "type": "string", + "description": "mysql socket_path" + } + } + }, + "servicePort": { + "type": "integer", + "description": "pprof server service port", + "maximum": 65535, + "minimum": 0 + } + } + } + } + }, + "servers": { + "type": "object", + "properties": { + "grpc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "gRPC server enabled" + }, + "host": { + "type": "string", + "description": "gRPC server host" + }, + "port": { + "type": "integer", + "description": "gRPC server port", + "maximum": 65535, + "minimum": 0 + }, + "server": { + "type": "object", + "properties": { + "grpc": { + "type": "object", + "properties": { + "bidirectional_stream_concurrency": { + "type": "integer", + "description": "gRPC server bidirectional stream concurrency" + }, + "connection_timeout": { + "type": "string", + "description": "gRPC server connection timeout" + }, + "enable_reflection": { + "type": "boolean", + "description": "gRPC server reflection option" + }, + "header_table_size": { + "type": "integer", + "description": "gRPC server header table size" + }, + "initial_conn_window_size": { + "type": "integer", + "description": "gRPC server initial connection window size" + }, + "initial_window_size": { + "type": "integer", + "description": "gRPC server initial window size" + }, + "interceptors": { + "type": "array", + "description": "gRPC server interceptors", + "items": { + "type": "string", + "enum": [ + "RecoverInterceptor", + "AccessLogInterceptor", + "TraceInterceptor", + "MetricInterceptor" + ] + } + }, + "keepalive": { + "type": "object", + "properties": { + "max_conn_age": { + "type": "string", + "description": "gRPC server keep alive max connection age" + }, + "max_conn_age_grace": { + "type": "string", + "description": "gRPC server keep alive max connection age grace" + }, + "max_conn_idle": { + "type": "string", + "description": "gRPC server keep alive max connection idle" + }, + "min_time": { + "type": "string", + "description": "gRPC server keep alive min_time" + }, + "permit_without_stream": { + "type": "boolean", + "description": "gRPC server keep alive permit_without_stream" + }, + "time": { + "type": "string", + "description": "gRPC server keep alive time" + }, + "timeout": { + "type": "string", + "description": "gRPC server keep alive timeout" + } + } + }, + "max_header_list_size": { + "type": "integer", + "description": "gRPC server max header list size" + }, + "max_receive_message_size": { + "type": "integer", + "description": "gRPC server max receive message size" + }, + "max_send_message_size": { + "type": "integer", + "description": "gRPC server max send message size" + }, + "read_buffer_size": { + "type": "integer", + "description": "gRPC server read buffer size" + }, + "write_buffer_size": { + "type": "integer", + "description": "gRPC server write buffer size" + } + } + }, + "mode": { + "type": "string", + "description": "gRPC server server mode" + }, + "network": { + "type": "string", + "description": "mysql network", + "enum": [ + "tcp", + "tcp4", + "tcp6", + "udp", + "udp4", + "udp6", + "unix", + "unixgram", + "unixpacket" + ] + }, + "probe_wait_time": { + "type": "string", + "description": "gRPC server probe wait time" + }, + "restart": { + "type": "boolean", + "description": "gRPC server restart" + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "socket_path": { + "type": "string", + "description": "mysql socket_path" + } + } + }, + "servicePort": { + "type": "integer", + "description": "gRPC server service port", + "maximum": 65535, + "minimum": 0 + } + } + }, + "rest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "REST server enabled" + }, + "host": { + "type": "string", + "description": "REST server host" + }, + "port": { + "type": "integer", + "description": "REST server port", + "maximum": 65535, + "minimum": 0 + }, + "server": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "handler_timeout": { + "type": "string", + "description": "REST server handler timeout" + }, + "idle_timeout": { + "type": "string", + "description": "REST server idle timeout" + }, + "read_header_timeout": { + "type": "string", + "description": "REST server read header timeout" + }, + "read_timeout": { + "type": "string", + "description": "REST server read timeout" + }, + "shutdown_duration": { + "type": "string", + "description": "REST server shutdown duration" + }, + "write_timeout": { + "type": "string", + "description": "REST server write timeout" + } + } + }, + "mode": { + "type": "string", + "description": "REST server server mode" + }, + "network": { + "type": "string", + "description": "mysql network", + "enum": [ + "tcp", + "tcp4", + "tcp6", + "udp", + "udp4", + "udp6", + "unix", + "unixgram", + "unixpacket" + ] + }, + "probe_wait_time": { + "type": "string", + "description": "REST server probe wait time" + }, + "socket_option": { + "type": "object", + "properties": { + "ip_recover_destination_addr": { + "type": "boolean", + "description": "server listen socket option for ip_recover_destination_addr functionality" + }, + "ip_transparent": { + "type": "boolean", + "description": "server listen socket option for ip_transparent functionality" + }, + "reuse_addr": { + "type": "boolean", + "description": "server listen socket option for reuse_addr functionality" + }, + "reuse_port": { + "type": "boolean", + "description": "server listen socket option for reuse_port functionality" + }, + "tcp_cork": { + "type": "boolean", + "description": "server listen socket option for tcp_cork functionality" + }, + "tcp_defer_accept": { + "type": "boolean", + "description": "server listen socket option for tcp_defer_accept functionality" + }, + "tcp_fast_open": { + "type": "boolean", + "description": "server listen socket option for tcp_fast_open functionality" + }, + "tcp_no_delay": { + "type": "boolean", + "description": "server listen socket option for tcp_no_delay functionality" + }, + "tcp_quick_ack": { + "type": "boolean", + "description": "server listen socket option for tcp_quick_ack functionality" + } + } + }, + "socket_path": { + "type": "string", + "description": "mysql socket_path" + } + } + }, + "servicePort": { + "type": "integer", + "description": "REST server service port", + "maximum": 65535, + "minimum": 0 + } + } + } + } + }, + "tls": { + "type": "object", + "properties": { + "ca": { "type": "string", "description": "TLS ca path" }, + "cert": { + "type": "string", + "description": "TLS cert path" + }, + "enabled": { + "type": "boolean", + "description": "TLS enabled" + }, + "insecure_skip_verify": { + "type": "boolean", + "description": "enable/disable skip SSL certificate verification" + }, + "key": { "type": "string", "description": "TLS key path" } + } + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "description": "service annotations" + }, + "labels": { "type": "object", "description": "service labels" } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "creates service account" + }, + "name": { + "type": "string", + "description": "name of service account" + } + } + }, + "serviceType": { + "type": "string", + "description": "service type: ClusterIP, LoadBalancer or NodePort", + "enum": ["ClusterIP", "LoadBalancer", "NodePort"] + }, + "terminationGracePeriodSeconds": { + "type": "integer", + "description": "duration in seconds pod needs to terminate gracefully", + "minimum": 0 + }, + "time_zone": { "type": "string", "description": "Time zone" }, + "tolerations": { + "type": "array", + "description": "tolerations", + "items": { "type": "object" } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "topology spread constraints of gateway pods", + "items": { "type": "object" } + }, + "version": { + "type": "string", + "description": "version of gateway config", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]$" + }, + "volumeMounts": { + "type": "array", + "description": "volume mounts", + "items": { "type": "object" } + }, + "volumes": { + "type": "array", + "description": "volumes", + "items": { "type": "object" } + } + } } } }, diff --git a/charts/vald/values.yaml b/charts/vald/values.yaml index d7e6cfa666..312fc09df7 100644 --- a/charts/vald/values.yaml +++ b/charts/vald/values.yaml @@ -1412,6 +1412,355 @@ gateway: # @schema {"name": "gateway.filter.gateway_config.egress_filter.distance_filters", "type": "array", "items": {"type": "string"}} # gateway.filter.gateway_config.egress_filter.distance_filters -- distance egress vector filter targets distance_filters: [] + # @schema {"name": "gateway.mirror", "type": "object"} + mirror: + # @schema {"name": "gateway.mirror.enabled", "type": "boolean"} + # gateway.mirror.enabled -- gateway enabled + enabled: false + # @schema {"name": "gateway.mirror.version", "type": "string", "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]$", "anchor": "version"} + # gateway.mirror.version -- version of gateway config + version: v0.0.0 + # @schema {"name": "gateway.mirror.time_zone", "type": "string"} + # gateway.mirror.time_zone -- Time zone + time_zone: "" + # @schema {"name": "gateway.mirror.logging", "alias": "logging"} + # gateway.mirror.logging -- logging config (overrides defaults.logging) + logging: {} + # @schema {"name": "gateway.mirror.name", "type": "string"} + # gateway.mirror.name -- name of gateway deployment + name: vald-mirror-gateway + # @schema {"name": "gateway.mirror.kind", "type": "string", "enum": ["Deployment", "DaemonSet"]} + # gateway.mirror.kind -- deployment kind: Deployment or DaemonSet + kind: Deployment + # @schema {"name": "gateway.mirror.serviceType", "type": "string", "enum": ["ClusterIP", "LoadBalancer", "NodePort"]} + # gateway.mirror.serviceType -- service type: ClusterIP, LoadBalancer or NodePort + serviceType: ClusterIP + # @schema {"name": "gateway.mirror.externalTrafficPolicy", "type": "string"} + # gateway.mirror.externalTrafficPolicy -- external traffic policy (can be specified when service type is LoadBalancer or NodePort) : Cluster or Local + externalTrafficPolicy: "" + # @schema {"name": "gateway.mirror.internalTrafficPolicy", "type": "string"} + # gateway.mirror.internalTrafficPolicy -- internal traffic policy (can be specified when service type is LoadBalancer or NodePort) : Cluster or Local + internalTrafficPolicy: "" + # @schema {"name": "gateway.mirror.progressDeadlineSeconds", "type": "integer"} + # gateway.mirror.progressDeadlineSeconds -- progress deadline seconds + progressDeadlineSeconds: 600 + # @schema {"name": "gateway.mirror.minReplicas", "type": "integer", "minimum": 0} + # gateway.mirror.minReplicas -- minimum number of replicas. + # if HPA is disabled, the replicas will be set to this value + minReplicas: 3 + # @schema {"name": "gateway.mirror.maxReplicas", "type": "integer", "minimum": 0} + # gateway.mirror.maxReplicas -- maximum number of replicas. + # if HPA is disabled, this value will be ignored. + maxReplicas: 9 + # @schema {"name": "gateway.mirror.maxUnavailable", "type": "string"} + # gateway.mirror.maxUnavailable -- maximum number of unavailable replicas + maxUnavailable: 50% + # @schema {"name": "gateway.mirror.revisionHistoryLimit", "type": "integer", "minimum": 0} + # gateway.mirror.revisionHistoryLimit -- number of old history to retain to allow rollback + revisionHistoryLimit: 2 + # @schema {"name": "gateway.mirror.terminationGracePeriodSeconds", "type": "integer", "minimum": 0} + # gateway.mirror.terminationGracePeriodSeconds -- duration in seconds pod needs to terminate gracefully + terminationGracePeriodSeconds: 30 + # @schema {"name": "gateway.mirror.podSecurityContext", "type": "object"} + # gateway.mirror.podSecurityContext -- security context for pod + podSecurityContext: + runAsUser: 65532 + runAsNonRoot: true + runAsGroup: 65532 + fsGroup: 65532 + fsGroupChangePolicy: "OnRootMismatch" + # @schema {"name": "gateway.mirror.securityContext", "type": "object"} + # gateway.mirror.securityContext -- security context for container + securityContext: + runAsUser: 65532 + runAsNonRoot: true + runAsGroup: 65532 + privileged: false + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + # @schema {"name": "gateway.mirror.podPriority", "type": "object", "anchor": "podPriority"} + podPriority: + # @schema {"name": "gateway.mirror.podPriority.enabled", "type": "boolean"} + # gateway.mirror.podPriority.enabled -- gateway pod PriorityClass enabled + enabled: true + # @schema {"name": "gateway.mirror.podPriority.value", "type": "integer"} + # gateway.mirror.podPriority.value -- gateway pod PriorityClass value + value: 1000000 + # @schema {"name": "gateway.mirror.annotations", "type": "object"} + # gateway.mirror.annotations -- deployment annotations + annotations: {} + # @schema {"name": "gateway.mirror.podAnnotations", "type": "object"} + # gateway.mirror.podAnnotations -- pod annotations + podAnnotations: {} + # @schema {"name": "gateway.mirror.service", "type": "object", "anchor": "service"} + service: + # @schema {"name": "gateway.mirror.service.annotations", "type": "object"} + # gateway.mirror.service.annotations -- service annotations + annotations: {} + # @schema {"name": "gateway.mirror.service.labels", "type": "object"} + # gateway.mirror.service.labels -- service labels + labels: {} + # @schema {"name": "gateway.mirror.hpa", "type": "object", "anchor": "hpa"} + hpa: + # @schema {"name": "gateway.mirror.hpa.enabled", "type": "boolean"} + # gateway.mirror.hpa.enabled -- HPA enabled + enabled: true + # @schema {"name": "gateway.mirror.hpa.targetCPUUtilizationPercentage", "type": "integer"} + # gateway.mirror.hpa.targetCPUUtilizationPercentage -- HPA CPU utilization percentage + targetCPUUtilizationPercentage: 80 + # @schema {"name": "gateway.mirror.image", "type": "object", "anchor": "image"} + image: + # @schema {"name": "gateway.mirror.image.repository", "type": "string"} + # gateway.mirror.image.repository -- image repository + repository: vdaas/vald-mirror-gateway + # @schema {"name": "gateway.mirror.image.tag", "type": "string"} + # gateway.mirror.image.tag -- image tag (overrides defaults.image.tag) + tag: "" + # @schema {"name": "gateway.mirror.image.pullPolicy", "type": "string", "enum": ["Always", "Never", "IfNotPresent"]} + # gateway.mirror.image.pullPolicy -- image pull policy + pullPolicy: Always + # @schema {"name": "gateway.mirror.rollingUpdate", "type": "object", "anchor": "rollingUpdate"} + rollingUpdate: + # @schema {"name": "gateway.mirror.rollingUpdate.maxSurge", "type": "string"} + # gateway.mirror.rollingUpdate.maxSurge -- max surge of rolling update + maxSurge: 25% + # @schema {"name": "gateway.mirror.rollingUpdate.maxUnavailable", "type": "string"} + # gateway.mirror.rollingUpdate.maxUnavailable -- max unavailable of rolling update + maxUnavailable: 25% + # @schema {"name": "gateway.mirror.initContainers", "type": "array", "items": {"type": "object"}, "anchor": "initContainers"} + # gateway.mirror.initContainers -- init containers + initContainers: + - type: wait-for + name: wait-for-gateway-lb + target: gateway-lb + image: busybox:stable + sleepDuration: 2 + # @schema {"name": "gateway.mirror.env", "type": "array", "items": {"type": "object"}, "anchor": "env"} + # gateway.mirror.env -- environment variables + env: + - name: MY_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # @schema {"name": "gateway.mirror.volumeMounts", "type": "array", "items": {"type": "object"}, "anchor": "volumeMounts"} + # gateway.mirror.volumeMounts -- volume mounts + volumeMounts: [] + # @schema {"name": "gateway.mirror.volumes", "type": "array", "items": {"type": "object"}, "anchor": "volumes"} + # gateway.mirror.volumes -- volumes + volumes: [] + # @schema {"name": "gateway.mirror.nodeName", "type": "string"} + # gateway.mirror.nodeName -- node name + nodeName: "" + # @schema {"name": "gateway.mirror.nodeSelector", "type": "object", "anchor": "nodeSelector"} + # gateway.mirror.nodeSelector -- node selector + nodeSelector: {} + # @schema {"name": "gateway.mirror.tolerations", "type": "array", "items": {"type": "object"}, "anchor": "tolerations"} + # gateway.mirror.tolerations -- tolerations + tolerations: [] + # @schema {"name": "gateway.mirror.affinity", "type": "object", "anchor": "affinity"} + affinity: + # @schema {"name": "gateway.mirror.affinity.nodeAffinity", "type": "object"} + nodeAffinity: + # @schema {"name": "gateway.mirror.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution", "type": "array", "items": {"type": "object"}} + # gateway.mirror.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution -- node affinity preferred scheduling terms + preferredDuringSchedulingIgnoredDuringExecution: [] + # @schema {"name": "gateway.mirror.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution", "type": "object"} + requiredDuringSchedulingIgnoredDuringExecution: + # @schema {"name": "gateway.mirror.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms", "type": "array", "items": {"type": "object"}} + # gateway.mirror.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms -- node affinity required node selectors + nodeSelectorTerms: [] + # @schema {"name": "gateway.mirror.affinity.podAffinity", "type": "object"} + podAffinity: + # @schema {"name": "gateway.mirror.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution", "type": "array", "items": {"type": "object"}} + # gateway.mirror.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution -- pod affinity preferred scheduling terms + preferredDuringSchedulingIgnoredDuringExecution: [] + # @schema {"name": "gateway.mirror.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution", "type": "array", "items": {"type": "object"}} + # gateway.mirror.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution -- pod affinity required scheduling terms + requiredDuringSchedulingIgnoredDuringExecution: [] + # @schema {"name": "gateway.mirror.affinity.podAntiAffinity", "type": "object"} + podAntiAffinity: + # @schema {"name": "gateway.mirror.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution", "type": "array", "items": {"type": "object"}} + # gateway.mirror.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution -- pod anti-affinity preferred scheduling terms + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - vald-mirror-gateway + # @schema {"name": "gateway.mirror.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution", "type": "array", "items": {"type": "object"}} + # gateway.mirror.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution -- pod anti-affinity required scheduling terms + requiredDuringSchedulingIgnoredDuringExecution: [] + # @schema {"name": "gateway.mirror.topologySpreadConstraints", "type": "array", "items": {"type": "object"}, "anchor": "topologySpreadConstraints"} + # gateway.mirror.topologySpreadConstraints -- topology spread constraints of gateway pods + topologySpreadConstraints: [] + # @schema {"name": "gateway.mirror.server_config", "alias": "server_config"} + # gateway.mirror.server_config -- server config (overrides defaults.server_config) + server_config: + servers: + rest: {} + grpc: {} + healths: + liveness: {} + readiness: {} + startup: {} + metrics: + pprof: {} + # @schema {"name": "gateway.mirror.observability", "alias": "observability"} + # gateway.mirror.observability -- observability config (overrides defaults.observability) + observability: + otlp: + attribute: + service_name: vald-mirror-gateway + # @schema {"name": "gateway.mirror.ingress", "type": "object"} + ingress: + # @schema {"name": "gateway.mirror.ingress.pathType", "type": "string"} + # gateway.mirror.ingress.pathType -- gateway ingress pathType + pathType: ImplementationSpecific + # @schema {"name": "gateway.mirror.ingress.enabled", "type": "boolean"} + # gateway.mirror.ingress.enabled -- gateway ingress enabled + enabled: false + # @schema {"name": "gateway.mirror.ingress.annotations", "type": "object"} + # gateway.mirror.ingress.annotations -- annotations for ingress + annotations: + nginx.ingress.kubernetes.io/grpc-backend: "true" + # @schema {"name": "gateway.mirror.ingress.host", "type": "string"} + # gateway.mirror.ingress.host -- ingress hostname + host: mirror.gateway.vald.vdaas.org + # @schema {"name": "gateway.mirror.ingress.servicePort", "type": "string"} + # gateway.mirror.ingress.servicePort -- service port to be exposed by ingress + servicePort: grpc + # @schema {"name": "gateway.mirror.ingress.defaultBackend", "type": "object"} + # gateway.mirror.ingress.defaultBackend -- defaultBackend config + defaultBackend: + # @schema {"name": "gateway.mirror.ingress.defaultBackend.enabled", "type": "boolean"} + # gateway.mirror.ingress.defaultBackend.enabled -- gateway ingress defaultBackend enabled + enabled: true + # @schema {"name": "gateway.mirror.resources", "type": "object", "anchor": "resources"} + # gateway.mirror.resources -- compute resources + resources: + # @schema {"name": "gateway.mirror.resources.requests", "type": "object"} + requests: + cpu: 200m + memory: 150Mi + # @schema {"name": "gateway.mirror.resources.limits", "type": "object"} + limits: + cpu: 2000m + memory: 700Mi + # @schema {"name": "gateway.mirror.gateway_config", "type": "object"} + gateway_config: + # @schema {"name": "gateway.mirror.gateway_config.net", "alias": "net"} + net: + dns: + # gateway.mirror.gateway_config.net.dns.cache_enabled -- TCP DNS cache enabled + cache_enabled: true + # gateway.mirror.gateway_config.net.dns.refresh_duration -- TCP DNS cache refresh duration + refresh_duration: 5m + # gateway.mirror.gateway_config.net.dns.cache_expiration -- TCP DNS cache expiration + cache_expiration: 24h + dialer: + # gateway.mirror.gateway_config.net.dialer.timeout -- TCP dialer timeout + timeout: 30s + # gateway.mirror.gateway_config.net.dialer.keepalive -- TCP dialer keep alive + keepalive: 10m + # gateway.mirror.gateway_config.net.dialer.dual_stack_enabled -- TCP dialer dual stack enabled + dual_stack_enabled: false + tls: + # gateway.mirror.gateway_config.net.tls.enabled -- TLS enabled + enabled: false + # gateway.mirror.gateway_config.net.tls.cert -- TLS cert path + cert: /path/to/cert + # gateway.mirror.gateway_config.net.tls.key -- TLS key path + key: /path/to/key + # gateway.mirror.gateway_config.net.tls.ca -- TLS ca path + ca: /path/to/ca + # gateway.mirror.gateway_config.net.tls.insecure_skip_verify -- enable/disable skip SSL certificate verification + insecure_skip_verify: false + # @schema {"name": "gateway.mirror.gateway_config.net.socket_option", "alias": "socket_option"} + socket_option: + # gateway.mirror.gateway_config.net.socket_option.reuse_port -- server listen socket option for reuse_port functionality + reuse_port: true + # gateway.mirror.gateway_config.net.socket_option.reuse_addr -- server listen socket option for reuse_addr functionality + reuse_addr: true + # gateway.mirror.gateway_config.net.socket_option.tcp_fast_open -- server listen socket option for tcp_fast_open functionality + tcp_fast_open: true + # gateway.mirror.gateway_config.net.socket_option.tcp_no_delay -- server listen socket option for tcp_no_delay functionality + tcp_no_delay: true + # gateway.mirror.gateway_config.net.socket_option.tcp_cork -- server listen socket option for tcp_cork functionality + tcp_cork: false + # gateway.mirror.gateway_config.net.socket_option.tcp_quick_ack -- server listen socket option for tcp_quick_ack functionality + tcp_quick_ack: true + # gateway.mirror.gateway_config.net.socket_option.tcp_defer_accept -- server listen socket option for tcp_defer_accept functionality + tcp_defer_accept: true + # gateway.mirror.gateway_config.net.socket_option.ip_transparent -- server listen socket option for ip_transparent functionality + ip_transparent: false + # gateway.mirror.gateway_config.net.socket_option.ip_recover_destination_addr -- server listen socket option for ip_recover_destination_addr functionality + ip_recover_destination_addr: false + # @schema {"name": "gateway.mirror.gateway_config.client", "alias": "grpc.client"} + # gateway.mirror.gateway_config.client -- gRPC client (overrides defaults.grpc.client) + client: {} + # @schema {"name": "gateway.mirror.gateway_config.self_mirror_addr", "type": "string"} + # gateway.mirror.gateway_config.self_mirror_addr -- address for self mirror-gateway + self_mirror_addr: "" + # @schema {"name": "gateway.mirror.gateway_config.gateway_addr", "type": "string"} + # gateway.mirror.gateway_config.gateway_addr -- address for lb-gateway + gateway_addr: "" + # @schema {"name": "gateway.mirror.gateway_config.pod_name", "type": "string"} + # gateway.mirror.gateway_config.pod_name -- self mirror gateway pod name + pod_name: _MY_POD_NAME_ + # @schema {"name": "gateway.mirror.gateway_config.register_duration", "type": "string"} + # gateway.mirror.gateway_config.register_duration -- duration to register mirror-gateway. + register_duration: "1s" + # @schema {"name": "gateway.mirror.gateway_config.namespace", "type": "string"} + # gateway.mirror.gateway_config.namespace -- namespace to discovery + namespace: _MY_POD_NAMESPACE_ + # @schema {"name": "gateway.mirror.gateway_config.discovery_duration", "type": "string"} + # gateway.mirror.gateway_config.discovery_duration -- duration to discovery + discovery_duration: 1s + # @schema {"name": "gateway.mirror.gateway_config.colocation", "type": "string"} + # gateway.mirror.gateway_config.colocation -- colocation name + colocation: "dc1" + # @schema {"name": "gateway.mirror.gateway_config.group", "type": "string"} + # gateway.mirror.gateway_config.group -- mirror group name + group: "" + # @schema {"name": "gateway.mirror.clusterRole", "type": "object"} + clusterRole: + # @schema {"name": "gateway.mirror.clusterRole.enabled", "type": "boolean"} + # gateway.mirror.clusterRole.enabled -- creates clusterRole resource + enabled: true + # @schema {"name": "gateway.mirror.clusterRole.name", "type": "string"} + # gateway.mirror.clusterRole.name -- name of clusterRole + name: gateway-mirror + # @schema {"name": "gateway.mirror.clusterRoleBinding", "type": "object"} + clusterRoleBinding: + # @schema {"name": "gateway.mirror.clusterRoleBinding.enabled", "type": "boolean"} + # gateway.mirror.clusterRoleBinding.enabled -- creates clusterRoleBinding resource + enabled: true + # @schema {"name": "gateway.mirror.clusterRoleBinding.name", "type": "string"} + # gateway.mirror.clusterRoleBinding.name -- name of clusterRoleBinding + name: gateway-mirror + # @schema {"name": "gateway.mirror.serviceAccount", "type": "object"} + serviceAccount: + # @schema {"name": "gateway.mirror.serviceAccount.enabled", "type": "boolean"} + # gateway.mirror.serviceAccount.enabled -- creates service account + enabled: true + # @schema {"name": "gateway.mirror.serviceAccount.name", "type": "string"} + # gateway.mirror.serviceAccount.name -- name of service account + name: gateway-mirror # @schema {"name": "agent", "type": "object"} agent: # @schema {"name": "agent.enabled", "type": "boolean"} diff --git a/charts/vald/values/multi-vald/dev-vald-01.yaml b/charts/vald/values/multi-vald/dev-vald-01.yaml new file mode 100644 index 0000000000..9c26c7c425 --- /dev/null +++ b/charts/vald/values/multi-vald/dev-vald-01.yaml @@ -0,0 +1,32 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +discoverer: + clusterRoleBinding: + name: vald-01 + serviceAccount: + name: vald-01 + +gateway: + mirror: + enabled: true + clusterRoleBinding: + name: vald-mirror-01 + serviceAccount: + name: vald-mirror-01 + +agent: + ngt: + dimension: 784 diff --git a/charts/vald/values/multi-vald/dev-vald-02.yaml b/charts/vald/values/multi-vald/dev-vald-02.yaml new file mode 100644 index 0000000000..35d03c177b --- /dev/null +++ b/charts/vald/values/multi-vald/dev-vald-02.yaml @@ -0,0 +1,36 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +discoverer: + clusterRole: + enabled: false + clusterRoleBinding: + name: vald-02 + serviceAccount: + name: vald-02 + +gateway: + mirror: + enabled: true + clusterRole: + enabled: false + clusterRoleBinding: + name: vald-mirror-02 + serviceAccount: + name: vald-mirror-02 + +agent: + ngt: + dimension: 784 diff --git a/charts/vald/values/multi-vald/dev-vald-03.yaml b/charts/vald/values/multi-vald/dev-vald-03.yaml new file mode 100644 index 0000000000..4579a564db --- /dev/null +++ b/charts/vald/values/multi-vald/dev-vald-03.yaml @@ -0,0 +1,36 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +discoverer: + clusterRole: + enabled: false + clusterRoleBinding: + name: vald-03 + serviceAccount: + name: vald-03 + +gateway: + mirror: + enabled: true + clusterRole: + enabled: false + clusterRoleBinding: + name: vald-mirror-03 + serviceAccount: + name: vald-mirror-03 + +agent: + ngt: + dimension: 784 diff --git a/charts/vald/values/multi-vald/dev-vald-with-mirror.yaml b/charts/vald/values/multi-vald/dev-vald-with-mirror.yaml new file mode 100644 index 0000000000..b4933f8cbe --- /dev/null +++ b/charts/vald/values/multi-vald/dev-vald-with-mirror.yaml @@ -0,0 +1,100 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +defaults: + image: + tag: pr-2262 # TODO: Delete it later. + server_config: + metrics: + pprof: + enabled: true + servers: + grpc: + server: + grpc: + interceptors: + - RecoverInterceptor + - TraceInterceptor + - AccessLogInterceptor + - MetricInterceptor + grpc: + client: + dial_option: + interceptors: + - TraceInterceptor + observability: + enabled: true + otlp: + collector_endpoint: "opentelemetry-collector-collector.default.svc.cluster.local:4317" + trace: + enabled: true + +gateway: + lb: + podAnnotations: + profefe.com/enable: "true" + profefe.com/port: "6060" + profefe.com/service: "vald-lb-gateway" + resources: + requests: + cpu: 100m + memory: 50Mi + gateway_config: + index_replica: 2 + + mirror: + enabled: true + minReplicas: 3 + maxReplicas: 3 + ingress: + enabled: false + gateway_config: + self_mirror_addr: "" + +agent: + podAnnotations: + profefe.com/enable: "true" + profefe.com/port: "6060" + profefe.com/service: "vald-agent-ngt" + minReplicas: 5 + maxReplicas: 10 + podManagementPolicy: Parallel + resources: + requests: + cpu: 100m + memory: 50Mi + ngt: + dimension: 784 + +discoverer: + podAnnotations: + profefe.com/enable: "true" + profefe.com/port: "6060" + profefe.com/service: "vald-discoverer" + resources: + requests: + cpu: 100m + memory: 50Mi + +manager: + index: + podAnnotations: + profefe.com/enable: "true" + profefe.com/port: "6060" + profefe.com/service: "vald-manager-index" + resources: + requests: + cpu: 100m + memory: 30Mi diff --git a/charts/vald/values/multi-vald/mirror-target.yaml b/charts/vald/values/multi-vald/mirror-target.yaml new file mode 100644 index 0000000000..f5f49cf3a9 --- /dev/null +++ b/charts/vald/values/multi-vald/mirror-target.yaml @@ -0,0 +1,35 @@ +--- +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +apiVersion: vald.vdaas.org/v1 +kind: ValdMirrorTarget +metadata: + name: mirror-target-01 +spec: + colocation: dc1 + target: + host: vald-mirror-gateway.vald-01.svc.cluster.local + port: 8081 +--- +apiVersion: vald.vdaas.org/v1 +kind: ValdMirrorTarget +metadata: + name: mirror-target-02 +spec: + colocation: dc1 + target: + host: vald-mirror-gateway.vald-02.svc.cluster.local + port: 8081 diff --git a/cmd/gateway/mirror/main.go b/cmd/gateway/mirror/main.go new file mode 100644 index 0000000000..ed7658c40d --- /dev/null +++ b/cmd/gateway/mirror/main.go @@ -0,0 +1,58 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 main + +import ( + "context" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/info" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/runner" + "github.com/vdaas/vald/internal/safety" + "github.com/vdaas/vald/pkg/gateway/mirror/config" + "github.com/vdaas/vald/pkg/gateway/mirror/usecase" +) + +const ( + maxVersion = "v0.0.10" + minVersion = "v0.0.0" + name = "gateway mirror" +) + +func main() { + if err := safety.RecoverFunc(func() error { + return runner.Do( + context.Background(), + runner.WithName(name), + runner.WithVersion(info.Version, maxVersion, minVersion), + runner.WithConfigLoader(func(path string) (interface{}, *config.GlobalConfig, error) { + cfg, err := config.NewConfig(path) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to load "+name+"'s configuration") + } + return cfg, &cfg.GlobalConfig, nil + }), + runner.WithDaemonInitializer(func(cfg interface{}) (runner.Runner, error) { + c, ok := cfg.(*config.Data) + if !ok { + return nil, errors.ErrInvalidConfig + } + return usecase.New(c) + }), + ) + })(); err != nil { + log.Fatal(err, info.Get()) + } +} diff --git a/cmd/gateway/mirror/sample.yaml b/cmd/gateway/mirror/sample.yaml new file mode 100644 index 0000000000..9646caf784 --- /dev/null +++ b/cmd/gateway/mirror/sample.yaml @@ -0,0 +1,148 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +version: v0.0.0 +time_zone: JST +logging: + format: raw + level: debug + logger: glg +server_config: + servers: + - name: grpc + host: 0.0.0.0 + port: 8081 + grpc: + bidirectional_stream_concurrency: 20 + connection_timeout: "" + header_table_size: 0 + initial_conn_window_size: 0 + initial_window_size: 0 + interceptors: [] + keepalive: + max_conn_age: "" + max_conn_age_grace: "" + max_conn_idle: "" + time: "" + timeout: "" + max_header_list_size: 0 + max_receive_message_size: 0 + max_send_message_size: 0 + read_buffer_size: 0 + write_buffer_size: 0 + mode: GRPC + probe_wait_time: 3s + restart: true + health_check_servers: + - name: liveness + host: 0.0.0.0 + port: 3000 + http: + handler_timeout: "" + idle_timeout: "" + read_header_timeout: "" + read_timeout: "" + shutdown_duration: 5s + write_timeout: "" + mode: "" + probe_wait_time: 3s + - name: readiness + host: 0.0.0.0 + port: 3001 + http: + handler_timeout: "" + idle_timeout: "" + read_header_timeout: "" + read_timeout: "" + shutdown_duration: 0s + write_timeout: "" + mode: "" + probe_wait_time: 3s + metrics_servers: + startup_strategy: + - liveness + - grpc + - readiness + full_shutdown_duration: 600s + tls: + ca: /path/to/ca + cert: /path/to/cert + enabled: false + key: /path/to/key +observability: + enabled: false + trace: + enabled: false +gateway: + client: + addrs: + - vald-discoverer.default.svc.cluster.local:8081 + health_check_duration: "1s" + connection_pool: + enable_dns_resolver: true + enable_rebalance: true + old_conn_close_duration: 3s + rebalance_duration: 30m + size: 3 + backoff: + backoff_factor: 1.1 + backoff_time_limit: 5s + enable_error_log: true + initial_duration: 5ms + jitter_limit: 100ms + maximum_duration: 5s + retry_count: 100 + call_option: + max_recv_msg_size: 0 + max_retry_rpc_buffer_size: 0 + max_send_msg_size: 0 + wait_for_ready: true + dial_option: + backoff_base_delay: 1s + backoff_jitter: 0.2 + backoff_max_delay: 120s + backoff_multiplier: 1.6 + enable_backoff: false + initial_connection_window_size: 0 + initial_window_size: 0 + insecure: true + keepalive: + permit_without_stream: false + time: "" + timeout: "" + max_msg_size: 0 + min_connection_timeout: 20s + read_buffer_size: 0 + tcp: + dialer: + dual_stack_enabled: true + keepalive: "" + timeout: "" + dns: + cache_enabled: true + cache_expiration: 1h + refresh_duration: 30m + tls: + enabled: false + ca: /path/to/ca + cert: /path/to/cert + key: /path/to/key + timeout: "" + write_buffer_size: 0 + tls: + enabled: false + ca: /path/to/ca + cert: /path/to/cert + key: /path/to/key diff --git a/dockers/gateway/mirror/Dockerfile b/dockers/gateway/mirror/Dockerfile new file mode 100644 index 0000000000..438790483a --- /dev/null +++ b/dockers/gateway/mirror/Dockerfile @@ -0,0 +1,92 @@ +# syntax = docker/dockerfile:latest +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# 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 +# +# https://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. +# +ARG GO_VERSION=latest +ARG DISTROLESS_IMAGE=gcr.io/distroless/static +ARG DISTROLESS_IMAGE_TAG=nonroot +ARG MAINTAINER="vdaas.org vald team " + +FROM golang:${GO_VERSION} AS golang + +FROM ubuntu:devel AS builder + +ENV GO111MODULE on +ENV DEBIAN_FRONTEND noninteractive +ENV INITRD No +ENV LANG en_US.UTF-8 +ENV GOROOT /opt/go +ENV GOPATH /go +ENV PATH ${PATH}:${GOROOT}/bin:${GOPATH}/bin +ENV ORG vdaas +ENV REPO vald +ENV PKG gateway/mirror +ENV APP_NAME mirror + +# skipcq: DOK-DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + curl \ + upx \ + git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=golang /usr/local/go $GOROOT +RUN mkdir -p "$GOPATH/src" + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO} + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/internal +COPY internal . + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/apis/grpc +COPY apis/grpc . + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/pkg/${PKG} +COPY pkg/${PKG} . + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/cmd/${PKG} +COPY cmd/${PKG} . + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/versions +COPY versions . + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/Makefile.d +COPY Makefile.d . + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO} +COPY Makefile . +COPY .git . + +RUN make REPO=${ORG} NAME=${REPO} cmd/${PKG}/${APP_NAME} \ + && mv "cmd/${PKG}/${APP_NAME}" "/usr/bin/${APP_NAME}" + +FROM ${DISTROLESS_IMAGE}:${DISTROLESS_IMAGE_TAG} +LABEL maintainer "${MAINTAINER}" + +ENV APP_NAME mirror + +COPY --from=builder /usr/bin/${APP_NAME} /go/bin/${APP_NAME} + +USER nonroot:nonroot + +ENTRYPOINT ["/go/bin/mirror"] diff --git a/docs/api/mirror-gateway.md b/docs/api/mirror-gateway.md new file mode 100644 index 0000000000..4b05dad5c2 --- /dev/null +++ b/docs/api/mirror-gateway.md @@ -0,0 +1,85 @@ +# Vald Mirror Gateway APIs + +## Overview + +Mirror Service is responsible for providing the `Register` interface for the Vald Mirror Gateway. + +```rpc +service Mirror { + rpc Register(payload.v1.Mirror.Targets) returns (payload.v1.Mirror.Targets) {} +} +``` + +## Register RPC + +Register RPC is the method to register other Vald Mirror Gateway targets. + +### Input + +- the scheme of `payload.v1.Mirror.Targets`. + + ```rpc + message Mirror { + message Target { + string host = 1; + uint32 port = 2; + } + + message Targets { + repeated Target targets = 1; + } + } + ``` + + - Mirror.Targets + + | field | type | label | required | desc. | + | :-----: | :------------ | :----------------------------- | :------: | :------------------------------- | + | targets | Mirror.Target | repeated(Array[Mirror.Target]) | \* | The multiple target information. | + + - Mirror.Target + + | field | type | label | required | desc. | + | :---: | :----- | :---- | :------: | :------------------- | + | host | string | | \* | The target hostname. | + | port | uint32 | | \* | The target port. | + +### Output + +- the scheme of `payload.v1.Mirror.Targets`. + + ```rpc + message Mirror { + message Target { + string host = 1; + uint32 port = 2; + } + + message Targets { + repeated Target targets = 1; + } + } + ``` + + - Mirror.Targets + + | field | type | label | required | desc. | + | :-----: | :------------ | :----------------------------- | :------: | :------------------------------- | + | targets | Mirror.Target | repeated(Array[Mirror.Target]) | | The multiple target information. | + + - Mirror.Target + + | field | type | label | required | desc. | + | :---: | :----- | :---- | :------: | :------------------- | + | host | string | | \* | The target hostname. | + | port | uint32 | | \* | The target port. | + +### Status Code + +| code | desc. | +| :--: | :---------------- | +| 0 | OK | +| 1 | CANCELLED | +| 3 | INVALID_ARGUMENT | +| 4 | DEADLINE_EXCEEDED | +| 13 | INTERNAL | diff --git a/docs/overview/component/mirror-gateway.md b/docs/overview/component/mirror-gateway.md new file mode 100644 index 0000000000..845ec1ed0b --- /dev/null +++ b/docs/overview/component/mirror-gateway.md @@ -0,0 +1,81 @@ +# Vald Mirror Gateway + +Vald Mirror Gateway is an optional component of the Vald, allowing the vector data to be synchronized across multiple Vald clusters. + +This component makes it possible to enhance availability during a cluster failure. + + + +## Responsibility + +Vald Mirror Gateway is responsible for the followings: + +- Forward user requests ([Insert](../../api/insert.md) / [Upsert](../../api/upsert.md) / [Update](../../api/update.md) / [Remove](../../api/remove.md)) to the other Vald Mirror Gateways in the same group. +- Manages the state of indexes stored in all clusters to ensure they are consistent. + +## Features + +This chapter shows the main features to fulfill Vald Mirror Gateway’s role: + +- Full mesh connection +- Request forwarding +- Automatic rollback on failure + +### Full mesh connection + + + +The Vald Mirror Gateway is designed to interconnect with Vald Mirror Gateways in other Vald clusters. + +Vald Mirror Gateway uses a Custom Resource called the `ValdMirrorTarget` to manage the connection destination information between Vald Mirror Gateways. + +The `ValdMirrorTarget` is a Custom Resource related to the connection destination to other Vald Mirror Gateway. + +When two Vald clusters contain Vald Mirror Gateways, Vald Mirror Gateways can send the request to each other by applying `ValdMirrorTarget`. + +For more information about `ValdMirrorTarget` configuration, please refer to [Custom Resource Configuration](../../user-guides/mirroring-configuration.md). + +### Request forwarding + + + +The Vald Mirror Gateway forwards the incoming user request ([Insert](../../api/insert.md) / [Upsert](../../api/upsert.md) / [Update](../../api/update.md) / [Remove](../../api/remove.md)) to other Vald Mirror Gateways. +Then, while forwarding the user request, the Vald Mirror Gateway bypasses the incoming user request to Vald LB Gateway in its own cluster. + +On the other hand, if the incoming user request is an [Object API](../../api/object.md) or [Search API](../../api/search.md), it is bypassed to only a Vald LB Gateway in its own cluster without forwarding it to other Vald Mirror Gateways. + +### Continuous processing on failure + +The request may fail at the forwarding destination or the bypass destination. + +If some of the requests fails, the processing continues based on their status code. + +Here's an overview of how the Mirror Gateway handles failures for each type of request. + +For more information about status code, please refer to [Mirror Gateway Troubleshooting](../../troubleshooting/mirror-gateway.md). + +- Insert Request + + - If the target host returns a status code of `ALREADY_EXISTS`, the Update request is sent to this host. + - If the target host returns a status code other than `OK`, `ALREADY_EXISTS`, the Mirror Gateway returns that status code without continuous processing. + - If all target hosts return a status code `ALREADY_EXISTS`, the Mirror Gateway returns `ALREADY_EXISTS`. + - If all target hosts return a status code `OK` or `ALREADY_EXISTS`, the Mirror Gateway returns `OK`. + +- Update Request + + - If the target host returns a status code `NOT_FOUND`, the Insert request is sent to this host. + - If the target host returns a status code other than `OK`, `ALREADY_EXISTS`, the Mirror Gateway returns that status code without continuous processing. + - If all target hosts return a status code `ALREADY_EXISTS`, the Mirror Gateway returns `ALREADY_EXISTS`. + - If all target hosts return a status code `OK` or `ALREADY_EXISTS`, the Mirror Gateway returns `OK`. + +- Upsert Request + + - If all target hosts return a status code `ALREADY_EXISTS`, the Mirror Gateway returns `ALREADY_EXISTS`. + - If the target host returns a status code other than `OK` or `ALREADY_EXISTS`, the Mirror Gateway returns that status code without continuous processing. + - If all target hosts return a status code `OK` or `ALREADY_EXISTS`, the Mirror Gateway returns `OK`. + +- Remove/RemoveByTimestamp Request + + - If all target hosts return a status code `NOT_FOUND`, the Mirror Gateway returns `NOT_FOUND`. + - If the target host returns a status code other than `OK` or `NOT_FOUND`, the Mirror Gateway returns that status code without continuous processing. + - If all target hosts return a status code `OK` or `NOT_FOUND`, the Mirror Gateway returns `OK`. diff --git a/docs/troubleshooting/mirror-gateway.md b/docs/troubleshooting/mirror-gateway.md new file mode 100644 index 0000000000..eab467e302 --- /dev/null +++ b/docs/troubleshooting/mirror-gateway.md @@ -0,0 +1,75 @@ +# Mirror Gateway Troubleshooting + +This page introduces the popular troubleshooting for Mirror Gateway. + +Additionally, if you encounter some errors when using API, the [API status code](../api/status.md) helps you, too. + +## Insert Operation + +Mirror Gateway sends an Update request to its host if some requests are `ALREADY_EXISTS`. + +Therefore, in addition to the [Insert API status code](../api/insert.md#status-code), the [Update API status code](../api/update.md#status-code) may also be returned to the user. + +Here are some common reasons of error. + +| name | common reason | +| :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | +| CANCELLED | Executed cancel() of rpc from client/server-side or network problems between client and server. | +| INVALID_ARGUMENT | The Dimension of the request vector is NOT the same as Vald Agent's config, the requested vector's ID is empty, or some request payload is invalid. | +| DEADLINE_EXCEEDED | The RPC timeout setting is too short on the client/server side. | +| ALREADY_EXISTS | Request ID is already inserted. This status code is returned when all target hosts return `ALREADY_EXISTS`. | +| NOT_FOUND | Requested ID is NOT inserted. This is the status code of the Update request. | +| INTERNAL | Target Vald cluster or network route has some critical error. | + +`0 (OK)` is also returned when all target hosts return `OK` or `ALREADY_EXISTS`. + +## Update Operation + +Mirror Gateway sends an Update request to its host if some requests are `NOT_FOUND`. + +Therefore, in addition to the [Update API status code](../api/update.md#status-code), the [Insert API status code](../api/insert.md#status-code) may also be returned to the user. + +Here are some common reasons of error. + +| name | common reason | +| :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | +| CANCELLED | Executed cancel() of rpc from client/server-side or network problems between client and server. | +| INVALID_ARGUMENT | The Dimension of the request vector is NOT the same as Vald Agent's config, the requested vector's ID is empty, or some request payload is invalid. | +| DEADLINE_EXCEEDED | The RPC timeout setting is too short on the client/server side. | +| NOT_FOUND | Requested ID is NOT inserted. This status code is returned when all target hosts return `NOT_FOUND`. | +| ALREADY_EXISTS | Request a pair of ID and vector is already inserted. This status code is returned when all hosts return `ALREADY_EXISTS`. | +| INTERNAL | Target Vald cluster or network route has some critical error. | + +`0 (OK)` is also returned when all target hosts return `OK` or `ALREADY_EXISTS`. + +## Upsert Operation + +The request process may not be completed when the response code is NOT `0 (OK)`. + +Here are some common reasons of error. + +| name | common reason | +| :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | +| CANCELLED | Executed cancel() of rpc from client/server-side or network problems between client and server. | +| INVALID_ARGUMENT | The Dimension of the request vector is NOT the same as Vald Agent's config, the requested vector's ID is empty, or some request payload is invalid. | +| DEADLINE_EXCEEDED | The RPC timeout setting is too short on the client/server side. | +| ALREADY_EXISTS | Requested pair of ID and vector is already inserted. This status code is returned when all target hosts return `ALREADY_EXISTS`. | +| INTERNAL | Target Vald cluster or network route has some critical error. | + +`0 (OK)` is also returned when all target hosts return `OK` or `ALREADY_EXISTS`. + +## Remove Operation + +The request process may not be completed when the response code is NOT `0 (OK)`. + +Here are some common reasons of error. + +| name | common reason | +| :---------------- | :--------------------------------------------------------------------------------------------------- | +| CANCELLED | Executed cancel() of rpc from client/server-side or network problems between client and server. | +| INVALID_ARGUMENT | The Requested vector's ID is empty, or some request payload is invalid. | +| DEADLINE_EXCEEDED | The RPC timeout setting is too short on the client/server side. | +| NOT_FOUND | Requested ID is NOT inserted. This status code is returned when all target hosts return `NOT_FOUND`. | +| INTERNAL | Target Vald cluster or network route has some critical error. | + +`0 (OK)` is also returned when all target hosts return `OK` or `NOT_FOUND`. diff --git a/docs/tutorial/vald-multicluster-on-k8s.md b/docs/tutorial/vald-multicluster-on-k8s.md new file mode 100644 index 0000000000..2664cdfc88 --- /dev/null +++ b/docs/tutorial/vald-multicluster-on-k8s.md @@ -0,0 +1,303 @@ +# Multiple Vald Clusters using Vald Mirror Gateway + +This article shows how to deploy multiple Vald clusters on your Kubernetes cluster. + +## Overview + +Vald cluster consists of multiple microservices. + +In [Get Started](https://vald.vdaas.org/docs/tutorial/get-started), you may use 4 kinds of components to deploy the Vald cluster. + +In this tutorial, you will deploy multiple Vald clusters with Vald Mirror Gateway, which connects another Vald cluster. + +The below image shows the architecture image of this case. + + + +## Requirements + +- Vald: v1.8 ~ +- Kubernetes: v1.27 ~ +- Go: v1.20 ~ +- Helm: v3 ~ +- libhdf5 (_only required to get started_) + +Helm is used to deploy Vald cluster on your Kubernetes and HDF5 is used to decode the sample data file to run the example code. + +If Helm or HDF5 is not installed, please install [Helm](https://helm.sh/docs/intro/install) and [HDF5](https://www.hdfgroup.org/). + +
Installation command for Helm
+ +```bash +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +``` + +
+ +
Installation command for HDF5
+ +```bash +# yum +yum install -y hdf5-devel + +# apt +apt-get install libhdf5-serial-dev + +# homebrew +brew install hdf5 +``` + +
+ +## Prepare the Kubernetes Cluster + +This tutorial requires the Kubernetes cluster. + +Vald cluster runs on public Cloud Kubernetes Services such as GKE, EKS. +In the sense of trying to `Get Started`, [k3d](https://k3d.io/) or [kind](https://kind.sigs.k8s.io/) are easy Kubernetes tools to use. + +This tutorial uses [Kubernetes Metrics Server](https://github.com/kubernetes-sigs/metrics-server) for running the Vald cluster. + +Please make sure these functions are available. + +The way to deploy Kubernetes Metrics Service is here: + +```bash +kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml && \ +kubectl wait -n kube-system --for=condition=ready pod -l k8s-app=metrics-server --timeout=600s +``` + +Please prepare three Namespaces on the Kubernetes cluster. + +```bash +kubectl create namespace vald-01 && \ +kubectl create namespace vald-02 && \ +kubectl create namespace vald-03 +``` + +## Deploy Vald Clusters on each Kubernetes Namespace + +This chapter shows how to deploy the multiple Vald clusters using Helm and run it on your Kubernetes cluster. +In this section, you will deploy three Vald clusters consisting of `vald-agent-ngt`, `vald-lb-gateway`, `vald-discoverer`, `vald-manager-index`, and `vald-mirror-gateway` using the basic configuration. + +1. Clone the repository + + To use the `deployment` YAML for deployment, let’s clone `[vdaas/vald](https://github.com/vdaas/vald.git)` repository. + + ```bash + git clone https://github.com/vdaas/vald.git && cd vald + ``` + +2. Deploy on the `vald-01` Namespace using [dev-vald-01.yaml](https://github.com/vdaas/vald/blob/feature/mirror-gateway-definition/charts/vald/values/multi-vald/dev-vald-01.yaml) and [values.yaml](https://github.com/vdaas/vald/blob/main/example/helm/values.yaml) + + ```bash + helm install vald-cluster-01 charts/vald \ + -f ./example/helm/values.yaml \ + -f ./charts/vald/values/multi-vald/dev-vald-01.yaml \ + -n vald-01 + ``` + +3. Deploy on the `vald-02` Namespace using [dev-vald-02.yaml](https://github.com/vdaas/vald/blob/feature/mirror-gateway-definition/charts/vald/values/multi-vald/dev-vald-02.yaml) and [values.yaml](https://github.com/vdaas/vald/blob/main/example/helm/values.yaml) + + ```bash + helm install vald-cluster-02 charts/vald \ + -f ./example/helm/values.yaml \ + -f ./charts/vald/values/multi-vald/dev-vald-02.yaml \ + -n vald-02 + ``` + +4. Deploy on the `vald-03` Namespace using [dev-vald-03.yaml](https://github.com/vdaas/vald/blob/feature/mirror-gateway-definition/charts/vald/values/multi-vald/dev-vald-03.yaml) and [values.yaml](https://github.com/vdaas/vald/blob/main/example/helm/values.yaml) + + ```bash + helm install vald-cluster-03 charts/vald \ + -f ./example/helm/values.yaml \ + -f ./charts/vald/values/multi-vald/dev-vald-03.yaml \ + -n vald-03 + ``` + +5. Verify + + If success deployment, the Vald cluster’s components should run on each Kubernetes Namespace. + +
vald-01 Namespace
+ + ```bash + kubectl get pods -n vald-01 + NAME READY STATUS RESTARTS AGE + vald-agent-ngt-0 1/1 Running 0 2m41s + vald-agent-ngt-2 1/1 Running 0 2m41s + vald-agent-ngt-3 1/1 Running 0 2m41s + vald-agent-ngt-4 1/1 Running 0 2m41s + vald-agent-ngt-5 1/1 Running 0 2m41s + vald-agent-ngt-1 1/1 Running 0 2m41s + vald-discoverer-77967c9697-brbsp 1/1 Running 0 2m41s + vald-lb-gateway-587879d598-xmws7 1/1 Running 0 2m41s + vald-lb-gateway-587879d598-dzn9c 1/1 Running 0 2m41s + vald-manager-index-56d474c848-wkh6b 1/1 Running 0 2m41s + vald-lb-gateway-587879d598-9wb5q 1/1 Running 0 2m41s + vald-mirror-gateway-6df75cf7cf-gzcr4 1/1 Running 0 2m26s + vald-mirror-gateway-6df75cf7cf-vjbqx 1/1 Running 0 2m26s + vald-mirror-gateway-6df75cf7cf-c2g7t 1/1 Running 0 2m41s + ``` + +
+ +
vald-02 Namespace
+ + ```bash + kubectl get pods -n vald-02 + NAME READY STATUS RESTARTS AGE + vald-agent-ngt-0 1/1 Running 0 2m52s + vald-agent-ngt-1 1/1 Running 0 2m52s + vald-agent-ngt-2 1/1 Running 0 2m52s + vald-agent-ngt-4 1/1 Running 0 2m52s + vald-agent-ngt-5 1/1 Running 0 2m52s + vald-agent-ngt-3 1/1 Running 0 2m52s + vald-discoverer-8cfcff76-vlmpg 1/1 Running 0 2m52s + vald-lb-gateway-54896f9f49-wtlcv 1/1 Running 0 2m52s + vald-lb-gateway-54896f9f49-hbklj 1/1 Running 0 2m52s + vald-manager-index-676855f8d7-bb4wb 1/1 Running 0 2m52s + vald-lb-gateway-54896f9f49-kgrdf 1/1 Running 0 2m52s + vald-mirror-gateway-6598cf957-t2nz4 1/1 Running 0 2m37s + vald-mirror-gateway-6598cf957-wr448 1/1 Running 0 2m52s + vald-mirror-gateway-6598cf957-jdd6q 1/1 Running 0 2m37s + ``` + +
+ +
vald-03 Namespace
+ + ```bash + kubectl get pods -n vald-03 + NAME READY STATUS RESTARTS AGE + vald-agent-ngt-0 1/1 Running 0 2m46s + vald-agent-ngt-1 1/1 Running 0 2m46s + vald-agent-ngt-2 1/1 Running 0 2m46s + vald-agent-ngt-3 1/1 Running 0 2m46s + vald-agent-ngt-4 1/1 Running 0 2m46s + vald-agent-ngt-5 1/1 Running 0 2m46s + vald-discoverer-879867b44-8m59h 1/1 Running 0 2m46s + vald-lb-gateway-6c8c6b468d-ghlpx 1/1 Running 0 2m46s + vald-lb-gateway-6c8c6b468d-rt688 1/1 Running 0 2m46s + vald-lb-gateway-6c8c6b468d-jq7pl 1/1 Running 0 2m46s + vald-manager-index-5596f89644-xfv4t 1/1 Running 0 2m46s + vald-mirror-gateway-7b95956f8b-l57jz 1/1 Running 0 2m31s + vald-mirror-gateway-7b95956f8b-xd9n5 1/1 Running 0 2m46s + vald-mirror-gateway-7b95956f8b-dnxbb 1/1 Running 0 2m31s + ``` + +
+ +## Deploy ValdMirrorTarget Resource (Custom Resource) + +It requires applying the `ValdMirrorTarget` Custom Resource to the one Namespace. + +When applied successfully, the destination information is automatically created on other Namespaces when interconnected with each `vald-mirror-gateway`. + +This tutorial will deploy the [ValdMirrorTarger](https://github.com/vdaas/vald/tree/main/charts/vald/values/mirror-target.yaml) Custom Resource to the `vald-03` Namespace with the following command. + +```bash +kubectl apply -f ./charts/vald/values/multi-vald/mirror-target.yaml -n vald-03 +``` + +The current connection status can be checked with the following command. + +
Example output
+ +```bash +kubectl get vmt -A -o wide +NAMESPACE NAME HOST PORT STATUS LAST TRANSITION TIME AGE +vald-03 mirror-target-01 vald-mirror-gateway.vald-01.svc.cluster.local 8081 Connected 2023-05-22T02:07:51Z 19m +vald-03 mirror-target-02 vald-mirror-gateway.vald-02.svc.cluster.local 8081 Connected 2023-05-22T02:07:51Z 19m +vald-02 mirror-target-3296010438411762394 vald-mirror-gateway.vald-01.svc.cluster.local 8081 Connected 2023-05-22T02:07:53Z 19m +vald-02 mirror-target-12697587923462644654 vald-mirror-gateway.vald-03.svc.cluster.local 8081 Connected 2023-05-22T02:07:53Z 19m +vald-01 mirror-target-13698925675968803691 vald-mirror-gateway.vald-02.svc.cluster.local 8081 Connected 2023-05-22T02:07:53Z 19m +vald-01 mirror-target-17825710563723389324 vald-mirror-gateway.vald-03.svc.cluster.local 8081 Connected 2023-05-22T02:07:53Z 19m +``` + +
+ +## Run Example Code + +In this chapter, you will execute insert, search, get, and delete vectors to your Vald clusters using the example code. + +The [Fashion-MNIST](https://github.com/zalandoresearch/fashion-mnist) is used as a dataset for indexing and search query. + +The example code is implemented in Go and uses [vald-client-go](https://github.com/vdaas/vald-client-go), one of the official Vald client libraries, for requesting to Vald cluster. + +Vald provides multiple language client libraries such as Go, Java, Node.js, Python, etc. + +If you are interested, please refer to [SDKs](https://vald.vdaas.org/docs/user-guides/sdks). + +1. Port Forward(option) + + If you do NOT use Kubernetes Ingress, port forwarding is required to make requests from your local environment. + + ```bash + kubectl port-forward svc/vald-mirror-gateway 8080:8081 -n vald-01 & \ + kubectl port-forward svc/vald-mirror-gateway 8081:8081 -n vald-02 & \ + kubectl port-forward svc/vald-mirror-gateway 8082:8081 -n vald-03 + ``` + +2. Download dataset + + Move to the working directory. + + ```bash + cd example/client/mirror + ``` + + Download [Fashion-MNIST](https://github.com/zalandoresearch/fashion-mnist), which is used as a dataset for indexing and search query. + + ```bash + wget https://ann-benchmarks.com/fashion-mnist-784-euclidean.hdf5 + ``` + +3. Run Example + + We use [example/client/mirror/main.go](https://github.com/vdaas/vald/blob/feature/mirror-gateway-example/example/client/mirror/main.go) to run the example. + + This example will insert and index 400 vectors into the Vald cluster from the Fashion-MNIST dataset via [gRPC](https://grpc.io/). + And then, after waiting for indexing, it will request to search the nearest vector 10 times to all Vald clusters. You will get the 10 nearest neighbor vectors for each search query. + And it will request to get vectors using inserted vector ID. + + Run example codes by executing the below command. + + ```bash + go run main.go + ``` + + See [GetStarted](https://vald.vdaas.org/docs/tutorial/get-started/) for an explanation of the example code. + +## Cleanup + +Last, you can remove the deployed Vald cluster by executing the below command. + +```bash +helm uninstall vald-cluster-01 -n vald-01 && \ +helm uninstall vald-cluster-02 -n vald-02 && \ +helm uninstall vald-cluster-03 -n vald-03 +``` + +You can remove all `ValdMirrorTarget` resources (Custom Resource) with the below command. + +```bash +kubectl delete --all ValdMirrorTargets -A +``` + +You can remove all created Namespaces with the below command. + +```bash +kubectl delete namespaces vald-01 vald-02 vald-03 +``` + +## Next Steps + +Congratulation! +You completely entered the World of multiple Vald clusters! + +For more information, we recommend you check the following: + +- [Configuration](https://vald.vdaas.org/docs/user-guides/configuration/) +- [Mirror Configuration](https://vald.vdaas.org/docs/user-guides/mirroring-configuration/) +- [Network Policy](https://vald.vdaas.org/docs/user-guides/network-policy/) diff --git a/docs/user-guides/cluster-role-binding.md b/docs/user-guides/cluster-role-binding.md index 3ade19d6d6..36404ec157 100644 --- a/docs/user-guides/cluster-role-binding.md +++ b/docs/user-guides/cluster-role-binding.md @@ -133,6 +133,105 @@ If you disable these configurations, the Vald Discoverer will not work, and the If you want to modify or disable these configurations, you need to grant the [cluster role configuration](https://github.com/vdaas/vald/blob/main/k8s/discoverer/clusterrole.yaml) and bind it to the Vald Discoverer to retrieve required information to operate the Vald cluster. +## Configuration for Vald Mirror Gateway + +The Vald Mirror Gateway requires configuration on cluster role and cluster role binding to create/update/retrive [ValdMirrorTarget](https://vald.vdaas.org/docs/user-guides/mirroring-configuration/) resource from the Kubernetes Cluster. + +In this section, we will describe how to configure it and how to customize these configurations. + +### Cluster role configuration for Vald Mirror Gateway + +By looking at the [cluster role configuration](https://github.com/vdaas/vald/blob/main/k8s/gatewat/mirror/clusterrole.yaml), the access right of the following resources are granted to the cluster role `gateway-mirror`. + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gateway-mirror +--- +rules: + - apiGroups: + - vald.vdaas.org + resources: + - valdmirrortargets + verbs: + - create + - update + - delete + - get + - list + - watch + - patch + - apiGroups: + - vald.vdaas.org + resources: + - valdmirrortargets/status + verbs: + - create + - update + - get + - list + - patch + - apiGroups: + - vald.vdaas.org + resources: + - valdmirrortargets/finalizers + verbs: + - update +``` + +### Cluster role binding configuration for Vald Mirror Gateway + +The cluster role binding configuration binds the cluster role `gateway-mirror` described in the previous section to the service account `gateway-mirror` according to the [configuration file](https://github.com/vdaas/vald/blob/main/k8s/gateway/mirror/clusterrolebinding.yaml). + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: gateway-mirror + ... +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gateway-mirror +subjects: + - kind: ServiceAccount + name: gateway-mirror + namespace: default +``` + +When the role binds to the service account, the access right of the role will be granted to the service account. +In this case, all the access rights of the role `gateway-mirror` will be granted to the service account `gateway-mirror`. + +### Customize cluster role and cluster role binding configuration on Helm chart for Vald Mirror Gateway + +To customize the cluster role configuration on the Helm chart for Vald Mirror Gateway, you may need to change the `gateway.mirror.clusterRole` configuration on the Helm chart file. The cluster role configurations are enabled by default. + +```yaml +gateway: + mirror: + --- + clusterRole: + # gateway.mirror.clusterRole.enabled -- creates clusterRole resource + enabled: true + # gateway.mirror.clusterRole.name -- name of clusterRole + name: gateway-mirror + clusterRoleBinding: + # gateway.mirror.clusterRoleBinding.enabled -- creates clusterRoleBinding resource + enabled: true + # gateway.mirror..clusterRoleBinding.name -- name of clusterRoleBinding + name: gateway-mirror + serviceAccount: + # gateway.mirror.serviceAccount.enabled -- creates service account + enabled: true + # gateway.mirror.serviceAccount.name -- name of service account + name: gateway-mirror +``` + +
+If you disable these configurations, the Vald Mirror Gateway will not work properly. +
+ ## Customize cluster role configuration on Cloud Providers Please refer to the official guidelines to configure cluster role configuration for your cloud provider, and configure the service account name for Vald Discoverer. diff --git a/docs/user-guides/mirroring-configuration.md b/docs/user-guides/mirroring-configuration.md new file mode 100644 index 0000000000..6926f53985 --- /dev/null +++ b/docs/user-guides/mirroring-configuration.md @@ -0,0 +1,89 @@ +# Mirror Configuration + +This page describes how to enable mirroring features on the Vald cluster. + +Before you use the mirroring functions, please look at [the Vald Mirror Gateway document](../overview/component/mirror-gateway.md) for what you can do. + +## Requirement + +- Vald version: v1.8 +- The number of Vald clusters: 2~ + +## Configuration + +This chapter shows how to configure values.yaml to enable Vald Mirror Gateway and how to interconnect Vald Mirror Gateways. + +The setting points are the followings: + +1. Enable the Vald Mirror Gateway using Helm values configuration +2. Interconnect Vald Mirror Gateways using the Custom Resource configuration + +### Helm Values Configuration + +The Helm values configuration is required for each Vald cluster to be deployed. + +It is easy to enable the mirroring feature. + +```yaml +--- +gateway: + mirror: + enabled: true +``` + +If you want to make more detailed settings, please set the following parameters. + +```yaml +gateway: + mirror: + ... + gateway_config: + ... + # gRPC client configuration (overrides defaults.grpc.client) + client: {} + # The duration to register other Mirror Gateways. + register_duration: "1s" + # The target namespace to discover ValdMirrorTarget (CR) resource. + # The default value is its own namespace. + namespace: "vald" + # The group name of the Mirror Gateways (optional). + # It is used to discover ValdMirrorTarget resources (CR) with the same group name. + # The default value is empty. + group: "group1" + # The duration to discover other mirror gateways in the same group. + discovery_duration: 1s + # The colocation name of the data center (optional). + colocation: "dc1" +``` + +The cluster role configuration is required when you deploy Vald clusters with Vald Mirror Gateway on multiple Namespaces in the Kubernetes cluster. + +Please refer to [Cluster Role Configuration](./cluster-role-binding.md) about cluster role settings for Mirror Gateway. + +### Custom Resource Configuration + +The Mirror Gateway is not connected to other mirror gateways when deployed. + +The Vald Mirror Gateway connects to another Mirror Gateway component specified in the `ValdMirrorTarget` resource (Custom Resource). + +Based on this resource, if the connection succeeds, the Mirror Gateway will interconnect with another. + +```yaml +apiVersion: vald.vdaas.org/v1 +kind: ValdMirrorTarget +metadata: + name: mirror-target-01 + namespace: vald-03 + labels: + # The group name of the Mirror Gateways. + group: mirror-group-01 +spec: + # Colocation name. (optional) + colocation: dc1 + # The connection target to another mirror gateway. + target: + # The hostname. + host: vald-mirror-gateway.vald-01.svc.cluster.local + # The port number + port: 8081 +``` diff --git a/example/client/mirror/main.go b/example/client/mirror/main.go new file mode 100644 index 0000000000..38d586ed64 --- /dev/null +++ b/example/client/mirror/main.go @@ -0,0 +1,246 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 main + +import ( + "context" + "encoding/json" + "flag" + "log" + "strings" + "time" + + "github.com/kpango/fuid" + "github.com/kpango/glg" + "github.com/vdaas/vald-client-go/v1/payload" + "github.com/vdaas/vald-client-go/v1/vald" + "gonum.org/v1/hdf5" + "google.golang.org/grpc" +) + +const ( + insertCount = 400 + testCount = 20 +) + +var ( + datasetPath string + grpcServerAddr string + grpcServerAddrs []string + indexingWaitSeconds uint +) + +func init() { + /** + Path option specifies hdf file by path. Default value is `fashion-mnist-784-euclidean.hdf5`. + Addr option specifies grpc server addresses. Default value is `127.0.0.1:8080`,`127.0.0.1:8081`,`127.0.0.1:8082`. + Wait option specifies indexing wait time (in seconds). Default value is `60`. + **/ + flag.StringVar(&datasetPath, "path", "fashion-mnist-784-euclidean.hdf5", "dataset path") + flag.StringVar(&grpcServerAddr, "addrs", "localhost:8080,localhost:8081,localhost:8082", "gRPC server addresses") + flag.UintVar(&indexingWaitSeconds, "wait", 60, "indexing wait seconds") + flag.Parse() + grpcServerAddrs = strings.Split(grpcServerAddr, ",") +} + +func main() { + /** + Gets training data, test data and ids based on the dataset path. + the number of ids is equal to that of training dataset. + **/ + ids, train, test, err := load(datasetPath) + if err != nil { + glg.Fatal(err) + } + ctx := context.Background() + + // Creates Vald clients for connecting to Vald clusters. + clients := make([]vald.Client, 0, len(grpcServerAddrs)) + for _, addr := range grpcServerAddrs { + conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure()) + if err != nil { + glg.Fatal(err) + } + defer conn.Close() + + // Creates Vald client for gRPC. + clients = append(clients, vald.NewValdClient(conn)) + } + + glg.Infof("Start Inserting %d training vector to Vald", insertCount) + // Insert 400 example vectors into Vald cluster. + for i := range ids[:insertCount] { + // Calls `Insert` function of Vald client. + // Sends set of vector and id to server via gRPC. + _, err := clients[0].Insert(ctx, &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: ids[i], + Vector: train[i], + }, + Config: &payload.Insert_Config{ + SkipStrictExistCheck: true, + }, + }) + if err != nil { + glg.Fatal(err) + } + if i%10 == 0 { + glg.Infof("Inserted: %d", i+10) + } + } + glg.Info("Finish Inserting dataset. \n\n") + + // Vald starts indexing automatically after insert. It needs to wait until the indexing is completed before a search action is performed. + wt := time.Duration(indexingWaitSeconds) * time.Second + glg.Infof("Wait %s for indexing to finish", wt) + time.Sleep(wt) + + /** + Gets approximate vectors, which is based on the value of `SearchConfig`, from the indexed tree based on the training data. + In this example, Vald gets 10 approximate vectors each search vector. + **/ + glg.Infof("Start searching %d times", testCount) + for i, vec := range test[:testCount] { + for j, client := range clients { + // Send searching vector and configuration object to the Vald server via gRPC. + res, err := client.Search(ctx, &payload.Search_Request{ + Vector: vec, + // Conditions for hitting the search. + Config: &payload.Search_Config{ + Num: 10, // the number of search results + Radius: -1, // Radius is used to determine the space of search candidate radius for neighborhood vectors. -1 means infinite circle. + Epsilon: 0.1, // Epsilon is used to determines how much to expand from search candidate radius. + Timeout: 100000000, // Timeout is used for search time deadline. The unit is nano-seconds. + }, + }) + if err != nil { + glg.Fatal(err) + } + + // NOTE: Search result may differ due to the indexing timing of each Vald cluster. + b, _ := json.MarshalIndent(res.GetResults(), "", " ") + glg.Infof("%s: %d - Results : %s\n\n", grpcServerAddrs[j], i+1, string(b)) + } + time.Sleep(1 * time.Second) + } + glg.Infof("Finish searching %d times", testCount) + + /** + Gets the vector using inserted vector id from Vald cluster. + **/ + glg.Infof("Start getting %d times", insertCount) + for i, id := range ids[:insertCount] { + for j, client := range clients { + vec, err := client.GetObject(ctx, &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: id, + }, + }) + if err != nil { + log.Fatal(err) + } + glg.Infof("%s: %d - Result : %s", grpcServerAddrs[j], i+1, vec.GetId()) + } + } + glg.Infof("Finish getting %d times", insertCount) + + glg.Info("Start removing vector") + // Remove indexed 400 vectors from vald cluster. + for i := range ids[:insertCount] { + // Call `Remove` function of Vald client. + // Sends id to server via gRPC. + _, err := clients[0].Remove(ctx, &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: ids[i], + }, + }) + if err != nil { + glg.Fatal(err) + } + if i%10 == 0 { + glg.Infof("Removed: %d", i+10) + } + } + glg.Info("Finish removing vector") +} + +// load function loads training and test vector from hdf file. The size of ids is same to the number of training data. +// Each id, which is an element of ids, will be set a random number. +func load(path string) (ids []string, train, test [][]float32, err error) { + var f *hdf5.File + f, err = hdf5.OpenFile(path, hdf5.F_ACC_RDONLY) + if err != nil { + return nil, nil, nil, err + } + defer f.Close() + + // readFn function reads vectors of the hierarchy with the given the name. + readFn := func(name string) ([][]float32, error) { + // Opens and returns a named Dataset. + // The returned dataset must be closed by the user when it is no longer needed. + d, err := f.OpenDataset(name) + if err != nil { + return nil, err + } + defer d.Close() + + // Space returns an identifier for a copy of the dataspace for a dataset. + sp := d.Space() + defer sp.Close() + + // SimpleExtentDims returns dataspace dimension size and maximum size. + dims, _, _ := sp.SimpleExtentDims() + row, dim := int(dims[0]), int(dims[1]) + + // Gets the stored vector. All are represented as one-dimensional arrays. + // The type of the slice depends on your dataset. + // For fashion-mnist-784-euclidean.hdf5, the datatype is float32. + vec := make([]float32, sp.SimpleExtentNPoints()) + if err := d.Read(&vec); err != nil { + return nil, err + } + + // Converts a one-dimensional array to a two-dimensional array. + // Use the `dim` variable as a separator. + vecs := make([][]float32, row) + for i := 0; i < row; i++ { + vecs[i] = make([]float32, dim) + for j := 0; j < dim; j++ { + vecs[i][j] = float32(vec[i*dim+j]) + } + } + + return vecs, nil + } + + // Gets vector of `train` hierarchy. + train, err = readFn("train") + if err != nil { + return nil, nil, nil, err + } + + // Gets vector of `test` hierarchy. + test, err = readFn("test") + if err != nil { + return nil, nil, nil, err + } + + // Generate as many random ids for training vectors. + ids = make([]string, 0, len(train)) + for i := 0; i < len(train); i++ { + ids = append(ids, fuid.String()) + } + + return +} diff --git a/internal/client/v1/client/mirror/mirror.go b/internal/client/v1/client/mirror/mirror.go new file mode 100644 index 0000000000..c90b3b9c2b --- /dev/null +++ b/internal/client/v1/client/mirror/mirror.go @@ -0,0 +1,90 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 mirror + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/mirror" + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/observability/trace" +) + +const ( + apiName = "vald/internal/client/v1/client/mirror" +) + +type Client interface { + mirror.MirrorClient + GRPCClient() grpc.Client + Start(context.Context) (<-chan error, error) + Stop(context.Context) error +} + +type client struct { + addrs []string + c grpc.Client +} + +func New(opts ...Option) (Client, error) { + c := new(client) + for _, opt := range append(defaultOpts, opts...) { + if err := opt(c); err != nil { + return nil, err + } + } + if c.c == nil { + if len(c.addrs) == 0 { + return nil, errors.ErrGRPCTargetAddrNotFound + } + c.c = grpc.New(grpc.WithAddrs(c.addrs...)) + } + return c, nil +} + +func (c *client) Start(ctx context.Context) (<-chan error, error) { + return c.c.StartConnectionMonitor(ctx) +} + +func (c *client) Stop(ctx context.Context) error { + return c.c.Close(ctx) +} + +func (c *client) GRPCClient() grpc.Client { + return c.c +} + +func (c *client) Register(ctx context.Context, in *payload.Mirror_Targets, opts ...grpc.CallOption) (res *payload.Mirror_Targets, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "internal/client/"+vald.RegisterRPCName), apiName+"/"+vald.RegisterRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = c.c.RoundRobin(ctx, func(ctx context.Context, conn *grpc.ClientConn, copts ...grpc.CallOption) (interface{}, error) { + res, err = mirror.NewMirrorClient(conn).Register(ctx, in, append(copts, opts...)...) + if err != nil { + return nil, err + } + return res, nil + }) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/internal/client/v1/client/mirror/option.go b/internal/client/v1/client/mirror/option.go new file mode 100644 index 0000000000..4d671a8771 --- /dev/null +++ b/internal/client/v1/client/mirror/option.go @@ -0,0 +1,45 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 mirror + +import ( + "github.com/vdaas/vald/internal/net/grpc" +) + +type Option func(c *client) error + +var defaultOpts = []Option{} + +func WithAddrs(addrs ...string) Option { + return func(c *client) error { + if addrs == nil { + return nil + } + if c.addrs != nil { + c.addrs = append(c.addrs, addrs...) + } else { + c.addrs = addrs + } + return nil + } +} + +func WithClient(gc grpc.Client) Option { + return func(c *client) error { + if gc != nil { + c.c = gc + } + return nil + } +} diff --git a/internal/config/mirror.go b/internal/config/mirror.go new file mode 100644 index 0000000000..c545fd0cb8 --- /dev/null +++ b/internal/config/mirror.go @@ -0,0 +1,72 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 config + +// Mirror represents the Mirror Gateway configuration. +type Mirror struct { + // Net represents the network configuration tcp, udp, unix domain socket. + Net *Net `json:"net,omitempty" yaml:"net"` + + // GRPCClient represents the configurations for gRPC client. + Client *GRPCClient `json:"client" yaml:"client"` + + // SelfMirrorAddr represents the address for the self Mirror Gateway. + SelfMirrorAddr string `json:"self_mirror_addr" yaml:"self_mirror_addr"` + + // GatewayAddr represents the address for the Vald Gateway (e.g lb-gateway). + GatewayAddr string `json:"gateway_addr" yaml:"gateway_addr"` + + // PodName represents the mirror gateway pod name. + PodName string `json:"pod_name" yaml:"pod_name"` + + // RegisterDuration represents the duration to register Mirror Gateway. + RegisterDuration string `json:"register_duration" yaml:"register_duration"` + + // Namespace represents the target namespace to discover ValdMirrorTarget resource. + Namespace string `json:"namespace" yaml:"namespace"` + + // DiscoveryDuration represents the duration to discover. + DiscoveryDuration string `json:"discovery_duration" yaml:"discovery_duration"` + + // Colocation represents the colocation name. + Colocation string `json:"colocation" yaml:"colocation"` + + // Group represents the group name of the Mirror Gateways. + // It is used to discover ValdMirrorTarget resources with the same group name. + Group string `json:"group" yaml:"group"` +} + +// Bind binds the actual data from the Mirror receiver fields. +func (m *Mirror) Bind() *Mirror { + m.SelfMirrorAddr = GetActualValue(m.SelfMirrorAddr) + m.GatewayAddr = GetActualValue(m.GatewayAddr) + m.PodName = GetActualValue(m.PodName) + m.RegisterDuration = GetActualValue(m.RegisterDuration) + m.Namespace = GetActualValue(m.Namespace) + m.DiscoveryDuration = GetActualValue(m.DiscoveryDuration) + m.Colocation = GetActualValue(m.Colocation) + m.Group = GetActualValue(m.Group) + + if m.Net != nil { + m.Net = m.Net.Bind() + } else { + m.Net = new(Net).Bind() + } + if m.Client != nil { + m.Client = m.Client.Bind() + } else { + m.Client = new(GRPCClient).Bind() + } + return m +} diff --git a/internal/hash/hash.go b/internal/hash/hash.go new file mode 100644 index 0000000000..8dad439a7a --- /dev/null +++ b/internal/hash/hash.go @@ -0,0 +1,25 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 info provides hash functionality +package hash + +import "github.com/zeebo/xxh3" + +// String returns the hash of the string. +func String(s string) uint64 { + return xxh3.HashString(s) +} diff --git a/internal/hash/hash_test.go b/internal/hash/hash_test.go new file mode 100644 index 0000000000..2d1a46b843 --- /dev/null +++ b/internal/hash/hash_test.go @@ -0,0 +1,106 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 info provides hash functionality +package hash + +// NOT IMPLEMENTED BELOW + +// func TestString(t *testing.T) { +// type args struct { +// s string +// } +// type want struct { +// want uint64 +// } +// type test struct { +// name string +// args args +// want want +// checkFunc func(want, uint64) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got uint64) error { +// if !reflect.DeepEqual(got, w.want) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// s:"", +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// s:"", +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// +// got := String(test.args.s) +// if err := checkFunc(test.want, got); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } diff --git a/internal/k8s/reconciler.go b/internal/k8s/reconciler.go index 83a79b660e..ecd6391c13 100644 --- a/internal/k8s/reconciler.go +++ b/internal/k8s/reconciler.go @@ -25,7 +25,7 @@ import ( "github.com/vdaas/vald/internal/net" "github.com/vdaas/vald/internal/safety" "github.com/vdaas/vald/internal/sync/errgroup" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,7 +37,7 @@ import ( type ( Manager = manager.Manager - OwnerReference = v1.OwnerReference + OwnerReference = metav1.OwnerReference ) type Controller interface { @@ -45,6 +45,8 @@ type Controller interface { GetManager() Manager } +var Now = metav1.Now + type ResourceController interface { GetName() string NewReconciler(ctx context.Context, mgr manager.Manager) reconcile.Reconciler diff --git a/internal/k8s/vald/mirror/api/v1/target_types.go b/internal/k8s/vald/mirror/api/v1/target_types.go new file mode 100644 index 0000000000..0263e5ad26 --- /dev/null +++ b/internal/k8s/vald/mirror/api/v1/target_types.go @@ -0,0 +1,175 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "vald.vdaas.org", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +func init() { + SchemeBuilder.Register( + &ValdMirrorTarget{}, + &ValdMirrorTargetList{}, + ) +} + +// ValdMirrorTarget is a mirror information. +type ValdMirrorTarget struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Spec MirrorTargetSpec `json:"spec,omitempty"` + Status MirrorTargetStatus `json:"status,omitempty"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdMirrorTarget) DeepCopyInto(out *ValdMirrorTarget) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValdMirror. +func (in *ValdMirrorTarget) DeepCopy() *ValdMirrorTarget { + if in == nil { + return nil + } + out := new(ValdMirrorTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdMirrorTarget) DeepCopyObject() runtime.Object { + return in.DeepCopy() +} + +// MirrorTargetSpec is a description of a ValdMirrorTarget. +type MirrorTargetSpec struct { + Colocation string `json:"colocation,omitempty"` + Target MirrorTarget `json:"target,omitempty"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MirrorTargetSpec) DeepCopyInto(out *MirrorTargetSpec) { + *out = *in + out.Colocation = in.Colocation + out.Target = in.Target +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MirrorSpec. +func (in *MirrorTargetSpec) DeepCopy() *MirrorTargetSpec { + if in == nil { + return nil + } + out := new(MirrorTargetSpec) + in.DeepCopyInto(out) + return out +} + +// MirrorTarget is a target information. +type MirrorTarget struct { + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MirrorTarget) DeepCopyInto(out *MirrorTarget) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MirrorTarget. +func (in *MirrorTarget) DeepCopy() *MirrorTarget { + if in == nil { + return nil + } + out := new(MirrorTarget) + in.DeepCopyInto(out) + return out +} + +// MirrorTargetStatus is a status of ValdMirrorTarget. +type MirrorTargetStatus struct { + Phase MirrorTargetPhase `json:"phase,omitempty"` + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` +} + +// MirrorTargetPhase is a label for the condition of a ValdMirrorTarget at the current time. +type MirrorTargetPhase string + +const ( + // MirrorTargetConnected means that the ValdMirrorTarget has been accepted by the system. + MirrorTargetPending = MirrorTargetPhase("Pending") + + // MirrorTargetConnected means that the target was connected. + MirrorTargetConnected = MirrorTargetPhase("Connected") + + // MirrorTargetDisconnected means that the target was disconnected. + MirrorTargetDisconnected = MirrorTargetPhase("Disconnected") + + // MirrorTargetUnknown means that for some reason the state of the ValdMirrorTarget could not be obtained. + MirrorTargetUnknown = MirrorTargetPhase("Unknown") +) + +// ValdMirrorTargetList is the whole list of all ValdMirror which have been registered with master. +type ValdMirrorTargetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Items []ValdMirrorTarget `json:"items,omitempty"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdMirrorTargetList) DeepCopyInto(out *ValdMirrorTargetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if len(in.Items) != 0 { + out.Items = make([]ValdMirrorTarget, len(in.Items)) + for i := 0; i < len(in.Items); i++ { + out.Items[i] = *in.Items[i].DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValdMirrorList. +func (in *ValdMirrorTargetList) DeepCopy() *ValdMirrorTargetList { + if in == nil { + return nil + } + out := new(ValdMirrorTargetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValdMirrorTargetList) DeepCopyObject() runtime.Object { + return in.DeepCopy() +} diff --git a/internal/k8s/vald/mirror/target/option.go b/internal/k8s/vald/mirror/target/option.go new file mode 100644 index 0000000000..cbf3829d5d --- /dev/null +++ b/internal/k8s/vald/mirror/target/option.go @@ -0,0 +1,93 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 target + +import ( + "context" + + "github.com/vdaas/vald/internal/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// Option represents the functional option for reconciler. +type Option func(r *reconciler) error + +var defaultOptions = []Option{} + +// WithControllerName returns the option to set the name of controller. +func WithControllerName(name string) Option { + return func(r *reconciler) error { + if name == "" { + return errors.NewErrInvalidOption("controllerName", name) + } + r.name = name + return nil + } +} + +// WithManager returns the option to set the controller manager. +func WithManager(mgr manager.Manager) Option { + return func(r *reconciler) error { + if mgr == nil { + return errors.NewErrInvalidOption("manager", mgr) + } + r.mgr = mgr + return nil + } +} + +// WithOnErrorFunc returns the option to set the function to notify an error. +func WithOnErrorFunc(f func(error)) Option { + return func(r *reconciler) error { + if f == nil { + return errors.NewErrInvalidOption("onErrorFunc", f) + } + r.onError = f + return nil + } +} + +// WithOnReconcileFunc returns the option to set the function to get the reconciled result. +func WithOnReconcileFunc(f func(context.Context, map[string]Target)) Option { + return func(r *reconciler) error { + if f == nil { + return errors.NewErrInvalidOption("onReconcileFunc", f) + } + r.onReconcile = f + return nil + } +} + +// WithNamespace returns the option to set the namespace to get resources matching the given namespace.. +func WithNamespace(ns string) Option { + return func(r *reconciler) error { + if ns == "" { + return errors.NewErrInvalidOption("namespace", ns) + } + r.addListOpts(client.InNamespace(ns)) + return nil + } +} + +// WithLabels returns the option to set the label selector to get resources matching the given label. +func WithLabels(labels map[string]string) Option { + return func(r *reconciler) error { + if len(labels) == 0 { + return errors.NewErrInvalidOption("labels", labels) + } + r.addListOpts(client.MatchingLabels(labels)) + return nil + } +} diff --git a/internal/k8s/vald/mirror/target/target.go b/internal/k8s/vald/mirror/target/target.go new file mode 100644 index 0000000000..cbfb1f60ae --- /dev/null +++ b/internal/k8s/vald/mirror/target/target.go @@ -0,0 +1,144 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 target + +import ( + "context" + "reflect" + "time" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/k8s" + mirrv1 "github.com/vdaas/vald/internal/k8s/vald/mirror/api/v1" + "github.com/vdaas/vald/internal/log" + apierr "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type ( + MirrorTargetWatcher k8s.ResourceController + MirrorTarget = mirrv1.ValdMirrorTarget + MirrorTargetStatus = mirrv1.MirrorTargetStatus + MirrorTargetPhase = mirrv1.MirrorTargetPhase +) + +const ( + MirrorTargetPhasePending = mirrv1.MirrorTargetPending + MirrorTargetPhaseConnected = mirrv1.MirrorTargetConnected + MirrorTargetPhaseDisconnected = mirrv1.MirrorTargetDisconnected + MirrorTargetPhaseUnknown = mirrv1.MirrorTargetUnknown +) + +const ( + reconcileRequeueDurationForInvalidError = 100 * time.Millisecond + reconcileRequeueDurationForNotFoundError = time.Second +) + +type reconciler struct { + mgr manager.Manager + name string + onError func(err error) + onReconcile func(ctx context.Context, mm map[string]Target) + lopts []client.ListOption +} + +type Target struct { + Colocation string + Host string + Port int + Phase MirrorTargetPhase +} + +func New(opts ...Option) (MirrorTargetWatcher, error) { + r := new(reconciler) + for _, opt := range append(defaultOptions, opts...) { + if err := opt(r); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(err, &e) { + log.Error(oerr) + return nil, oerr + } + log.Warn(oerr) + } + } + return r, nil +} + +func (r *reconciler) addListOpts(opt client.ListOption) { + if opt == nil { + return + } + r.lopts = append(r.lopts, opt) +} + +func (r *reconciler) Reconcile(ctx context.Context, _ reconcile.Request) (res reconcile.Result, err error) { + ml := &mirrv1.ValdMirrorTargetList{} + err = r.mgr.GetClient().List(ctx, ml, r.lopts...) + if err != nil { + if r.onError != nil { + r.onError(err) + } + if apierr.IsNotFound(err) { + return reconcile.Result{ + Requeue: true, + RequeueAfter: reconcileRequeueDurationForNotFoundError, + }, nil + } + return reconcile.Result{ + Requeue: true, + RequeueAfter: reconcileRequeueDurationForInvalidError, + }, err + } + tm := make(map[string]Target) + for _, m := range ml.Items { + name := m.GetObjectMeta().GetName() + tm[name] = Target{ + Colocation: m.Spec.Colocation, + Host: m.Spec.Target.Host, + Port: m.Spec.Target.Port, + Phase: m.Status.Phase, + } + } + r.onReconcile(ctx, tm) + return res, nil +} + +func (r *reconciler) GetName() string { + return r.name +} + +func (r *reconciler) NewReconciler(_ context.Context, mgr manager.Manager) reconcile.Reconciler { + if r.mgr == nil && mgr != nil { + r.mgr = mgr + } + mirrv1.AddToScheme(r.mgr.GetScheme()) + return r +} + +func (*reconciler) For() (client.Object, []builder.ForOption) { + return new(mirrv1.ValdMirrorTarget), nil +} + +func (*reconciler) Owns() (client.Object, []builder.OwnsOption) { + return nil, nil +} + +func (*reconciler) Watches() (client.Object, handler.EventHandler, []builder.WatchesOption) { + return nil, nil, nil +} diff --git a/internal/k8s/vald/mirror/target/target_template.go b/internal/k8s/vald/mirror/target/target_template.go new file mode 100644 index 0000000000..e037aea8d9 --- /dev/null +++ b/internal/k8s/vald/mirror/target/target_template.go @@ -0,0 +1,37 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 target + +import ( + "reflect" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" +) + +func NewMirrorTargetTemplate(opts ...MirrorTargetOption) (*MirrorTarget, error) { + mt := new(MirrorTarget) + for _, opt := range append(defaultMirrorTargetOptions, opts...) { + if err := opt(mt); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(err, &e) { + log.Error(oerr) + return nil, oerr + } + log.Warn(oerr) + } + } + return mt, nil +} diff --git a/internal/k8s/vald/mirror/target/target_template_option.go b/internal/k8s/vald/mirror/target/target_template_option.go new file mode 100644 index 0000000000..2a7f68a8cb --- /dev/null +++ b/internal/k8s/vald/mirror/target/target_template_option.go @@ -0,0 +1,90 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 target + +import "github.com/vdaas/vald/internal/errors" + +type MirrorTargetOption func(*MirrorTarget) error + +var defaultMirrorTargetOptions = []MirrorTargetOption{ + WithMirrorTargetLabels(map[string]string{ + "app.kubernetes.io/name": "mirror-target", + "app.kubernetes.io/managed-by": "gateway-mirror", + }), +} + +func WithMirrorTargetNamespace(ns string) MirrorTargetOption { + return func(mt *MirrorTarget) error { + if ns != "" { + mt.ObjectMeta.Namespace = ns + } + return nil + } +} + +func WithMirrorTargetName(name string) MirrorTargetOption { + return func(mt *MirrorTarget) error { + if name == "" { + return errors.NewErrCriticalOption("name", name) + } + mt.ObjectMeta.Name = name + return nil + } +} + +func WithMirrorTargetStatus(st *MirrorTargetStatus) MirrorTargetOption { + return func(mt *MirrorTarget) error { + mt.Status = *st + return nil + } +} + +func WithMirrorTargetLabels(labels map[string]string) MirrorTargetOption { + return func(mt *MirrorTarget) error { + if len(labels) != 0 { + mt.ObjectMeta.Labels = labels + } + return nil + } +} + +func WithMirrorTargetColocation(n string) MirrorTargetOption { + return func(mt *MirrorTarget) error { + if n == "" { + return errors.NewErrCriticalOption("colocation", n) + } + mt.Spec.Colocation = n + return nil + } +} + +func WithMirrorTargetHost(n string) MirrorTargetOption { + return func(mt *MirrorTarget) error { + if n == "" { + return errors.NewErrCriticalOption("host", n) + } + mt.Spec.Target.Host = n + return nil + } +} + +func WithMirrorTargetPort(port int) MirrorTargetOption { + return func(mt *MirrorTarget) error { + if port <= 0 { + return errors.NewErrCriticalOption("port", port) + } + mt.Spec.Target.Port = port + return nil + } +} diff --git a/internal/net/grpc/interceptor/server/metric/metric.go b/internal/net/grpc/interceptor/server/metric/metric.go index e6915f0dbe..d6c85149a2 100644 --- a/internal/net/grpc/interceptor/server/metric/metric.go +++ b/internal/net/grpc/interceptor/server/metric/metric.go @@ -65,7 +65,7 @@ func MetricInterceptors() (grpc.UnaryServerInterceptor, grpc.StreamServerInterce elapsedTime := time.Since(now) record(ctx, info.FullMethod, err, float64(elapsedTime)/float64(time.Millisecond)) return resp, err - }, func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + }, func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { now := time.Now() err = handler(srv, ss) elapsedTime := time.Since(now) diff --git a/internal/net/grpc/metadata.go b/internal/net/grpc/metadata.go new file mode 100644 index 0000000000..fdc1d26e74 --- /dev/null +++ b/internal/net/grpc/metadata.go @@ -0,0 +1,34 @@ +// +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 grpc provides generic functionality for grpc +package grpc + +import ( + "context" + + "google.golang.org/grpc/metadata" +) + +type MD = metadata.MD + +func NewOutgoingContext(ctx context.Context, md MD) context.Context { + return metadata.NewOutgoingContext(ctx, md) +} + +func FromIncomingContext(ctx context.Context) (metadata.MD, bool) { + return metadata.FromIncomingContext(ctx) +} diff --git a/internal/observability/metrics/gateway/mirror/mirror.go b/internal/observability/metrics/gateway/mirror/mirror.go new file mode 100644 index 0000000000..4c6dd3757e --- /dev/null +++ b/internal/observability/metrics/gateway/mirror/mirror.go @@ -0,0 +1,77 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 mirror + +import ( + "context" + + "github.com/vdaas/vald/internal/observability/attribute" + "github.com/vdaas/vald/internal/observability/metrics" + "github.com/vdaas/vald/pkg/gateway/mirror/service" + "go.opentelemetry.io/otel/sdk/metric/aggregation" + "go.opentelemetry.io/otel/sdk/metric/view" +) + +const ( + metricsName = "gateway_mirror_connecting_target" + metricsDescription = "Target to which the mirror gateway is connecting" + + targetAddrKey = "addr" +) + +type mirrorMetrics struct { + mirr service.Mirror +} + +func New(mirr service.Mirror) metrics.Metric { + return &mirrorMetrics{ + mirr: mirr, + } +} + +func (*mirrorMetrics) View() ([]*metrics.View, error) { + target, err := view.New( + view.MatchInstrumentName(metricsName), + view.WithSetDescription(metricsDescription), + view.WithSetAggregation(aggregation.LastValue{}), + ) + if err != nil { + return nil, err + } + return []*metrics.View{ + &target, + }, nil +} + +func (mm *mirrorMetrics) Register(m metrics.Meter) error { + targetGauge, err := m.AsyncInt64().Gauge( + metricsName, + metrics.WithDescription(metricsDescription), + metrics.WithUnit(metrics.Dimensionless), + ) + if err != nil { + return err + } + return m.RegisterCallback( + []metrics.AsynchronousInstrument{ + targetGauge, + }, + func(ctx context.Context) { + mm.mirr.RangeMirrorAddr(func(addr string, _ any) bool { + targetGauge.Observe(ctx, 1, attribute.String(targetAddrKey, addr)) + return true + }) + }, + ) +} diff --git a/internal/test/mock/client/mirror_client_mock.go b/internal/test/mock/client/mirror_client_mock.go new file mode 100644 index 0000000000..10cd4563ac --- /dev/null +++ b/internal/test/mock/client/mirror_client_mock.go @@ -0,0 +1,52 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 client + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/net/grpc" +) + +type MirrorClientMock struct { + vald.ClientWithMirror + + InsertFunc func(ctx context.Context, in *payload.Insert_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) + UpdateFunc func(ctx context.Context, in *payload.Update_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) + UpsertFunc func(ctx context.Context, in *payload.Upsert_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) + RemoveFunc func(ctx context.Context, in *payload.Remove_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) + RemoveByTimestampFunc func(ctx context.Context, in *payload.Remove_TimestampRequest, opts ...grpc.CallOption) (*payload.Object_Locations, error) +} + +func (mc *MirrorClientMock) Insert(ctx context.Context, in *payload.Insert_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) { + return mc.InsertFunc(ctx, in, opts...) +} + +func (mc *MirrorClientMock) Update(ctx context.Context, in *payload.Update_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) { + return mc.UpdateFunc(ctx, in, opts...) +} + +func (mc *MirrorClientMock) Upsert(ctx context.Context, in *payload.Upsert_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) { + return mc.UpsertFunc(ctx, in, opts...) +} + +func (mc *MirrorClientMock) Remove(ctx context.Context, in *payload.Remove_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) { + return mc.RemoveFunc(ctx, in, opts...) +} + +func (mc *MirrorClientMock) RemoveByTimestamp(ctx context.Context, in *payload.Remove_TimestampRequest, opts ...grpc.CallOption) (*payload.Object_Locations, error) { + return mc.RemoveByTimestampFunc(ctx, in, opts...) +} diff --git a/internal/test/mock/grpc/grpc_client_mock.go b/internal/test/mock/grpc/grpc_client_mock.go index a475361473..8d83768dac 100644 --- a/internal/test/mock/grpc/grpc_client_mock.go +++ b/internal/test/mock/grpc/grpc_client_mock.go @@ -17,6 +17,7 @@ import ( "context" "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/net/grpc/pool" ) // GRPCClientMock is the mock for gRPC client. @@ -29,6 +30,10 @@ type GRPCClientMock struct { addr string, conn *grpc.ClientConn, copts ...grpc.CallOption) error) error + ConnectFunc func(ctx context.Context, addr string, dopts ...grpc.DialOption) (pool.Conn, error) + DisconnectFunc func(ctx context.Context, addr string) error + IsConnectedFunc func(ctx context.Context, addr string) bool + ConnectedAddrsFunc func() []string } // OrderedRangeConcurrent calls the OrderedRangeConcurrentFunc object. @@ -42,3 +47,23 @@ func (gc *GRPCClientMock) OrderedRangeConcurrent(ctx context.Context, ) error { return gc.OrderedRangeConcurrentFunc(ctx, order, concurrency, f) } + +// ConnectedAddrs calls the ConnectedAddrsFunc object. +func (gc *GRPCClientMock) ConnectedAddrs() []string { + return gc.ConnectedAddrsFunc() +} + +// Connect calls the ConnectFunc object. +func (gc *GRPCClientMock) Connect(ctx context.Context, addr string, dopts ...grpc.DialOption) (pool.Conn, error) { + return gc.ConnectFunc(ctx, addr, dopts...) +} + +// Disconnect calls the DisconnectFunc object. +func (gc *GRPCClientMock) Disconnect(ctx context.Context, addr string) error { + return gc.DisconnectFunc(ctx, addr) +} + +// IsConnected calls the IsConnectedFunc object. +func (gc *GRPCClientMock) IsConnected(ctx context.Context, addr string) bool { + return gc.IsConnectedFunc(ctx, addr) +} diff --git a/internal/test/mock/k8s/controller_runtime.go b/internal/test/mock/k8s/controller_runtime.go new file mode 100644 index 0000000000..c08fec5414 --- /dev/null +++ b/internal/test/mock/k8s/controller_runtime.go @@ -0,0 +1,100 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 k8s + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type SubResourceWriterMock struct { + client.SubResourceWriter + + UpdateFunc func(context.Context, client.Object, ...client.SubResourceUpdateOption) error +} + +func (sm *SubResourceWriterMock) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return sm.UpdateFunc(ctx, obj, opts...) +} + +type ClientMock struct { + client.Client + + StatusFunc func() client.SubResourceWriter + GetFunc func(context.Context, client.ObjectKey, client.Object, ...client.GetOption) error + CreateFunc func(context.Context, client.Object, ...client.CreateOption) error + DeleteFunc func(context.Context, client.Object, ...client.DeleteOption) error + DeleteAllOfFunc func(context.Context, client.Object, ...client.DeleteAllOfOption) error +} + +func (cm *ClientMock) Status() client.SubResourceWriter { + return cm.StatusFunc() +} + +func (cm *ClientMock) Get(ctx context.Context, objKey client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + return cm.GetFunc(ctx, objKey, obj, opts...) +} + +func (cm *ClientMock) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return cm.CreateFunc(ctx, obj, opts...) +} + +func (cm *ClientMock) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + return cm.DeleteFunc(ctx, obj, opts...) +} + +func (cm *ClientMock) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + return cm.DeleteAllOfFunc(ctx, obj, opts...) +} + +type ManagerMock struct { + manager.Manager + + GetClientFunc func() client.Client +} + +func (mm *ManagerMock) GetClient() client.Client { + return mm.GetClientFunc() +} + +// NewDefaultManagerMock returns default empty mock object. +func NewDefaultManagerMock() manager.Manager { + return &ManagerMock{ + GetClientFunc: func() client.Client { + return &ClientMock{ + StatusFunc: func() client.SubResourceWriter { + return &SubResourceWriterMock{ + UpdateFunc: func(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + return nil + }, + } + }, + GetFunc: func(_ context.Context, _ client.ObjectKey, _ client.Object, _ ...client.GetOption) error { + return nil + }, + CreateFunc: func(_ context.Context, _ client.Object, _ ...client.CreateOption) error { + return nil + }, + DeleteFunc: func(_ context.Context, _ client.Object, _ ...client.DeleteOption) error { + return nil + }, + DeleteAllOfFunc: func(_ context.Context, _ client.Object, _ ...client.DeleteAllOfOption) error { + return nil + }, + } + }, + } +} diff --git a/internal/test/mock/k8s/reconciler.go b/internal/test/mock/k8s/reconciler.go new file mode 100644 index 0000000000..09809d7012 --- /dev/null +++ b/internal/test/mock/k8s/reconciler.go @@ -0,0 +1,33 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 k8s + +import ( + "context" + + "github.com/vdaas/vald/internal/k8s" +) + +type ControllerMock struct { + StartFunc func(ctx context.Context) (<-chan error, error) + GetManagerFunc func() k8s.Manager +} + +func (m *ControllerMock) Start(ctx context.Context) (<-chan error, error) { + return m.StartFunc(ctx) +} + +func (m *ControllerMock) GetManager() k8s.Manager { + return m.GetManagerFunc() +} diff --git a/pkg/gateway/mirror/README.md b/pkg/gateway/mirror/README.md new file mode 100755 index 0000000000..668dce401c --- /dev/null +++ b/pkg/gateway/mirror/README.md @@ -0,0 +1 @@ +# vald Mirror gateway diff --git a/pkg/gateway/mirror/config/config.go b/pkg/gateway/mirror/config/config.go new file mode 100644 index 0000000000..8528da24c0 --- /dev/null +++ b/pkg/gateway/mirror/config/config.go @@ -0,0 +1,76 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 config + +import ( + "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errors" +) + +type ( + // GlobalConfig is a type alias of config.GlobalConfig representing application base configurations. + GlobalConfig = config.GlobalConfig + + // Server is a type alias of config.Server representing server base configurations. + Server = config.Server +) + +// Data represent a application setting data content (config.yaml). +// In K8s environment, this configuration is stored in K8s ConfigMap. +type Data struct { + config.GlobalConfig `json:",inline" yaml:",inline"` + + // Server represent all server configurations + Server *config.Servers `json:"server_config" yaml:"server_config"` + + // Observability represent observability configurations + Observability *config.Observability `json:"observability" yaml:"observability"` + + // Mirror represent mirror gateway service configuration + Mirror *config.Mirror `json:"gateway" yaml:"gateway"` +} + +// NewConfig load configurations from file path. +func NewConfig(path string) (cfg *Data, err error) { + cfg = new(Data) + + if err = config.Read(path, &cfg); err != nil { + return nil, err + } + + if cfg != nil { + _ = cfg.Bind() + } else { + return nil, errors.ErrInvalidConfig + } + + if cfg.Server != nil { + cfg.Server = cfg.Server.Bind() + } else { + return nil, errors.ErrInvalidConfig + } + + if cfg.Observability != nil { + cfg.Observability = cfg.Observability.Bind() + } else { + cfg.Observability = new(config.Observability).Bind() + } + + if cfg.Mirror != nil { + cfg.Mirror = cfg.Mirror.Bind() + } else { + return nil, errors.ErrInvalidConfig + } + return cfg, nil +} diff --git a/pkg/gateway/mirror/handler/doc.go b/pkg/gateway/mirror/handler/doc.go new file mode 100644 index 0000000000..75fbe95246 --- /dev/null +++ b/pkg/gateway/mirror/handler/doc.go @@ -0,0 +1,14 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 diff --git a/pkg/gateway/mirror/handler/grpc/handler.go b/pkg/gateway/mirror/handler/grpc/handler.go new file mode 100644 index 0000000000..9a04ccc00e --- /dev/null +++ b/pkg/gateway/mirror/handler/grpc/handler.go @@ -0,0 +1,3344 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 grpc + +import ( + "context" + "fmt" + "io" + "reflect" + "sync/atomic" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/net/grpc/codes" + "github.com/vdaas/vald/internal/net/grpc/errdetails" + "github.com/vdaas/vald/internal/net/grpc/status" + "github.com/vdaas/vald/internal/observability/trace" + "github.com/vdaas/vald/internal/safety" + "github.com/vdaas/vald/internal/sync" + "github.com/vdaas/vald/internal/sync/errgroup" + "github.com/vdaas/vald/pkg/gateway/mirror/service" +) + +type server struct { + eg errgroup.Group + gateway service.Gateway // Mirror gateway client service. + mirror service.Mirror + vAddr string // Vald gateway address (LB gateway). + streamConcurrency int + name string + ip string + vald.UnimplementedValdServerWithMirror +} + +const apiName = "vald/gateway/mirror" + +// New returns a Vald server as gRPC handler with mirror using the provided options. +func New(opts ...Option) (vald.ServerWithMirror, error) { + s := new(server) + for _, opt := range append(defaultOptions, opts...) { + if err := opt(s); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(err, &e) { + log.Error(oerr) + return nil, oerr + } + log.Warn(oerr) + } + } + return s, nil +} + +// Register handles the registration of mirror targets. +// The function connects to the mirror using the provided targets, and if successful, +// returns the addresses of connected Mirror gateways. +func (s *server) Register(ctx context.Context, req *payload.Mirror_Targets) (*payload.Mirror_Targets, error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.MirrorRPCServiceName+"/"+vald.RegisterRPCName), apiName+"/"+vald.RegisterRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err := s.mirror.Connect(ctx, req.GetTargets()...) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RegisterRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.RegisterRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithCanceled( + vald.RegisterRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.RegisterRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInvalidArgument( + vald.RegisterRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInvalidArgument(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.RegisterRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // Get own address and the addresses of other mirror gateways to which this gateway is currently connected. + tgts, err := s.mirror.MirrorTargets(ctx) + if err != nil { + err = status.WrapWithInternal(vald.RegisterRPCName+" API failed to get connected vald gateway targets", err, + &errdetails.BadRequest{ + FieldViolations: []*errdetails.BadRequestFieldViolation{ + { + Field: "mirror gateway targets", + Description: err.Error(), + }, + }, + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RegisterRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return &payload.Mirror_Targets{ + Targets: tgts, + }, nil +} + +// Exists bypasses the incoming Exist request to Vald gateway (LB gateway) in its own cluster. +func (s *server) Exists(ctx context.Context, meta *payload.Object_ID) (id *payload.Object_ID, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.ObjectRPCServiceName+"/"+vald.ExistsRPCName), apiName+"/"+vald.ExistsRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + id, err = vc.Exists(ctx, meta, copts...) + return id, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(meta), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.ExistsRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.ExistsRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.ExistsRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.ExistsRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.ExistsRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.ExistsRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return id, nil +} + +// Search bypasses the incoming Search request to Vald gateway (LB gateway) in its own cluster. +func (s *server) Search(ctx context.Context, req *payload.Search_Request) (res *payload.Search_Response, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.SearchRPCName), apiName+"/"+vald.SearchRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.Search(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetConfig().GetRequestId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.SearchRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.SearchRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.SearchRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.SearchRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.SearchRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.SearchRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// SearchByID bypasses the incoming SearchByID request to Vald gateway (LB gateway) in its own cluster. +func (s *server) SearchByID(ctx context.Context, req *payload.Search_IDRequest) ( + res *payload.Search_Response, err error, +) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.SearchByIDRPCName), apiName+"/"+vald.SearchByIDRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.SearchByID(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetConfig().GetRequestId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.SearchByIDRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.SearchByIDRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.SearchByIDRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.SearchByIDRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.SearchByIDRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.SearchByIDRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// StreamSearch bypasses it as a Search request to Vald gateway (LB gateway) in its own cluster. +func (s *server) StreamSearch(stream vald.Search_StreamSearchServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.StreamSearchRPCName), apiName+"/"+vald.StreamSearchRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Search_Request) (*payload.Search_StreamResponse, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamSearchRPCName+"/requestID-"+req.GetConfig().GetRequestId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.Search(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.SearchRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Response{ + Response: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.StreamSearchRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// StreamSearchByID bypasses it as a SearchByID request to Vald gateway (LB gateway) in its own cluster. +func (s *server) StreamSearchByID(stream vald.Search_StreamSearchByIDServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.StreamSearchByIDRPCName), apiName+"/"+vald.StreamSearchByIDRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Search_IDRequest) (*payload.Search_StreamResponse, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"."+vald.StreamSearchByIDRPCName+"/id-"+req.GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.SearchByID(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.SearchByIDRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Response{ + Response: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.StreamSearchByIDRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// MultiSearch bypasses the incoming MultiSearch request to Vald gateway (LB gateway) in its own cluster. +func (s *server) MultiSearch(ctx context.Context, req *payload.Search_MultiRequest) (res *payload.Search_Responses, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.MultiSearchRPCName), apiName+"/"+vald.MultiSearchRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.MultiSearch(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiSearchRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.MultiSearchRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.MultiSearchRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.MultiSearchRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.MultiSearchRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.MultiSearchRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// MultiSearchByID bypasses the incoming MultiSearchByID request to Vald gateway (LB gateway) in its own cluster. +func (s *server) MultiSearchByID(ctx context.Context, req *payload.Search_MultiIDRequest) (res *payload.Search_Responses, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.MultiSearchByIDRPCName), apiName+"/"+vald.MultiSearchByIDRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.MultiSearchByID(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiSearchByIDRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.MultiSearchByIDRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.MultiSearchByIDRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.MultiSearchByIDRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.MultiSearchByIDRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.MultiSearchByIDRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// LinearSearch bypasses the incoming LinearSearch request to Vald gateway (LB gateway) in its own cluster. +func (s *server) LinearSearch(ctx context.Context, req *payload.Search_Request) (res *payload.Search_Response, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.LinearSearchRPCName), apiName+"/"+vald.LinearSearchRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.LinearSearch(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetConfig().GetRequestId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.LinearSearchRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.LinearSearchRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.LinearSearchRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.LinearSearchRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.LinearSearchRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.LinearSearchRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// LinearSearchByID bypasses the incoming LinearSearchByID request to Vald gateway (LB gateway) in its own cluster. +func (s *server) LinearSearchByID(ctx context.Context, req *payload.Search_IDRequest) ( + res *payload.Search_Response, err error, +) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.LinearSearchByIDRPCName), apiName+"/"+vald.LinearSearchByIDRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.LinearSearchByID(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetConfig().GetRequestId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.LinearSearchByIDRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.LinearSearchByIDRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.LinearSearchByIDRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.LinearSearchByIDRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.LinearSearchByIDRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.LinearSearchByIDRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// StreamLinearSearch bypasses it as a LinearSearch request to Vald gateway (LB gateway) in its own cluster. +func (s *server) StreamLinearSearch(stream vald.Search_StreamLinearSearchServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.StreamLinearSearchRPCName), apiName+"/"+vald.StreamLinearSearchRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Search_Request) (*payload.Search_StreamResponse, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamLinearSearchRPCName+"/requestID-"+req.GetConfig().GetRequestId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.LinearSearch(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.LinearSearchRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Response{ + Response: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.StreamLinearSearchRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// StreamLinearSearchByID bypasses it as a LinearSearchByID request to Vald gateway (LB gateway) in its own cluster. +func (s *server) StreamLinearSearchByID(stream vald.Search_StreamLinearSearchByIDServer) (err error) { + ctx, span := trace.StartSpan( + grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.StreamLinearSearchByIDRPCName), + apiName+"/"+vald.StreamLinearSearchByIDRPCName, + ) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Search_IDRequest) (*payload.Search_StreamResponse, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"."+vald.StreamLinearSearchByIDRPCName+"/id-"+req.GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.LinearSearchByID(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.LinearSearchByIDRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Search_StreamResponse{ + Payload: &payload.Search_StreamResponse_Response{ + Response: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.StreamLinearSearchByIDRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// MultiLinearSearch bypasses the incoming MultiLinearSearch request to Vald gateway (LB gateway) in its own cluster. +func (s *server) MultiLinearSearch(ctx context.Context, req *payload.Search_MultiRequest) (res *payload.Search_Responses, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.MultiLinearSearchRPCName), apiName+"/"+vald.MultiLinearSearchRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.MultiLinearSearch(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiLinearSearchRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.MultiLinearSearchRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.MultiLinearSearchRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.MultiLinearSearchRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.MultiLinearSearchRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.MultiLinearSearchRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// MultiLinearSearchByID bypasses the incoming MultiLinearSearchByID request to Vald gateway (LB gateway) in its own cluster. +func (s *server) MultiLinearSearchByID(ctx context.Context, req *payload.Search_MultiIDRequest) (res *payload.Search_Responses, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.SearchRPCServiceName+"/"+vald.MultiLinearSearchByIDRPCName), apiName+"/"+vald.MultiLinearSearchByIDRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + res, err = vc.MultiLinearSearchByID(ctx, req, copts...) + return res, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiLinearSearchByIDRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.MultiLinearSearchByIDRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.MultiLinearSearchByIDRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.MultiLinearSearchByIDRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.MultiLinearSearchByIDRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.MultiLinearSearchByIDRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return res, nil +} + +// Insert handles the insertion of an object with the given request. +// If the request is proxied from another Mirror gateway, the request is forwarded to the Vald gateway (LB gateway) of its own cluster. +// If the request is from a user, it is sent to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. +// The result is a location of the inserted object. +func (s *server) Insert(ctx context.Context, req *payload.Insert_Request) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.InsertRPCServiceName+"/"+vald.InsertRPCName), apiName+"/"+vald.InsertRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + // If this condition is matched, it means that the request was proxied from another Mirror gateway. + // So this component sends requests only to the Vald gateway (LB gateway) of its own cluster. + if s.isProxied(ctx) { + loc, err = s.doInsert(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + _, derr := s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + loc, err = vc.Insert(ctx, req, copts...) + return loc, err + }) + return loc, errors.Join(derr, err) + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf("Insert API succeeded to %#v", loc) + return loc, nil + } + + // If this condition is matched, it means the request from user. + // So this component sends requests to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. + return s.handleInsert(ctx, req) +} + +func (s *server) handleInsert(ctx context.Context, req *payload.Insert_Request) (loc *payload.Object_Location, err error) { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleInsert"), apiName+"/handleInsert") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + var result sync.Map[string, *errorState] // map[target host: error state] + loc = &payload.Object_Location{ + Uuid: req.GetVector().GetId(), + Ips: make([]string, 0), + } + err = s.gateway.BroadCast(ctx, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BroadCast/"+target), apiName+"/"+vald.InsertRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + ce, err := s.doInsert(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + return vc.Insert(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName + ".BroadCast/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && ce != nil { + mu.Lock() + loc.Name = ce.GetName() + loc.Ips = append(loc.Ips, ce.GetIps()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName + ".BroadCast", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.InsertRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + alreadyExistsTgts := make([]string, 0, result.Len()/2) + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, es *errorState) bool { + switch { + case es.err == nil: + successTgts = append(successTgts, target) + case es.code == codes.AlreadyExists: + alreadyExistsTgts = append(alreadyExistsTgts, target) + err = errors.Join(err, es.err) + default: + err = errors.Join(es.err, err) + } + return true + }) + if err == nil { + log.Debugf(vald.InsertRPCName+" API request succeeded to %#v", loc) + return loc, nil + } + + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == len(alreadyExistsTgts): + err = status.WrapWithAlreadyExists(vald.InsertRPCName+" API target same vector already exists", err, reqInfo, resInfo) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + case result.Len() > len(successTgts)+len(alreadyExistsTgts): // Contains errors except for ALREADY_EXIST. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // In this case, the status code in the result object contains only OK or ALREADY_EXIST. + // And send Update API requst to ALREADY_EXIST cluster using the query requested by the user. + log.Warnf("failed to "+vald.InsertRPCName+" API: %#v", err) + + resLoc, err := s.handleInsertResult(ctx, alreadyExistsTgts, &payload.Update_Request{ + Vector: req.GetVector(), + Config: &payload.Update_Config{ + Timestamp: req.GetConfig().GetTimestamp(), + }, + }, &result) + if err != nil { + return nil, err + } + loc.Name = resLoc.Name + loc.Ips = append(loc.Ips, resLoc.Ips...) + return loc, nil +} + +func (s *server) handleInsertResult( // skipcq: GO-R1005 + ctx context.Context, + alreadyExistsTgts []string, + req *payload.Update_Request, + result *sync.Map[string, *errorState], +) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleInsertResult"), apiName+"/handleInsertResult") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + loc = &payload.Object_Location{ + Uuid: req.GetVector().GetId(), + Ips: make([]string, 0), + } + + err = s.gateway.DoMulti(ctx, alreadyExistsTgts, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "DoMulti/"+target), apiName+"/"+vald.UpdateRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + ce, err := s.doUpdate(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + return vc.Update(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName + ".DoMulti/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && ce != nil { + mu.Lock() + loc.Name = ce.GetName() + loc.Ips = append(loc.Ips, ce.GetIps()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName + "." + vald.InsertRPCName + ".DoMulti", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.UpdateRPCName+" for "+vald.InsertRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" for "+vald.InsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + alreadyExistsTgts = alreadyExistsTgts[0:0] + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, es *errorState) bool { + switch { + case es.err == nil: + successTgts = append(successTgts, target) + case es.code == codes.AlreadyExists: + alreadyExistsTgts = append(alreadyExistsTgts, target) + err = errors.Join(err, es.err) + default: + err = errors.Join(es.err, err) + } + return true + }) + if err == nil || (len(successTgts) > 0 && result.Len() == len(successTgts)+len(alreadyExistsTgts)) { + log.Debugf(vald.UpdateRPCName+" for "+vald.InsertRPCName+" API request succeeded to %#v", loc) + return loc, nil + } + + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName + "." + vald.UpdateRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == len(alreadyExistsTgts): + err = status.WrapWithAlreadyExists(vald.UpdateRPCName+" for "+vald.InsertRPCName+" API target same vector already exists", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + case result.Len() > len(successTgts)+len(alreadyExistsTgts): // Contains errors except for ALREADY_EXIST. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" for "+vald.InsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf(vald.UpdateRPCName+"for "+vald.InsertRPCName+" API request succeeded to %#v, err: %v", loc, err) + return loc, nil +} + +func (s *server) doInsert(ctx context.Context, req *payload.Insert_Request, f func(ctx context.Context) (*payload.Object_Location, error)) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "doInsert"), apiName+"/doInsert") + defer func() { + if span != nil { + span.End() + } + }() + + loc, err = f(ctx) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.InsertRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.InsertRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.InsertRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.InsertRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return loc, nil +} + +// StreamInsert handles bidirectional streaming for inserting objects. +// It wraps the bidirectional stream logic for the Insert RPC method. +// For each incoming request in the bidirectional stream, it calls the Insert function. +// The response is then sent back through the stream with the corresponding status or location information. +func (s *server) StreamInsert(stream vald.Insert_StreamInsertServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.InsertRPCServiceName+"/"+vald.StreamInsertRPCName), apiName+"/"+vald.StreamInsertRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Insert_Request) (*payload.Object_StreamLocation, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamInsertRPCName+"/id-"+req.GetVector().GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.Insert(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.InsertRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Location{ + Location: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.StreamInsertRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// MultiInsert handles the insertion of multiple objects with the given requests. +// For each request in parallel, it calls the Insert function to insert an object. +// If an error occurs during any of the insertions, it accumulates the errors and returns them along with the successfully inserted locations. +func (s *server) MultiInsert(ctx context.Context, reqs *payload.Insert_MultiRequest) (res *payload.Object_Locations, errs error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.InsertRPCServiceName+"/"+vald.MultiInsertRPCName), apiName+"/"+vald.MultiInsertRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + res = &payload.Object_Locations{ + Locations: make([]*payload.Object_Location, len(reqs.GetRequests())), + } + + var mu, emu sync.Mutex + var wg sync.WaitGroup + + for i, r := range reqs.GetRequests() { + idx, req := i, r + wg.Add(1) + s.eg.Go(safety.RecoverFunc(func() error { + defer wg.Done() + ti := "errgroup.Go/id-" + req.GetVector().GetId() + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, ti), apiName+"/"+vald.MultiInsertRPCName+"/"+ti) + defer func() { + if span != nil { + span.End() + } + }() + + loc, err := s.Insert(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.InsertRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + emu.Lock() + errs = errors.Join(errs, err) + emu.Unlock() + return nil + } + mu.Lock() + res.Locations[idx] = loc + mu.Unlock() + return nil + })) + } + wg.Wait() + if errs != nil { + st, msg, err := status.ParseError(errs, codes.Internal, "failed to parse "+vald.MultiInsertRPCName+" gRPC error response", + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiInsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return res, err + } + return res, nil +} + +// Update handles the update of an object with the given request. +// If the request is proxied from another Mirror gateway, it sends the request only to the Vald gateway (LB gateway) of its own cluster. +// If the request is from a user, it sends requests to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. +// The result is a location of the updated object. +func (s *server) Update(ctx context.Context, req *payload.Update_Request) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.UpdateRPCServiceName+"/"+vald.UpdateRPCName), apiName+"/"+vald.UpdateRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + // If this condition is matched, it means that the request was proxied from another Mirror gateway. + // So this component sends requests only to the Vald gateway (LB gateway) of its own cluster. + if s.isProxied(ctx) { + loc, err = s.doUpdate(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + _, derr := s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + loc, err = vc.Update(ctx, req, copts...) + return loc, err + }) + return loc, errors.Join(derr, err) + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf("Update API succeeded to %#v", loc) + return loc, nil + } + + // If this condition is matched, it means the request from user. + // So this component sends requests to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. + return s.handleUpdate(ctx, req) +} + +func (s *server) handleUpdate(ctx context.Context, req *payload.Update_Request) (loc *payload.Object_Location, err error) { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleUpdate"), apiName+"/handleUpdate") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + var result sync.Map[string, *errorState] // map[target host: error state] + loc = &payload.Object_Location{ + Uuid: req.GetVector().GetId(), + Ips: make([]string, 0), + } + err = s.gateway.BroadCast(ctx, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BroadCast/"+target), apiName+"/"+vald.UpdateRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + ce, err := s.doUpdate(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + return vc.Update(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName + ".BroadCast/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && ce != nil { + mu.Lock() + loc.Name = ce.GetName() + loc.Ips = append(loc.Ips, ce.GetIps()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName + ".BroadCast", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.UpdateRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + var alreadyExistsCnt int + notFoundTgts := make([]string, 0, result.Len()/2) + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, es *errorState) bool { + switch { + case es.err == nil: + successTgts = append(successTgts, target) + case es.code == codes.AlreadyExists: + alreadyExistsCnt++ + err = errors.Join(err, es.err) + case es.code == codes.NotFound: + notFoundTgts = append(notFoundTgts, target) + err = errors.Join(err, es.err) + default: + err = errors.Join(es.err, err) + } + return true + }) + if err == nil || (len(successTgts) > 0 && result.Len() == len(successTgts)+alreadyExistsCnt) { + log.Debugf(vald.UpdateRPCName+" API request succeeded to %#v", loc) + return loc, nil + } + + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == len(notFoundTgts): + err = status.WrapWithNotFound(vald.UpdateRPCName+" API target not found", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + case result.Len() == alreadyExistsCnt: + err = status.WrapWithAlreadyExists(vald.UpdateRPCName+" API target same vector already exists", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeNotFound(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + case result.Len() > len(successTgts)+len(notFoundTgts)+alreadyExistsCnt: // Contains errors except for NOT_FOUND and ALREADY_EXIST. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" gRPC error response", reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // In this case, the status code in the result object contains only OK or ALREADY_EXIST or NOT_FOUND. + // And send Insert API requst to NOT_FOUND cluster using query requested by the user. + log.Warnf("failed to "+vald.UpdateRPCName+" API: %#v", err) + + resLoc, err := s.handleUpdateResult(ctx, notFoundTgts, &payload.Insert_Request{ + Vector: req.GetVector(), + Config: &payload.Insert_Config{ + Timestamp: req.GetConfig().GetTimestamp(), + }, + }, &result) + if err != nil { + return nil, err + } + loc.Name = resLoc.Name + loc.Ips = append(loc.Ips, resLoc.Ips...) + return loc, nil +} + +func (s *server) handleUpdateResult( // skipcq: GO-R1005 + ctx context.Context, + notFoundTgts []string, + req *payload.Insert_Request, + result *sync.Map[string, *errorState], +) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleUpdateResult"), apiName+"/handleUpdateResult") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + loc = &payload.Object_Location{ + Uuid: req.GetVector().GetId(), + Ips: make([]string, 0), + } + + err = s.gateway.DoMulti(ctx, notFoundTgts, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BroadCast/"+target), apiName+"/"+vald.InsertRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + ce, err := s.doInsert(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + return vc.Insert(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.InsertRPCName + ".BroadCast/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && ce != nil { + mu.Lock() + loc.Name = ce.GetName() + loc.Ips = append(loc.Ips, ce.GetIps()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName + "." + vald.InsertRPCName + ".BroadCast", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.InsertRPCName+" for "+vald.UpdateRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" for "+vald.UpdateRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + alreadyExistsCnt := 0 + notFoundTgts = notFoundTgts[0:0] + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, em *errorState) bool { + switch { + case em.err == nil: + successTgts = append(successTgts, target) + case em.code == codes.AlreadyExists: + alreadyExistsCnt++ + err = errors.Join(err, em.err) + case em.code == codes.NotFound: + notFoundTgts = append(notFoundTgts, target) + err = errors.Join(err, em.err) + default: + err = errors.Join(em.err, err) + } + return true + }) + if err == nil || (len(successTgts) > 0 && result.Len() == len(successTgts)+alreadyExistsCnt) { + log.Debugf(vald.InsertRPCName+" for "+vald.UpdateRPCName+" API request succeeded to %#v", loc) + return loc, nil + } + + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName + "." + vald.InsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == len(notFoundTgts): + err = status.WrapWithNotFound(vald.InsertRPCName+" for "+vald.UpdateRPCName+" API target not found", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + case result.Len() == alreadyExistsCnt: + err = status.WrapWithAlreadyExists(vald.InsertRPCName+" for "+vald.UpdateRPCName+" API target same vector already exists", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeNotFound(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + case result.Len() > len(successTgts)+len(notFoundTgts)+alreadyExistsCnt: // Contains errors except for NOT_FOUND and ALREADY_EXIST. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.InsertRPCName+" for "+vald.UpdateRPCName+" gRPC error response", reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf(vald.InsertRPCName+" for "+vald.UpdateRPCName+" API request succeeded to %#v, err: %v", loc, err) + return loc, nil +} + +func (s *server) doUpdate(ctx context.Context, req *payload.Update_Request, f func(ctx context.Context) (*payload.Object_Location, error)) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "doUpdate"), apiName+"/doUpdate") + defer func() { + if span != nil { + span.End() + } + }() + + loc, err = f(ctx) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpdateRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.UpdateRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.UpdateRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.UpdateRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.UpdateRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpdateRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return loc, nil +} + +// StreamUpdate handles bidirectional streaming for updating objects. +// It wraps the bidirectional stream logic for the Update RPC method. +// For each incoming request in the bidirectional stream, it calls the Update function. +// The response is then sent back through the stream with the corresponding status or location information. +func (s *server) StreamUpdate(stream vald.Update_StreamUpdateServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.UpdateRPCServiceName+"/"+vald.StreamUpdateRPCName), apiName+"/"+vald.StreamUpdateRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Update_Request) (*payload.Object_StreamLocation, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamUpdateRPCName+"/id-"+req.GetVector().GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.Update(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.UpdateRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Location{ + Location: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.StreamUpdateRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// MultiUpdate handles the update of multiple objects with the given requests. +// For each request in parallel, it calls the Update function to update an object. +// If an error occurs during any of the insertions, it accumulates the errors and returns them along with the successfully updated locations. +func (s *server) MultiUpdate(ctx context.Context, reqs *payload.Update_MultiRequest) (res *payload.Object_Locations, errs error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.UpdateRPCServiceName+"/"+vald.MultiUpdateRPCName), apiName+"/"+vald.MultiUpdateRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + res = &payload.Object_Locations{ + Locations: make([]*payload.Object_Location, len(reqs.GetRequests())), + } + + var mu, emu sync.Mutex + var wg sync.WaitGroup + + for i, r := range reqs.GetRequests() { + idx, req := i, r + wg.Add(1) + s.eg.Go(safety.RecoverFunc(func() error { + defer wg.Done() + ti := "errgroup.Go/id-" + req.GetVector().GetId() + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, ti), apiName+"/"+vald.MultiUpdateRPCName+"/"+ti) + defer func() { + if span != nil { + span.End() + } + }() + + loc, err := s.Update(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.UpdateRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + emu.Lock() + errs = errors.Join(errs, err) + emu.Unlock() + return nil + } + mu.Lock() + res.Locations[idx] = loc + mu.Unlock() + return nil + })) + } + wg.Wait() + if errs != nil { + st, msg, err := status.ParseError(errs, codes.Internal, "failed to parse "+vald.MultiUpdateRPCName+" gRPC error response", + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiUpdateRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return res, err + } + return res, nil +} + +// Upsert handles the upsert of an object with the given request. +// If the request is proxied from another Mirror gateway, the request is forwarded to the Vald gateway (LB gateway) of its own cluster. +// If the request is from a user, it is sent to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. +// The result is a location of the upserted object. +func (s *server) Upsert(ctx context.Context, req *payload.Upsert_Request) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.UpsertRPCServiceName+"/"+vald.UpsertRPCName), apiName+"/"+vald.UpsertRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + // If this condition is matched, it means that the request was proxied from another Mirror gateway. + // So this component sends requests only to the Vald gateway (LB gateway) of its own cluster. + if s.isProxied(ctx) { + loc, err = s.doUpsert(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + loc, err = vc.Upsert(ctx, req, copts...) + return loc, err + }) + return loc, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf("Upsert API succeeded to %#v", loc) + return loc, nil + } + + // If this condition is matched, it means the request from user. + // So this component sends requests to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. + return s.handleUpsert(ctx, req) +} + +func (s *server) handleUpsert(ctx context.Context, req *payload.Upsert_Request) (loc *payload.Object_Location, err error) { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleUpsert"), apiName+"/handleUpsert") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + var result sync.Map[string, *errorState] // map[target host: error state] + loc = &payload.Object_Location{ + Uuid: req.GetVector().GetId(), + Ips: make([]string, 0), + } + err = s.gateway.BroadCast(ctx, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BroadCast/"+target), apiName+"/"+vald.UpsertRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + ce, err := s.doUpsert(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + return vc.Upsert(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpsertRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpsertRPCName + ".BroadCast/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && ce != nil { + mu.Lock() + loc.Name = ce.GetName() + loc.Ips = append(loc.Ips, ce.GetIps()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpsertRPCName + ".BroadCast", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.UpsertRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + var alreadyExistsCnt int + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, es *errorState) bool { + switch { + case es.err == nil: + successTgts = append(successTgts, target) + case es.code == codes.AlreadyExists: + alreadyExistsCnt++ + err = errors.Join(err, es.err) + default: + err = errors.Join(es.err, err) + } + return true + }) + if err == nil || (len(successTgts) > 0 && result.Len() == len(successTgts)+alreadyExistsCnt) { + log.Debugf(vald.UpsertRPCName+" API request succeeded to %#v", loc) + return loc, nil + } + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == alreadyExistsCnt: + err = status.WrapWithAlreadyExists(vald.UpsertRPCName+" API target same vector already exists", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + default: + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpsertRPCName+" gRPC error response", reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } +} + +func (s *server) doUpsert(ctx context.Context, req *payload.Upsert_Request, f func(ctx context.Context) (*payload.Object_Location, error)) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "doUpsert"), apiName+"/doUpsert") + defer func() { + if span != nil { + span.End() + } + }() + + loc, err = f(ctx) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.UpsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.UpsertRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.UpsertRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.UpsertRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.UpsertRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.UpsertRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return loc, nil +} + +// StreamUpsert handles bidirectional streaming for upserting objects. +// It wraps the bidirectional stream logic for the Upsert RPC method. +// For each incoming request in the bidirectional stream, it calls the Upsert function. +// The response is then sent back through the stream with the corresponding status or location information. +func (s *server) StreamUpsert(stream vald.Upsert_StreamUpsertServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.UpsertRPCServiceName+"/"+vald.StreamUpsertRPCName), apiName+"/"+vald.StreamUpsertRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Upsert_Request) (*payload.Object_StreamLocation, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamUpsertRPCName+"/id-"+req.GetVector().GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.Upsert(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.UpsertRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Location{ + Location: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.StreamUpsertRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// MultiUpsert handles the upsert of multiple objects with the given requests. +// For each request in parallel, it calls the Upsert function to upsert an object. +// If an error occurs during any of the insertions, it accumulates the errors and returns them along with the successfully upserted locations. +func (s *server) MultiUpsert(ctx context.Context, reqs *payload.Upsert_MultiRequest) (res *payload.Object_Locations, errs error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.UpsertRPCServiceName+"/"+vald.MultiUpsertRPCName), apiName+"/"+vald.MultiUpsertRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + res = &payload.Object_Locations{ + Locations: make([]*payload.Object_Location, len(reqs.GetRequests())), + } + + var mu, emu sync.Mutex + var wg sync.WaitGroup + + for i, r := range reqs.GetRequests() { + idx, req := i, r + wg.Add(1) + s.eg.Go(safety.RecoverFunc(func() error { + defer wg.Done() + ti := "errgroup.Go/id-" + req.GetVector().GetId() + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, ti), apiName+"/"+vald.MultiUpsertRPCName+"/"+ti) + defer func() { + if span != nil { + span.End() + } + }() + + loc, err := s.Upsert(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.UpsertRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetVector().GetId(), + ServingData: errdetails.Serialize(req), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + emu.Lock() + errs = errors.Join(errs, err) + emu.Unlock() + return nil + } + mu.Lock() + res.Locations[idx] = loc + mu.Unlock() + return nil + })) + } + wg.Wait() + if errs != nil { + st, msg, err := status.ParseError(errs, codes.Internal, "failed to parse "+vald.MultiUpsertRPCName+" gRPC error response", + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiUpsertRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return res, err + } + return res, nil +} + +// Remove handles the remove of an object with the given request. +// If the request is proxied from another Mirror gateway, the request is forwarded to the Vald gateway (LB gateway) of its own cluster. +// If the request is from a user, it is sent to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. +// The result is a location of the removed object. +func (s *server) Remove(ctx context.Context, req *payload.Remove_Request) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.RemoveRPCServiceName+"/"+vald.RemoveRPCName), apiName+"/"+vald.RemoveRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + // If this condition is matched, it means that the request was proxied from another Mirror gateway. + // So this component sends requests only to the Vald gateway (LB gateway) of its own cluster. + if s.isProxied(ctx) { + loc, err = s.doRemove(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + loc, err = vc.Remove(ctx, req, copts...) + return loc, err + }) + return loc, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf("Remove API remove succeeded to %#v", loc) + return loc, nil + } + + // If this condition is matched, it means the request from user. + // So this component sends requests to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. + return s.handleRemove(ctx, req) +} + +func (s *server) handleRemove(ctx context.Context, req *payload.Remove_Request) (loc *payload.Object_Location, err error) { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleRemove"), apiName+"/handleRemove") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + var result sync.Map[string, *errorState] // map[target host: error state] + loc = &payload.Object_Location{ + Uuid: req.GetId().GetId(), + Ips: make([]string, 0), + } + err = s.gateway.BroadCast(ctx, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BroadCast/"+target), apiName+"/"+vald.RemoveRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + ce, err := s.doRemove(ctx, req, func(ctx context.Context) (*payload.Object_Location, error) { + return vc.Remove(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + }, + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveRPCName + ".BroadCast/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && ce != nil { + mu.Lock() + loc.Name = ce.GetName() + loc.Ips = append(loc.Ips, ce.GetIps()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveRPCName + ".BroadCast", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.RemoveRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + var notFoundCnt int + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, es *errorState) bool { + switch { + case es.err == nil: + successTgts = append(successTgts, target) + case es.code == codes.NotFound: + notFoundCnt++ + err = errors.Join(err, es.err) + default: + err = errors.Join(es.err, err) + } + return true + }) + if err == nil || (len(successTgts) > 0 && result.Len() == len(successTgts)+notFoundCnt) { + log.Debugf(vald.RemoveRPCName+" API request succeeded to %#v", loc) + return loc, nil + } + + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == notFoundCnt: + err = status.WrapWithNotFound(vald.RemoveRPCName+" API id "+req.GetId().GetId()+" not found", err, reqInfo, resInfo) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + default: + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveRPCName+" gRPC error response", reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } +} + +func (s *server) doRemove(ctx context.Context, req *payload.Remove_Request, f func(ctx context.Context) (*payload.Object_Location, error)) (loc *payload.Object_Location, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "doRemove"), apiName+"/doRemove") + defer func() { + if span != nil { + span.End() + } + }() + + loc, err = f(ctx) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.RemoveRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.RemoveRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.RemoveRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.RemoveRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return loc, nil +} + +// StreamRemove handles bidirectional streaming for removing objects. +// It wraps the bidirectional stream logic for the Remove RPC method. +// For each incoming request in the bidirectional stream, it calls the Remove function. +// The response is then sent back through the stream with the corresponding status or location information. +func (s *server) StreamRemove(stream vald.Remove_StreamRemoveServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.RemoveRPCServiceName+"/"+vald.StreamRemoveRPCName), apiName+"/"+vald.StreamRemoveRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Remove_Request) (*payload.Object_StreamLocation, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamRemoveRPCName+"/id-"+req.GetId().GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.Remove(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.RemoveRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Object_StreamLocation{ + Payload: &payload.Object_StreamLocation_Location{ + Location: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.StreamRemoveRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// MultiRemove handles the remove of multiple objects with the given requests. +// For each request in parallel, it calls the Remove function to insert an object. +// If an error occurs during any of the insertions, it accumulates the errors and returns them along with the successfully removed locations. +func (s *server) MultiRemove(ctx context.Context, reqs *payload.Remove_MultiRequest) (res *payload.Object_Locations, errs error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.RemoveRPCServiceName+"/"+vald.MultiRemoveRPCName), apiName+"/"+vald.MultiRemoveRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + res = &payload.Object_Locations{ + Locations: make([]*payload.Object_Location, len(reqs.GetRequests())), + } + + var mu, emu sync.Mutex + var wg sync.WaitGroup + + for i, r := range reqs.GetRequests() { + idx, req := i, r + wg.Add(1) + s.eg.Go(safety.RecoverFunc(func() error { + defer wg.Done() + ti := "errgroup.Go/id-" + req.GetId().GetId() + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, ti), apiName+"/"+vald.MultiRemoveRPCName+"/"+ti) + defer func() { + if span != nil { + span.End() + } + }() + + loc, err := s.Remove(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.RemoveRPCName+" gRPC error response", + &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + ServingData: errdetails.Serialize(req), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + emu.Lock() + errs = errors.Join(errs, err) + emu.Unlock() + return nil + } + mu.Lock() + res.Locations[idx] = loc + mu.Unlock() + return nil + })) + } + wg.Wait() + if errs != nil { + st, msg, err := status.ParseError(errs, codes.Internal, "failed to parse "+vald.MultiRemoveRPCName+" gRPC error response", + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.MultiRemoveRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + }) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return res, err + } + return res, nil +} + +// RemoveByTimestamp handles the remove of an object with the given request. +// If the request is proxied from another Mirror gateway, the request is forwarded to the Vald gateway (LB gateway) of its own cluster. +// If the request is from a user, it is sent to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. +// The result is a location of the removed object. +func (s *server) RemoveByTimestamp(ctx context.Context, req *payload.Remove_TimestampRequest) (locs *payload.Object_Locations, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.RemoveRPCServiceName+"/"+vald.RemoveByTimestampRPCName), apiName+"/"+vald.RemoveByTimestampRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + // If this condition is matched, it means that the request was proxied from another Mirror gateway. + // So this component sends requests only to the Vald gateway (LB gateway) of its own cluster. + if s.isProxied(ctx) { + locs, err = s.doRemoveByTimestamp(ctx, req, func(ctx context.Context) (*payload.Object_Locations, error) { + _, derr := s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + locs, err = vc.RemoveByTimestamp(ctx, req, copts...) + return locs, err + }) + return locs, errors.Join(derr, err) + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveByTimestampRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + log.Debugf("RemoveByTimestamp API remove succeeded to %#v", locs) + return locs, nil + } + + // If this condition is matched, it means the request from user. + // So this component sends requests to other Mirror gateways and the Vald gateway (LB gateway) of its own cluster. + return s.handleRemoveByTimestamp(ctx, req) +} + +func (s *server) handleRemoveByTimestamp(ctx context.Context, req *payload.Remove_TimestampRequest) (locs *payload.Object_Locations, err error) { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, "handleRemoveByTimestamp"), apiName+"/handleRemoveByTimestamp") + defer func() { + if span != nil { + span.End() + } + }() + + var mu sync.Mutex + var result sync.Map[string, *errorState] // map[target host: error state] + locs = new(payload.Object_Locations) + + err = s.gateway.BroadCast(ctx, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BroadCast/"+target), apiName+"/"+vald.RemoveByTimestampRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + code := codes.OK + res, err := s.doRemoveByTimestamp(ctx, req, func(ctx context.Context) (*payload.Object_Locations, error) { + return vc.RemoveByTimestamp(ctx, req, copts...) + }) + if err != nil { + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveByTimestampRPCName+" gRPC error response", + &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveByTimestampRPCName + ".BroadCast/" + target, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, target), + }, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + code = st.Code() + } + if err == nil && res != nil { + mu.Lock() + locs.Locations = append(locs.Locations, res.GetLocations()...) + mu.Unlock() + } + result.Store(target, &errorState{err, code}) + return err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveByTimestampRPCName + ".BroadCast", + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.RemoveByTimestampRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + // There is no possibility to reach this part, but we add error handling just in case. + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveByTimestampRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + var notFoundCnt int + successTgts := make([]string, 0, result.Len()/2) + result.Range(func(target string, es *errorState) bool { + switch { + case es.err == nil: + successTgts = append(successTgts, target) + case es.code == codes.NotFound: + notFoundCnt++ + err = errors.Join(err, es.err) + default: + err = errors.Join(es.err, err) + } + return true + }) + if err == nil || (len(successTgts) > 0 && result.Len() == len(successTgts)+notFoundCnt) { + log.Debugf(vald.RemoveByTimestampRPCName+" API request succeeded to %#v", locs) + return locs, nil + } + + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveByTimestampRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + + switch { + case result.Len() == notFoundCnt: + err = status.WrapWithNotFound(vald.RemoveByTimestampRPCName+" API target not found", err, reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeAlreadyExists(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + default: + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveByTimestampRPCName+" gRPC error response", reqInfo, resInfo) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } +} + +func (s *server) doRemoveByTimestamp( + ctx context.Context, + req *payload.Remove_TimestampRequest, + f func(ctx context.Context) (*payload.Object_Locations, error), +) (locs *payload.Object_Locations, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "doRemoveByTimestamp"), apiName+"/doRemoveByTimestamp") + defer func() { + if span != nil { + span.End() + } + }() + + locs, err = f(ctx) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RemoveByTimestampRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s)", apiName, s.name, s.ip), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.RemoveByTimestampRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.RemoveByTimestampRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.RemoveByTimestampRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.RemoveByTimestampRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.RemoveByTimestampRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return locs, nil +} + +// GetObject bypasses the incoming GetObject request to Vald LB gateway in its own cluster. +func (s *server) GetObject(ctx context.Context, req *payload.Object_VectorRequest) (vec *payload.Object_Vector, err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.ObjectRPCServiceName+"/"+vald.GetObjectRPCName), apiName+"/"+vald.GetObjectRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err = s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, _ string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error) { + vec, err = vc.GetObject(ctx, req, copts...) + return vec, err + }) + if err != nil { + reqInfo := &errdetails.RequestInfo{ + RequestId: req.GetId().GetId(), + ServingData: errdetails.Serialize(req), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.GetObjectRPCName, + ResourceName: fmt.Sprintf("%s: %s(%s) to %s", apiName, s.name, s.ip, s.vAddr), + } + var attrs trace.Attributes + + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.GetObjectRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded( + vald.GetObjectRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInternal( + vald.GetObjectRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.GetObjectRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.GetObjectRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + return vec, nil +} + +// StreamGetObject bypasses it as a GetObject request to the Vald LB gateway in its own cluster. +func (s *server) StreamGetObject(stream vald.Object_StreamGetObjectServer) (err error) { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.ObjectRPCServiceName+"/"+vald.StreamGetObjectRPCName), apiName+"/"+vald.StreamGetObjectRPCName) + defer func() { + if span != nil { + span.End() + } + }() + err = grpc.BidirectionalStream(ctx, stream, s.streamConcurrency, + func(ctx context.Context, req *payload.Object_VectorRequest) (*payload.Object_StreamVector, error) { + ctx, sspan := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "BidirectionalStream"), apiName+"/"+vald.StreamInsertRPCName+"/id-"+req.GetId().GetId()) + defer func() { + if sspan != nil { + sspan.End() + } + }() + res, err := s.GetObject(ctx, req) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.GetObjectRPCName+" gRPC error response") + if sspan != nil { + sspan.RecordError(err) + sspan.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + sspan.SetStatus(trace.StatusError, err.Error()) + } + return &payload.Object_StreamVector{ + Payload: &payload.Object_StreamVector_Status{ + Status: st.Proto(), + }, + }, err + } + return &payload.Object_StreamVector{ + Payload: &payload.Object_StreamVector_Vector{ + Vector: res, + }, + }, nil + }, + ) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.StreamGetObjectRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +// StreamListObject bypasses it as a StreamListObject request to the Vald gateway (LB gateway) in its own cluster. +func (s *server) StreamListObject(req *payload.Object_List_Request, stream vald.Object_StreamListObjectServer) error { + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(stream.Context(), vald.PackageName+"."+vald.ObjectRPCServiceName+"/"+vald.StreamListObjectRPCName), apiName+"/"+vald.StreamListObjectRPCName) + defer func() { + if span != nil { + span.End() + } + }() + + _, err := s.gateway.Do(ctx, s.vAddr, func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (obj interface{}, err error) { + ctx, span := trace.StartSpan(grpc.WrapGRPCMethod(ctx, "Do/"+target), apiName+"/"+vald.StreamListObjectRPCName+"/"+target) + defer func() { + if span != nil { + span.End() + } + }() + client, err := vc.StreamListObject(ctx, req, copts...) + if err != nil { + return nil, err + } + return obj, s.doStreamListObject(ctx, client, stream) + }) + if err != nil { + st, msg, err := status.ParseError(err, codes.Internal, "failed to parse "+vald.StreamListObjectRPCName+" gRPC error response") + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil +} + +func (s *server) doStreamListObject(ctx context.Context, client vald.Object_StreamListObjectClient, server vald.Object_StreamListObjectServer) (err error) { // skipcq: GO-R1005 + cctx, cancel := context.WithCancel(ctx) + defer cancel() + eg, egctx := errgroup.WithContext(cctx) + eg.SetLimit(s.streamConcurrency) + + var mu, rmu sync.Mutex + var egCnt int64 + for { + select { + case <-egctx.Done(): + // If the root context is not canceld error, it is treated as an error. + if ctx.Err() != nil && !errors.Is(ctx.Err(), context.Canceled) { + err = errors.Join(ctx.Err(), err) + } + if egerr := eg.Wait(); egerr != nil { + err = errors.Join(err, egerr) + } + return err + default: + eg.Go(safety.RecoverFunc(func() (err error) { + id := fmt.Sprintf("stream-%020d", atomic.AddInt64(&egCnt, 1)) + _, span := trace.StartSpan(egctx, apiName+"/streamListObject/"+id) + defer func() { + if span != nil { + span.End() + } + }() + + rmu.Lock() + res, err := client.Recv() + rmu.Unlock() + if err != nil { + if errors.Is(err, io.EOF) { + cancel() + return nil + } + err = errors.ErrServerStreamClientRecv(err) + var attr trace.Attributes + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled("Stream Recv returned canceld error at "+id, err) + attr = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded("Stream Recv returned deadlin exceeded error at "+id, err) + attr = trace.StatusCodeDeadlineExceeded(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, "Stream Recv returned an error at "+id) + if st != nil { + attr = trace.FromGRPCStatus(st.Code(), msg) + } + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attr...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + if res.GetVector() == nil { + return nil + } + + mu.Lock() + err = server.Send(res) + mu.Unlock() + if err != nil { + if errors.Is(err, io.EOF) { + cancel() + return nil + } + err = errors.ErrServerStreamServerSend(err) + var attr trace.Attributes + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled("Stream Send returned canceld error at "+id, err) + attr = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithDeadlineExceeded("Stream Send returned deadlin exceeded error at "+id, err) + attr = trace.StatusCodeDeadlineExceeded(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, "Stream Send returned an error at "+id) + if st != nil { + attr = trace.FromGRPCStatus(st.Code(), msg) + } + } + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attr...) + span.SetStatus(trace.StatusError, err.Error()) + } + return err + } + return nil + })) + } + } +} + +func (s *server) isProxied(ctx context.Context) bool { + return s.gateway.FromForwardedContext(ctx) != "" +} + +type errorState struct { + err error + code codes.Code +} diff --git a/pkg/gateway/mirror/handler/grpc/handler_test.go b/pkg/gateway/mirror/handler/grpc/handler_test.go new file mode 100644 index 0000000000..fb55eb1b53 --- /dev/null +++ b/pkg/gateway/mirror/handler/grpc/handler_test.go @@ -0,0 +1,5574 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 grpc + +import ( + "context" + "reflect" + "testing" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/net/grpc/codes" + "github.com/vdaas/vald/internal/net/grpc/status" + "github.com/vdaas/vald/internal/sync/errgroup" + "github.com/vdaas/vald/internal/test/data/vector" + "github.com/vdaas/vald/internal/test/goleak" + clientmock "github.com/vdaas/vald/internal/test/mock/client" + "github.com/vdaas/vald/pkg/gateway/mirror/service" +) + +func Test_server_Insert(t *testing.T) { // skipcq: GO-R1005 + t.Parallel() + const dimension = 128 + defaultInsertConfig := &payload.Insert_Config{ + SkipStrictExistCheck: true, + } + type args struct { + ctx context.Context + req *payload.Insert_Request + } + type fields struct { + eg errgroup.Group + gateway service.Gateway + mirror service.Mirror + vAddr string + streamConcurrency int + name string + ip string + UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror + } + type want struct { + wantCe *payload.Object_Location + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, *payload.Object_Location, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotCe *payload.Object_Location, err error) error { + if !errors.Is(err, w.err) { + gotSt, gotOk := status.FromError(err) + wantSt, wantOk := status.FromError(w.err) + if gotOk != wantOk || gotSt.Code() != wantSt.Code() { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + } + if !reflect.DeepEqual(gotCe, w.wantCe) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotCe, w.wantCe) + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Success: insert with new ID", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantCe: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1", "127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + }, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Success: when the last status codes are (OK, OK) after updating the target that returned AlreadyExists", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.ErrTargetNotFound + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + wantCe: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1", "127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + }, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Success: when the last status codes are (OK, AlreadyExists) after updating the target that returned AlreadyExists", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.New("target not found") + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + wantCe: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (AlreadyExists, AlreadyExists)", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.AlreadyExists, vald.InsertRPCName+" API target same vector already exists"), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (OK, Internal)", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, Internal)", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.Join( + status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()), + status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + ).Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + }, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + } + return test{ + name: "Fail: when the last status codes are (OK, Internal) after updating the target that returned AlreadyExists", + args: args{ + ctx: egctx, + req: &payload.Insert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultInsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.New("target not found") + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + s := &server{ + eg: test.fields.eg, + gateway: test.fields.gateway, + mirror: test.fields.mirror, + vAddr: test.fields.vAddr, + streamConcurrency: test.fields.streamConcurrency, + name: test.fields.name, + ip: test.fields.ip, + UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, + } + + gotCe, err := s.Insert(test.args.ctx, test.args.req) + if err := checkFunc(test.want, gotCe, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_server_Update(t *testing.T) { // skipcq: GO-R1005 + t.Parallel() + const dimension = 128 + defaultUpdateConfig := &payload.Update_Config{ + SkipStrictExistCheck: true, + } + type args struct { + ctx context.Context + req *payload.Update_Request + } + type fields struct { + eg errgroup.Group + gateway service.Gateway + mirror service.Mirror + vAddr string + streamConcurrency int + name string + ip string + UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror + } + type want struct { + wantLoc *payload.Object_Location + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, *payload.Object_Location, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotLoc *payload.Object_Location, err error) error { + if !errors.Is(err, w.err) { + gotSt, gotOk := status.FromError(err) + wantSt, wantOk := status.FromError(w.err) + if gotOk != wantOk || gotSt.Code() != wantSt.Code() { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + } + if !reflect.DeepEqual(gotLoc, w.wantLoc) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotLoc, w.wantLoc) + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Success: update with new ID", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLoc: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1", "127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Success: when the status codes are (AlreadyExists, OK)", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLoc: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", "vald-03", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[2]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Success: when the last status codes are (OK, OK, OK) after inserting the target that returned NotFound", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.ErrTargetNotFound + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + wantLoc: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{ + "127.0.0.1", "127.0.0.1", "127.0.0.1", + }, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", "vald-03", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[2]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Success: when the last status codes are (OK, OK, AlreadyExists) after inserting the target that returned NotFound", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.ErrTargetNotFound + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + wantLoc: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{ + "127.0.0.1", "127.0.0.1", + }, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (NotFound, NotFound)", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.NotFound, vald.UpdateRPCName+" API id "+uuid+" not found"), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, OK)", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, AlreadyExists)", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.Join( + status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()), + ).Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", "vald-03", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + targets[2]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Fail: when the last status codes are (AlreadyExists, AlreadyExists, AlreadyExists) after inserting the target that returned NotFound", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.ErrTargetNotFound + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.AlreadyExists, vald.InsertRPCName+" for "+vald.UpdateRPCName+" API target same vector already exists"), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", "vald-03", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + InsertFunc: func(_ context.Context, _ *payload.Insert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + targets[2]: &clientmock.MirrorClientMock{ + UpdateFunc: func(_ context.Context, _ *payload.Update_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Fail: when the last status codes are (OK, OK, Internal) after inserting the target that returned NotFound", + args: args{ + ctx: egctx, + req: &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpdateConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + DoMultiFunc: func(ctx context.Context, targets []string, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, target := range targets { + if c, ok := cmap[target]; !ok { + return errors.New("target not found") + } else { + f(ctx, target, c) + } + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + s := &server{ + eg: test.fields.eg, + gateway: test.fields.gateway, + mirror: test.fields.mirror, + vAddr: test.fields.vAddr, + streamConcurrency: test.fields.streamConcurrency, + name: test.fields.name, + ip: test.fields.ip, + UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, + } + + gotLoc, err := s.Update(test.args.ctx, test.args.req) + if err := checkFunc(test.want, gotLoc, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_server_Upsert(t *testing.T) { + t.Parallel() + const dimension = 128 + defaultUpsertConfig := &payload.Upsert_Config{ + SkipStrictExistCheck: true, + } + type args struct { + ctx context.Context + req *payload.Upsert_Request + } + type fields struct { + eg errgroup.Group + gateway service.Gateway + mirror service.Mirror + vAddr string + streamConcurrency int + name string + ip string + UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror + } + type want struct { + wantLoc *payload.Object_Location + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, *payload.Object_Location, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotLoc *payload.Object_Location, err error) error { + if !errors.Is(err, w.err) { + gotSt, gotOk := status.FromError(err) + wantSt, wantOk := status.FromError(w.err) + if gotOk != wantOk || gotSt.Code() != wantSt.Code() { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + } + if !reflect.DeepEqual(gotLoc, w.wantLoc) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotLoc, w.wantLoc) + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Success: upsert with new ID", + args: args{ + ctx: egctx, + req: &payload.Upsert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLoc: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1", "127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpsertFunc: func(ctx context.Context, in *payload.Upsert_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Success: when the status codes are (AlreadyExists, OK)", + args: args{ + ctx: egctx, + req: &payload.Upsert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(_ context.Context, _ string, _ vald.ClientWithMirror, _ ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLoc: loc, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpsertFunc: func(ctx context.Context, in *payload.Upsert_Request, opts ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.AlreadyExists, errors.ErrMetaDataAlreadyExists(uuid).Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (AlreadyExists, AlreadyExists)", + args: args{ + ctx: egctx, + req: &payload.Upsert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(_ context.Context, _ string, _ vald.ClientWithMirror, _ ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.AlreadyExists, vald.UpsertRPCName+" API target same vector already exists"), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, OK)", + args: args{ + ctx: egctx, + req: &payload.Upsert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + UpsertFunc: func(_ context.Context, _ *payload.Upsert_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()) + }, + }, + } + return test{ + name: "Fail: upsert when the status codes are (Internal, Internal)", + args: args{ + ctx: egctx, + req: &payload.Upsert_Request{ + Vector: &payload.Object_Vector{ + Id: uuid, + Vector: vector.GaussianDistributedFloat32VectorGenerator(1, dimension)[0], + }, + Config: defaultUpsertConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.Join( + status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()), + ).Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + s := &server{ + eg: test.fields.eg, + gateway: test.fields.gateway, + mirror: test.fields.mirror, + vAddr: test.fields.vAddr, + streamConcurrency: test.fields.streamConcurrency, + name: test.fields.name, + ip: test.fields.ip, + UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, + } + + gotLoc, err := s.Upsert(test.args.ctx, test.args.req) + if err := checkFunc(test.want, gotLoc, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_server_Remove(t *testing.T) { + t.Parallel() + const dimension = 128 + defaultRemoveConfig := &payload.Remove_Config{ + SkipStrictExistCheck: true, + } + type args struct { + ctx context.Context + req *payload.Remove_Request + } + type fields struct { + eg errgroup.Group + gateway service.Gateway + mirror service.Mirror + vAddr string + streamConcurrency int + name string + ip string + UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror + } + type want struct { + wantLoc *payload.Object_Location + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, *payload.Object_Location, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotLoc *payload.Object_Location, err error) error { + if !errors.Is(err, w.err) { + gotSt, gotOk := status.FromError(err) + wantSt, wantOk := status.FromError(w.err) + if gotOk != wantOk || gotSt.Code() != wantSt.Code() { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + } + if !reflect.DeepEqual(gotLoc, w.wantLoc) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotLoc, w.wantLoc) + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + } + return test{ + name: "Success: remove with existing ID", + args: args{ + ctx: egctx, + req: &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: uuid, + }, + Config: defaultRemoveConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLoc: &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1", "127.0.0.1"}, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid).Error()) + }, + }, + } + return test{ + name: "Success: when the status codes are (NotFound, OK)", + args: args{ + ctx: egctx, + req: &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: uuid, + }, + Config: defaultRemoveConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLoc: loc, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + loc := &payload.Object_Location{ + Uuid: uuid, + Ips: []string{"127.0.0.1"}, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return loc, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, OK)", + args: args{ + ctx: egctx, + req: &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: uuid, + }, + Config: defaultRemoveConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, Internal)", + args: args{ + ctx: egctx, + req: &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: uuid, + }, + Config: defaultRemoveConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.Join( + status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()), + ).Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid := "test" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrIndexNotFound.Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveFunc: func(_ context.Context, _ *payload.Remove_Request, _ ...grpc.CallOption) (*payload.Object_Location, error) { + return nil, status.Error(codes.NotFound, errors.ErrIndexNotFound.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (NotFound, NotFound)", + args: args{ + ctx: egctx, + req: &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: uuid, + }, + Config: defaultRemoveConfig, + }, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.NotFound, vald.RemoveRPCName+" API id "+uuid+" not found"), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + s := &server{ + eg: test.fields.eg, + gateway: test.fields.gateway, + mirror: test.fields.mirror, + vAddr: test.fields.vAddr, + streamConcurrency: test.fields.streamConcurrency, + name: test.fields.name, + ip: test.fields.ip, + UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, + } + + gotLoc, err := s.Remove(test.args.ctx, test.args.req) + if err := checkFunc(test.want, gotLoc, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_server_RemoveByTimestamp(t *testing.T) { + t.Parallel() + defaultRemoveByTimestampReq := &payload.Remove_TimestampRequest{ + Timestamps: []*payload.Remove_Timestamp{}, + } + type args struct { + ctx context.Context + req *payload.Remove_TimestampRequest + } + type fields struct { + eg errgroup.Group + gateway service.Gateway + mirror service.Mirror + vAddr string + streamConcurrency int + name string + ip string + UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror + } + type want struct { + wantLocs *payload.Object_Locations + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, *payload.Object_Locations, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotLocs *payload.Object_Locations, err error) error { + if !errors.Is(err, w.err) { + gotSt, gotOk := status.FromError(err) + wantSt, wantOk := status.FromError(w.err) + if gotOk != wantOk || gotSt.Code() != wantSt.Code() { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + } + if !reflect.DeepEqual(gotLocs, w.wantLocs) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotLocs, w.wantLocs) + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + loc := &payload.Object_Location{ + Uuid: "test", + Ips: []string{ + "127.0.0.1", + }, + } + loc2 := &payload.Object_Location{ + Uuid: "test02", + Ips: []string{ + "127.0.0.1", + }, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return &payload.Object_Locations{ + Locations: []*payload.Object_Location{ + loc, + }, + }, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return &payload.Object_Locations{ + Locations: []*payload.Object_Location{ + loc2, + }, + }, nil + }, + }, + } + return test{ + name: "Success: removeByTimestamp", + args: args{ + ctx: egctx, + req: defaultRemoveByTimestampReq, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLocs: &payload.Object_Locations{ + Locations: []*payload.Object_Location{ + loc, loc2, + }, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + loc := &payload.Object_Location{ + Uuid: "test", + Ips: []string{ + "127.0.0.1", + }, + } + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return &payload.Object_Locations{ + Locations: []*payload.Object_Location{ + loc, + }, + }, nil + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound("test02").Error()) + }, + }, + } + return test{ + name: "Success: when the status codes are (NotFound, OK)", + args: args{ + ctx: egctx, + req: defaultRemoveByTimestampReq, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + wantLocs: &payload.Object_Locations{ + Locations: []*payload.Object_Location{ + loc, + }, + }, + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return nil, status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (Internal, Internal)", + args: args{ + ctx: egctx, + req: defaultRemoveByTimestampReq, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.Internal, errors.Join( + status.Error(codes.Internal, errors.ErrCircuitBreakerHalfOpenFlowLimitation.Error()), + status.Error(codes.Internal, errors.ErrCircuitBreakerOpenState.Error()), + ).Error()), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + eg, egctx := errgroup.New(ctx) + + uuid1 := "test01" + uuid2 := "test02" + targets := []string{ + "vald-01", "vald-02", + } + cmap := map[string]vald.ClientWithMirror{ + targets[0]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid1).Error()) + }, + }, + targets[1]: &clientmock.MirrorClientMock{ + RemoveByTimestampFunc: func(_ context.Context, _ *payload.Remove_TimestampRequest, _ ...grpc.CallOption) (*payload.Object_Locations, error) { + return nil, status.Error(codes.NotFound, errors.ErrObjectIDNotFound(uuid2).Error()) + }, + }, + } + return test{ + name: "Fail: when the status codes are (NotFound, NotFound)", + args: args{ + ctx: egctx, + req: defaultRemoveByTimestampReq, + }, + fields: fields{ + eg: eg, + gateway: &gatewayMock{ + FromForwardedContextFunc: func(_ context.Context) string { + return "" + }, + BroadCastFunc: func(ctx context.Context, f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error { + for _, tgt := range targets { + f(ctx, tgt, cmap[tgt]) + } + return nil + }, + }, + }, + want: want{ + err: status.Error(codes.NotFound, vald.RemoveByTimestampRPCName+" API target not found"), + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + s := &server{ + eg: test.fields.eg, + gateway: test.fields.gateway, + mirror: test.fields.mirror, + vAddr: test.fields.vAddr, + streamConcurrency: test.fields.streamConcurrency, + name: test.fields.name, + ip: test.fields.ip, + UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, + } + + gotLocs, err := s.RemoveByTimestamp(test.args.ctx, test.args.req) + if err := checkFunc(test.want, gotLocs, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// NOT IMPLEMENTED BELOW + +// func TestNew(t *testing.T) { +// t.Parallel() +// type args struct { +// opts []Option +// } +// type want struct { +// want vald.ServerWithMirror +// err error +// } +// type test struct { +// name string +// args args +// want want +// checkFunc func(want, vald.Server, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got vald.Server, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(got, w.want) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// opts:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// opts:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// +// got, err := New(test.args.opts...) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_Register(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Mirror_Targets +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// want *payload.Mirror_Targets +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Mirror_Targets, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got *payload.Mirror_Targets, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(got, w.want) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// got, err := s.Register(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_Exists(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// meta *payload.Object_ID +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantId *payload.Object_ID +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Object_ID, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotID *payload.Object_ID, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotID, w.wantId) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotID, w.wantId) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// meta:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// meta:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotID, err := s.Exists(test.args.ctx, test.args.meta) +// if err := checkFunc(test.want, gotID, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_Search(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_Request +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Response +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Response, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Response, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.Search(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_SearchByID(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_IDRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Response +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Response, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Response, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.SearchByID(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamSearch(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Search_StreamSearchServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamSearch(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamSearchByID(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Search_StreamSearchByIDServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamSearchByID(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiSearch(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_MultiRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Responses +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Responses, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Responses, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiSearch(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiSearchByID(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_MultiIDRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Responses +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Responses, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Responses, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiSearchByID(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_LinearSearch(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_Request +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Response +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Response, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Response, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.LinearSearch(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_LinearSearchByID(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_IDRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Response +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Response, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Response, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.LinearSearchByID(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamLinearSearch(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Search_StreamLinearSearchServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamLinearSearch(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamLinearSearchByID(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Search_StreamLinearSearchByIDServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamLinearSearchByID(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiLinearSearch(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_MultiRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Responses +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Responses, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Responses, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiLinearSearch(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiLinearSearchByID(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Search_MultiIDRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Search_Responses +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Search_Responses, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Search_Responses, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiLinearSearchByID(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamInsert(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Insert_StreamInsertServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamInsert(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiInsert(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// reqs *payload.Insert_MultiRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Object_Locations +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Object_Locations, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Object_Locations, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiInsert(test.args.ctx, test.args.reqs) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamUpdate(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Update_StreamUpdateServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamUpdate(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiUpdate(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// reqs *payload.Update_MultiRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Object_Locations +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Object_Locations, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Object_Locations, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiUpdate(test.args.ctx, test.args.reqs) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamUpsert(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Upsert_StreamUpsertServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamUpsert(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiUpsert(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// reqs *payload.Upsert_MultiRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Object_Locations +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Object_Locations, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Object_Locations, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiUpsert(test.args.ctx, test.args.reqs) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamRemove(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Remove_StreamRemoveServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamRemove(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_MultiRemove(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// reqs *payload.Remove_MultiRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantRes *payload.Object_Locations +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Object_Locations, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotRes *payload.Object_Locations, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotRes, w.wantRes) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// reqs:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotRes, err := s.MultiRemove(test.args.ctx, test.args.reqs) +// if err := checkFunc(test.want, gotRes, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_GetObject(t *testing.T) { +// t.Parallel() +// type args struct { +// ctx context.Context +// req *payload.Object_VectorRequest +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// wantVec *payload.Object_Vector +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, *payload.Object_Vector, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotVec *payload.Object_Vector, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotVec, w.wantVec) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotVec, w.wantVec) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// req:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// gotVec, err := s.GetObject(test.args.ctx, test.args.req) +// if err := checkFunc(test.want, gotVec, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_server_StreamGetObject(t *testing.T) { +// t.Parallel() +// type args struct { +// stream vald.Object_StreamGetObjectServer +// } +// type fields struct { +// eg errgroup.Group +// gateway service.Gateway +// mirror service.Mirror +// vAddr string +// streamConcurrency int +// name string +// ip string +// UnimplementedValdServerWithMirror vald.UnimplementedValdServerWithMirror +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// stream:nil, +// }, +// fields: fields { +// eg:nil, +// gateway:nil, +// mirror:nil, +// vAddr:"", +// streamConcurrency:0, +// name:"", +// ip:"", +// UnimplementedValdServerWithMirror:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// s := &server{ +// eg: test.fields.eg, +// gateway: test.fields.gateway, +// mirror: test.fields.mirror, +// vAddr: test.fields.vAddr, +// streamConcurrency: test.fields.streamConcurrency, +// name: test.fields.name, +// ip: test.fields.ip, +// UnimplementedValdServerWithMirror: test.fields.UnimplementedValdServerWithMirror, +// } +// +// err := s.StreamGetObject(test.args.stream) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } diff --git a/pkg/gateway/mirror/handler/grpc/mock_test.go b/pkg/gateway/mirror/handler/grpc/mock_test.go new file mode 100644 index 0000000000..a09cbb567a --- /dev/null +++ b/pkg/gateway/mirror/handler/grpc/mock_test.go @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 grpc + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/pkg/gateway/mirror/service" +) + +type gatewayMock struct { + service.Gateway + + StartFunc func(ctx context.Context) (<-chan error, error) + ForwardedContextFunc func(ctx context.Context, podName string) context.Context + FromForwardedContextFunc func(ctx context.Context) string + BroadCastFunc func(ctx context.Context, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error + DoMultiFunc func(ctx context.Context, targets []string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error +} + +func (gm *gatewayMock) ForwardedContext(ctx context.Context, podName string) context.Context { + return gm.ForwardedContextFunc(ctx, podName) +} + +func (gm *gatewayMock) FromForwardedContext(ctx context.Context) string { + return gm.FromForwardedContextFunc(ctx) +} + +func (gm *gatewayMock) BroadCast(ctx context.Context, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error, +) error { + return gm.BroadCastFunc(ctx, f) +} + +func (gm *gatewayMock) DoMulti(ctx context.Context, targets []string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error, +) error { + return gm.DoMultiFunc(ctx, targets, f) +} diff --git a/pkg/gateway/mirror/handler/grpc/option.go b/pkg/gateway/mirror/handler/grpc/option.go new file mode 100644 index 0000000000..5832e37be9 --- /dev/null +++ b/pkg/gateway/mirror/handler/grpc/option.go @@ -0,0 +1,106 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 grpc + +import ( + "os" + "runtime" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/net" + "github.com/vdaas/vald/internal/sync/errgroup" + "github.com/vdaas/vald/pkg/gateway/mirror/service" +) + +type Option func(*server) error + +var defaultOptions = []Option{ + WithErrGroup(errgroup.Get()), + WithStreamConcurrency(runtime.GOMAXPROCS(-1) * 10), + WithName(func() string { + name, err := os.Hostname() + if err != nil { + log.Warn(err) + } + return name + }()), + WithIP(net.LoadLocalIP()), +} + +// WithIP returns the option to set the IP for server. +func WithIP(ip string) Option { + return func(s *server) error { + if ip != "" { + s.ip = ip + } + return nil + } +} + +// WithName returns the option to set the name for server. +func WithName(name string) Option { + return func(s *server) error { + if name != "" { + s.name = name + } + return nil + } +} + +func WithGateway(g service.Gateway) Option { + return func(s *server) error { + if g != nil { + s.gateway = g + } + return nil + } +} + +func WithMirror(m service.Mirror) Option { + return func(s *server) error { + if m != nil { + s.mirror = m + } + return nil + } +} + +func WithErrGroup(eg errgroup.Group) Option { + return func(s *server) error { + if eg != nil { + s.eg = eg + } + return nil + } +} + +func WithStreamConcurrency(c int) Option { + return func(s *server) error { + if c > 0 { + s.streamConcurrency = c + } + return nil + } +} + +func WithValdAddr(addr string) Option { + return func(s *server) error { + if addr == "" { + return errors.NewErrCriticalOption("valdAddr", addr) + } + s.vAddr = addr + return nil + } +} diff --git a/pkg/gateway/mirror/handler/rest/handler.go b/pkg/gateway/mirror/handler/rest/handler.go new file mode 100644 index 0000000000..70ed6dbff7 --- /dev/null +++ b/pkg/gateway/mirror/handler/rest/handler.go @@ -0,0 +1,273 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 rest + +import ( + "net/http" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/net/http/dump" + "github.com/vdaas/vald/internal/net/http/json" +) + +// Handler represents an interface for rest handler. +type Handler interface { + Register(w http.ResponseWriter, r *http.Request) (int, error) + Index(w http.ResponseWriter, r *http.Request) (int, error) + Exists(w http.ResponseWriter, r *http.Request) (int, error) + Search(w http.ResponseWriter, r *http.Request) (int, error) + SearchByID(w http.ResponseWriter, r *http.Request) (int, error) + MultiSearch(w http.ResponseWriter, r *http.Request) (int, error) + MultiSearchByID(w http.ResponseWriter, r *http.Request) (int, error) + LinearSearch(w http.ResponseWriter, r *http.Request) (int, error) + LinearSearchByID(w http.ResponseWriter, r *http.Request) (int, error) + MultiLinearSearch(w http.ResponseWriter, r *http.Request) (int, error) + MultiLinearSearchByID(w http.ResponseWriter, r *http.Request) (int, error) + Insert(w http.ResponseWriter, r *http.Request) (int, error) + MultiInsert(w http.ResponseWriter, r *http.Request) (int, error) + Update(w http.ResponseWriter, r *http.Request) (int, error) + MultiUpdate(w http.ResponseWriter, r *http.Request) (int, error) + Upsert(w http.ResponseWriter, r *http.Request) (int, error) + MultiUpsert(w http.ResponseWriter, r *http.Request) (int, error) + Remove(w http.ResponseWriter, r *http.Request) (int, error) + RemoveByTimestamp(w http.ResponseWriter, r *http.Request) (int, error) + MultiRemove(w http.ResponseWriter, r *http.Request) (int, error) + GetObject(w http.ResponseWriter, r *http.Request) (int, error) +} + +type handler struct { + vald vald.ServerWithMirror +} + +// New returns a Vald server as rest handler with mirror using the provided options. +func New(opts ...Option) Handler { + h := new(handler) + + for _, opt := range append(defaultOptions, opts...) { + opt(h) + } + return h +} + +// Register is an HTTP handler function that processes registration requests. +// It decodes the incoming JSON payload into a payload.Mirror_Targets struct, +// then invokes the vald.Register method to handle the registration logic. +// The response is written to the http.ResponseWriter. +func (h *handler) Register(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Mirror_Targets + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Register(r.Context(), req) + }) +} + +// Index is an HTTP handler function that handles requests to the index endpoint. +// It returns an HTTP status code and an error. It creates a map to store data, +// then uses json.Handler to process the request, extract data, and log the request using dump.Request. +func (*handler) Index(w http.ResponseWriter, r *http.Request) (int, error) { + data := make(map[string]interface{}) + return json.Handler(w, r, &data, func() (interface{}, error) { + return dump.Request(nil, data, r) + }) +} + +// Search is an HTTP handler function that processes search requests. +// It decodes the incoming JSON payload into a payload.Search_Request struct, +// then invokes the vald.Search method to handle the search logic. +func (h *handler) Search(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_Request + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Search(r.Context(), req) + }) +} + +// SearchByID is an HTTP handler function that processes search by ID requests. +// It decodes the incoming JSON payload into a payload.Search_IDRequest struct, +// then invokes the vald.SearchByID method to handle the search by ID logic. +func (h *handler) SearchByID(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_IDRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.SearchByID(r.Context(), req) + }) +} + +// MultiSearch is an HTTP handler function that processes multi-search requests. +// It decodes the incoming JSON payload into a payload.Search_MultiRequest struct, +// then invokes the vald.MultiSearch method to handle the multi-search logic. +func (h *handler) MultiSearch(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_MultiRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiSearch(r.Context(), req) + }) +} + +// MultiSearchByID is an HTTP handler function that processes multi-search by ID requests. +// It decodes the incoming JSON payload into a payload.Search_MultiIDRequest struct, +// then invokes the vald.MultiSearchByID method to handle the multi-search by ID logic. +func (h *handler) MultiSearchByID(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_MultiIDRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiSearchByID(r.Context(), req) + }) +} + +// LinearSearch is an HTTP handler function that processes linear search requests. +// It decodes the incoming JSON payload into a payload.Search_Request struct, +// then invokes the vald.LinearSearch method to handle the linear search logic. +func (h *handler) LinearSearch(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_Request + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.LinearSearch(r.Context(), req) + }) +} + +// LinearSearchByID is an HTTP handler function that processes linear search by ID requests. +// It decodes the incoming JSON payload into a payload.Search_IDRequest struct, +// then invokes the vald.LinearSearchByID method to handle the linear search by ID logic. +func (h *handler) LinearSearchByID(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_IDRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.LinearSearchByID(r.Context(), req) + }) +} + +// MultiLinearSearch is an HTTP handler function that processes multi-linear search requests. +// It decodes the incoming JSON payload into a payload.Search_MultiRequest struct, +// then invokes the vald.MultiLinearSearch method to handle the multi-linear search logic. +func (h *handler) MultiLinearSearch(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_MultiRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiLinearSearch(r.Context(), req) + }) +} + +// MultiLinearSearchByID is an HTTP handler function that processes multi-linear search by ID requests. +// It decodes the incoming JSON payload into a payload.Search_MultiIDRequest struct, +// then invokes the vald.MultiLinearSearchByID method to handle the multi-linear search by ID logic. +func (h *handler) MultiLinearSearchByID(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Search_MultiIDRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiLinearSearchByID(r.Context(), req) + }) +} + +// Insert is an HTTP handler function that processes insert requests. +// It decodes the incoming JSON payload into a payload.Insert_Request struct, +// then invokes the vald.Insert method to handle the insert logic. +func (h *handler) Insert(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Insert_Request + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Insert(r.Context(), req) + }) +} + +// MultiInsert is an HTTP handler function that processes multi-insert requests. +// It decodes the incoming JSON payload into a payload.Insert_MultiRequest struct, +// then invokes the vald.MultiInsert method to handle the multi-insert logic. +func (h *handler) MultiInsert(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Insert_MultiRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiInsert(r.Context(), req) + }) +} + +// Update is an HTTP handler function that processes update requests. +// It decodes the incoming JSON payload into a payload.Update_Request struct, +// then invokes the vald.Update method to handle the update logic. +func (h *handler) Update(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Update_Request + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Update(r.Context(), req) + }) +} + +// MultiUpdate is an HTTP handler function that processes multi-update requests. +// It decodes the incoming JSON payload into a payload.Update_MultiRequest struct, +// then invokes the vald.MultiUpdate method to handle the multi-update logic. +func (h *handler) MultiUpdate(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Update_MultiRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiUpdate(r.Context(), req) + }) +} + +// Upsert is an HTTP handler function that processes upsert requests. +// It decodes the incoming JSON payload into a payload.Upsert_Request struct, +// then invokes the vald.Upsert method to handle the upsert logic. +func (h *handler) Upsert(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Upsert_Request + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Upsert(r.Context(), req) + }) +} + +// MultiUpsert is an HTTP handler function that processes multi-upsert requests. +// It decodes the incoming JSON payload into a payload.Upsert_MultiRequest struct, +// then invokes the vald.MultiUpsert method to handle the multi-upsert logic. +func (h *handler) MultiUpsert(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Upsert_MultiRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiUpsert(r.Context(), req) + }) +} + +// Remove is an HTTP handler function that processes remove requests. +// It decodes the incoming JSON payload into a payload.Remove_Request struct, +// then invokes the vald.Remove method to handle the remove logic. +func (h *handler) Remove(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Remove_Request + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Remove(r.Context(), req) + }) +} + +// RemoveByTimestamp is an HTTP handler function that processes remove-by-timestamp requests. +// It decodes the incoming JSON payload into a payload.Remove_TimestampRequest struct, +// then invokes the vald.RemoveByTimestamp method to handle the remove-by-timestamp logic. +func (h *handler) RemoveByTimestamp(w http.ResponseWriter, r *http.Request) (int, error) { + var req *payload.Remove_TimestampRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.RemoveByTimestamp(r.Context(), req) + }) +} + +// MultiRemove is an HTTP handler function that processes multi-remove requests. +// It decodes the incoming JSON payload into a payload.Remove_MultiRequest struct, +// then invokes the vald.MultiRemove method to handle the multi-remove logic. +func (h *handler) MultiRemove(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Remove_MultiRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.MultiRemove(r.Context(), req) + }) +} + +// GetObject is an HTTP handler function that processes get-object requests. +// It decodes the incoming JSON payload into a payload.Object_VectorRequest struct, +// then invokes the vald.GetObject method to handle the get-object logic. +func (h *handler) GetObject(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Object_VectorRequest + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.GetObject(r.Context(), req) + }) +} + +// Exists is an HTTP handler function that processes exists requests. +// It decodes the incoming JSON payload into a payload.Object_ID struct, +// then invokes the vald.Exists method to handle the exists logic. +func (h *handler) Exists(w http.ResponseWriter, r *http.Request) (code int, err error) { + var req *payload.Object_ID + return json.Handler(w, r, &req, func() (interface{}, error) { + return h.vald.Exists(r.Context(), req) + }) +} diff --git a/pkg/gateway/mirror/handler/rest/option.go b/pkg/gateway/mirror/handler/rest/option.go new file mode 100644 index 0000000000..56272ff02e --- /dev/null +++ b/pkg/gateway/mirror/handler/rest/option.go @@ -0,0 +1,28 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 rest + +import ( + "github.com/vdaas/vald/apis/grpc/v1/vald" +) + +type Option func(*handler) + +var defaultOptions = []Option{} + +func WithVald(v vald.ServerWithMirror) Option { + return func(h *handler) { + h.vald = v + } +} diff --git a/pkg/gateway/mirror/router/option.go b/pkg/gateway/mirror/router/option.go new file mode 100644 index 0000000000..7b3e7d1782 --- /dev/null +++ b/pkg/gateway/mirror/router/option.go @@ -0,0 +1,36 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 router + +import ( + "github.com/vdaas/vald/pkg/gateway/mirror/handler/rest" +) + +type Option func(*router) + +var defaultOptions = []Option{ + WithTimeout("3s"), +} + +func WithHandler(h rest.Handler) Option { + return func(r *router) { + r.handler = h + } +} + +func WithTimeout(timeout string) Option { + return func(r *router) { + r.timeout = timeout + } +} diff --git a/pkg/gateway/mirror/router/router.go b/pkg/gateway/mirror/router/router.go new file mode 100644 index 0000000000..d65b106652 --- /dev/null +++ b/pkg/gateway/mirror/router/router.go @@ -0,0 +1,179 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 router + +import ( + "net/http" + + "github.com/vdaas/vald/internal/net/http/routing" + "github.com/vdaas/vald/pkg/gateway/mirror/handler/rest" +) + +type router struct { + handler rest.Handler + timeout string +} + +// New returns REST route&method information from handler interface. +func New(opts ...Option) http.Handler { + r := new(router) + + for _, opt := range append(defaultOptions, opts...) { + opt(r) + } + + h := r.handler + + return routing.New( + routing.WithRoutes([]routing.Route{ + { + Name: "Index", + Methods: []string{ + http.MethodGet, + }, + Pattern: "/", + HandlerFunc: h.Index, + }, + { + Name: "Register", + Methods: []string{ + http.MethodPost, + }, + Pattern: "/register", + HandlerFunc: h.Register, + }, + { + Name: "Search", + Methods: []string{ + http.MethodPost, + }, + Pattern: "/search", + HandlerFunc: h.Search, + }, + { + Name: "Search By ID", + Methods: []string{ + http.MethodGet, + }, + Pattern: "/search/{id}", + HandlerFunc: h.SearchByID, + }, + + { + Name: "Multi Search", + Methods: []string{ + http.MethodPost, + }, + Pattern: "/search/multi", + HandlerFunc: h.MultiSearch, + }, + { + Name: "Multi Search By ID", + Methods: []string{ + http.MethodGet, + }, + Pattern: "/search/multi/{id}", + HandlerFunc: h.MultiSearchByID, + }, + { + Name: "Insert", + Methods: []string{ + http.MethodPost, + }, + Pattern: "/insert", + HandlerFunc: h.Insert, + }, + { + Name: "Multiple Insert", + Methods: []string{ + http.MethodPost, + }, + Pattern: "/insert/multi", + HandlerFunc: h.MultiInsert, + }, + { + Name: "Update", + Methods: []string{ + http.MethodPost, + http.MethodPatch, + http.MethodPut, + }, + Pattern: "/update", + HandlerFunc: h.Update, + }, + { + Name: "Multiple Update", + Methods: []string{ + http.MethodPost, + http.MethodPatch, + http.MethodPut, + }, + Pattern: "/update/multi", + HandlerFunc: h.MultiUpdate, + }, + { + Name: "Upsert", + Methods: []string{ + http.MethodPost, + http.MethodPatch, + http.MethodPut, + }, + Pattern: "/upsert", + HandlerFunc: h.Upsert, + }, + { + Name: "Multiple Upsert", + Methods: []string{ + http.MethodPost, + http.MethodPatch, + http.MethodPut, + }, + Pattern: "/upsert/multi", + HandlerFunc: h.MultiUpsert, + }, + { + Name: "Remove", + Methods: []string{ + http.MethodDelete, + }, + Pattern: "/delete/{id}", + HandlerFunc: h.Remove, + }, + { + Name: "RemoveByTimestamp", + Methods: []string{ + http.MethodDelete, + }, + Pattern: "/delete/timestamp", + HandlerFunc: h.RemoveByTimestamp, + }, + { + Name: "Multiple Remove", + Methods: []string{ + http.MethodDelete, + http.MethodPost, + }, + Pattern: "/delete/multi", + HandlerFunc: h.MultiRemove, + }, + { + Name: "GetObject", + Methods: []string{ + http.MethodGet, + }, + Pattern: "/object/{id}", + HandlerFunc: h.GetObject, + }, + }...)) +} diff --git a/pkg/gateway/mirror/service/discovery.go b/pkg/gateway/mirror/service/discovery.go new file mode 100644 index 0000000000..bb65304288 --- /dev/null +++ b/pkg/gateway/mirror/service/discovery.go @@ -0,0 +1,359 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + "reflect" + "strconv" + "sync/atomic" + "time" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/hash" + "github.com/vdaas/vald/internal/k8s" + k8sclient "github.com/vdaas/vald/internal/k8s/client" + "github.com/vdaas/vald/internal/k8s/vald/mirror/target" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/net" + "github.com/vdaas/vald/internal/strings" + "github.com/vdaas/vald/internal/sync/errgroup" +) + +const ( + resourcePrefix = "mirror-target" + groupKey = "group" +) + +// Discovery represents an interface for the main logic of service discovery. +// The primary purpose of the Discovery interface is to reconcile custom resources, +// initiating or terminating gRPC connections based on the state of the custom resources. +type Discovery interface { + Start(ctx context.Context) (<-chan error, error) +} + +type discovery struct { + namespace string + labels map[string]string + colocation string + der net.Dialer + + targetsByName atomic.Pointer[map[string]target.Target] // latest reconciliation results. + ctrl k8s.Controller + dur time.Duration + selfMirrAddrs []string + selfMirrAddrStr string + + mirr Mirror + eg errgroup.Group +} + +// NewDiscovery creates the Discovery object with optional configuration options. +// It returns the initialized Discovery object and an error if the creation process fails. +func NewDiscovery(opts ...DiscoveryOption) (dsc Discovery, err error) { + d := new(discovery) + for _, opt := range append(defaultDiscovererOpts, opts...) { + if err := opt(d); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(err, &e) { + log.Error(oerr) + return nil, oerr + } + log.Warn(oerr) + } + } + d.targetsByName.Store(&map[string]target.Target{}) + d.selfMirrAddrStr = strings.Join(d.selfMirrAddrs, ",") + + watcher, err := target.New( + target.WithControllerName("mirror discovery"), + target.WithNamespace(d.namespace), + target.WithLabels(d.labels), + target.WithOnErrorFunc(func(err error) { + log.Error("failed to reconcile:", err) + }), + target.WithOnReconcileFunc(d.onReconcile), + ) + if err != nil { + return nil, err + } + + if d.ctrl == nil { + d.ctrl, err = k8s.New( + k8s.WithDialer(d.der), + k8s.WithControllerName("vald k8s mirror discovery"), + k8s.WithDisableLeaderElection(), + k8s.WithResourceController(watcher), + ) + } + return d, err +} + +func (d *discovery) onReconcile(_ context.Context, list map[string]target.Target) { + log.Debugf("mirror reconciled\t%#v", list) + d.targetsByName.Store(&list) +} + +// Start initiates the service discovery process. +// It returns a channel for receiving errors and an error if the initialization fails. +func (d *discovery) Start(ctx context.Context) (<-chan error, error) { + dech, err := d.ctrl.Start(ctx) + if err != nil { + return nil, err + } + ech := make(chan error, 2) + d.eg.Go(func() (err error) { + defer close(ech) + tic := time.NewTicker(d.dur) + defer tic.Stop() + + prev := d.loadTargets() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-tic.C: + prev, err = d.startSync(ctx, prev) + if err != nil { + select { + case <-ctx.Done(): + return errors.Join(ctx.Err(), err) + case ech <- err: + } + } + case err := <-dech: + if err != nil { + select { + case <-ctx.Done(): + return errors.Join(ctx.Err(), err) + case ech <- err: + } + } + } + } + }) + return ech, nil +} + +func (d *discovery) loadTargets() map[string]target.Target { + if v := d.targetsByName.Load(); v != nil { + return *v + } + return map[string]target.Target{} +} + +type createdTarget struct { + name string + tgt target.Target +} + +type updatedTarget struct { + name string + old target.Target + new target.Target +} + +type deletedTarget struct { + name string + host string + port uint32 +} + +func (d *discovery) startSync(ctx context.Context, prev map[string]target.Target) (current map[string]target.Target, errs error) { + current = d.loadTargets() + curAddrs := map[string]string{} // map[addr: metadata.name] + + created := map[string]*createdTarget{} // map[addr: target.Target] + updated := map[string]*updatedTarget{} // map[addr: *updatedTarget] + for name, ctgt := range current { + addr := net.JoinHostPort(ctgt.Host, uint16(ctgt.Port)) + curAddrs[addr] = name + if ptgt, ok := prev[name]; !ok { + created[addr] = &createdTarget{ + name: name, + tgt: ctgt, + } + } else if ptgt.Host != ctgt.Host || ptgt.Port != ctgt.Port { + updated[addr] = &updatedTarget{ + name: name, + old: ptgt, + new: ctgt, + } + } + } + + deleted := map[string]*deletedTarget{} // map[addr: *deletedTarget] + for name, ptgt := range prev { + if _, ok := current[name]; !ok { + addr := net.JoinHostPort(ptgt.Host, uint16(ptgt.Port)) + deleted[addr] = &deletedTarget{ + name: name, + host: ptgt.Host, + port: uint32(ptgt.Port), + } + } + } + + if len(created) != 0 || len(deleted) != 0 || len(updated) != 0 { + log.Infof("created: %#v\tupdated: %#v\tdeleted: %#v", created, updated, deleted) + if err := d.connectTarget(ctx, created); err != nil { + errs = errors.Join(errs, err) + } + if err := d.disconnectTarget(ctx, deleted); err != nil { + errs = errors.Join(errs, err) + } + if err := d.updateTarget(ctx, updated); err != nil { + errs = errors.Join(errs, err) + } + return current, errs + } + return current, d.syncWithAddr(ctx, current, curAddrs) +} + +func (d *discovery) syncWithAddr(ctx context.Context, current map[string]target.Target, curAddrs map[string]string) (errs error) { + for addr, name := range curAddrs { + // When the status code of a regularly running Register RPC is Unimplemented, the connection to the target will be disconnected + // so the status of the resource (CR) may be misaligned. To prevent this, change the status of the resource to Disconnected. + connected := d.mirr.IsConnected(ctx, addr) + if !connected && isConnectedPhase(current[name].Phase) { + errs = errors.Join(errs, d.updateMirrorTargetPhase(ctx, name, target.MirrorTargetPhaseDisconnected)) + } else if connected && !isConnectedPhase(current[name].Phase) { + errs = errors.Join(errs, d.updateMirrorTargetPhase(ctx, name, target.MirrorTargetPhaseConnected)) + } + } + + d.mirr.RangeMirrorAddr(func(addr string, _ any) bool { + connected := d.mirr.IsConnected(ctx, addr) + if name, ok := curAddrs[addr]; ok { + if connected && !isConnectedPhase(current[name].Phase) { + errs = errors.Join(errs, + d.updateMirrorTargetPhase(ctx, name, target.MirrorTargetPhaseConnected), + ) + } else if !connected && !isDisconnectedPhase(current[name].Phase) { + errs = errors.Join(errs, + d.updateMirrorTargetPhase(ctx, name, target.MirrorTargetPhaseDisconnected), + ) + } + } else if !ok && connected { + host, port, err := net.SplitHostPort(addr) + if err != nil { + log.Error(err) + return true + } + name := resourcePrefix + "-" + strconv.FormatUint(hash.String(d.selfMirrAddrStr+addr), 10) + errs = errors.Join(errs, d.createMirrorTargetResource(ctx, name, host, int(port))) + } + return true + }) + return errs +} + +func (d *discovery) connectTarget(ctx context.Context, req map[string]*createdTarget) (errs error) { + for _, created := range req { + phase := target.MirrorTargetPhaseConnected + err := d.mirr.Connect(ctx, &payload.Mirror_Target{ + Host: created.tgt.Host, + Port: uint32(created.tgt.Port), + }) + if err != nil { + errs = errors.Join(errs, err) + phase = target.MirrorTargetPhaseDisconnected + } + errs = errors.Join(errs, d.updateMirrorTargetPhase(ctx, created.name, phase)) + } + return errs +} + +func (d *discovery) createMirrorTargetResource(ctx context.Context, name, host string, port int) error { + mt, err := target.NewMirrorTargetTemplate( + target.WithMirrorTargetName(name), + target.WithMirrorTargetNamespace(d.namespace), + target.WithMirrorTargetStatus(&target.MirrorTargetStatus{ + Phase: target.MirrorTargetPhasePending, + }), + target.WithMirrorTargetLabels(d.labels), + target.WithMirrorTargetColocation(d.colocation), + target.WithMirrorTargetHost(host), + target.WithMirrorTargetPort(port), + ) + if err != nil { + return err + } + return d.ctrl.GetManager().GetClient().Create(ctx, mt) +} + +func (d *discovery) disconnectTarget(ctx context.Context, req map[string]*deletedTarget) (errs error) { + for _, deleted := range req { + phase := target.MirrorTargetPhaseDisconnected + err := d.mirr.Disconnect(ctx, &payload.Mirror_Target{ + Host: deleted.host, + Port: deleted.port, + }) + if err != nil { + errs = errors.Join(errs, err) + phase = target.MirrorTargetPhaseUnknown + } + errs = errors.Join(errs, d.updateMirrorTargetPhase(ctx, deleted.name, phase)) + } + return errs +} + +func (d *discovery) updateMirrorTargetPhase(ctx context.Context, name string, phase target.MirrorTargetPhase) error { + c := d.ctrl.GetManager().GetClient() + mt := &target.MirrorTarget{} + err := c.Get(ctx, k8sclient.ObjectKey{ + Namespace: d.namespace, + Name: name, + }, mt) + if err != nil { + return err + } + if mt.Status.Phase == phase { + return nil + } + mt.Status.Phase = phase + mt.Status.LastTransitionTime = k8s.Now() + return c.Status().Update(ctx, mt) +} + +func (d *discovery) updateTarget(ctx context.Context, req map[string]*updatedTarget) (errs error) { + for _, updated := range req { + err := d.mirr.Disconnect(ctx, &payload.Mirror_Target{ + Host: updated.old.Host, + Port: uint32(updated.old.Port), + }) + if err != nil { + errs = errors.Join(errs, err, d.updateMirrorTargetPhase(ctx, updated.name, target.MirrorTargetPhaseUnknown)) + } else { + err := d.mirr.Connect(ctx, &payload.Mirror_Target{ + Host: updated.new.Host, + Port: uint32(updated.new.Port), + }) + if err != nil { + errs = errors.Join(errs, err, d.updateMirrorTargetPhase(ctx, updated.name, target.MirrorTargetPhaseDisconnected)) + } + } + } + return errs +} + +func isConnectedPhase(phase target.MirrorTargetPhase) bool { + return phase == target.MirrorTargetPhaseConnected +} + +func isDisconnectedPhase(phase target.MirrorTargetPhase) bool { + return phase == target.MirrorTargetPhaseDisconnected +} diff --git a/pkg/gateway/mirror/service/discovery_option.go b/pkg/gateway/mirror/service/discovery_option.go new file mode 100644 index 0000000000..f43ec58c82 --- /dev/null +++ b/pkg/gateway/mirror/service/discovery_option.go @@ -0,0 +1,132 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/k8s" + "github.com/vdaas/vald/internal/net" + "github.com/vdaas/vald/internal/sync/errgroup" + "github.com/vdaas/vald/internal/timeutil" +) + +// DiscoveryOption represents the functional option for discovery. +type DiscoveryOption func(d *discovery) error + +var defaultDiscovererOpts = []DiscoveryOption{ + WithDiscoveryDuration("1s"), + WithDiscoveryErrGroup(errgroup.Get()), + WithDiscoveryColocation("dc1"), +} + +// WithDiscoveryMirror returns the option to set the Mirror service. +func WithDiscoveryMirror(m Mirror) DiscoveryOption { + return func(d *discovery) error { + if m == nil { + return errors.NewErrCriticalOption("discoveryMirror", m) + } + d.mirr = m + return nil + } +} + +// WithDiscoveryDialer returns the option to set the dialer for controller manager. +func WithDiscoveryDialer(der net.Dialer) DiscoveryOption { + return func(d *discovery) error { + if der != nil { + d.der = der + } + return nil + } +} + +// WithDiscoveryNamespace returns the option to set the namespace for discovery. +func WithDiscoveryNamespace(ns string) DiscoveryOption { + return func(d *discovery) error { + if ns != "" { + d.namespace = ns + } + return nil + } +} + +// WithDiscoveryGroup returns the option to set the Mirror group for discovery. +func WithDiscoveryGroup(g string) DiscoveryOption { + return func(d *discovery) error { + if g != "" { + if d.labels == nil { + d.labels = make(map[string]string) + } + d.labels[groupKey] = g + } + return nil + } +} + +// WithDiscoveryColocation returns the option to set the colocation name of datacenter. +func WithDiscoveryColocation(loc string) DiscoveryOption { + return func(d *discovery) error { + if loc != "" { + d.colocation = loc + } + return nil + } +} + +// WithDiscoveryDuration returns the option to set the duration of the discovery. +func WithDiscoveryDuration(s string) DiscoveryOption { + return func(d *discovery) error { + if s == "" { + return nil + } + dur, err := timeutil.Parse(s) + if err != nil { + return errors.NewErrInvalidOption("discoveryDuration", s, err) + } + d.dur = dur + return nil + } +} + +// WithDiscoveryErrGroup returns the option to set the errgroup. +func WithDiscoveryErrGroup(eg errgroup.Group) DiscoveryOption { + return func(d *discovery) error { + if eg != nil { + d.eg = eg + } + return nil + } +} + +// WithDiscoverySelfMirrorAddrs returns the option to set the self Mirror addresses. +func WithDiscoverySelfMirrorAddrs(addrs ...string) DiscoveryOption { + return func(d *discovery) error { + if len(addrs) == 0 { + return errors.NewErrCriticalOption("discoverySelfMirrorAddrs", addrs) + } + d.selfMirrAddrs = append(d.selfMirrAddrs, addrs...) + return nil + } +} + +// WithDiscoveryController returns the option to set the k8s controller. +func WithDiscoveryController(ctrl k8s.Controller) DiscoveryOption { + return func(d *discovery) error { + if ctrl == nil { + return errors.NewErrInvalidOption("discoveryController", ctrl) + } + d.ctrl = ctrl + return nil + } +} diff --git a/pkg/gateway/mirror/service/discovery_test.go b/pkg/gateway/mirror/service/discovery_test.go new file mode 100644 index 0000000000..cc4665bbe7 --- /dev/null +++ b/pkg/gateway/mirror/service/discovery_test.go @@ -0,0 +1,579 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + "reflect" + "testing" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/k8s" + "github.com/vdaas/vald/internal/k8s/vald/mirror/target" + "github.com/vdaas/vald/internal/test/goleak" + k8smock "github.com/vdaas/vald/internal/test/mock/k8s" +) + +func Test_discovery_startSync(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + prev map[string]target.Target + } + type fields struct { + ctrl k8s.Controller + mirr Mirror + } + type want struct { + want map[string]target.Target + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, map[string]target.Target, error) error + beforeFunc func(*testing.T, *discovery, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, got map[string]target.Target, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + if !reflect.DeepEqual(got, w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + return nil + } + tests := []test{ + func() test { + prev := make(map[string]target.Target) + current := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + }, + } + return test{ + name: "Succeeded detecting the created resource and connecting to the new target", + args: args{ + ctx: context.Background(), + prev: prev, + }, + fields: fields{ + mirr: &MirrorMock{ + ConnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return nil + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + want: want{ + want: current, + }, + } + }(), + func() test { + prev := make(map[string]target.Target) + current := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + }, + } + return test{ + name: "Succeeded detecting the created resource but failed to connect to the new target", + args: args{ + ctx: context.Background(), + prev: prev, + }, + fields: fields{ + mirr: &MirrorMock{ + ConnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return errors.New("err") + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + want: want{ + want: current, + err: errors.New("err"), + }, + } + }(), + func() test { + prev := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + }, + } + current := make(map[string]target.Target) + return test{ + name: "Succeeded detecting the deleted resource and disconnecting the target", + args: args{ + ctx: context.Background(), + prev: prev, + }, + fields: fields{ + mirr: &MirrorMock{ + DisconnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return nil + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + want: want{ + want: current, + }, + } + }(), + func() test { + prev := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + }, + } + current := make(map[string]target.Target) + return test{ + name: "Succeeded detecting the deleted resource but failed to disconnect the target", + args: args{ + ctx: context.Background(), + prev: prev, + }, + fields: fields{ + mirr: &MirrorMock{ + DisconnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return errors.New("err") + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + want: want{ + want: current, + err: errors.New("err"), + }, + } + }(), + func() test { + prev := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + }, + } + current := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.3", + Port: 8081, + }, + } + return test{ + name: "Succeeded detecting the updated resource and updating the target connection", + args: args{ + ctx: context.Background(), + prev: prev, + }, + fields: fields{ + mirr: &MirrorMock{ + ConnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return nil + }, + DisconnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return nil + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + want: want{ + want: current, + }, + } + }(), + func() test { + prev := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + }, + } + current := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.3", + Port: 8081, + }, + } + return test{ + name: "Succeeded detecting the updated resource and failed to update the target connection", + args: args{ + ctx: context.Background(), + prev: prev, + }, + fields: fields{ + mirr: &MirrorMock{ + ConnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return nil + }, + DisconnectFunc: func(_ context.Context, _ ...*payload.Mirror_Target) error { + return errors.New("err") + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + want: want{ + want: current, + err: errors.New("err"), + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + d, err := NewDiscovery( + WithDiscoveryController(test.fields.ctrl), + WithDiscoveryMirror(test.fields.mirr), + ) + if err != nil { + t.Fatal(err) + } + + if dis, ok := d.(*discovery); ok { + if test.beforeFunc != nil { + test.beforeFunc(tt, dis, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got, err := dis.startSync(test.args.ctx, test.args.prev) + if err := checkFunc(test.want, got, err); err != nil { + tt.Errorf("error = %v", err) + } + } + }) + } +} + +func Test_discovery_syncWithAddr(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + current map[string]target.Target + curAddrs map[string]string + } + type fields struct { + ctrl k8s.Controller + mirr Mirror + } + type want struct { + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, error) error + beforeFunc func(*testing.T, *discovery, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + } + tests := []test{ + func() test { + current := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + Phase: target.MirrorTargetPhaseConnected, + }, + } + curAddrs := map[string]string{ + "192.168.1.2:8081": "mirror-1", + } + + return test{ + name: "Succeeded to change to disconnected phase when curAddrs are not connected", + args: args{ + ctx: context.Background(), + current: current, + curAddrs: curAddrs, + }, + fields: fields{ + mirr: &MirrorMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return false + }, + RangeMirrorAddrFunc: func(_ func(addr string, _ any) bool) { + // There is no connection. + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + beforeFunc: func(t *testing.T, d *discovery, _ args) { + t.Helper() + d.onReconcile(context.Background(), current) + }, + } + }(), + func() test { + current := map[string]target.Target{ + "mirror-1": { + Host: "192.168.1.2", + Port: 8081, + Phase: target.MirrorTargetPhaseDisconnected, + }, + } + curAddrs := map[string]string{ + "192.168.1.2:8081": "mirror-1", + } + + return test{ + name: "Succeeded to change to connected phase when curAddrs are connected", + args: args{ + ctx: context.Background(), + current: current, + curAddrs: curAddrs, + }, + fields: fields{ + mirr: &MirrorMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return true + }, + RangeMirrorAddrFunc: func(f func(addr string, _ any) bool) { + for addr := range curAddrs { + f(addr, struct{}{}) + } + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + } + }(), + func() test { + current := map[string]target.Target{} + curAddrs := map[string]string{} + newAddrs := []string{ + "192.168.1.2:8081", + } + + return test{ + name: "Succeeded to create new resource when there is a new connection", + args: args{ + ctx: context.Background(), + current: current, + curAddrs: curAddrs, + }, + fields: fields{ + mirr: &MirrorMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return true + }, + RangeMirrorAddrFunc: func(f func(addr string, _ any) bool) { + for _, addr := range newAddrs { + f(addr, struct{}{}) + } + }, + }, + ctrl: &k8smock.ControllerMock{ + GetManagerFunc: k8smock.NewDefaultManagerMock, + }, + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + d, err := NewDiscovery( + WithDiscoveryController(test.fields.ctrl), + WithDiscoveryMirror(test.fields.mirr), + ) + if err != nil { + t.Fatal(err) + } + + if dis, ok := d.(*discovery); ok { + if test.beforeFunc != nil { + test.beforeFunc(tt, dis, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + err := dis.syncWithAddr(test.args.ctx, test.args.current, test.args.curAddrs) + if err := checkFunc(test.want, err); err != nil { + tt.Errorf("error = %v", err) + } + } + }) + } +} + +// NOT IMPLEMENTED BELOW +// +// func TestNewDiscovery(t *testing.T) { +// type args struct { +// opts []DiscoveryOption +// } +// type want struct { +// wantDsc Discovery +// err error +// } +// type test struct { +// name string +// args args +// want want +// checkFunc func(want, Discovery, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, gotDsc Discovery, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(gotDsc, w.wantDsc) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotDsc, w.wantDsc) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// opts:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// opts:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// +// gotDsc, err := NewDiscovery(test.args.opts...) +// if err := checkFunc(test.want, gotDsc, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// diff --git a/pkg/gateway/mirror/service/doc.go b/pkg/gateway/mirror/service/doc.go new file mode 100644 index 0000000000..d65bc59868 --- /dev/null +++ b/pkg/gateway/mirror/service/doc.go @@ -0,0 +1,14 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service diff --git a/pkg/gateway/mirror/service/gateway.go b/pkg/gateway/mirror/service/gateway.go new file mode 100644 index 0000000000..632f9db8a6 --- /dev/null +++ b/pkg/gateway/mirror/service/gateway.go @@ -0,0 +1,177 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + "reflect" + + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/client/v1/client/mirror" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/observability/trace" + "github.com/vdaas/vald/internal/sync/errgroup" +) + +const ( + // forwardedContextKey is the key used to store forwarding-related information in a context. + forwardedContextKey = "forwarded-for" +) + +// Gateway represents an interface for interacting with gRPC clients. +type Gateway interface { + ForwardedContext(ctx context.Context, podName string) context.Context + FromForwardedContext(ctx context.Context) string + BroadCast(ctx context.Context, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error + Do(ctx context.Context, target string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error)) (interface{}, error) + DoMulti(ctx context.Context, targets []string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error + GRPCClient() grpc.Client +} + +type gateway struct { + // client is the Mirror gateway client for other clusters and the Vald gateway (e.g. LB gateway) client for the own cluster. + client mirror.Client + eg errgroup.Group + podName string +} + +// NewGateway returns Gateway object if no error occurs. +func NewGateway(opts ...Option) (Gateway, error) { + g := new(gateway) + for _, opt := range append(defaultGatewayOpts, opts...) { + if err := opt(g); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(err, &e) { + log.Error(oerr) + return nil, oerr + } + log.Warn(oerr) + } + } + return g, nil +} + +// GRPCClient returns the underlying gRPC client associated with this object. +// It provides access to the low-level gRPC client for advanced use cases. +func (g *gateway) GRPCClient() grpc.Client { + return g.client.GRPCClient() +} + +// ForwardedContext takes a context and a podName, returning a new context +// with additional information related to forwarding. +func (*gateway) ForwardedContext(ctx context.Context, podName string) context.Context { + return grpc.NewOutgoingContext(ctx, grpc.MD{ + forwardedContextKey: []string{ + podName, + }, + }) +} + +// FromForwardedContext extracts information from the forwarded context +// and returns the podName associated with it. +func (*gateway) FromForwardedContext(ctx context.Context) string { + md, ok := grpc.FromIncomingContext(ctx) + if !ok { + return "" + } + vals, ok := md[forwardedContextKey] + if !ok { + return "" + } + if len(vals) > 0 { + return vals[0] + } + return "" +} + +// BroadCast performs a broadcast operation using the provided function +// to interact with gRPC clients for multiple targets. +// The provided function should handle the communication logic for a target. +func (g *gateway) BroadCast(ctx context.Context, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error, +) (err error) { + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Gateway.BroadCast") + defer func() { + if span != nil { + span.End() + } + }() + return g.client.GRPCClient().RangeConcurrent(g.ForwardedContext(ctx, g.podName), -1, func(ictx context.Context, + addr string, conn *grpc.ClientConn, copts ...grpc.CallOption, + ) (err error) { + select { + case <-ictx.Done(): + return nil + default: + return f(ictx, addr, vald.NewValdClientWithMirror(conn), copts...) + } + }) +} + +// Do performs a gRPC operation on a single target using the provided function. +// It returns the result of the operation and any associated error. +// The provided function should handle the communication logic for a target. +func (g *gateway) Do(ctx context.Context, target string, + f func(ctx context.Context, addr string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error), +) (res interface{}, err error) { + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Gateway.Do") + defer func() { + if span != nil { + span.End() + } + }() + + if target == "" { + return nil, errors.ErrTargetNotFound + } + return g.client.GRPCClient().Do(g.ForwardedContext(ctx, g.podName), target, + func(ictx context.Context, conn *grpc.ClientConn, copts ...grpc.CallOption) (interface{}, error) { + return f(ictx, target, vald.NewValdClientWithMirror(conn), copts...) + }, + ) +} + +// DoMulti performs a gRPC operation on multiple targets using the provided function. +// It returns an error if any of the operations fails. +// The provided function should handle the communication logic for a target. +func (g *gateway) DoMulti(ctx context.Context, targets []string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error, +) error { + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Gateway.DoMulti") + defer func() { + if span != nil { + span.End() + } + }() + + if len(targets) == 0 { + return errors.ErrTargetNotFound + } + return g.client.GRPCClient().OrderedRangeConcurrent(g.ForwardedContext(ctx, g.podName), targets, -1, + func(ictx context.Context, addr string, conn *grpc.ClientConn, copts ...grpc.CallOption) (err error) { + select { + case <-ictx.Done(): + return nil + default: + return f(ictx, addr, vald.NewValdClientWithMirror(conn), copts...) + } + }, + ) +} diff --git a/pkg/gateway/mirror/service/gateway_mock_test.go b/pkg/gateway/mirror/service/gateway_mock_test.go new file mode 100644 index 0000000000..9bd0641033 --- /dev/null +++ b/pkg/gateway/mirror/service/gateway_mock_test.go @@ -0,0 +1,71 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/net/grpc" +) + +// GatewayMock represents mock struct for Gateway. +type GatewayMock struct { + Gateway + ForwardedContextFunc func(ctx context.Context, podName string) context.Context + FromForwardedContextFunc func(ctx context.Context) string + BroadCastFunc func(ctx context.Context, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error + DoFunc func(ctx context.Context, target string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) (interface{}, error)) (interface{}, error) + DoMultiFunc func(ctx context.Context, targets []string, + f func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error) error + GRPCClientFunc func() grpc.Client +} + +// ForwardedContext calls ForwardedContextFunc object. +func (gm *GatewayMock) ForwardedContext(ctx context.Context, podName string) context.Context { + return gm.ForwardedContextFunc(ctx, podName) +} + +// FromForwardedContext calls FromForwardedContextFunc object. +func (gm *GatewayMock) FromForwardedContext(ctx context.Context) string { + return gm.FromForwardedContextFunc(ctx) +} + +// BroadCast calls BroadCastFunc object. +func (gm *GatewayMock) BroadCast(ctx context.Context, + f func(_ context.Context, _ string, _ vald.ClientWithMirror, _ ...grpc.CallOption) error, +) error { + return gm.BroadCastFunc(ctx, f) +} + +// Do calls DoFunc object. +func (gm *GatewayMock) Do(ctx context.Context, target string, + f func(_ context.Context, _ string, _ vald.ClientWithMirror, _ ...grpc.CallOption) (interface{}, error), +) (interface{}, error) { + return gm.DoFunc(ctx, target, f) +} + +// DoMulti calls DoMultiFunc object. +func (gm *GatewayMock) DoMulti(ctx context.Context, targets []string, + f func(_ context.Context, _ string, _ vald.ClientWithMirror, _ ...grpc.CallOption) error, +) error { + return gm.DoMultiFunc(ctx, targets, f) +} + +// GRPCClient calls GRPCClientFunc object. +func (gm *GatewayMock) GRPCClient() grpc.Client { + return gm.GRPCClientFunc() +} diff --git a/pkg/gateway/mirror/service/mirror.go b/pkg/gateway/mirror/service/mirror.go new file mode 100644 index 0000000000..892ef39f1a --- /dev/null +++ b/pkg/gateway/mirror/service/mirror.go @@ -0,0 +1,404 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + "reflect" + "time" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/net" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/net/grpc/codes" + "github.com/vdaas/vald/internal/net/grpc/errdetails" + "github.com/vdaas/vald/internal/net/grpc/status" + "github.com/vdaas/vald/internal/observability/trace" + "github.com/vdaas/vald/internal/sync" + "github.com/vdaas/vald/internal/sync/errgroup" +) + +// Mirror represents an interface for managing mirroring operations. +// It provides methods for starting the mirroring service, connecting and disconnecting targets, +// checking the connectivity status of a given address, checking the existence of an address, +// retrieving all mirror targets, and iterating over all mirror addresses. +type Mirror interface { + Start(ctx context.Context) <-chan error + Connect(ctx context.Context, targets ...*payload.Mirror_Target) error + Disconnect(ctx context.Context, targets ...*payload.Mirror_Target) error + IsConnected(ctx context.Context, addr string) bool + MirrorTargets(ctx context.Context) ([]*payload.Mirror_Target, error) + RangeMirrorAddr(f func(addr string, _ any) bool) +} + +type mirr struct { + addrl sync.Map[string, any] // List of all connected addresses + selfMirrTgts []*payload.Mirror_Target // Targets of self mirror gateway + selfMirrAddrl sync.Map[string, any] // List of self Mirror gateway addresses + gwAddrl sync.Map[string, any] // List of Vald gateway (LB gateway) addresses + eg errgroup.Group + registerDur time.Duration + gateway Gateway +} + +// NewMirror creates the Mirror object with optional configuration options. +// It returns the initialized Mirror object and an error if the creation process fails. +func NewMirror(opts ...MirrorOption) (_ Mirror, err error) { + m := new(mirr) + for _, opt := range append(defaultMirrOpts, opts...) { + if err := opt(m); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(err, &e) { + log.Error(oerr) + return nil, oerr + } + log.Warn(oerr) + } + } + + m.selfMirrTgts = make([]*payload.Mirror_Target, 0) + m.selfMirrAddrl.Range(func(addr string, _ any) bool { + var ( + host string + port uint16 + ) + host, port, err = net.SplitHostPort(addr) + if err != nil { + return false + } + m.selfMirrTgts = append(m.selfMirrTgts, &payload.Mirror_Target{ + Host: host, + Port: uint32(port), + }) + return true + }) + return m, err +} + +// Start starts the mirroring service. +// It returns a channel for receiving errors during the mirroring process. +func (m *mirr) Start(ctx context.Context) <-chan error { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Mirror.Start") + defer func() { + if span != nil { + span.End() + } + }() + ech := make(chan error, 100) + + m.eg.Go(func() error { + tic := time.NewTicker(m.registerDur) + defer close(ech) + defer tic.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-tic.C: + tgt, err := m.MirrorTargets(ctx) + if err != nil { + select { + case <-ctx.Done(): + return errors.Join(ctx.Err(), err) + case ech <- err: + break + } + } + + resTgts, err := m.registers(ctx, &payload.Mirror_Targets{Targets: tgt}) + if err != nil || len(resTgts) == 0 { + if !errors.Is(err, errors.ErrTargetNotFound) && len(resTgts) == 0 { + err = errors.Join(err, errors.ErrTargetNotFound) + } else if len(resTgts) == 0 { + err = errors.ErrTargetNotFound + } + select { + case <-ctx.Done(): + return errors.Join(ctx.Err(), err) + case ech <- err: + } + } + if len(resTgts) > 0 { + if err := m.Connect(ctx, resTgts...); err != nil { + select { + case <-ctx.Done(): + return errors.Join(ctx.Err(), err) + case ech <- err: + } + } + } + log.Debugf("[mirror]: connected mirror gateway targets: %v", m.gateway.GRPCClient().ConnectedAddrs()) + } + } + }) + return ech +} + +func (m *mirr) registers(ctx context.Context, tgts *payload.Mirror_Targets) ([]*payload.Mirror_Target, error) { // skipcq: GO-R1005 + ctx, span := trace.StartSpan(grpc.WithGRPCMethod(ctx, vald.PackageName+"."+vald.MirrorRPCServiceName+"/"+vald.RegisterRPCName), "vald/gateway/mirror/service/Mirror.registers") + defer func() { + if span != nil { + span.End() + } + }() + + reqInfo := &errdetails.RequestInfo{ + ServingData: errdetails.Serialize(tgts), + } + resInfo := &errdetails.ResourceInfo{ + ResourceType: errdetails.ValdGRPCResourceTypePrefix + "/vald.v1." + vald.RegisterRPCName, + } + resTgts := make([]*payload.Mirror_Target, 0, len(tgts.GetTargets())) + exists := make(map[string]bool) + var result sync.Map[string, error] // map[target host: error] + var mu sync.Mutex + + err := m.gateway.DoMulti(ctx, m.connectedOtherMirrorAddrs(ctx), func(ctx context.Context, target string, vc vald.ClientWithMirror, copts ...grpc.CallOption) error { + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Mirror.registers/"+target) + defer func() { + if span != nil { + span.End() + } + }() + + res, err := vc.Register(ctx, tgts, copts...) + if err != nil { + var attrs trace.Attributes + switch { + case errors.Is(err, context.Canceled): + err = status.WrapWithCanceled( + vald.RegisterRPCName+" API canceld", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeCancelled(err.Error()) + case errors.Is(err, context.DeadlineExceeded): + err = status.WrapWithCanceled( + vald.RegisterRPCName+" API deadline exceeded", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeDeadlineExceeded(err.Error()) + case errors.Is(err, errors.ErrGRPCClientConnNotFound("*")): + err = status.WrapWithInternal( + vald.RegisterRPCName+" API connection not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInternal(err.Error()) + case errors.Is(err, errors.ErrTargetNotFound): + err = status.WrapWithInvalidArgument( + vald.RegisterRPCName+" API target not found", err, reqInfo, resInfo, + ) + attrs = trace.StatusCodeInvalidArgument(err.Error()) + default: + var ( + st *status.Status + msg string + ) + st, msg, err = status.ParseError(err, codes.Internal, + "failed to parse "+vald.RegisterRPCName+" gRPC error response", reqInfo, resInfo, + ) + attrs = trace.FromGRPCStatus(st.Code(), msg) + + // When the ingress resource is deleted, the controller's default backend results(Unimplemented error) are returned so that the connection should be disconnected. + // If it is a different namespace on the same cluster, the connection is automatically disconnected because the net.grpc health check fails. + if st != nil && st.Code() == codes.Unimplemented { + host, port, err := net.SplitHostPort(target) + if err != nil { + log.Warn(err) + } else { + if err := m.Disconnect(ctx, &payload.Mirror_Target{ + Host: host, + Port: uint32(port), + }); err != nil { + log.Errorf("failed to disconnect %s, err: %v", target, err) + } + } + } + } + log.Error("failed to send Register API to %s\t: %v", target, err) + if span != nil { + span.RecordError(err) + span.SetAttributes(attrs...) + span.SetStatus(trace.StatusError, err.Error()) + } + result.Store(target, err) + return err + } + if res != nil && len(res.GetTargets()) > 0 { + for _, tgt := range res.GetTargets() { + addr := net.JoinHostPort(tgt.Host, uint16(tgt.Port)) + mu.Lock() + if !exists[addr] { + exists[addr] = true + resTgts = append(resTgts, res.GetTargets()...) + } + mu.Unlock() + } + } + return nil + }) + result.Range(func(target string, rerr error) bool { + if rerr != nil { + err = errors.Join(err, errors.Wrapf(rerr, "failed to "+vald.RegisterRPCName+" API to %s", target)) + } + return true + }) + if err != nil { + if errors.Is(err, errors.ErrGRPCClientConnNotFound("*")) { + err = status.WrapWithInternal( + vald.RegisterRPCName+" API connection not found", err, reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.StatusCodeInternal(err.Error())...) + span.SetStatus(trace.StatusError, err.Error()) + } + return nil, err + } + + st, msg, err := status.ParseError(err, codes.Internal, + "failed to parse "+vald.RegisterRPCName+" gRPC error response", reqInfo, resInfo, + ) + log.Warn(err) + if span != nil { + span.RecordError(err) + span.SetAttributes(trace.FromGRPCStatus(st.Code(), msg)...) + span.SetStatus(trace.StatusError, err.Error()) + } + return resTgts, err + } + return resTgts, err +} + +// Connect establishes gRPC connections to the specified Mirror targets, excluding this gateway and the Vald gateway (LB gateway). +func (m *mirr) Connect(ctx context.Context, targets ...*payload.Mirror_Target) error { + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Mirror.Connect") + defer func() { + if span != nil { + span.End() + } + }() + if len(targets) == 0 { + return errors.ErrTargetNotFound + } + for _, target := range targets { + addr := net.JoinHostPort(target.GetHost(), uint16(target.GetPort())) // addr: host:port + if !m.isSelfMirrorAddr(addr) && !m.isGatewayAddr(addr) { + _, ok := m.addrl.Load(addr) + if !ok || !m.IsConnected(ctx, addr) { + _, err := m.gateway.GRPCClient().Connect(ctx, addr) + if err != nil { + m.addrl.Delete(addr) + return err + } + } + m.addrl.Store(addr, struct{}{}) + } + } + return nil +} + +// Disconnect terminates gRPC connections to the specified Mirror targets. +func (m *mirr) Disconnect(ctx context.Context, targets ...*payload.Mirror_Target) error { + ctx, span := trace.StartSpan(ctx, "vald/gateway/mirror/service/Mirror.Disconnect") + defer func() { + if span != nil { + span.End() + } + }() + if len(targets) == 0 { + return errors.ErrTargetNotFound + } + for _, target := range targets { + addr := net.JoinHostPort(target.GetHost(), uint16(target.GetPort())) + if !m.isGatewayAddr(addr) { + _, ok := m.addrl.Load(addr) + if ok || m.IsConnected(ctx, addr) { + if err := m.gateway.GRPCClient().Disconnect(ctx, addr); err != nil && + !errors.Is(err, errors.ErrGRPCClientConnNotFound(addr)) { + return err + } + m.addrl.Delete(addr) + } + } + } + return nil +} + +// IsConnected checks if the gRPC connection to the given address is connected. +func (m *mirr) IsConnected(ctx context.Context, addr string) bool { + return m.gateway.GRPCClient().IsConnected(ctx, addr) +} + +// MirrorTargets returns the Mirror targets, including the address of this gateway and the addresses of other Mirror gateways +// to which this gateway is currently connected. +func (m *mirr) MirrorTargets(ctx context.Context) (tgts []*payload.Mirror_Target, err error) { + tgts = make([]*payload.Mirror_Target, 0, m.addrl.Len()) + m.RangeMirrorAddr(func(addr string, _ any) bool { + if m.IsConnected(ctx, addr) { + var ( + host string + port uint16 + ) + host, port, err = net.SplitHostPort(addr) + if err != nil { + return false + } + tgts = append(tgts, &payload.Mirror_Target{ + Host: host, + Port: uint32(port), + }) + } + return true + }) + if err != nil { + return nil, err + } + return append(tgts, m.selfMirrTgts...), nil +} + +func (m *mirr) isSelfMirrorAddr(addr string) bool { + _, ok := m.selfMirrAddrl.Load(addr) + return ok +} + +func (m *mirr) isGatewayAddr(addr string) bool { + _, ok := m.gwAddrl.Load(addr) + return ok +} + +// connectedOtherMirrorAddrs returns the addresses of other Mirror gateways to which this gateway is currently connected. +func (m *mirr) connectedOtherMirrorAddrs(ctx context.Context) (addrs []string) { + m.RangeMirrorAddr(func(addr string, _ any) bool { + if m.IsConnected(ctx, addr) { + addrs = append(addrs, addr) + } + return true + }) + return addrs +} + +// RangeMirrorAddr calls f sequentially for each key and value present in the connection map. If f returns false, range stops the iteration. +func (m *mirr) RangeMirrorAddr(f func(addr string, _ any) bool) { + m.addrl.Range(func(addr string, value any) bool { + if !m.isGatewayAddr(addr) && !m.isSelfMirrorAddr(addr) { + if !f(addr, value) { + return false + } + } + return true + }) +} diff --git a/pkg/gateway/mirror/service/mirror_mock_test.go b/pkg/gateway/mirror/service/mirror_mock_test.go new file mode 100644 index 0000000000..56d828b305 --- /dev/null +++ b/pkg/gateway/mirror/service/mirror_mock_test.go @@ -0,0 +1,49 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/payload" +) + +// MirrorMock represents mock struct for Gateway. +type MirrorMock struct { + Mirror + ConnectFunc func(ctx context.Context, targets ...*payload.Mirror_Target) error + DisconnectFunc func(ctx context.Context, targets ...*payload.Mirror_Target) error + IsConnectedFunc func(ctx context.Context, addr string) bool + RangeMirrorAddrFunc func(f func(addr string, _ any) bool) +} + +// Connect calls ConnectFunc object. +func (mm *MirrorMock) Connect(ctx context.Context, targets ...*payload.Mirror_Target) error { + return mm.ConnectFunc(ctx, targets...) +} + +// Disconnect calls DisconnectFunc object. +func (mm *MirrorMock) Disconnect(ctx context.Context, targets ...*payload.Mirror_Target) error { + return mm.DisconnectFunc(ctx, targets...) +} + +// IsConnected calls IsConnectedFunc object. +func (mm *MirrorMock) IsConnected(ctx context.Context, addr string) bool { + return mm.IsConnectedFunc(ctx, addr) +} + +// RangeMirrorAddr calls RangeMirrorAddrFunc object. +func (mm *MirrorMock) RangeMirrorAddr(f func(addr string, _ any) bool) { + mm.RangeMirrorAddrFunc(f) +} diff --git a/pkg/gateway/mirror/service/mirror_option.go b/pkg/gateway/mirror/service/mirror_option.go new file mode 100644 index 0000000000..1b7243c382 --- /dev/null +++ b/pkg/gateway/mirror/service/mirror_option.go @@ -0,0 +1,89 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "time" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/sync/errgroup" +) + +// MirrorOption represents the functional option for mirror. +type MirrorOption func(m *mirr) error + +var defaultMirrOpts = []MirrorOption{ + WithRegisterDuration("500ms"), +} + +// WithErrorGroup returns the option to set the error group. +func WithErrorGroup(eg errgroup.Group) MirrorOption { + return func(m *mirr) error { + if eg != nil { + m.eg = eg + } + return nil + } +} + +// WithGatewayAddrs returns the option to set the gateway addresses. +func WithGatewayAddrs(addrs ...string) MirrorOption { + return func(m *mirr) error { + if len(addrs) == 0 { + return errors.NewErrCriticalOption("lbAddrs", addrs) + } + for _, addr := range addrs { + m.gwAddrl.Store(addr, struct{}{}) + } + return nil + } +} + +// WithSelfMirrorAddrs returns the option to set the self Mirror gateway addresses. +func WithSelfMirrorAddrs(addrs ...string) MirrorOption { + return func(m *mirr) error { + if len(addrs) == 0 { + return errors.NewErrCriticalOption("selfMirrorAddrs", addrs) + } + for _, addr := range addrs { + m.selfMirrAddrl.Store(addr, struct{}{}) + } + return nil + } +} + +// WithGateway returns the option to set the Gateway service. +func WithGateway(g Gateway) MirrorOption { + return func(m *mirr) error { + if g != nil { + m.gateway = g + } + return nil + } +} + +// WithRegisterDuration returns the option to set the register duration. +func WithRegisterDuration(s string) MirrorOption { + return func(m *mirr) error { + if s == "" { + return errors.NewErrInvalidOption("registerDuration", s) + } + dur, err := time.ParseDuration(s) + if err != nil { + return errors.NewErrInvalidOption("registerDuration", s, err) + } + m.registerDur = dur + return nil + } +} diff --git a/pkg/gateway/mirror/service/mirror_test.go b/pkg/gateway/mirror/service/mirror_test.go new file mode 100644 index 0000000000..598a3b1841 --- /dev/null +++ b/pkg/gateway/mirror/service/mirror_test.go @@ -0,0 +1,1139 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "context" + "reflect" + "sort" + "testing" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/net/grpc/pool" + "github.com/vdaas/vald/internal/test/goleak" + grpcmock "github.com/vdaas/vald/internal/test/mock/grpc" +) + +func Test_mirr_Connect(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + targets []*payload.Mirror_Target + } + type fields struct { + gatewayAddr string + selfMirrAddr string + gateway Gateway + } + type want struct { + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + } + tests := []test{ + func() test { + gatewayAddr := "192.168.1.2:8081" + selfMirrorAddr := "192.168.1.3:8081" + return test{ + name: "Succeeded to connect to other mirror gateways", + args: args{ + ctx: context.Background(), + targets: []*payload.Mirror_Target{ + { + Host: "192.168.2.2", + Port: 8081, + }, + { + Host: "192.168.3.2", + Port: 8081, + }, + }, + }, + fields: fields{ + selfMirrAddr: selfMirrorAddr, + gatewayAddr: gatewayAddr, + gateway: &GatewayMock{ + GRPCClientFunc: func() grpc.Client { + return &grpcmock.GRPCClientMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return false + }, + ConnectFunc: func(_ context.Context, _ string, _ ...grpc.DialOption) (conn pool.Conn, err error) { + return conn, err + }, + } + }, + }, + }, + } + }(), + func() test { + gatewayAddr := "192.168.1.2:8081" + selfMirrorAddr := "192.168.1.3:8081" + return test{ + name: "Failed to connect to other mirror gateways due to an invalid address", + args: args{ + ctx: context.Background(), + targets: []*payload.Mirror_Target{ + { + Host: "192.168.2.2", + }, + }, + }, + fields: fields{ + selfMirrAddr: selfMirrorAddr, + gatewayAddr: gatewayAddr, + gateway: &GatewayMock{ + GRPCClientFunc: func() grpc.Client { + return &grpcmock.GRPCClientMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return false + }, + ConnectFunc: func(_ context.Context, _ string, _ ...grpc.DialOption) (pool.Conn, error) { + return nil, errors.New("missing port in address") + }, + } + }, + }, + }, + want: want{ + err: errors.New("missing port in address"), + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + m, err := NewMirror( + WithSelfMirrorAddrs(test.fields.selfMirrAddr), + WithGatewayAddrs(test.fields.gatewayAddr), + WithGateway(test.fields.gateway), + ) + if err != nil { + t.Fatal(err) + } + + err = m.Connect(test.args.ctx, test.args.targets...) + if err := checkFunc(test.want, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_mirr_Disconnect(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + targets []*payload.Mirror_Target + } + type fields struct { + gatewayAddr string + selfMirrAddr string + gateway Gateway + } + type want struct { + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + } + tests := []test{ + func() test { + gatewayAddr := "192.168.1.2:8081" + selfMirrorAddr := "192.168.1.3:8081" + return test{ + name: "Succeeded to disconnect to other mirror gateways", + args: args{ + ctx: context.Background(), + targets: []*payload.Mirror_Target{ + { + Host: "192.168.2.2", + Port: 8081, + }, + { + Host: "192.168.3.2", + Port: 8081, + }, + }, + }, + fields: fields{ + selfMirrAddr: selfMirrorAddr, + gatewayAddr: gatewayAddr, + gateway: &GatewayMock{ + GRPCClientFunc: func() grpc.Client { + return &grpcmock.GRPCClientMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return true + }, + DisconnectFunc: func(_ context.Context, _ string) error { + return nil + }, + } + }, + }, + }, + } + }(), + func() test { + gatewayAddr := "192.168.1.2:8081" + selfMirrorAddr := "192.168.1.3:8081" + return test{ + name: "Failed to connect to other mirror gateways due to an invalid address", + args: args{ + ctx: context.Background(), + targets: []*payload.Mirror_Target{ + { + Host: "192.168.2.2", + }, + }, + }, + fields: fields{ + selfMirrAddr: selfMirrorAddr, + gatewayAddr: gatewayAddr, + gateway: &GatewayMock{ + GRPCClientFunc: func() grpc.Client { + return &grpcmock.GRPCClientMock{ + IsConnectedFunc: func(_ context.Context, _ string) bool { + return true + }, + DisconnectFunc: func(_ context.Context, _ string) error { + return errors.New("missing port in address") + }, + } + }, + }, + }, + want: want{ + err: errors.New("missing port in address"), + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + m, err := NewMirror( + WithSelfMirrorAddrs(test.fields.selfMirrAddr), + WithGatewayAddrs(test.fields.gatewayAddr), + WithGateway(test.fields.gateway), + ) + if err != nil { + t.Fatal(err) + } + + err = m.Disconnect(test.args.ctx, test.args.targets...) + if err := checkFunc(test.want, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_mirr_MirrorTargets(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + } + type fields struct { + gatewayAddr string + selfMirrAddr string + gateway Gateway + } + type want struct { + want []*payload.Mirror_Target + err error + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, []*payload.Mirror_Target, error) error + beforeFunc func(*testing.T, Mirror) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, got []*payload.Mirror_Target, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + if len(got) != len(w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + sort.Slice(got, func(i, j int) bool { + return got[i].Host > got[j].Host + }) + sort.Slice(w.want, func(i, j int) bool { + return w.want[i].Host > w.want[j].Host + }) + if !reflect.DeepEqual(got, w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + return nil + } + tests := []test{ + func() test { + gatewayAddr := "192.168.1.2:8081" + selfMirrorAddr := "192.168.1.3:8081" + connectTargets := []*payload.Mirror_Target{ + { + Host: "192.168.1.2", // gateway addresses + Port: 8081, + }, + { + Host: "192.168.2.2", // other mirror address + Port: 8081, + }, + { + Host: "192.168.3.2", // other mirror address + Port: 8081, + }, + } + connected := make(map[string]bool) + return test{ + name: "returns only the addresses of the mirror gateways", + args: args{ + ctx: context.Background(), + }, + fields: fields{ + gatewayAddr: gatewayAddr, + selfMirrAddr: selfMirrorAddr, + gateway: &GatewayMock{ + GRPCClientFunc: func() grpc.Client { + return &grpcmock.GRPCClientMock{ + ConnectFunc: func(_ context.Context, addr string, _ ...grpc.DialOption) (conn pool.Conn, err error) { + connected[addr] = true + return conn, err + }, + IsConnectedFunc: func(_ context.Context, addr string) bool { + return connected[addr] + }, + } + }, + }, + }, + beforeFunc: func(t *testing.T, m Mirror) { + t.Helper() + if err := m.Connect(context.Background(), connectTargets...); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: []*payload.Mirror_Target{ + { + Host: "192.168.2.2", // other mirror address + Port: 8081, + }, + { + Host: "192.168.3.2", // other mirror address + Port: 8081, + }, + { + Host: "192.168.1.3", // self mirror address + Port: 8081, + }, + }, + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + m, err := NewMirror( + WithSelfMirrorAddrs(test.fields.selfMirrAddr), + WithGatewayAddrs(test.fields.gatewayAddr), + WithGateway(test.fields.gateway), + ) + if err != nil { + t.Fatal(err) + } + + if test.beforeFunc != nil { + test.beforeFunc(tt, m) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got, err := m.MirrorTargets(test.args.ctx) + if err := checkFunc(test.want, got, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_mirr_connectedOtherMirrorAddrs(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + } + type fields struct { + gatewayAddr string + selfMirrAddr string + gateway Gateway + } + type want struct { + want []string + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, []string) error + beforeFunc func(*testing.T, Mirror) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, got []string) error { + if !reflect.DeepEqual(got, w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + return nil + } + tests := []test{ + func() test { + gatewayAddr := "192.168.1.2:8081" + selfMirrorAddr := "192.168.1.3:8081" + connectTargets := []*payload.Mirror_Target{ + { + Host: "192.168.1.2", // gateway addresses + Port: 8081, + }, + { + Host: "192.168.2.2", // other mirror address + Port: 8081, + }, + } + connected := make(map[string]bool) + return test{ + name: "returns only the address of the other mirror gateway", + args: args{ + ctx: context.Background(), + }, + fields: fields{ + selfMirrAddr: selfMirrorAddr, + gatewayAddr: gatewayAddr, + gateway: &GatewayMock{ + GRPCClientFunc: func() grpc.Client { + return &grpcmock.GRPCClientMock{ + ConnectFunc: func(_ context.Context, addr string, _ ...grpc.DialOption) (conn pool.Conn, err error) { + connected[addr] = true + return conn, err + }, + IsConnectedFunc: func(_ context.Context, addr string) bool { + return connected[addr] + }, + } + }, + }, + }, + beforeFunc: func(t *testing.T, m Mirror) { + t.Helper() + if err := m.Connect(context.Background(), connectTargets...); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: []string{ + "192.168.2.2:8081", + }, + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + m, err := NewMirror( + WithSelfMirrorAddrs(test.fields.selfMirrAddr), + WithGatewayAddrs(test.fields.gatewayAddr), + WithGateway(test.fields.gateway), + ) + if err != nil { + t.Fatal(err) + } + + if test.beforeFunc != nil { + test.beforeFunc(tt, m) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + if mirr, ok := m.(*mirr); ok { + got := mirr.connectedOtherMirrorAddrs(test.args.ctx) + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + } + }) + } +} + +// NOT IMPLEMENTED BELOW +// +// func TestNewMirror(t *testing.T) { +// type args struct { +// opts []MirrorOption +// } +// type want struct { +// want Mirror +// err error +// } +// type test struct { +// name string +// args args +// want want +// checkFunc func(want, Mirror, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got Mirror, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// if !reflect.DeepEqual(got, w.want) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// opts:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// opts:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// +// got, err := NewMirror(test.args.opts...) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// +// func Test_mirr_Start(t *testing.T) { +// type args struct { +// ctx context.Context +// } +// type fields struct { +// addrl sync.Map[string, any] +// selfMirrTgts []*payload.Mirror_Target +// selfMirrAddrl sync.Map[string, any] +// gwAddrl sync.Map[string, any] +// eg errgroup.Group +// registerDur time.Duration +// gateway Gateway +// } +// type want struct { +// want <-chan error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, <-chan error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got <-chan error) error { +// if !reflect.DeepEqual(got, w.want) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// m := &mirr{ +// addrl: test.fields.addrl, +// selfMirrTgts: test.fields.selfMirrTgts, +// selfMirrAddrl: test.fields.selfMirrAddrl, +// gwAddrl: test.fields.gwAddrl, +// eg: test.fields.eg, +// registerDur: test.fields.registerDur, +// gateway: test.fields.gateway, +// } +// +// got := m.Start(test.args.ctx) +// if err := checkFunc(test.want, got); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// +// func Test_mirr_Disconnect(t *testing.T) { +// type args struct { +// ctx context.Context +// targets []*payload.Mirror_Target +// } +// type fields struct { +// addrl sync.Map[string, any] +// selfMirrTgts []*payload.Mirror_Target +// selfMirrAddrl sync.Map[string, any] +// gwAddrl sync.Map[string, any] +// eg errgroup.Group +// registerDur time.Duration +// gateway Gateway +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// targets:nil, +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// targets:nil, +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// m := &mirr{ +// addrl: test.fields.addrl, +// selfMirrTgts: test.fields.selfMirrTgts, +// selfMirrAddrl: test.fields.selfMirrAddrl, +// gwAddrl: test.fields.gwAddrl, +// eg: test.fields.eg, +// registerDur: test.fields.registerDur, +// gateway: test.fields.gateway, +// } +// +// err := m.Disconnect(test.args.ctx, test.args.targets...) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// +// func Test_mirr_IsConnected(t *testing.T) { +// type args struct { +// ctx context.Context +// addr string +// } +// type fields struct { +// addrl sync.Map[string, any] +// selfMirrTgts []*payload.Mirror_Target +// selfMirrAddrl sync.Map[string, any] +// gwAddrl sync.Map[string, any] +// eg errgroup.Group +// registerDur time.Duration +// gateway Gateway +// } +// type want struct { +// want bool +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, bool) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got bool) error { +// if !reflect.DeepEqual(got, w.want) { +// return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// addr:"", +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// ctx:nil, +// addr:"", +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// m := &mirr{ +// addrl: test.fields.addrl, +// selfMirrTgts: test.fields.selfMirrTgts, +// selfMirrAddrl: test.fields.selfMirrAddrl, +// gwAddrl: test.fields.gwAddrl, +// eg: test.fields.eg, +// registerDur: test.fields.registerDur, +// gateway: test.fields.gateway, +// } +// +// got := m.IsConnected(test.args.ctx, test.args.addr) +// if err := checkFunc(test.want, got); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// +// func Test_mirr_RangeAllMirrorAddr(t *testing.T) { +// type args struct { +// f func(addr string, _ any) bool +// } +// type fields struct { +// addrl sync.Map[string, any] +// selfMirrTgts []*payload.Mirror_Target +// selfMirrAddrl sync.Map[string, any] +// gwAddrl sync.Map[string, any] +// eg errgroup.Group +// registerDur time.Duration +// gateway Gateway +// } +// type want struct { +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want) error { +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// f:nil, +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// f:nil, +// }, +// fields: fields { +// addrl:nil, +// selfMirrTgts:nil, +// selfMirrAddrl:nil, +// gwAddrl:nil, +// eg:nil, +// registerDur:nil, +// gateway:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// m := &mirr{ +// addrl: test.fields.addrl, +// selfMirrTgts: test.fields.selfMirrTgts, +// selfMirrAddrl: test.fields.selfMirrAddrl, +// gwAddrl: test.fields.gwAddrl, +// eg: test.fields.eg, +// registerDur: test.fields.registerDur, +// gateway: test.fields.gateway, +// } +// +// m.RangeAllMirrorAddr(test.args.f) +// if err := checkFunc(test.want); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } diff --git a/pkg/gateway/mirror/service/option.go b/pkg/gateway/mirror/service/option.go new file mode 100644 index 0000000000..6408cf9fcd --- /dev/null +++ b/pkg/gateway/mirror/service/option.go @@ -0,0 +1,58 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 service + +import ( + "github.com/vdaas/vald/internal/client/v1/client/mirror" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/sync/errgroup" +) + +// Option represents the functional option for gateway. +type Option func(g *gateway) error + +var defaultGatewayOpts = []Option{ + WithErrGroup(errgroup.Get()), +} + +// WithMirrorClient returns the option to set the Mirror client. +func WithMirrorClient(c mirror.Client) Option { + return func(g *gateway) error { + if c != nil { + g.client = c + } + return nil + } +} + +// WithErrGroup returns the option to set the error group. +func WithErrGroup(eg errgroup.Group) Option { + return func(g *gateway) error { + if eg != nil { + g.eg = eg + } + return nil + } +} + +// WithPodName returns the option to set the pod name. +func WithPodName(s string) Option { + return func(g *gateway) error { + if s == "" { + return errors.NewErrCriticalOption("podName", s) + } + g.podName = s + return nil + } +} diff --git a/pkg/gateway/mirror/usecase/vald.go b/pkg/gateway/mirror/usecase/vald.go new file mode 100644 index 0000000000..7937ba4728 --- /dev/null +++ b/pkg/gateway/mirror/usecase/vald.go @@ -0,0 +1,267 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// 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 +// +// https://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 usecase + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/vald" + "github.com/vdaas/vald/internal/client/v1/client/mirror" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/net" + "github.com/vdaas/vald/internal/net/grpc" + "github.com/vdaas/vald/internal/observability" + bometrics "github.com/vdaas/vald/internal/observability/metrics/backoff" + cbmetrics "github.com/vdaas/vald/internal/observability/metrics/circuitbreaker" + mirrmetrics "github.com/vdaas/vald/internal/observability/metrics/gateway/mirror" + "github.com/vdaas/vald/internal/runner" + "github.com/vdaas/vald/internal/safety" + "github.com/vdaas/vald/internal/servers/server" + "github.com/vdaas/vald/internal/servers/starter" + "github.com/vdaas/vald/internal/sync/errgroup" + "github.com/vdaas/vald/pkg/gateway/mirror/config" + handler "github.com/vdaas/vald/pkg/gateway/mirror/handler/grpc" + "github.com/vdaas/vald/pkg/gateway/mirror/handler/rest" + "github.com/vdaas/vald/pkg/gateway/mirror/router" + "github.com/vdaas/vald/pkg/gateway/mirror/service" +) + +type run struct { + eg errgroup.Group + dialer net.Dialer + cfg *config.Data + server starter.Server + client mirror.Client + gateway service.Gateway + mirror service.Mirror + discover service.Discovery + observability observability.Observability +} + +// New returns Runner instance. +func New(cfg *config.Data) (r runner.Runner, err error) { + eg := errgroup.Get() + + netOpts, err := cfg.Mirror.Net.Opts() + if err != nil { + return nil, err + } + dialer, err := net.NewDialer(netOpts...) + if err != nil { + return nil, err + } + + cOpts, err := cfg.Mirror.Client.Opts() + if err != nil { + return nil, err + } + // skipcq: CRT-D0001 + cOpts = append(cOpts, grpc.WithErrGroup(eg)) + + client, err := mirror.New( + mirror.WithAddrs(cfg.Mirror.Client.Addrs...), + mirror.WithClient(grpc.New(cOpts...)), + ) + if err != nil { + return nil, err + } + + gateway, err := service.NewGateway( + service.WithErrGroup(eg), + service.WithMirrorClient(client), + service.WithPodName(cfg.Mirror.PodName), + ) + if err != nil { + return nil, err + } + mirror, err := service.NewMirror( + service.WithErrorGroup(eg), + service.WithRegisterDuration(cfg.Mirror.RegisterDuration), + service.WithGatewayAddrs(cfg.Mirror.GatewayAddr), + service.WithSelfMirrorAddrs(cfg.Mirror.SelfMirrorAddr), + service.WithGateway(gateway), + ) + if err != nil { + return nil, err + } + discover, err := service.NewDiscovery( + service.WithDiscoveryNamespace(cfg.Mirror.Namespace), + service.WithDiscoveryGroup(cfg.Mirror.Group), + service.WithDiscoveryDuration(cfg.Mirror.DiscoveryDuration), + service.WithDiscoverySelfMirrorAddrs(cfg.Mirror.SelfMirrorAddr), + service.WithDiscoveryColocation(cfg.Mirror.Colocation), + service.WithDiscoveryDialer(dialer), + service.WithDiscoveryMirror(mirror), + service.WithDiscoveryErrGroup(eg), + ) + if err != nil { + return nil, err + } + + v, err := handler.New( + handler.WithValdAddr(cfg.Mirror.GatewayAddr), + handler.WithErrGroup(eg), + handler.WithGateway(gateway), + handler.WithMirror(mirror), + handler.WithStreamConcurrency(cfg.Server.GetGRPCStreamConcurrency()), + ) + if err != nil { + return nil, err + } + + grpcServerOptions := []server.Option{ + server.WithGRPCRegistFunc(func(srv *grpc.Server) { + vald.RegisterValdServerWithMirror(srv, v) + }), + server.WithPreStopFunction(func() error { + return nil + }), + } + + var obs observability.Observability + if cfg.Observability.Enabled { + obs, err = observability.NewWithConfig( + cfg.Observability, + bometrics.New(), + cbmetrics.New(), + mirrmetrics.New(mirror), + ) + if err != nil { + return nil, err + } + } + + srv, err := starter.New( + starter.WithConfig(cfg.Server), + starter.WithREST(func(sc *config.Server) []server.Option { + return []server.Option{ + server.WithHTTPHandler( + router.New( + router.WithHandler( + rest.New( + rest.WithVald(v), + ), + ), + ), + ), + } + }), + starter.WithGRPC(func(sc *config.Server) []server.Option { + return grpcServerOptions + }), + ) + if err != nil { + return nil, err + } + + return &run{ + eg: eg, + dialer: dialer, + cfg: cfg, + server: srv, + client: client, + gateway: gateway, + mirror: mirror, + discover: discover, + observability: obs, + }, nil +} + +// PreStart is a method called before execution of Start. +func (r *run) PreStart(ctx context.Context) error { + if r.dialer != nil { + r.dialer.StartDialerCache(ctx) + } + if r.observability != nil { + return r.observability.PreStart(ctx) + } + return nil +} + +// Start is a method used to initiate an operation in the run, and it returns a channel for receiving errors +// during the operation and an error representing any initialization errors. +func (r *run) Start(ctx context.Context) (_ <-chan error, err error) { // skipcq: GO-R1005 + ech := make(chan error, 6) + var mech, dech, cech, sech, oech <-chan error + + sech = r.server.ListenAndServe(ctx) + if r.client != nil { + cech, err = r.client.Start(ctx) + if err != nil { + close(ech) + return nil, err + } + } + if r.mirror != nil { + mech = r.mirror.Start(ctx) + } + if r.discover != nil { + dech, err = r.discover.Start(ctx) + if err != nil { + close(ech) + return nil, err + } + } + if r.observability != nil { + oech = r.observability.Start(ctx) + } + + r.eg.Go(safety.RecoverFunc(func() (err error) { + defer close(ech) + for { + select { + case <-ctx.Done(): + return ctx.Err() + case err = <-mech: + case err = <-dech: + case err = <-cech: + case err = <-sech: + case err = <-oech: + } + if err != nil { + select { + case <-ctx.Done(): + return errors.Join(ctx.Err(), err) + case ech <- err: + } + } + } + })) + return ech, nil +} + +// PreStop is a method called before execution of Stop. +func (*run) PreStop(_ context.Context) error { + return nil +} + +// Stop is a method used to stop an operation in the run. +func (r *run) Stop(ctx context.Context) (errs error) { + if r.observability != nil { + if err := r.observability.Stop(ctx); err != nil { + errs = errors.Join(errs, err) + } + } + if r.server != nil { + if err := r.server.Shutdown(ctx); err != nil { + errs = errors.Join(errs, err) + } + } + return errs +} + +// PtopStop is a method called after execution of Stop. +func (*run) PostStop(_ context.Context) error { + return nil +}