diff --git a/.github/workflows/dockers-benchmark-job-image.yml b/.github/workflows/dockers-benchmark-job-image.yml new file mode 100644 index 00000000000..b45c698aabd --- /dev/null +++ b/.github/workflows/dockers-benchmark-job-image.yml @@ -0,0 +1,129 @@ +# +# Copyright (C) 2019-2022 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: benchmark-job" +on: + push: + branches: + - master + tags: + - "*.*.*" + - "v*.*.*" + - "*.*.*-*" + - "v*.*.*-*" + paths: + - ".github/actions/docker-build/actions.yaml" + - ".github/workflows/dockers-benchmak-job-image.yml" + - "go.mod" + - "go.sum" + - "internal/**" + - "!internal/**/*_test.go" + - "!internal/db/**" + - "!internal/k8s/**" + - "apis/grpc/**" + - "pkg/benchmark/job/**" + - "cmd/benchmark/job/**" + - "dockers/benchmark/job/Dockerfile" + - "versions/GO_VERSION" + - "versions/NGT_VERSION" + pull_request: + paths: + - ".github/actions/docker-build/actions.yaml" + - ".github/workflows/dockers-benchmak-job-image.yml" + - "go.mod" + - "go.sum" + - "internal/**" + - "!internal/**/*_test.go" + - "!internal/db/**" + - "!internal/k8s/**" + - "apis/grpc/**" + - "pkg/benchmark/job/**" + - "cmd/benchmark/job/**" + - "dockers/benchmark/job/Dockerfile" + - "versions/GO_VERSION" + - "versions/NGT_VERSION" + +jobs: + build: + strategy: + max-parallel: 4 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + - name: Setup Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + with: + buildkitd-flags: "--debug" + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PASS }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ secrets.PACKAGE_USER }} + password: ${{ secrets.PACKAGE_TOKEN }} + - name: Build and Publish + id: build_and_publish + uses: ./.github/actions/docker-build + with: + target: benchmark-job + builder: ${{ steps.buildx.outputs.name }} + - name: Initialize CodeQL + if: startsWith( github.ref, 'refs/tags/') + uses: github/codeql-action/init@v2 + - name: Run vulnerability scanner (table) + if: startsWith( github.ref, 'refs/tags/') + uses: aquasecurity/trivy-action@master + with: + image-ref: "${{ steps.build_and_publish.outputs.IMAGE_NAME }}:${{ steps.build_and_publish.outputs.PRIMARY_TAG }}" + format: "table" + - name: Run vulnerability scanner (sarif) + if: startsWith( github.ref, 'refs/tags/') + uses: aquasecurity/trivy-action@master + with: + image-ref: "${{ steps.build_and_publish.outputs.IMAGE_NAME }}:${{ steps.build_and_publish.outputs.PRIMARY_TAG }}" + format: "template" + template: "@/contrib/sarif.tpl" + output: "trivy-results.sarif" + - name: Upload Trivy scan results to Security tab + if: startsWith( github.ref, 'refs/tags/') + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results.sarif" + slack: + name: Slack notification + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' || startsWith( github.ref, 'refs/tags/') + steps: + - uses: technote-space/workflow-conclusion-action@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: 8398a7/action-slack@v3 + with: + author_name: benchmark-job image build + status: ${{ env.WORKFLOW_CONCLUSION }} + only_mention_fail: channel + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_NOTIFY_WEBHOOK_URL }} diff --git a/Makefile b/Makefile index 7319d6147e3..7a0d172c1e0 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,8 @@ INDEX_CREATION_IMAGE = $(NAME)-index-creation INDEX_SAVE_IMAGE = $(NAME)-index-save READREPLICA_ROTATE_IMAGE = $(NAME)-readreplica-rotate MANAGER_INDEX_IMAGE = $(NAME)-manager-index +BENCHMARK_JOB_IMAGE = $(NAME)-benchmark-job +BENCHMARK_OPERATOR_IMAGE = $(NAME)-benchmark-operator MAINTAINER = "$(ORG).org $(NAME) team <$(NAME)@$(ORG).org>" VERSION ?= $(eval VERSION := $(shell cat versions/VALD_VERSION))$(VERSION) diff --git a/Makefile.d/build.mk b/Makefile.d/build.mk index 0911abaf4fb..2427143c1bc 100644 --- a/Makefile.d/build.mk +++ b/Makefile.d/build.mk @@ -22,7 +22,9 @@ binary/build: \ cmd/discoverer/k8s/discoverer \ cmd/gateway/lb/lb \ cmd/gateway/filter/filter \ - cmd/manager/index/index + cmd/manager/index/index \ + cmd/tools/benchmark/job/job \ + cmd/tools/benchmark/operator/operator cmd/agent/core/ngt/ngt: \ ngt/install \ @@ -322,6 +324,68 @@ cmd/index/job/readreplica/rotate/readreplica-rotate: \ $(dir $@)main.go $@ -version +cmd/tools/benchmark/job/job: \ + $(GO_SOURCES_INTERNAL) \ + $(PBGOS) \ + $(shell find ./cmd/tools/benchmark/job -type f -name '*.go' -not -name '*_test.go' -not -name 'doc.go') \ + $(shell find ./pkg/tools/benchmark/job -type f -name '*.go' -not -name '*_test.go' -not -name 'doc.go') + CFLAGS="$(CFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" \ + CGO_ENABLED=1 \ + CGO_CXXFLAGS="-g -Ofast -march=native" \ + CGO_FFLAGS="-g -Ofast -march=native" \ + CGO_LDFLAGS="-g -Ofast -march=native" \ + GO111MODULE=on \ + GOPRIVATE=$(GOPRIVATE) \ + go build \ + --ldflags "-s -w \ + -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.NGTVersion=$(NGT_VERSION)' \ + -X '$(GOPKG)/internal/info.BuildCPUInfoFlags=$(CPU_INFO_FLAGS)' \ + -buildid=" \ + -mod=readonly \ + -modcacherw \ + -a \ + -tags "cgo osusergo netgo" \ + -trimpath \ + -o $@ \ + $(dir $@)main.go + $@ -version + +cmd/tools/benchmark/operator/operator: \ + $(GO_SOURCES_INTERNAL) \ + $(PBGOS) \ + $(shell find ./cmd/tools/benchmark/operator -type f -name '*.go' -not -name '*_test.go' -not -name 'doc.go') \ + $(shell find ./pkg/tools/benchmark/operator -type f -name '*.go' -not -name '*_test.go' -not -name 'doc.go') + CFLAGS="$(CFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" \ + CGO_ENABLED=1 \ + CGO_CXXFLAGS="-g -Ofast -march=native" \ + CGO_FFLAGS="-g -Ofast -march=native" \ + CGO_LDFLAGS="-g -Ofast -march=native" \ + GO111MODULE=on \ + GOPRIVATE=$(GOPRIVATE) \ + go build \ + --ldflags "-s -w \ + -X '$(GOPKG)/internal/info.CGOEnabled=$${CGO_ENABLED}' \ + -X '$(GOPKG)/internal/info.NGTVersion=$(NGT_VERSION)' \ + -X '$(GOPKG)/internal/info.BuildCPUInfoFlags=$(CPU_INFO_FLAGS)' \ + -buildid=" \ + -mod=readonly \ + -modcacherw \ + -a \ + -tags "cgo osusergo netgo" \ + -trimpath \ + -o $@ \ + $(dir $@)main.go + $@ -version + .PHONY: binary/build/zip ## build all binaries and zip them binary/build/zip: \ @@ -356,3 +420,10 @@ artifacts/vald-manager-index-$(GOOS)-$(GOARCH).zip: cmd/manager/index/index $(call mkdir, $(dir $@)) zip --junk-paths $@ $< +artifacts/vald-benchmark-job-$(GOOS)-$(GOARCH).zip: cmd/tools/benchmark/job/job + $(call mkdir, $(dir $@)) + zip --junk-paths $@ $< + +artifacts/vald-benchmark-operator-$(GOOS)-$(GOARCH).zip: cmd/tools/benchmark/operator/operator + $(call mkdir, $(dir $@)) + zip --junk-paths $@ $< diff --git a/Makefile.d/docker.mk b/Makefile.d/docker.mk index e9e924eb2fb..1146957195c 100644 --- a/Makefile.d/docker.mk +++ b/Makefile.d/docker.mk @@ -22,7 +22,9 @@ docker/build: \ docker/build/gateway-lb \ docker/build/gateway-filter \ docker/build/manager-index \ - docker/build/operator/helm + docker/build/benchmark-job \ + docker/build/benchmark-operator \ + docker/build/helm-operator .PHONY: docker/name/org docker/name/org: @@ -226,3 +228,33 @@ docker/build/readreplica-rotate: @make DOCKERFILE="$(ROOTDIR)/dockers/index/job/readreplica/rotate/Dockerfile" \ IMAGE=$(READREPLICA_ROTATE_IMAGE) \ docker/build/image + +.PHONY: docker/name/benchmark-job +docker/name/benchmark-job: + @echo "$(ORG)/$(BENCHMARK_JOB_IMAGE)" + +.PHONY: docker/build/benchmark-job +## build benchmark job +docker/build/benchmark-job: + $(DOCKER) build \ + $(DOCKER_OPTS) \ + -f dockers/tools/benchmark/job/Dockerfile \ + -t $(ORG)/$(BENCHMARK_JOB_IMAGE):$(TAG) . \ + --build-arg GO_VERSION=$(GO_VERSION) \ + --build-arg DISTROLESS_IMAGE=$(DISTROLESS_IMAGE) \ + --build-arg DISTROLESS_IMAGE_TAG=$(DISTROLESS_IMAGE_TAG) + +.PHONY: docker/name/benchmark-operator +docker/name/benchmark-operator: + @echo "$(ORG)/$(BENCHMARK_OPERATOR_IMAGE)" + +.PHONY: docker/build/benchmark-operator +## build benchmark operator +docker/build/benchmark-operator: + $(DOCKER) build \ + $(DOCKER_OPTS) \ + -f dockers/tools/benchmark/operator/Dockerfile \ + -t $(ORG)/$(BENCHMARK_OPERATOR_IMAGE):$(TAG) . \ + --build-arg GO_VERSION=$(GO_VERSION) \ + --build-arg DISTROLESS_IMAGE=$(DISTROLESS_IMAGE) \ + --build-arg DISTROLESS_IMAGE_TAG=$(DISTROLESS_IMAGE_TAG) diff --git a/Makefile.d/helm.mk b/Makefile.d/helm.mk index 9d69b9063e2..3cea0c582bd 100644 --- a/Makefile.d/helm.mk +++ b/Makefile.d/helm.mk @@ -90,6 +90,17 @@ charts/vald-helm-operator/values.schema.json: \ GOPRIVATE=$(GOPRIVATE) \ go run -mod=readonly hack/helm/schema/gen/main.go charts/vald-helm-operator/values.yaml > charts/vald-helm-operator/values.schema.json +.PHONY: helm/schema/vald-benchmark-operator +## generate json schema for Vald Benchmark Operator Chart +helm/schema/vald-benchmark-operator: charts/vald-benchmark-operator/values.schema.json + +charts/vald-benchmark-operator/values.schema.json: \ + charts/vald-benchmark-operator/values.yaml \ + hack/helm/schema/gen/main.go + GOPRIVATE=$(GOPRIVATE) \ + go run -mod=readonly hack/helm/schema/gen/main.go charts/vald-benchmark-operator/values.yaml > charts/vald-benchmark-operator/values.schema.json + + .PHONY: yq/install ## install yq yq/install: $(BINDIR)/yq @@ -121,3 +132,14 @@ helm/schema/crd/vald-helm-operator: \ charts/vald-helm-operator/values.yaml > $(TEMP_DIR)/valdhelmoperatorrelease-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)/valdhelmoperatorrelease.yaml $(TEMP_DIR)/valdhelmoperatorrelease-spec.yaml > charts/vald-helm-operator/crds/valdhelmoperatorrelease.yaml + +.PHONY: helm/schema/crd/vald-benchmark-operator +## generate OpenAPI v3 schema for ValdBenchmarkOperatorRelease +helm/schema/crd/vald-benchmark-operator: \ + yq/install + mv charts/vald-benchmark-operator/crds/valdbenchmarkoperatorrelease.yaml $(TEMP_DIR)/valdbenchmarkoperatorrelease.yaml + GOPRIVATE=$(GOPRIVATE) \ + go run -mod=readonly hack/helm/schema/crd/main.go \ + charts/vald-benchmark-operator/values.yaml > $(TEMP_DIR)/valdbenchmarkoperatorrelease-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)/valdbenchmarkoperatorrelease.yaml $(TEMP_DIR)/valdbenchmarkoperatorrelease-spec.yaml > charts/vald-benchmark-operator/crds/valdbenchmarkoperatorrelease.yaml diff --git a/apis/docs/v1/docs.md b/apis/docs/v1/docs.md index 80245dd00c2..3923f8714fd 100644 --- a/apis/docs/v1/docs.md +++ b/apis/docs/v1/docs.md @@ -134,6 +134,25 @@ - [Update](#vald-v1-Update) - [v1/vald/upsert.proto](#v1_vald_upsert-proto) - [Upsert](#vald-v1-Upsert) +- [apis/proto/v1/rpc/error_details.proto](#apis_proto_v1_rpc_error_details-proto) + - [BadRequest](#rpc-v1-BadRequest) + - [BadRequest.FieldViolation](#rpc-v1-BadRequest-FieldViolation) + - [DebugInfo](#rpc-v1-DebugInfo) + - [ErrorInfo](#rpc-v1-ErrorInfo) + - [ErrorInfo.MetadataEntry](#rpc-v1-ErrorInfo-MetadataEntry) + - [Help](#rpc-v1-Help) + - [Help.Link](#rpc-v1-Help-Link) + - [LocalizedMessage](#rpc-v1-LocalizedMessage) + - [PreconditionFailure](#rpc-v1-PreconditionFailure) + - [PreconditionFailure.Violation](#rpc-v1-PreconditionFailure-Violation) + - [QuotaFailure](#rpc-v1-QuotaFailure) + - [QuotaFailure.Violation](#rpc-v1-QuotaFailure-Violation) + - [RequestInfo](#rpc-v1-RequestInfo) + - [ResourceInfo](#rpc-v1-ResourceInfo) + - [RetryInfo](#rpc-v1-RetryInfo) +- [apis/proto/v1/benchmark/benchmark.proto](#apis_proto_v1_benchmark_benchmark-proto) + - [Controller](#benchmark-v1-Controller) + - [Job](#benchmark-v1-Job) - [Scalar Value Types](#scalar-value-types) diff --git a/apis/grpc/v1/benchmark/benchmark.pb.go b/apis/grpc/v1/benchmark/benchmark.pb.go new file mode 100644 index 00000000000..62c2735b802 --- /dev/null +++ b/apis/grpc/v1/benchmark/benchmark.pb.go @@ -0,0 +1,87 @@ +// +// Copyright (C) 2019-2022 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.28.0 +// protoc v3.6.1 +// source: apis/proto/v1/benchmark/benchmark.proto + +package benchmark + +import ( + reflect "reflect" + + 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_apis_proto_v1_benchmark_benchmark_proto protoreflect.FileDescriptor + +var file_apis_proto_v1_benchmark_benchmark_proto_rawDesc = []byte{ + 0x0a, 0x27, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, + 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x2f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, + 0x61, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x62, 0x65, 0x6e, 0x63, 0x68, + 0x6d, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x32, 0x0c, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x32, 0x05, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x42, 0x5c, 0x0a, 0x1f, + 0x6f, 0x72, 0x67, 0x2e, 0x76, 0x64, 0x61, 0x61, 0x73, 0x2e, 0x76, 0x61, 0x6c, 0x64, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x42, + 0x09, 0x42, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x01, 0x5a, 0x2c, 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, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var file_apis_proto_v1_benchmark_benchmark_proto_goTypes = []interface{}{} +var file_apis_proto_v1_benchmark_benchmark_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] 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_apis_proto_v1_benchmark_benchmark_proto_init() } +func file_apis_proto_v1_benchmark_benchmark_proto_init() { + if File_apis_proto_v1_benchmark_benchmark_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_apis_proto_v1_benchmark_benchmark_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_apis_proto_v1_benchmark_benchmark_proto_goTypes, + DependencyIndexes: file_apis_proto_v1_benchmark_benchmark_proto_depIdxs, + }.Build() + File_apis_proto_v1_benchmark_benchmark_proto = out.File + file_apis_proto_v1_benchmark_benchmark_proto_rawDesc = nil + file_apis_proto_v1_benchmark_benchmark_proto_goTypes = nil + file_apis_proto_v1_benchmark_benchmark_proto_depIdxs = nil +} diff --git a/apis/grpc/v1/benchmark/benchmark_vtproto.pb.go b/apis/grpc/v1/benchmark/benchmark_vtproto.pb.go new file mode 100644 index 00000000000..450737e3544 --- /dev/null +++ b/apis/grpc/v1/benchmark/benchmark_vtproto.pb.go @@ -0,0 +1,132 @@ +// +// Copyright (C) 2019-2022 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 benchmark + +import ( + 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 + +// ControllerClient is the client API for Controller 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 ControllerClient interface { +} + +type controllerClient struct { + cc grpc.ClientConnInterface +} + +func NewControllerClient(cc grpc.ClientConnInterface) ControllerClient { + return &controllerClient{cc} +} + +// ControllerServer is the server API for Controller service. +// All implementations must embed UnimplementedControllerServer +// for forward compatibility +type ControllerServer interface { + mustEmbedUnimplementedControllerServer() +} + +// UnimplementedControllerServer must be embedded to have forward compatible implementations. +type UnimplementedControllerServer struct { +} + +func (UnimplementedControllerServer) mustEmbedUnimplementedControllerServer() {} + +// UnsafeControllerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ControllerServer will +// result in compilation errors. +type UnsafeControllerServer interface { + mustEmbedUnimplementedControllerServer() +} + +func RegisterControllerServer(s grpc.ServiceRegistrar, srv ControllerServer) { + s.RegisterService(&Controller_ServiceDesc, srv) +} + +// Controller_ServiceDesc is the grpc.ServiceDesc for Controller service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Controller_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "benchmark.v1.Controller", + HandlerType: (*ControllerServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "apis/proto/v1/benchmark/benchmark.proto", +} + +// JobClient is the client API for Job 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 JobClient interface { +} + +type jobClient struct { + cc grpc.ClientConnInterface +} + +func NewJobClient(cc grpc.ClientConnInterface) JobClient { + return &jobClient{cc} +} + +// JobServer is the server API for Job service. +// All implementations must embed UnimplementedJobServer +// for forward compatibility +type JobServer interface { + mustEmbedUnimplementedJobServer() +} + +// UnimplementedJobServer must be embedded to have forward compatible implementations. +type UnimplementedJobServer struct { +} + +func (UnimplementedJobServer) mustEmbedUnimplementedJobServer() {} + +// UnsafeJobServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to JobServer will +// result in compilation errors. +type UnsafeJobServer interface { + mustEmbedUnimplementedJobServer() +} + +func RegisterJobServer(s grpc.ServiceRegistrar, srv JobServer) { + s.RegisterService(&Job_ServiceDesc, srv) +} + +// Job_ServiceDesc is the grpc.ServiceDesc for Job service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Job_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "benchmark.v1.Job", + HandlerType: (*JobServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "apis/proto/v1/benchmark/benchmark.proto", +} diff --git a/apis/proto/v1/benchmark/benchmark.proto b/apis/proto/v1/benchmark/benchmark.proto new file mode 100644 index 00000000000..f798b27e050 --- /dev/null +++ b/apis/proto/v1/benchmark/benchmark.proto @@ -0,0 +1,32 @@ +// +// Copyright (C) 2019-2022 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 benchmark.v1; + +option go_package = "github.com/vdaas/vald/apis/grpc/v1/benchmark"; +option java_multiple_files = true; +option java_package = "org.vdaas.vald.api.v1.benchmark"; +option java_outer_classname = "Benchmark"; + +service Controller { + // TODO define API spec here +} + +service Job { + // TODO define API spec here +} diff --git a/apis/swagger/v1/benchmark/apis/proto/v1/benchmark/benchmark.swagger.json b/apis/swagger/v1/benchmark/apis/proto/v1/benchmark/benchmark.swagger.json new file mode 100644 index 00000000000..85c012ecacb --- /dev/null +++ b/apis/swagger/v1/benchmark/apis/proto/v1/benchmark/benchmark.swagger.json @@ -0,0 +1,45 @@ +{ + "swagger": "2.0", + "info": { + "title": "apis/proto/v1/benchmark/benchmark.proto", + "version": "version not set" + }, + "consumes": ["application/json"], + "produces": ["application/json"], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "runtimeError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/charts/vald-benchmark-operator/crds/valdbenchmarkoperatorrelease.yaml b/charts/vald-benchmark-operator/crds/valdbenchmarkoperatorrelease.yaml new file mode 100644 index 00000000000..ae88b72dc55 --- /dev/null +++ b/charts/vald-benchmark-operator/crds/valdbenchmarkoperatorrelease.yaml @@ -0,0 +1,225 @@ +# +# Copyright (C) 2019-2023 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: valdbenchmarkscenarios.vald.vdaas.org +spec: + group: vald.vdaas.org + names: + kind: ValdBenchmarkScenario + listKind: ValdBenchmarkScenarioList + plural: valdbenchmarkscenarios + singular: valdbenchmarkscenario + shortNames: + - vbo + - vbos + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status + name: STATUS + type: string + schema: + openAPIV3Schema: + description: ValdBenchmarkScenario is the Schema for the valdbenchmarkscenarios 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: ValdBenchmarkScenarioStatus defines the observed state of ValdBenchmarkScenario + enum: + - NotReady + - Available + - Healthy + type: string + spec: + type: object + properties: + client_config: + 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: "" + tls: + type: "" + 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: "" + wait_for_ready: + type: boolean + dataset: + type: object + properties: + group: + type: string + indexes: + type: integer + name: + type: string + range: + type: object + properties: + end: + type: integer + start: + type: integer + jobs: + type: array + items: + type: object + x-kubernetes-preserve-unknown-fields: true + target: + type: object + properties: + host: + type: string + port: + type: integer diff --git a/charts/vald-benchmark-operator/values.schema.json b/charts/vald-benchmark-operator/values.schema.json new file mode 100644 index 00000000000..0764934803e --- /dev/null +++ b/charts/vald-benchmark-operator/values.schema.json @@ -0,0 +1,263 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Values", + "type": "object", + "properties": { + "client_config": { + "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": "" }, + "tls": { "type": "" } + } + }, + "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": "" }, + "wait_for_ready": { "type": "boolean" } + } + }, + "dataset": { + "type": "object", + "description": "dataset information", + "properties": { + "group": { + "type": "string", + "description": "the hdf5 group name of dataset" + }, + "indexes": { + "type": "integer", + "description": "the amount of indexes" + }, + "name": { "type": "string", "description": "the name of dataset" }, + "range": { + "type": "object", + "description": "the data range of indexes", + "properties": { + "end": { "type": "integer", "description": "end index number" }, + "start": { "type": "integer", "description": "start index number" } + } + } + } + }, + "jobs": { + "type": "array", + "description": "benchmark jobs", + "items": { "type": "object" } + }, + "target": { + "type": "array", + "description": "target cluster host\u0026port", + "items": { "type": "object" } + } + } +} diff --git a/charts/vald-benchmark-operator/values.yaml b/charts/vald-benchmark-operator/values.yaml new file mode 100644 index 00000000000..064136e226b --- /dev/null +++ b/charts/vald-benchmark-operator/values.yaml @@ -0,0 +1,274 @@ +# +# Copyright (C) 2019-2023 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": "dataset", "type": "object"} +# dataset -- dataset information +dataset: + # @schema {"name": "dataset.name", "type": "string" } + # dataset.name -- the name of dataset + name: "fashion-mnist" + # @schema {"name": "dataset.indexes", "type": "integer"} + # dataset.indexes -- the amount of indexes + indexes: 10000 + # @schema {"name": "dataset.group", "type": "string"} + # dataset.group -- the hdf5 group name of dataset + group: "train" + # @schema {"name": "dataset.range", "type": "object"} + # dataset.range -- the data range of indexes + range: + # @schema {"name": "dataset.range.start", "type": "integer"} + # dataset.range.start -- start index number + start: 0 + # @schema {"name": "dataset.range.end", "type": "integer"} + # dataset.range.end -- end index number + end: 10000 + +# @schema {"name": "target", "type": "object"} +# target -- target cluster host&port +target: + # @schema {"name": "target.host", "type": "string"} + # target.host -- target cluster host + host: "vald-lb-gateway.vald.svc.cluster.local" + # @schema {"name": "target.port", "type": "integer"} + # target.port -- target cluster port + port: 8081 + +# @schema {"name": "jobs", "type": "array", "items": {"type": "object"}} +# jobs -- benchmark jobs +jobs: + # @schema {"name": "jobs.items.dataset", "type": "object"} + - dataset: + name: "fashion-mnist" + indexes: 10000 + group: "train" + range: + start: 0 + end: 10000 + repetition: 1 + replica: 1 + dimension: 784 + target: + host: "localhost" + port: "8081" + rules: [] + search_config: + epsilon: 0.1 + radius: -1 + num: 10 + min_num: 5 + timeout: "1m" + # @schema {"name": "client_config", "type": "object", "anchor": "client_config"} + client_config: + # @schema {"name": "client_config.addrs", "type": "array", "items": {"type": "string"}} + # client_config.addrs -- gRPC client addresses + addrs: [] + # @schema {"name": "client_config.health_check_duration", "type": "string"} + # client_config.health_check_duration -- gRPC client health check duration + health_check_duration: "1s" + # @schema {"name": "client_config.connection_pool", "type": "object"} + connection_pool: + # @schema {"name": "client_config.connection_pool.enable_dns_resolver", "type": "boolean"} + # client_config.connection_pool.enable_dns_resolver -- enables gRPC client connection pool dns resolver, when enabled vald uses ip handshake exclude dns discovery which improves network performance + enable_dns_resolver: true + # @schema {"name": "client_config.connection_pool.enable_rebalance", "type": "boolean"} + # client_config.connection_pool.enable_rebalance -- enables gRPC client connection pool rebalance + enable_rebalance: true + # @schema {"name": "client_config.connection_pool.rebalance_duration", "type": "string"} + # client_config.connection_pool.rebalance_duration -- gRPC client connection pool rebalance duration + rebalance_duration: 30m + # @schema {"name": "client_config.connection_pool.size", "type": "integer"} + # client_config.connection_pool.size -- gRPC client connection pool size + size: 3 + # @schema {"name": "client_config.connection_pool.old_conn_close_duration", "type": "string"} + # client_config.connection_pool.old_conn_close_duration -- makes delay before gRPC client connection closing during connection pool rebalance + old_conn_close_duration: "2m" + # @schema {"name": "client_config.backoff", "type": "object", "anchor": "backoff"} + backoff: + # @schema {"name": "client_config.backoff.initial_duration", "type": "string"} + # client_config.backoff.initial_duration -- gRPC client backoff initial duration + initial_duration: 5ms + # @schema {"name": "client_config.backoff.backoff_time_limit", "type": "string"} + # client_config.backoff.backoff_time_limit -- gRPC client backoff time limit + backoff_time_limit: 5s + # @schema {"name": "client_config.backoff.maximum_duration", "type": "string"} + # client_config.backoff.maximum_duration -- gRPC client backoff maximum duration + maximum_duration: 5s + # @schema {"name": "client_config.backoff.jitter_limit", "type": "string"} + # client_config.backoff.jitter_limit -- gRPC client backoff jitter limit + jitter_limit: 100ms + # @schema {"name": "client_config.backoff.backoff_factor", "type": "number"} + # client_config.backoff.backoff_factor -- gRPC client backoff factor + backoff_factor: 1.1 + # @schema {"name": "client_config.backoff.retry_count", "type": "integer"} + # client_config.backoff.retry_count -- gRPC client backoff retry count + retry_count: 100 + # @schema {"name": "client_config.backoff.enable_error_log", "type": "boolean"} + # client_config.backoff.enable_error_log -- gRPC client backoff log enabled + enable_error_log: true + # @schema {"name": "client_config.circuit_breaker", "type": "object"} + circuit_breaker: + # @schema {"name": "client_config.circuit_breaker.closed_error_rate", "type": "number"} + # client_config.circuit_breaker.closed_error_rate -- gRPC client circuitbreaker closed error rate + closed_error_rate: 0.7 + # @schema {"name": "client_config.circuit_breaker.half_open_error_rate", "type": "number"} + # client_config.circuit_breaker.half_open_error_rate -- gRPC client circuitbreaker half-open error rate + half_open_error_rate: 0.5 + # @schema {"name": "client_config.circuit_breaker.min_samples", "type": "integer"} + # client_config.circuit_breaker.min_samples -- gRPC client circuitbreaker minimum sampling count + min_samples: 1000 + # @schema {"name": "client_config.circuit_breaker.open_timeout", "type": "string"} + # client_config.circuit_breaker.open_timeout -- gRPC client circuitbreaker open timeout + open_timeout: "1s" + # @schema {"name": "client_config.circuit_breaker.closed_refresh_timeout", "type": "string"} + # client_config.circuit_breaker.closed_refresh_timeout -- gRPC client circuitbreaker closed refresh timeout + closed_refresh_timeout: "10s" + # @schema {"name": "client_config.call_option", "type": "object"} + call_option: + # @schema {"name": "client_config.wait_for_ready", "type": "boolean"} + # client_config.call_option.wait_for_ready -- gRPC client call option wait for ready + wait_for_ready: true + # @schema {"name": "client_config.max_retry_rpc_buffer_size", "type": "integer"} + # client_config.call_option.max_retry_rpc_buffer_size -- gRPC client call option max retry rpc buffer size + max_retry_rpc_buffer_size: 0 + # @schema {"name": "client_config.max_recv_msg_size", "type": "integer"} + # client_config.call_option.max_recv_msg_size -- gRPC client call option max receive message size + max_recv_msg_size: 0 + # @schema {"name": "client_config.max_send_msg_size", "type": "integer"} + # client_config.call_option.max_send_msg_size -- gRPC client call option max send message size + max_send_msg_size: 0 + # @schema {"name": "client_config.dial_option", "type": "object"} + dial_option: + # @schema {"name": "client_config.dial_option.write_buffer_size", "type": "integer"} + # client_config.dial_option.write_buffer_size -- gRPC client dial option write buffer size + write_buffer_size: 0 + # @schema {"name": "client_config.dial_option.read_buffer_size", "type": "integer"} + # client_config.dial_option.read_buffer_size -- gRPC client dial option read buffer size + read_buffer_size: 0 + # @schema {"name": "client_config.dial_option.initial_window_size", "type": "integer"} + # client_config.dial_option.initial_window_size -- gRPC client dial option initial window size + initial_window_size: 0 + # @schema {"name": "client_config.dial_option.initial_connection_window_size", "type": "integer"} + # client_config.dial_option.initial_connection_window_size -- gRPC client dial option initial connection window size + initial_connection_window_size: 0 + # @schema {"name": "client_config.dial_option.max_msg_size", "type": "integer"} + # client_config.dial_option.max_msg_size -- gRPC client dial option max message size + max_msg_size: 0 + # @schema {"name": "client_config.dial_option.backoff_max_delay", "type": "string"} + # client_config.dial_option.backoff_max_delay -- gRPC client dial option max backoff delay + backoff_max_delay: "120s" + # @schema {"name": "client_config.dial_option.backoff_base_delay", "type": "string"} + # client_config.dial_option.backoff_base_delay -- gRPC client dial option base backoff delay + backoff_base_delay: "1s" + # @schema {"name": "client_config.dial_option.backoff_multiplier", "type": "number"} + # client_config.dial_option.backoff_multiplier -- gRPC client dial option base backoff delay + backoff_multiplier: 1.6 + # @schema {"name": "client_config.dial_option.backoff_jitter", "type": "number"} + # client_config.dial_option.backoff_jitter -- gRPC client dial option base backoff delay + backoff_jitter: 0.2 + # @schema {"name": "client_config.dial_option.min_connection_timeout", "type": "string"} + # client_config.dial_option.min_connection_timeout -- gRPC client dial option minimum connection timeout + min_connection_timeout: "20s" + # @schema {"name": "client_config.dial_option.enable_backoff", "type": "boolean"} + # client_config.dial_option.enable_backoff -- gRPC client dial option backoff enabled + enable_backoff: false + # @schema {"name": "client_config.dial_option.insecure", "type": "boolean"} + # client_config.dial_option.insecure -- gRPC client dial option insecure enabled + insecure: true + # @schema {"name": "client_config.dial_option.timeout", "type": "string"} + # client_config.dial_option.timeout -- gRPC client dial option timeout + timeout: "" + # @schema {"name": "client_config.dial_option.interceptors", "type": "array", "items": {"type": "string", "enum": ["TraceInterceptor"]}} + # client_config.dial_option.interceptors -- gRPC client interceptors + interceptors: [] + # @schema {"name": "client_config.dial_option.net", "type": "object", "anchor": "net"} + net: + # @schema {"name": "client_config.dial_option.net.dns", "type": "object"} + dns: + # @schema {"name": "client_config.dial_option.net.dns.cache_enabled", "type": "boolean"} + # client_config.dial_option.net.dns.cache_enabled -- gRPC client TCP DNS cache enabled + cache_enabled: true + # @schema {"name": "client_config.dial_option.net.dns.refresh_duration", "type": "string"} + # client_config.dial_option.net.dns.refresh_duration -- gRPC client TCP DNS cache refresh duration + refresh_duration: 30m + # @schema {"name": "client_config.dial_option.net.dns.cache_expiration", "type": "string"} + # client_config.dial_option.net.dns.cache_expiration -- gRPC client TCP DNS cache expiration + cache_expiration: 1h + # @schema {"name": "client_config.dial_option.net.dialer", "type": "object"} + dialer: + # @schema {"name": "client_config.dial_option.net.dialer.timeout", "type": "string"} + # client_config.dial_option.net.dialer.timeout -- gRPC client TCP dialer timeout + timeout: "" + # @schema {"name": "client_config.dial_option.net.dialer.keepalive", "type": "string"} + # client_config.dial_option.net.dialer.keepalive -- gRPC client TCP dialer keep alive + keepalive: "" + # @schema {"name": "client_config.dial_option.net.dialer.dual_stack_enabled", "type": "boolean"} + # client_config.dial_option.net.dialer.dual_stack_enabled -- gRPC client TCP dialer dual stack enabled + dual_stack_enabled: true + # @schema {"name": "client_config.dial_option.net.tls"} + tls: + # client_config.dial_option.net.tls.enabled -- TLS enabled + enabled: false + # client_config.dial_option.net.tls.cert -- TLS cert path + cert: /path/to/cert + # client_config.dial_option.net.tls.key -- TLS key path + key: /path/to/key + # client_config.dial_option.net.tls.ca -- TLS ca path + ca: /path/to/ca + # client_config.dial_option.net.tls.insecure_skip_verify -- enable/disable skip SSL certificate verification + insecure_skip_verify: false + # @schema {"name": "client_config.dial_option.net.socket_option"} + socket_option: + # client_config.dial_option.net.socket_option.reuse_port -- server listen socket option for reuse_port functionality + reuse_port: true + # client_config.dial_option.net.socket_option.reuse_addr -- server listen socket option for reuse_addr functionality + reuse_addr: true + # client_config.dial_option.net.socket_option.tcp_fast_open -- server listen socket option for tcp_fast_open functionality + tcp_fast_open: true + # client_config.dial_option.net.socket_option.tcp_no_delay -- server listen socket option for tcp_no_delay functionality + tcp_no_delay: true + # client_config.dial_option.net.socket_option.tcp_cork -- server listen socket option for tcp_cork functionality + tcp_cork: false + # client_config.dial_option.net.socket_option.tcp_quick_ack -- server listen socket option for tcp_quick_ack functionality + tcp_quick_ack: true + # client_config.dial_option.net.socket_option.tcp_defer_accept -- server listen socket option for tcp_defer_accept functionality + tcp_defer_accept: true + # client_config.dial_option.net.socket_option.ip_transparent -- server listen socket option for ip_transparent functionality + ip_transparent: false + # client_config.dial_option.net.socket_option.ip_recover_destination_addr -- server listen socket option for ip_recover_destination_addr functionality + ip_recover_destination_addr: false + # @schema {"name": "client_config.dial_option.keepalive", "type": "object"} + keepalive: + # @schema {"name": "client_config.dial_option.keepalive.time", "type": "string"} + # client_config.dial_option.keepalive.time -- gRPC client keep alive time + time: "120s" + # @schema {"name": "client_config.dial_option.keepalive.timeout", "type": "string"} + # client_config.dial_option.keepalive.timeout -- gRPC client keep alive timeout + timeout: "30s" + # @schema {"name": "client_config.dial_option.keepalive.permit_without_stream", "type": "boolean"} + # client_config.dial_option.keepalive.permit_without_stream -- gRPC client keep alive permit without stream + permit_without_stream: true + # @schema {"name": "client_config.tls"} + tls: + # client_config.tls.enabled -- TLS enabled + enabled: false + # client_config.tls.cert -- TLS cert path + cert: /path/to/cert + # client_config.tls.key -- TLS key path + key: /path/to/key + # client_config.tls.ca -- TLS ca path + ca: /path/to/ca + # client_config.tls.insecure_skip_verify -- enable/disable skip SSL certificate verification + insecure_skip_verify: false diff --git a/cmd/tools/benchmark/job/main.go b/cmd/tools/benchmark/job/main.go new file mode 100644 index 00000000000..b63331da0f3 --- /dev/null +++ b/cmd/tools/benchmark/job/main.go @@ -0,0 +1,60 @@ +// +// Copyright (C) 2019-2022 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 provides program main +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/tools/benchmark/job/config" + "github.com/vdaas/vald/pkg/tools/benchmark/job/usecase" +) + +const ( + maxVersion = "v0.0.10" + minVersion = "v0.0.0" + name = "benchmark job" +) + +func main() { + if err := safety.RecoverFunc(func() error { + ctx := context.Background() + return runner.Do( + ctx, + runner.WithName(name), + runner.WithVersion(info.Version, maxVersion, minVersion), + runner.WithConfigLoader(func(path string) (interface{}, *config.GlobalConfig, error) { + cfg, err := config.NewConfig(ctx, 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) { + return usecase.New(cfg.(*config.Config)) + }), + ) + })(); err != nil { + log.Fatal(err, info.Get()) + return + } +} diff --git a/cmd/tools/benchmark/job/sample.yaml b/cmd/tools/benchmark/job/sample.yaml new file mode 100644 index 00000000000..76dd83f8ea2 --- /dev/null +++ b/cmd/tools/benchmark/job/sample.yaml @@ -0,0 +1,168 @@ +# +# Copyright (C) 2019-2022 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: 60s + 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: 60s + - 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: 60s + 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 + collector: + duration: 5s + metrics: + enable_cgo: true + enable_goroutine: true + enable_memory: true + enable_version_info: true + version_info_labels: + - vald_version + - server_name + - git_commit + - build_time + - go_version + - go_os + - go_arch + - ngt_version + trace: + enabled: false + sampling_rate: 1 + prometheus: + enabled: false + endpoint: /metrics + namespace: vald + jaeger: + enabled: false + collector_endpoint: "" + agent_endpoint: "jaeger-agent.default.svc.cluster.local:6831" + username: "" + password: "" + service_name: "vald-benchmark-job" + buffer_max_count: 10 + stackdriver: + project_id: "" + client: + api_key: "" + audiences: [] + authentication_enabled: true + credentials_file: "" + credentials_json: "" + endpoint: "" + quota_project: "" + request_reason: "" + scopes: [] + telemetry_enabled: true + user_agent: "" + exporter: + bundle_count_threshold: 0 + bundle_delay_threshold: "0" + location: "" + metric_prefix: vald.vdaas.org + monitoring_enabled: false + number_of_workers: 1 + reporting_interval: 1m + skip_cmd: false + timeout: 5s + trace_spans_buffer_max_bytes: 0 + tracing_enabled: false + profiler: + enabled: false + service: "vald-benchmark-job" + service_version: "" + debug_logging: false + mutex_profiling: true + cpu_profiling: true + alloc_profiling: true + heap_profiling: true + goroutine_profiling: true + alloc_force_gc: false + api_addr: "" + instance: "" + zone: "" +job: + job_type: "search" + dimension: 784 + iter: 100 + num: 10 + minNum: 10 + radius: -1 + epsilon: 0.1 + timeout: 5s + gateway_client: + addrs: + - vald-lb-gateway.default.svc.cluster.local:8081 diff --git a/cmd/tools/benchmark/operator/main.go b/cmd/tools/benchmark/operator/main.go new file mode 100644 index 00000000000..13660db9838 --- /dev/null +++ b/cmd/tools/benchmark/operator/main.go @@ -0,0 +1,59 @@ +// +// Copyright (C) 2019-2022 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 provides program main +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/tools/benchmark/operator/config" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/usecase" +) + +const ( + maxVersion = "v0.0.10" + minVersion = "v0.0.0" + name = "benchmark operator" +) + +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) { + return usecase.New(cfg.(*config.Config)) + }), + ) + })(); err != nil { + log.Fatal(err, info.Get()) + return + } +} diff --git a/cmd/tools/benchmark/operator/sample.yaml b/cmd/tools/benchmark/operator/sample.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dockers/tools/benchmark/job/Dockerfile b/dockers/tools/benchmark/job/Dockerfile new file mode 100644 index 00000000000..4c378a90a6f --- /dev/null +++ b/dockers/tools/benchmark/job/Dockerfile @@ -0,0 +1,100 @@ +# +# Copyright (C) 2019-2023 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 UPX_OPTIONS=-9 +ARG MAINTAINER="vdaas.org vald team " + +FROM golang:${GO_VERSION} AS builder + +ARG UPX_OPTIONS + +ENV GO111MODULE on +ENV LANG en_US.UTF-8 +ENV ORG vdaas +ENV REPO vald +ENV APP_NAME job +ENV PKG tools/benchmark/${APP_NAME} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + upx \ + git \ + libhdf5-dev \ + && ldconfig \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +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} \ + && upx ${UPX_OPTIONS} -o "/usr/bin/${APP_NAME}" "cmd/${PKG}/${APP_NAME}" + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/cmd/${PKG} +RUN cp sample.yaml /tmp/config.yaml + +FROM ${DISTROLESS_IMAGE}:${DISTROLESS_IMAGE_TAG} +LABEL maintainer "Vald team " +ENV APP_NAME job + +COPY --from=builder /usr/lib/x86_64-linux-gnu/libaec* /usr/lib/x86_64-linux-gnu/ +COPY --from=builder /usr/lib/x86_64-linux-gnu/libhdf5* /usr/lib/x86_64-linux-gnu/ +COPY --from=builder /usr/lib/x86_64-linux-gnu/libsz* /usr/lib/x86_64-linux-gnu/ +COPY --from=builder /usr/lib/x86_64-linux-gnu/libhdf5_serial.so /usr/lib/x86_64-linux-gnu/ +COPY --from=builder /usr/lib/x86_64-linux-gnu/libhdf5_serial_hl.so /usr/lib/x86_64-linux-gnu/ +COPY --from=builder /lib/x86_64-linux-gnu/libpthread.so.0 /lib/x86_64-linux-gnu/ +COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/ +COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/ +COPY --from=builder /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/ +COPY --from=builder /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/ +COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ + +COPY --from=builder /usr/bin/${APP_NAME} /go/bin/${APP_NAME} +COPY --from=builder /tmp/config.yaml /etc/server/config.yaml + +USER nonroot:nonroot + +ENTRYPOINT ["/go/bin/job"] diff --git a/dockers/tools/benchmark/operator/Dockerfile b/dockers/tools/benchmark/operator/Dockerfile new file mode 100644 index 00000000000..48f0e1dc54a --- /dev/null +++ b/dockers/tools/benchmark/operator/Dockerfile @@ -0,0 +1,87 @@ +# +# Copyright (C) 2019-2023 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 UPX_OPTIONS=-9 +ARG MAINTAINER="vdaas.org vald team " + +FROM golang:${GO_VERSION} AS builder + +ARG UPX_OPTIONS + +ENV GO111MODULE on +ENV LANG en_US.UTF-8 +ENV ORG vdaas +ENV REPO vald +ENV APP_NAME operator +ENV PKG tools/benchmark/${APP_NAME} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + upx \ + git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +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} \ + && upx ${UPX_OPTIONS} -o "/usr/bin/${APP_NAME}" "cmd/${PKG}/${APP_NAME}" + +WORKDIR ${GOPATH}/src/github.com/${ORG}/${REPO}/cmd/${PKG} +RUN cp sample.yaml /tmp/config.yaml + +FROM ${DISTROLESS_IMAGE}:${DISTROLESS_IMAGE_TAG} +LABEL maintainer "Vald team " +ENV APP_NAME operator + +COPY --from=builder /usr/bin/${APP_NAME} /go/bin/${APP_NAME} +COPY --from=builder /tmp/config.yaml /etc/server/config.yaml + +USER nonroot:nonroot + +ENTRYPOINT ["/go/bin/operator"] diff --git a/internal/config/benchmark.go b/internal/config/benchmark.go new file mode 100644 index 00000000000..37bcba94b90 --- /dev/null +++ b/internal/config/benchmark.go @@ -0,0 +1,116 @@ +// +// Copyright (C) 2019-2022 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 providers configuration type and load configuration logic +package config + +// BenchmarkJob represents the configuration for the internal benchmark search job. +type BenchmarkJob struct { + Target *BenchmarkTarget `json:"target,omitempty" yaml:"target"` + Dataset *BenchmarkDataset `json:"dataset,omitempty" yaml:"dataset"` + Dimension int `json:"dimension,omitempty" yaml:"dimension"` + Replica int `json:"replica,omitempty" yaml:"replica"` + Repetition int `json:"repetition,omitempty" yaml:"repetition"` + JobType string `json:"job_type,omitempty" yaml:"job_type"` + InsertConfig *InsertConfig `json:"insert_config,omitempty" yaml:"insert_config"` + UpdateConfig *UpdateConfig `json:"update_config,omitempty" yaml:"update_config"` + UpsertConfig *UpsertConfig `json:"upsert_config,omitempty" yaml:"upsert_config"` + SearchConfig *SearchConfig `json:"search_config,omitempty" yaml:"search_config"` + RemoveConfig *RemoveConfig `json:"remove_config,omitempty" yaml:"remove_config"` + ClientConfig *GRPCClient `json:"client_config,omitempty" yaml:"client_config"` + Rules []*BenchmarkJobRule `json:"rules,omitempty" yaml:"rules"` +} + +// BenchmarkScenario represents the configuration for the internal benchmark scenario. +type BenchmarkScenario struct { + Target *BenchmarkTarget `json:"target" yaml:"target"` + Dataset *BenchmarkDataset `jon:"dataset" yaml:"dataset"` + Jobs []*BenchmarkJob `job:"jobs" yaml:jobs` +} + +// BenchmarkTarget defines the desired state of BenchmarkTarget +type BenchmarkTarget struct { + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` +} + +// BenchmarkDataset defines the desired state of BenchmarkDateset +type BenchmarkDataset struct { + Name string `json:"name,omitempty"` + Group string `json:"group,omitempty"` + Indexes int `json:"indexes,omitempty"` + Range *BenchmarkDatasetRange `json:"range,omitempty"` +} + +// BenchmarkDatasetRange defines the desired state of BenchmarkDatesetRange +type BenchmarkDatasetRange struct { + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` +} + +// BenchmarkJobRule defines the desired state of BenchmarkJobRule +type BenchmarkJobRule struct { + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` +} + +// InsertConfig defines the desired state of insert config +type InsertConfig struct { + SkipStrictExistCheck bool `json:"skip_strict_exist_check,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +// UpdateConfig defines the desired state of update config +type UpdateConfig struct { + SkipStrictExistCheck bool `json:"skip_strict_exist_check,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +// UpsertConfig defines the desired state of upsert config +type UpsertConfig struct { + SkipStrictExistCheck bool `json:"skip_strict_exist_check,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +// SearchConfig defines the desired state of search config +type SearchConfig struct { + Epsilon float32 `json:"epsilon,omitempty"` + Radius float32 `json:"radius,omitempty"` + Num int32 `json:"num,omitempty"` + MinNum int32 `json:"min_num,omitempty"` + Timeout string `json:"timeout,omitempty"` +} + +// RemoveConfig defines the desired state of remove config +type RemoveConfig struct { + SkipStrictExistCheck bool `json:"skip_strict_exist_check,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +// Bind binds the actual data from the Job receiver fields. +func (b *BenchmarkJob) Bind() *BenchmarkJob { + b.JobType = GetActualValue(b.JobType) + + if b.ClientConfig != nil { + b.ClientConfig = b.ClientConfig.Bind() + } + return b +} + +// Bind binds the actual data from the BenchmarkScenario receiver fields. +func (b *BenchmarkScenario) Bind() *BenchmarkScenario { + return b +} diff --git a/internal/config/server.go b/internal/config/server.go index 5bbf8564ee9..b6d592e9b3d 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -39,7 +39,7 @@ type Servers struct { // StartUpStrategy represent starting order of server name StartUpStrategy []string `json:"startup_strategy" yaml:"startup_strategy"` - // ShutdownStrategy represent shutdonw order of server name + // ShutdownStrategy represent shutdown order of server name ShutdownStrategy []string `json:"shutdown_strategy" yaml:"shutdown_strategy"` // FullShutdownDuration represent summary duration of shutdown time diff --git a/internal/errors/http.go b/internal/errors/http.go index 586a3c74e8a..49398dd7640 100644 --- a/internal/errors/http.go +++ b/internal/errors/http.go @@ -53,4 +53,9 @@ var ( // ErrTransportRetryable represents an error that the transport is retryable. ErrTransportRetryable = New("transport is retryable") + + // ErrInvalidStatusCode represents a function to generate an error that the http status code is invalid. + ErrInvalidStatusCode = func(code int) error { + return Errorf("invalid status code: %d", code) + } ) diff --git a/internal/errors/http_test.go b/internal/errors/http_test.go index 2498de2c335..3b010df8176 100644 --- a/internal/errors/http_test.go +++ b/internal/errors/http_test.go @@ -526,4 +526,65 @@ func TestErrTransportRetryable(t *testing.T) { } } +func TestErrInvalidStatusCode(t *testing.T) { + type args struct { + code int + } + type want struct { + want error + } + type test struct { + name string + args args + want want + checkFunc func(want, error) error + beforeFunc func() + afterFunc func() + } + + defaultCheckFunc := func(w want, got error) error { + if !Is(got, w.want) { + return Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + return nil + } + tests := []test{ + { + name: "return ErrInvalidStatusCode error when code is empty", + want: want{ + want: New("invalid status code: 0"), + }, + }, + { + name: "return ErrInvalidStatusCode error when code is 500", + args: args{ + code: 500, + }, + want: want{ + want: New("invalid status code: 500"), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + if test.beforeFunc != nil { + test.beforeFunc() + } + if test.afterFunc != nil { + defer test.afterFunc() + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got := ErrInvalidStatusCode(test.args.code) + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + // NOT IMPLEMENTED BELOW diff --git a/internal/k8s/client/client.go b/internal/k8s/client/client.go index 2f7c56b36d3..c0a86a341e7 100644 --- a/internal/k8s/client/client.go +++ b/internal/k8s/client/client.go @@ -1,3 +1,4 @@ +// // Copyright (C) 2019-2023 vdaas.org vald team // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,6 +12,9 @@ // 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 is Kubernetes client for getting resource from Kubernetes cluster. package client import ( @@ -89,7 +93,6 @@ func New(opts ...Option) (Client, error) { if c.scheme == nil { c.scheme = runtime.NewScheme() } - for _, opt := range opts { if err := opt(c); err != nil { return nil, err @@ -103,7 +106,6 @@ func New(opts ...Option) (Client, error) { if err := snapshotv1.AddToScheme(c.scheme); err != nil { return nil, err } - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), manager.Options{ Scheme: c.scheme, }) diff --git a/internal/k8s/crd/benchmark/valdbenchmarkjob.yaml b/internal/k8s/crd/benchmark/valdbenchmarkjob.yaml new file mode 100644 index 00000000000..34a92c68caa --- /dev/null +++ b/internal/k8s/crd/benchmark/valdbenchmarkjob.yaml @@ -0,0 +1,381 @@ +# +# Copyright (C) 2019-2022 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: valdbenchmarkjobs.vald.vdaas.org +spec: + group: vald.vdaas.org + names: + kind: ValdBenchmarkJob + listKind: ValdBenchmarkJobList + plural: valdbenchmarkjobs + singular: valdbenchmarkjob + shortNames: + - vbj + - vbjs + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .spec.replica + name: REPLICAS + type: integer + - jsonPath: .status + name: STATUS + type: string + schema: + openAPIV3Schema: + description: ValdBenchmarkJob is the Schema for the valdbenchmarkjobs 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: ValdBenchmarkJobStatus defines the observed state of ValdBenchmarkJob + enum: + - NotReady + - Available + - Healthy + type: string + spec: + description: ValdBenchmarkJobSpec defines the desired state of ValdBenchmarkJob + type: object + properties: + target: + description: BenchmarkTarget defines the desired state of BenchmarkTarget + properties: + host: + type: string + port: + type: integer + required: + - host + - port + type: object + dataset: + description: BenchmarkDataset defines the desired state of BenchmarkDateset + properties: + group: + type: string + indexes: + type: integer + name: + type: string + range: + description: BenchmarkDatasetRange defines the desired state of BenchmarkDatesetRange + properties: + end: + type: integer + start: + type: integer + required: + - end + - start + type: object + required: + - group + - indexes + - name + type: object + dimension: + type: integer + job_type: + type: string + repetition: + type: integer + replica: + type: integer + rules: + items: + description: BenchmarkJobRule defines the desired state of BenchmarkJobRule + properties: + name: + type: string + type: + type: string + required: + - name + - type + type: object + type: array + insert_config: + description: InsertConfig defines the desired state of insert config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + remove_config: + description: RemoveConfig defines the desired state of remove config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + search_config: + description: SearchConfig defines the desired state of search config + properties: + epsilon: + type: number + min_num: + format: int32 + type: integer + num: + format: int32 + type: integer + radius: + type: number + timeout: + type: string + type: object + update_config: + description: UpdateConfig defines the desired state of update config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + upsert_config: + description: UpsertConfig defines the desired state of upsert config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + client_config: + description: ClientConfig represents the configurations for gRPC client. + properties: + addrs: + items: + type: string + type: array + backoff: + description: Backoff represents the configuration for the internal backoff package. + 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 + type: object + call_option: + description: CallOption represents the configurations for call option. + properties: + max_recv_msg_size: + type: integer + max_retry_rpc_buffer_size: + type: integer + max_send_msg_size: + type: integer + wait_for_ready: + type: boolean + type: object + circuit_breaker: + description: CircuitBreaker represents the configuration for the internal circuitbreaker package. + properties: + closed_error_rate: + type: number + closed_refresh_timeout: + type: string + half_open_error_rate: + type: number + min_samples: + format: int64 + type: integer + open_timeout: + type: string + type: object + connection_pool: + description: ConnectionPool represents the configurations for connection pool. + properties: + enable_rebalance: + type: boolean + old_conn_close_duration: + type: string + rebalance_duration: + type: string + resolve_dns: + type: boolean + size: + type: integer + type: object + dial_option: + description: DialOption represents the configurations for dial option. + 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: + items: + type: string + type: array + keepalive: + description: GRPCClientKeepalive represents the configurations for gRPC keep-alive. + properties: + permit_without_stream: + type: boolean + time: + type: string + timeout: + type: string + type: object + max_msg_size: + type: integer + minimum_connection_timeout: + type: string + net: + description: Net represents the network configuration tcp, udp, unix domain socket. + properties: + dialer: + description: Dialer represents the configuration for dial. + properties: + dual_stack_enabled: + type: boolean + fallback_delay: + type: string + keepalive: + type: string + timeout: + type: string + type: object + dns: + description: DNS represents the configuration for resolving DNS. + properties: + cache_enabled: + type: boolean + cache_expiration: + type: string + refresh_duration: + type: string + type: object + socket_option: + description: SocketOption represents the socket configurations. + 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 + type: object + tls: + description: TLS represent the TLS configuration for server. + properties: + ca: + description: CA represent the CA certificate environment variable key used to start server. + type: string + cert: + description: Cert represent the certificate environment variable key used to start server. + type: string + enabled: + description: Enable represent the server enable TLS or not. + type: boolean + insecure_skip_verify: + description: InsecureSkipVerify represent enable/disable skip SSL certificate verification + type: boolean + key: + description: Key represent the private key environment variable key used to start server. + type: string + type: object + type: object + read_buffer_size: + type: integer + timeout: + type: string + write_buffer_size: + type: integer + type: object + health_check_duration: + type: string + tls: + description: TLS represent the TLS configuration for server. + properties: + ca: + description: CA represent the CA certificate environment variable key used to start server. + type: string + cert: + description: Cert represent the certificate environment variable key used to start server. + type: string + enabled: + description: Enable represent the server enable TLS or not. + type: boolean + insecure_skip_verify: + description: InsecureSkipVerify represent enable/disable skip SSL certificate verification + type: boolean + key: + description: Key represent the private key environment variable key used to start server. + type: string + type: object + type: object + required: + - job_type + - dimension + - dataset diff --git a/internal/k8s/crd/benchmark/valdbenchmarkscenario.yaml b/internal/k8s/crd/benchmark/valdbenchmarkscenario.yaml new file mode 100644 index 00000000000..09a73f17ccd --- /dev/null +++ b/internal/k8s/crd/benchmark/valdbenchmarkscenario.yaml @@ -0,0 +1,422 @@ +# +# Copyright (C) 2019-2022 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: valdbenchmarkscenarios.vald.vdaas.org +spec: + group: vald.vdaas.org + names: + kind: ValdBenchmarkScenario + listKind: ValdBenchmarkScenarioList + plural: valdbenchmarkscenarios + singular: valdbenchmarkscenario + shortNames: + - vbo + - vbos + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status + name: STATUS + type: string + schema: + openAPIV3Schema: + description: ValdBenchmarkScenario is the Schema for the valdbenchmarkscenarios 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: ValdBenchmarkScenarioStatus defines the observed state of ValdBenchmarkScenario + enum: + - NotReady + - Available + - Healthy + type: string + spec: + description: ValdBenchmarkScenarioSpec defines the desired state of ValdBenchmarkScenario + type: object + properties: + dataset: + description: BenchmarkDataset defines the desired state of BenchmarkDateset + properties: + group: + type: string + indexes: + type: integer + name: + type: string + range: + description: BenchmarkDatasetRange defines the desired state of BenchmarkDatesetRange + properties: + end: + type: integer + start: + type: integer + required: + - end + - start + type: object + required: + - group + - indexes + - name + type: object + target: + properties: + host: + type: string + port: + type: integer + required: + - host + - port + type: object + jobs: + type: array + items: + description: BenchmarkJobSpec defines the desired state of ValdBenchmarkJob + type: object + properties: + target: + description: BenchmarkTarget defines the desired state of BenchmarkTarget + properties: + host: + type: string + port: + type: integer + required: + - host + - port + type: object + dataset: + description: BenchmarkDataset defines the desired state of BenchmarkDateset + properties: + group: + type: string + indexes: + type: integer + name: + type: string + range: + description: BenchmarkDatasetRange defines the desired state of BenchmarkDatesetRange + properties: + end: + type: integer + start: + type: integer + required: + - end + - start + type: object + required: + - group + - indexes + - name + type: object + dimension: + type: integer + job_type: + type: string + repetition: + type: integer + replica: + type: integer + rules: + items: + description: BenchmarkJobRule defines the desired state of BenchmarkJobRule + properties: + name: + type: string + type: + type: string + required: + - name + - type + type: object + type: array + insert_config: + description: InsertConfig defines the desired state of insert config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + remove_config: + description: RemoveConfig defines the desired state of remove config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + search_config: + description: SearchConfig defines the desired state of search config + properties: + epsilon: + type: number + min_num: + format: int32 + type: integer + num: + format: int32 + type: integer + radius: + type: number + timeout: + type: string + type: object + update_config: + description: UpdateConfig defines the desired state of update config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + upsert_config: + description: UpsertConfig defines the desired state of upsert config + properties: + skip_strict_exist_check: + type: boolean + timestamp: + type: string + type: object + client_config: + description: ClientConfig represents the configurations for gRPC client. + properties: + addrs: + items: + type: string + type: array + backoff: + description: Backoff represents the configuration for the internal backoff package. + 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 + type: object + call_option: + description: CallOption represents the configurations for call option. + properties: + max_recv_msg_size: + type: integer + max_retry_rpc_buffer_size: + type: integer + max_send_msg_size: + type: integer + wait_for_ready: + type: boolean + type: object + circuit_breaker: + description: CircuitBreaker represents the configuration for the internal circuitbreaker package. + properties: + closed_error_rate: + type: number + closed_refresh_timeout: + type: string + half_open_error_rate: + type: number + min_samples: + format: int64 + type: integer + open_timeout: + type: string + type: object + connection_pool: + description: ConnectionPool represents the configurations for connection pool. + properties: + enable_rebalance: + type: boolean + old_conn_close_duration: + type: string + rebalance_duration: + type: string + resolve_dns: + type: boolean + size: + type: integer + type: object + dial_option: + description: DialOption represents the configurations for dial option. + 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: + items: + type: string + type: array + keepalive: + description: GRPCClientKeepalive represents the configurations for gRPC keep-alive. + properties: + permit_without_stream: + type: boolean + time: + type: string + timeout: + type: string + type: object + max_msg_size: + type: integer + minimum_connection_timeout: + type: string + net: + description: Net represents the network configuration tcp, udp, unix domain socket. + properties: + dialer: + description: Dialer represents the configuration for dial. + properties: + dual_stack_enabled: + type: boolean + fallback_delay: + type: string + keepalive: + type: string + timeout: + type: string + type: object + dns: + description: DNS represents the configuration for resolving DNS. + properties: + cache_enabled: + type: boolean + cache_expiration: + type: string + refresh_duration: + type: string + type: object + socket_option: + description: SocketOption represents the socket configurations. + 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 + type: object + tls: + description: TLS represent the TLS configuration for server. + properties: + ca: + description: CA represent the CA certificate environment variable key used to start server. + type: string + cert: + description: Cert represent the certificate environment variable key used to start server. + type: string + enabled: + description: Enable represent the server enable TLS or not. + type: boolean + insecure_skip_verify: + description: InsecureSkipVerify represent enable/disable skip SSL certificate verification + type: boolean + key: + description: Key represent the private key environment variable key used to start server. + type: string + type: object + type: object + read_buffer_size: + type: integer + timeout: + type: string + write_buffer_size: + type: integer + type: object + health_check_duration: + type: string + tls: + description: TLS represent the TLS configuration for server. + properties: + ca: + description: CA represent the CA certificate environment variable key used to start server. + type: string + cert: + description: Cert represent the certificate environment variable key used to start server. + type: string + enabled: + description: Enable represent the server enable TLS or not. + type: boolean + insecure_skip_verify: + description: InsecureSkipVerify represent enable/disable skip SSL certificate verification + type: boolean + key: + description: Key represent the private key environment variable key used to start server. + type: string + type: object + type: object + required: + - job_type + - dimension + required: + - jobs + - dataset + - target diff --git a/internal/k8s/job/job.go b/internal/k8s/job/job.go new file mode 100644 index 00000000000..026baa69e2d --- /dev/null +++ b/internal/k8s/job/job.go @@ -0,0 +1,160 @@ +// Copyright (C) 2019-2022 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 job + +import ( + "context" + "reflect" + "strings" + "sync" + "time" + + batchv1 "k8s.io/api/batch/v1" + k8serrors "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" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/k8s" + "github.com/vdaas/vald/internal/log" +) + +// JobWatcher is a type alias for k8s resource controller. +type JobWatcher k8s.ResourceController + +type reconciler struct { + mgr manager.Manager + name string + namespaces []string + onError func(err error) + onReconcile func(ctx context.Context, jobList map[string][]Job) + listOpts []client.ListOption + jobsByAppNamePool sync.Pool // map[app][]Job +} + +// Job is a type alias for the k8s job definition. +type Job = batchv1.Job + +// New returns the JobWatcher that implements reconciliation loop, or any errors occurred. +func New(opts ...Option) (JobWatcher, error) { + r := &reconciler{ + jobsByAppNamePool: sync.Pool{ + New: func() interface{} { + return make(map[string][]Job) + }, + }, + } + + for _, opt := range append(defaultOpts, opts...) { + if err := opt(r); err != nil { + return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + } + } + + if len(r.namespaces) != 0 { + r.listOpts = make([]client.ListOption, 0, len(r.namespaces)) + for _, ns := range r.namespaces { + r.listOpts = append(r.listOpts, client.InNamespace(ns)) + } + } + + return r, nil +} + +// Reconcile implements k8s reconciliation loop to retrieve the Job information from k8s. +func (r *reconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) { + js := new(batchv1.JobList) + + err = r.mgr.GetClient().List(ctx, js, r.listOpts...) + if err != nil { + if r.onError != nil { + r.onError(err) + } + res = reconcile.Result{ + Requeue: true, + RequeueAfter: time.Millisecond * 100, + } + if k8serrors.IsNotFound(err) { + log.Error("not found", err) + return reconcile.Result{ + Requeue: true, + RequeueAfter: time.Second, + }, nil + } + return + } + + jobs := r.jobsByAppNamePool.Get().(map[string][]Job) + for _, job := range js.Items { + name, ok := job.GetObjectMeta().GetLabels()["app"] + if !ok { + jns := strings.Split(job.GetName(), "-") + name = strings.Join(jns[:len(jns)-1], "-") + } + + if _, ok := jobs[name]; !ok { + jobs[name] = make([]Job, 0, len(js.Items)) + } + jobs[name] = append(jobs[name], job) + } + + if r.onReconcile != nil { + r.onReconcile(ctx, jobs) + } + + for name := range jobs { + jobs[name] = jobs[name][:0:len(jobs[name])] + } + + r.jobsByAppNamePool.Put(jobs) + + return +} + +// GetName returns the name of resource controller. +func (r *reconciler) GetName() string { + return r.name +} + +// NewReconciler returns the reconciler for the Job. +func (r *reconciler) NewReconciler(ctx context.Context, mgr manager.Manager) reconcile.Reconciler { + if r.mgr == nil && mgr != nil { + r.mgr = mgr + } + + batchv1.AddToScheme(r.mgr.GetScheme()) + return r +} + +// For returns the runtime.Object which is job. +func (r *reconciler) For() (client.Object, []builder.ForOption) { + return new(batchv1.Job), nil +} + +// Owns returns the owner of the job watcher. +// It will always return nil. +func (r *reconciler) Owns() (client.Object, []builder.OwnsOption) { + return nil, nil +} + +// Watches returns the kind of the job and the event handler. +// It will always return nil. +func (r *reconciler) Watches() (*source.Kind, handler.EventHandler, []builder.WatchesOption) { + // return &source.Kind{Type: new(corev1.Pod)}, &handler.EnqueueRequestForObject{} + return nil, nil, nil +} diff --git a/internal/k8s/job/option.go b/internal/k8s/job/option.go new file mode 100644 index 00000000000..283f4b156c5 --- /dev/null +++ b/internal/k8s/job/option.go @@ -0,0 +1,65 @@ +// Copyright (C) 2019-2022 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 job + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// Option represents functional option for reconciler. +type Option func(*reconciler) error + +var defaultOpts = []Option{} + +// WithControllerName returns Option that sets r.name. +func WithControllerName(name string) Option { + return func(r *reconciler) error { + r.name = name + return nil + } +} + +// WithManager returns Option that sets r.mgr. +func WithManager(mgr manager.Manager) Option { + return func(r *reconciler) error { + r.mgr = mgr + return nil + } +} + +// WithNamespaces returns Option to set the namespace. +func WithNamespaces(nss ...string) Option { + return func(r *reconciler) error { + r.namespaces = nss + return nil + } +} + +// WithOnErrorFunc returns Option that sets r.onError. +func WithOnErrorFunc(f func(err error)) Option { + return func(r *reconciler) error { + r.onError = f + return nil + } +} + +// WithOnReconcileFunc returns Option that sets r.onReconcile. +func WithOnReconcileFunc(f func(ctx context.Context, jobList map[string][]Job)) Option { + return func(r *reconciler) error { + r.onReconcile = f + return nil + } +} diff --git a/internal/k8s/rbac/benchmark/job/clusterrole.yaml b/internal/k8s/rbac/benchmark/job/clusterrole.yaml new file mode 100644 index 00000000000..4cdc233ec49 --- /dev/null +++ b/internal/k8s/rbac/benchmark/job/clusterrole.yaml @@ -0,0 +1,92 @@ +# +# Copyright (C) 2019-2022 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: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: benchmark-job-role # TODO: fix + namespace: default # TODO: fix +rules: + - apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - vald.vdaas.org + resources: + - valdbenchmarkjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - vald.vdaas.org + resources: + - valdbenchmarkjobs/finalizers + verbs: + - update + - apiGroups: + - vald.vdaas.org + resources: + - valdbenchmarkjobs/status + verbs: + - get + - patch + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update + - apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/internal/k8s/rbac/benchmark/job/clusterrolebinding.yaml b/internal/k8s/rbac/benchmark/job/clusterrolebinding.yaml new file mode 100644 index 00000000000..f856e1b7535 --- /dev/null +++ b/internal/k8s/rbac/benchmark/job/clusterrolebinding.yaml @@ -0,0 +1,27 @@ +# +# Copyright (C) 2019-2022 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: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: benchmark-job-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: benchmark-job-role # TODO: fix +subjects: + - kind: ServiceAccount + name: benchmark-job-service # TODO: fix + namespace: default # TODO: fix diff --git a/internal/k8s/rbac/benchmark/job/serviceaccount.yaml b/internal/k8s/rbac/benchmark/job/serviceaccount.yaml new file mode 100644 index 00000000000..3bf6b046cab --- /dev/null +++ b/internal/k8s/rbac/benchmark/job/serviceaccount.yaml @@ -0,0 +1,20 @@ +# +# Copyright (C) 2019-2022 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. +# +apiVersoin: v1 +kind: ServiceAccount +metadata: + name: benchmark-job-service # TODO: fix + namespace: default # TODO: fix diff --git a/internal/k8s/rbac/benchmark/job/svc.yaml b/internal/k8s/rbac/benchmark/job/svc.yaml new file mode 100644 index 00000000000..ce79a170e1d --- /dev/null +++ b/internal/k8s/rbac/benchmark/job/svc.yaml @@ -0,0 +1,31 @@ +# +# Copyright (C) 2019-2022 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: v1 +kind: Service +metadata: + name: benchmark-job-svc +spec: + ports: + - name: prometheus + port: 4000 # TODO: fix + targetPort: 4000 # TODO: fix + protocol: TCP + selector: + app.kubernetes.io/name: benchmark-job-svc # TODO: fix + app.kubernetes.io/component: benchmark-job + clusterIP: None + type: ClusterIP + externalTrafficPolicy: "" diff --git a/internal/k8s/rbac/benchmark/operator/clusterrole.yaml b/internal/k8s/rbac/benchmark/operator/clusterrole.yaml new file mode 100644 index 00000000000..acd2ffe14c4 --- /dev/null +++ b/internal/k8s/rbac/benchmark/operator/clusterrole.yaml @@ -0,0 +1,95 @@ +# +# Copyright (C) 2019-2022 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: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: benchmark-scenario-role # TODO: fix + namespace: default # TODO: fix +rules: + - apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - vald.vdaas.org + resources: + - valdbenchmarkscenarios + - valdbenchmarkjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - vald.vdaas.org + resources: + - valdbenchmarkscenarios/finalizers + - valdbenchmarkjobs/finalizers + verbs: + - update + - apiGroups: + - vald.vdaas.org + resources: + - valdbenchmarkscenarios/status + - valdbenchmarkjobs/status + verbs: + - get + - patch + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update + - apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/internal/k8s/rbac/benchmark/operator/clusterrolebinding.yaml b/internal/k8s/rbac/benchmark/operator/clusterrolebinding.yaml new file mode 100644 index 00000000000..e87baf62420 --- /dev/null +++ b/internal/k8s/rbac/benchmark/operator/clusterrolebinding.yaml @@ -0,0 +1,27 @@ +# +# Copyright (C) 2019-2022 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: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: benchmark-scenario-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: benchmark-scenario-role # TODO: fix +subjects: + - kind: ServiceAccount + name: vald-benchmark-operator # TODO: fix + namespace: default # TODO: fix diff --git a/internal/k8s/rbac/benchmark/operator/serviceaccount.yaml b/internal/k8s/rbac/benchmark/operator/serviceaccount.yaml new file mode 100644 index 00000000000..612c1bf84a4 --- /dev/null +++ b/internal/k8s/rbac/benchmark/operator/serviceaccount.yaml @@ -0,0 +1,20 @@ +# +# Copyright (C) 2019-2022 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: v1 +kind: ServiceAccount +metadata: + name: vald-benchmark-operator # TODO: fix + namespace: default # TODO: fix diff --git a/internal/k8s/reconciler.go b/internal/k8s/reconciler.go index b5533169d42..08f3a5df49b 100644 --- a/internal/k8s/reconciler.go +++ b/internal/k8s/reconciler.go @@ -34,8 +34,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +type Manager = manager.Manager + type Controller interface { Start(ctx context.Context) (<-chan error, error) + GetManager() Manager } type ResourceController interface { @@ -135,3 +138,7 @@ func (c *controller) Start(ctx context.Context) (<-chan error, error) { return ech, nil } + +func (c *controller) GetManager() Manager { + return c.mgr +} diff --git a/internal/k8s/vald/benchmark/api/v1/info.go b/internal/k8s/vald/benchmark/api/v1/info.go new file mode 100644 index 00000000000..1ad2f03a4a6 --- /dev/null +++ b/internal/k8s/vald/benchmark/api/v1/info.go @@ -0,0 +1,40 @@ +// +// Copyright (C) 2019-2022 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 ( + "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( + &ValdBenchmarkScenario{}, + &ValdBenchmarkScenarioList{}, + &ValdBenchmarkJob{}, + &ValdBenchmarkJobList{}, + ) +} diff --git a/internal/k8s/vald/benchmark/api/v1/job_types.go b/internal/k8s/vald/benchmark/api/v1/job_types.go new file mode 100644 index 00000000000..dc3996dbb02 --- /dev/null +++ b/internal/k8s/vald/benchmark/api/v1/job_types.go @@ -0,0 +1,233 @@ +// +// Copyright (C) 2019-2022 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 ( + "github.com/vdaas/vald/internal/config" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type BenchmarkJobSpec struct { + Target *BenchmarkTarget `json:"target,omitempty"` + Dataset *BenchmarkDataset `json:"dataset,omitempty"` + Dimension int `json:"dimension,omitempty"` + Replica int `json:"replica,omitempty"` + Repetition int `json:"repetition,omitempty"` + JobType string `json:"job_type,omitempty"` + InsertConfig *config.InsertConfig `json:"insert_config,omitempty"` + UpdateConfig *config.UpdateConfig `json:"update_config,omitempty"` + UpsertConfig *config.UpsertConfig `json:"upsert_config,omitempty"` + SearchConfig *config.SearchConfig `json:"search_config,omitempty"` + RemoveConfig *config.RemoveConfig `json:"remove_config,omitempty"` + ClientConfig *config.GRPCClient `json:"client_config,omitempty"` + Rules []*config.BenchmarkJobRule `json:"rules,omitempty"` +} + +type BenchmarkJobStatus string + +const ( + BenchmarkJobNotReady = BenchmarkJobStatus("NotReady") + BenchmarkJobAvailable = BenchmarkJobStatus("Available") + BenchmarkJobHealthy = BenchmarkJobStatus("Healthy") +) + +// BenchmarkTarget defines the desired state of BenchmarkTarget +type BenchmarkTarget config.BenchmarkTarget + +// BenchmarkDataset defines the desired state of BenchmarkDateset +type BenchmarkDataset config.BenchmarkDataset + +// BenchmarkDatasetRange defines the desired state of BenchmarkDatesetRange +type BenchmarkDatasetRange config.BenchmarkDatasetRange + +// BenchmarkJobRule defines the desired state of BenchmarkJobRule +type BenchmarkJobRule config.BenchmarkJobRule + +type ValdBenchmarkJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Spec BenchmarkJobSpec `json:"spec,omitempty"` + Status BenchmarkJobStatus `json:"status,omitempty"` +} + +type ValdBenchmarkJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Items []ValdBenchmarkJob `json:"items,omitempty"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BenchmarkDataset) DeepCopyInto(out *BenchmarkDataset) { + *out = *in + if in.Range != nil { + in, out := &in.Range, &out.Range + *out = new(config.BenchmarkDatasetRange) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkDataset. +func (in *BenchmarkDataset) DeepCopy() *BenchmarkDataset { + if in == nil { + return nil + } + out := new(BenchmarkDataset) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BenchmarkDatasetRange) DeepCopyInto(out *BenchmarkDatasetRange) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkDatasetRange. +func (in *BenchmarkDatasetRange) DeepCopy() *BenchmarkDatasetRange { + if in == nil { + return nil + } + out := new(BenchmarkDatasetRange) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BenchmarkJobRule) DeepCopyInto(out *BenchmarkJobRule) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkJobRule. +func (in *BenchmarkJobRule) DeepCopy() *BenchmarkJobRule { + if in == nil { + return nil + } + out := new(BenchmarkJobRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BenchmarkJobSpec) DeepCopyInto(out *BenchmarkJobSpec) { + *out = *in + if in.Target != nil { + in, out := &in.Target, &out.Target + *out = new(BenchmarkTarget) + **out = **in + } + if in.Dataset != nil { + in, out := &in.Dataset, &out.Dataset + *out = new(BenchmarkDataset) + (*in).DeepCopyInto(*out) + } + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]*config.BenchmarkJobRule, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(config.BenchmarkJobRule) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkJobSpec. +func (in *BenchmarkJobSpec) DeepCopy() *BenchmarkJobSpec { + if in == nil { + return nil + } + out := new(BenchmarkJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BenchmarkTarget) DeepCopyInto(out *BenchmarkTarget) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkTarget. +func (in *BenchmarkTarget) DeepCopy() *BenchmarkTarget { + if in == nil { + return nil + } + out := new(BenchmarkTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdBenchmarkJob) DeepCopyInto(out *ValdBenchmarkJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkOperator. +func (in *ValdBenchmarkJob) DeepCopy() *ValdBenchmarkJob { + if in == nil { + return nil + } + out := new(ValdBenchmarkJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValdBenchmarkJob) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdBenchmarkJobList) DeepCopyInto(out *ValdBenchmarkJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ValdBenchmarkJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkOperatorList. +func (in *ValdBenchmarkJobList) DeepCopy() *ValdBenchmarkJobList { + if in == nil { + return nil + } + out := new(ValdBenchmarkJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValdBenchmarkJobList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/internal/k8s/vald/benchmark/api/v1/scenario_types.go b/internal/k8s/vald/benchmark/api/v1/scenario_types.go new file mode 100644 index 00000000000..0ef2adb341b --- /dev/null +++ b/internal/k8s/vald/benchmark/api/v1/scenario_types.go @@ -0,0 +1,144 @@ +// +// Copyright (C) 2019-2022 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" +) + +type ValdBenchmarkScenarioSpec struct { + Target *BenchmarkTarget `json:"target,omitempty"` + Dataset *BenchmarkDataset `json:"dataset,omitempty"` + Jobs []*BenchmarkJobSpec `json:"jobs,omitempty"` +} + +type ValdBenchmarkScenarioStatus string + +const ( + BenchmarkScenarioNotReady ValdBenchmarkScenarioStatus = "NotReady" + BenchmarkScenarioAvailable ValdBenchmarkScenarioStatus = "Available" + BenchmarkScenarioHealthy ValdBenchmarkScenarioStatus = "Healthy" +) + +type ValdBenchmarkScenario struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ValdBenchmarkScenarioSpec `json:"spec,omitempty"` + Status ValdBenchmarkScenarioStatus `json:"status,omitempty"` +} + +type ValdBenchmarkScenarioList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ValdBenchmarkScenario `json:"items"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdBenchmarkScenario) DeepCopyInto(out *ValdBenchmarkScenario) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkScenario. +func (in *ValdBenchmarkScenario) DeepCopy() *ValdBenchmarkScenario { + if in == nil { + return nil + } + out := new(ValdBenchmarkScenario) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValdBenchmarkScenario) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdBenchmarkScenarioList) DeepCopyInto(out *ValdBenchmarkScenarioList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ValdBenchmarkScenario, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkScenarioList. +func (in *ValdBenchmarkScenarioList) DeepCopy() *ValdBenchmarkScenarioList { + if in == nil { + return nil + } + out := new(ValdBenchmarkScenarioList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValdBenchmarkScenarioList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValdBenchmarkScenarioSpec) DeepCopyInto(out *ValdBenchmarkScenarioSpec) { + *out = *in + if in.Target != nil { + in, out := &in.Target, &out.Target + *out = new(BenchmarkTarget) + **out = **in + } + if in.Dataset != nil { + in, out := &in.Dataset, &out.Dataset + *out = new(BenchmarkDataset) + (*in).DeepCopyInto(*out) + } + if in.Jobs != nil { + in, out := &in.Jobs, &out.Jobs + *out = make([]*BenchmarkJobSpec, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(BenchmarkJobSpec) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BenchmarkScenarioSpec. +func (in *ValdBenchmarkScenarioSpec) DeepCopy() *ValdBenchmarkScenarioSpec { + if in == nil { + return nil + } + out := new(ValdBenchmarkScenarioSpec) + in.DeepCopyInto(out) + return out +} diff --git a/internal/k8s/vald/benchmark/job/doc.go b/internal/k8s/vald/benchmark/job/doc.go new file mode 100644 index 00000000000..678bce94ab7 --- /dev/null +++ b/internal/k8s/vald/benchmark/job/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 job provides benchmark job crd information and preriodically update +package job diff --git a/internal/k8s/vald/benchmark/job/job.go b/internal/k8s/vald/benchmark/job/job.go new file mode 100644 index 00000000000..3cfed17ed6d --- /dev/null +++ b/internal/k8s/vald/benchmark/job/job.go @@ -0,0 +1,140 @@ +// +// Copyright (C) 2019-2022 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 job + +import ( + "context" + "time" + + "github.com/vdaas/vald/internal/k8s" + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + "github.com/vdaas/vald/internal/log" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "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" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +type BenchmarkJobWatcher k8s.ResourceController + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "vald.benchmark.job", 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 +) + +type reconciler struct { + mgr manager.Manager + name string + namespaces []string + onError func(err error) + onReconcile func(ctx context.Context, jobList map[string]v1.ValdBenchmarkJob) + lopts []client.ListOption +} + +func New(opts ...Option) (BenchmarkJobWatcher, error) { + r := new(reconciler) + for _, opt := range append(defaultOpts, opts...) { + // TODO: impl error handling after implement functional option + opt(r) + } + return r, nil +} + +func (r *reconciler) AddListOpts(opt client.ListOption) { + if opt == nil { + return + } + if r.lopts == nil { + r.lopts = make([]client.ListOption, 0, 1) + } + r.lopts = append(r.lopts, opt) +} + +func (r *reconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) { + bj := new(v1.ValdBenchmarkJobList) + + if r.lopts == nil { + err = r.mgr.GetClient().List(ctx, bj, r.lopts...) + } else { + err = r.mgr.GetClient().List(ctx, bj) + } + + if err != nil { + if r.onError != nil { + r.onError(err) + } + res = reconcile.Result{ + Requeue: true, + RequeueAfter: time.Millisecond * 100, + } + if errors.IsNotFound(err) { + log.Errorf("not found: %s", err) + return reconcile.Result{ + Requeue: true, + RequeueAfter: time.Second, + }, nil + } + return + } + + jobs := make(map[string]v1.ValdBenchmarkJob, 0) + for _, item := range bj.Items { + name := item.GetName() + jobs[name] = item + } + + if r.onReconcile != nil { + r.onReconcile(ctx, jobs) + } + return +} + +func (r *reconciler) GetName() string { + return r.name +} + +func (r *reconciler) NewReconciler(ctx context.Context, mgr manager.Manager) reconcile.Reconciler { + if r.mgr == nil && mgr != nil { + r.mgr = mgr + } + + v1.AddToScheme(r.mgr.GetScheme()) + + return r +} + +func (r *reconciler) For() (client.Object, []builder.ForOption) { + return new(v1.ValdBenchmarkJob), nil +} + +func (r *reconciler) Owns() (client.Object, []builder.OwnsOption) { + return nil, nil +} + +func (r *reconciler) Watches() (*source.Kind, handler.EventHandler, []builder.WatchesOption) { + // return &source.Kind{Type: new(corev1.Pod)}, &handler.EnqueueRequestForObject{} + return nil, nil, nil +} diff --git a/internal/k8s/vald/benchmark/job/option.go b/internal/k8s/vald/benchmark/job/option.go new file mode 100644 index 00000000000..bf834ec347d --- /dev/null +++ b/internal/k8s/vald/benchmark/job/option.go @@ -0,0 +1,68 @@ +// +// Copyright (C) 2019-2022 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 job + +import ( + "context" + + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type Option func(*reconciler) error + +var defaultOpts = []Option{} + +// WithControllerName returns Option that sets r.name. +func WithControllerName(name string) Option { + return func(r *reconciler) error { + r.name = name + return nil + } +} + +// WithManager returns Option that sets r.mgr. +func WithManager(mgr manager.Manager) Option { + return func(r *reconciler) error { + r.mgr = mgr + return nil + } +} + +// WithNamespaces returns Option to set the namespace. +func WithNamespaces(nss ...string) Option { + return func(r *reconciler) error { + r.namespaces = nss + return nil + } +} + +// WithOnErrorFunc returns Option that sets r.onError. +func WithOnErrorFunc(f func(err error)) Option { + return func(r *reconciler) error { + r.onError = f + return nil + } +} + +// WithOnReconcileFunc returns Option that sets r.onReconcile. +func WithOnReconcileFunc(f func(ctx context.Context, jobList map[string]v1.ValdBenchmarkJob)) Option { + return func(r *reconciler) error { + r.onReconcile = f + return nil + } +} diff --git a/internal/k8s/vald/benchmark/scenario/doc.go b/internal/k8s/vald/benchmark/scenario/doc.go new file mode 100644 index 00000000000..c3800f67233 --- /dev/null +++ b/internal/k8s/vald/benchmark/scenario/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 operator provides benchmark operator crd information and preriodically update +package scenario diff --git a/internal/k8s/vald/benchmark/scenario/option.go b/internal/k8s/vald/benchmark/scenario/option.go new file mode 100644 index 00000000000..fcbc83af47f --- /dev/null +++ b/internal/k8s/vald/benchmark/scenario/option.go @@ -0,0 +1,68 @@ +// +// Copyright (C) 2019-2022 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 scenario + +import ( + "context" + + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type Option func(*reconciler) error + +var defaultOpts = []Option{} + +// WithControllerName returns Option that sets r.name. +func WithControllerName(name string) Option { + return func(r *reconciler) error { + r.name = name + return nil + } +} + +// WithManager returns Option that sets r.mgr. +func WithManager(mgr manager.Manager) Option { + return func(r *reconciler) error { + r.mgr = mgr + return nil + } +} + +// WithNamespaces returns Option to set the namespace. +func WithNamespaces(nss ...string) Option { + return func(r *reconciler) error { + r.namespaces = nss + return nil + } +} + +// WithOnErrorFunc returns Option that sets r.onError. +func WithOnErrorFunc(f func(err error)) Option { + return func(r *reconciler) error { + r.onError = f + return nil + } +} + +// WithOnReconcileFunc returns Option that sets r.onReconcile. +func WithOnReconcileFunc(f func(ctx context.Context, scenarioList map[string]v1.ValdBenchmarkScenario)) Option { + return func(r *reconciler) error { + r.onReconcile = f + return nil + } +} diff --git a/internal/k8s/vald/benchmark/scenario/scenario.go b/internal/k8s/vald/benchmark/scenario/scenario.go new file mode 100644 index 00000000000..c3e99b49870 --- /dev/null +++ b/internal/k8s/vald/benchmark/scenario/scenario.go @@ -0,0 +1,128 @@ +// +// Copyright (C) 2019-2022 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 scenario + +import ( + "context" + "time" + + "github.com/vdaas/vald/internal/k8s" + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + "github.com/vdaas/vald/internal/log" + "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" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +type BenchmarkScenarioWatcher k8s.ResourceController + +type reconciler struct { + mgr manager.Manager + name string + namespaces []string + onError func(err error) + onReconcile func(ctx context.Context, operatorList map[string]v1.ValdBenchmarkScenario) + lopts []client.ListOption +} + +func New(opts ...Option) (BenchmarkScenarioWatcher, error) { + r := new(reconciler) + for _, opt := range append(defaultOpts, opts...) { + // TODO: impl error handling after implement functional option + opt(r) + } + return r, nil +} + +func (r *reconciler) AddListOpts(opt client.ListOption) { + if opt == nil { + return + } + if r.lopts == nil { + r.lopts = make([]client.ListOption, 0, 1) + } + r.lopts = append(r.lopts, opt) +} + +func (r *reconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) { + bs := new(v1.ValdBenchmarkScenarioList) + + if r.lopts == nil { + err = r.mgr.GetClient().List(ctx, bs, r.lopts...) + } else { + err = r.mgr.GetClient().List(ctx, bs) + } + + if err != nil { + if r.onError != nil { + r.onError(err) + } + res = reconcile.Result{ + Requeue: true, + RequeueAfter: time.Millisecond * 100, + } + if errors.IsNotFound(err) { + log.Errorf("not found: %s", err) + return reconcile.Result{ + Requeue: true, + RequeueAfter: time.Second, + }, nil + } + return + } + scenarios := make(map[string]v1.ValdBenchmarkScenario, 0) + for _, item := range bs.Items { + name := item.Name + scenarios[name] = item + } + + if r.onReconcile != nil { + r.onReconcile(ctx, scenarios) + } + + return +} + +func (r *reconciler) GetName() string { + return r.name +} + +func (r *reconciler) NewReconciler(ctx context.Context, mgr manager.Manager) reconcile.Reconciler { + if r.mgr == nil && mgr != nil { + r.mgr = mgr + } + v1.AddToScheme(r.mgr.GetScheme()) + + return r +} + +func (r *reconciler) For() (client.Object, []builder.ForOption) { + return new(v1.ValdBenchmarkScenario), nil +} + +func (r *reconciler) Owns() (client.Object, []builder.OwnsOption) { + return nil, nil +} + +func (r *reconciler) Watches() (*source.Kind, handler.EventHandler, []builder.WatchesOption) { + // return &source.Kind{Type: new(corev1.Pod)}, &handler.EnqueueRequestForObject{} + return &source.Kind{Type: new(v1.ValdBenchmarkScenario)}, &handler.EnqueueRequestForObject{}, nil +} diff --git a/internal/test/data/hdf5/doc.go b/internal/test/data/hdf5/doc.go new file mode 100644 index 00000000000..f8650c005ef --- /dev/null +++ b/internal/test/data/hdf5/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 hdf5 is load hdf5 file +package hdf5 diff --git a/internal/test/data/hdf5/hdf5.go b/internal/test/data/hdf5/hdf5.go new file mode 100644 index 00000000000..e83965ec8c1 --- /dev/null +++ b/internal/test/data/hdf5/hdf5.go @@ -0,0 +1,268 @@ +// +// Copyright (C) 2019-2022 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 hdf5 is load hdf5 file +package hdf5 + +import ( + "os" + "reflect" + + "gonum.org/v1/hdf5" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/io" + "github.com/vdaas/vald/internal/net/http/client" +) + +type Data interface { + Download() error + Read() error + GetName() DatasetName + GetPath() string + GetTrain() [][]float32 + GetTest() [][]float32 + GetNeighbors() [][]int +} + +type DatasetName int + +const ( + FashionMNIST784Euclidean DatasetName = iota +) + +func (d DatasetName) String() string { + switch d { + case FashionMNIST784Euclidean: + return "fashion-mnist-784-euc" + default: + return "" + } +} + +type DatasetUrl int + +const ( + FashionMNIST784EuclideanUrl DatasetUrl = iota +) + +func (d DatasetUrl) String() string { + switch d { + case FashionMNIST784EuclideanUrl: + return "http://ann-benchmarks.com/fashion-mnist-784-euclidean.hdf5" + default: + return "" + } +} + +type hdf5Key int + +const ( + Train hdf5Key = iota + Test + Neighors +) + +func (h hdf5Key) String() string { + switch h { + case Train: + return "train" + case Test: + return "test" + case Neighors: + return "neighbors" + default: + return "" + } +} + +type data struct { + name DatasetName + path string + train [][]float32 + test [][]float32 + neighbors [][]int +} + +func New(opts ...Option) (Data, error) { + d := new(data) + for _, opt := range append(defaultOptions, opts...) { + if err := opt(d); err != nil { + return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + } + } + return d, nil +} + +// Get downloads the hdf5 file. +// https://github.com/erikbern/ann-benchmarks/#data-sets +func (d *data) Download() error { + switch d.name { + case FashionMNIST784Euclidean: + return downloadFile(FashionMNIST784EuclideanUrl.String(), d.path) + default: + return errors.NewErrInvalidOption("name", d.name) + } +} + +func (d *data) Read() error { + f, err := hdf5.OpenFile(d.path, hdf5.F_ACC_RDONLY) + if err != nil { + return err + } + defer f.Close() + + // load training data + train, err := ReadDatasetF32(f, Train) + if err != nil { + return err + } + d.train = train + + // load test data + test, err := ReadDatasetF32(f, Test) + if err != nil { + return err + } + d.test = test + + // load neighbors + neighbors32, err := ReadDatasetI32(f, Neighors) + if err != nil { + return err + } + neighbors := make([][]int, len(neighbors32)) + for i, ns := range neighbors32 { + neighbors[i] = make([]int, len(ns)) + for j, n := range ns { + neighbors[i][j] = int(n) + } + } + d.neighbors = neighbors + + return nil +} + +func (d *data) GetName() DatasetName { + return d.name +} + +func (d *data) GetPath() string { + return d.path +} + +func (d *data) GetTrain() [][]float32 { + return d.train +} + +func (d *data) GetTest() [][]float32 { + return d.test +} + +func (d *data) GetNeighbors() [][]int { + return d.neighbors +} + +func downloadFile(url, path string) error { + if len(path) == 0 { + return errors.NewErrInvalidOption("no path is specified", path) + } + cli, err := client.New() + if err != nil { + return err + } + + resp, err := cli.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return errors.ErrInvalidStatusCode(resp.StatusCode) + } + + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return err + } + + return nil +} + +func ReadDatasetF32(file *hdf5.File, key hdf5Key) ([][]float32, error) { + data, err := file.OpenDataset(key.String()) + if err != nil { + return nil, err + } + defer data.Close() + + dataspace := data.Space() + defer dataspace.Close() + + dims, _, err := dataspace.SimpleExtentDims() + if err != nil { + return nil, err + } + height, width := int(dims[0]), int(dims[1]) + + rawFloats := make([]float32, dataspace.SimpleExtentNPoints()) + if err := data.Read(&rawFloats); err != nil { + return nil, err + } + + vecs := make([][]float32, height) + for i := 0; i < height; i++ { + vecs[i] = rawFloats[i*width : i*width+width] + } + + return vecs, nil +} + +func ReadDatasetI32(file *hdf5.File, key hdf5Key) ([][]int32, error) { + data, err := file.OpenDataset(key.String()) + if err != nil { + return nil, err + } + defer data.Close() + + dataspace := data.Space() + defer dataspace.Close() + + dims, _, err := dataspace.SimpleExtentDims() + if err != nil { + return nil, err + } + height, width := int(dims[0]), int(dims[1]) + + rawFloats := make([]int32, dataspace.SimpleExtentNPoints()) + if err := data.Read(&rawFloats); err != nil { + return nil, err + } + + vecs := make([][]int32, height) + for i := 0; i < height; i++ { + vecs[i] = rawFloats[i*width : i*width+width] + } + + return vecs, nil +} diff --git a/internal/test/data/hdf5/option.go b/internal/test/data/hdf5/option.go new file mode 100644 index 00000000000..c18bcbd5e44 --- /dev/null +++ b/internal/test/data/hdf5/option.go @@ -0,0 +1,59 @@ +// +// Copyright (C) 2019-2022 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 hdf5 is load hdf5 file +package hdf5 + +import ( + "github.com/vdaas/vald/internal/errors" +) + +type Option func(d *data) error + +var defaultOptions = []Option{ + WithName(FashionMNIST784Euclidean), + WithFilePath("./data"), +} + +func WithNameByString(n string) Option { + var name DatasetName + switch n { + case FashionMNIST784Euclidean.String(): + name = FashionMNIST784Euclidean + } + return WithName(name) +} + +func WithName(dn DatasetName) Option { + return func(d *data) error { + switch dn { + case FashionMNIST784Euclidean: + d.name = dn + default: + return errors.NewErrInvalidOption("dataname", dn) + } + return nil + } +} + +func WithFilePath(f string) Option { + return func(d *data) error { + if len(f) != 0 { + d.path = f + } + return nil + } +} diff --git a/pkg/tools/benchmark/job/README.md b/pkg/tools/benchmark/job/README.md new file mode 100644 index 00000000000..75caad4fedc --- /dev/null +++ b/pkg/tools/benchmark/job/README.md @@ -0,0 +1 @@ +# vald benchmark job diff --git a/pkg/tools/benchmark/job/config/config.go b/pkg/tools/benchmark/job/config/config.go new file mode 100644 index 00000000000..36620e1dbc5 --- /dev/null +++ b/pkg/tools/benchmark/job/config/config.go @@ -0,0 +1,309 @@ +// +// Copyright (C) 2019-2022 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 stores all server application settings +package config + +import ( + "context" + "os" + + "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/k8s/client" + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + "github.com/vdaas/vald/internal/log" +) + +// GlobalConfig is type alias for config.GlobalConfig +type GlobalConfig = config.GlobalConfig + +// Config represent a application setting data content (config.yaml). +// In K8s environment, this configuration is stored in K8s ConfigMap. +type Config struct { + config.GlobalConfig `json:",inline" yaml:",inline"` + + // Server represent all server configuration + Server *config.Servers `json:"server_config" yaml:"server_config"` + + // Observability represent observability configurations + Observability *config.Observability `json:"observability" yaml:"observability"` + + // Job represents benchmark job configurations + Job *config.BenchmarkJob `json:"job" yaml:"job"` +} + +var ( + NAMESPACE = os.Getenv("CRD_NAMESPACE") + NAME = os.Getenv("CRD_NAME") +) + +// NewConfig represents the set config from the given setting file path. +func NewConfig(ctx context.Context, path string) (cfg *Config, err error) { + err = config.Read(path, &cfg) + if err != nil { + return nil, err + } + + if cfg != nil { + cfg.Bind() + } + + if cfg.Server != nil { + cfg.Server = cfg.Server.Bind() + } else { + cfg.Server = new(config.Servers) + } + + if cfg.Observability != nil { + cfg.Observability = cfg.Observability.Bind() + } else { + cfg.Observability = new(config.Observability) + } + + if cfg.Job != nil { + cfg.Job = cfg.Job.Bind() + } else { + cfg.Job = new(config.BenchmarkJob) + } + + if cfg.Job.ClientConfig == nil { + cfg.Job.ClientConfig = new(config.GRPCClient) + } + + // Get config from applied ValdBenchmarkJob custom resource + var jobResource v1.ValdBenchmarkJob + c, err := client.New(client.WithSchemeBuilder(*v1.SchemeBuilder)) + if err != nil { + log.Warn(err.Error()) + } + err = c.Get(ctx, NAME, NAMESPACE, &jobResource) + if err != nil { + log.Warn(err.Error()) + } else { + cfg.Job.Target = (*config.BenchmarkTarget)(jobResource.Spec.Target) + cfg.Job.Dataset = (*config.BenchmarkDataset)(jobResource.Spec.Dataset) + cfg.Job.Replica = jobResource.Spec.Replica + cfg.Job.Repetition = jobResource.Spec.Repetition + cfg.Job.JobType = jobResource.Spec.JobType + cfg.Job.Rules = jobResource.Spec.Rules + cfg.Job.InsertConfig = jobResource.Spec.InsertConfig + cfg.Job.UpdateConfig = jobResource.Spec.UpdateConfig + cfg.Job.UpsertConfig = jobResource.Spec.UpsertConfig + cfg.Job.SearchConfig = jobResource.Spec.SearchConfig + cfg.Job.RemoveConfig = jobResource.Spec.RemoveConfig + cfg.Job.ClientConfig = jobResource.Spec.ClientConfig + } + + return cfg, nil +} + +// func FakeData() { +// d := Config{ +// Version: "v0.0.1", +// Server: &config.Servers{ +// Servers: []*config.Server{ +// { +// Name: "agent-rest", +// Host: "127.0.0.1", +// Port: 8080, +// Mode: "REST", +// ProbeWaitTime: "3s", +// HTTP: &config.HTTP{ +// ShutdownDuration: "5s", +// HandlerTimeout: "5s", +// IdleTimeout: "2s", +// ReadHeaderTimeout: "1s", +// ReadTimeout: "1s", +// WriteTimeout: "1s", +// }, +// }, +// { +// Name: "agent-grpc", +// Host: "127.0.0.1", +// Port: 8082, +// Mode: "GRPC", +// }, +// }, +// MetricsServers: []*config.Server{ +// { +// Name: "pprof", +// Host: "127.0.0.1", +// Port: 6060, +// Mode: "REST", +// ProbeWaitTime: "3s", +// HTTP: &config.HTTP{ +// ShutdownDuration: "5s", +// HandlerTimeout: "5s", +// IdleTimeout: "2s", +// ReadHeaderTimeout: "1s", +// ReadTimeout: "1s", +// WriteTimeout: "1s", +// }, +// }, +// }, +// HealthCheckServers: []*config.Server{ +// { +// Name: "livenesss", +// Host: "127.0.0.1", +// Port: 3000, +// }, +// { +// Name: "readiness", +// Host: "127.0.0.1", +// Port: 3001, +// }, +// }, +// StartUpStrategy: []string{ +// "livenesss", +// "pprof", +// "agent-grpc", +// "agent-rest", +// "readiness", +// }, +// ShutdownStrategy: []string{ +// "readiness", +// "agent-rest", +// "agent-grpc", +// "pprof", +// "livenesss", +// }, +// FullShutdownDuration: "30s", +// TLS: &config.TLS{ +// Enabled: false, +// Cert: "/path/to/cert", +// Key: "/path/to/key", +// CA: "/path/to/ca", +// }, +// }, +// Job: &config.BenchmarkJob{ +// Target: &config.BenchmarkTarget{ +// Host: "vald-lb-gateway.svc.local", +// Port: 8081, +// }, +// Dataset: &config.BenchmarkDataset{ +// Name: "fashion-mnist", +// Group: "train", +// Indexes: 10000, +// Range: &config.BenchmarkDatasetRange{ +// Start: 0, +// End: 10000, +// }, +// }, +// Replica: 1, +// Repetition: 1, +// JobType: "search", +// InsertConfig: &config.InsertConfig{}, +// UpdateConfig: &config.UpdateConfig{}, +// UpsertConfig: &config.UpsertConfig{}, +// SearchConfig: &config.SearchConfig{}, +// RemoveConfig: &config.RemoveConfig{}, +// ClientConfig: &config.GRPCClient{ +// Addrs: []string{}, +// HealthCheckDuration: "1s", +// ConnectionPool: &config.ConnectionPool{ +// ResolveDNS: true, +// EnableRebalance: true, +// RebalanceDuration: "30m", +// Size: 3, +// OldConnCloseDuration: "2m", +// }, +// Backoff: &config.Backoff{ +// InitialDuration: "5ms", +// BackoffTimeLimit: "5s", +// MaximumDuration: "5s", +// JitterLimit: "100ms", +// BackoffFactor: 1.1, +// RetryCount: 100, +// EnableErrorLog: true, +// }, +// CircuitBreaker: &config.CircuitBreaker{ +// ClosedErrorRate: 0.7, +// HalfOpenErrorRate: 0.5, +// MinSamples: 1000, +// OpenTimeout: "1s", +// ClosedRefreshTimeout: "10s", +// }, +// CallOption: &config.CallOption{ +// WaitForReady: true, +// MaxRetryRPCBufferSize: 0, +// MaxRecvMsgSize: 0, +// MaxSendMsgSize: 0, +// }, +// DialOption: &config.DialOption{ +// WriteBufferSize: 0, +// ReadBufferSize: 0, +// InitialWindowSize: 0, +// InitialConnectionWindowSize: 0, +// MaxMsgSize: 0, +// BackoffMaxDelay: "120s", +// BackoffBaseDelay: "1s", +// BackoffJitter: 0.2, +// BackoffMultiplier: 1.6, +// MinimumConnectionTimeout: "20s", +// EnableBackoff: true, +// Insecure: true, +// Timeout: "", +// Interceptors: []string{}, +// Net: &config.Net{ +// DNS: &config.DNS{ +// CacheEnabled: true, +// RefreshDuration: "30m", +// CacheExpiration: "1h", +// }, +// Dialer: &config.Dialer{ +// Timeout: "", +// Keepalive: "", +// FallbackDelay: "", +// DualStackEnabled: true, +// }, +// TLS: &config.TLS{ +// Enabled: false, +// Cert: "path/to/cert", +// Key: "path/to/key", +// CA: "path/to/ca", +// InsecureSkipVerify: false, +// }, +// SocketOption: &config.SocketOption{ +// ReusePort: true, +// ReuseAddr: true, +// TCPFastOpen: true, +// TCPNoDelay: true, +// TCPQuickAck: true, +// TCPCork: false, +// TCPDeferAccept: true, +// IPTransparent: false, +// IPRecoverDestinationAddr: false, +// }, +// }, +// Keepalive: &config.GRPCClientKeepalive{ +// Time: "120s", +// Timeout: "30s", +// PermitWithoutStream: true, +// }, +// }, +// TLS: &config.TLS{ +// Enabled: false, +// Cert: "path/to/cert", +// Key: "path/to/key", +// CA: "path/to/ca", +// InsecureSkipVerify: false, +// }, +// }, +// Rules: []*config.BenchmarkJobRule{}, +// }, +// } +// fmt.Println(config.ToRawYaml(d)) +// } diff --git a/pkg/tools/benchmark/job/config/config_test.go b/pkg/tools/benchmark/job/config/config_test.go new file mode 100644 index 00000000000..dc35aae48ba --- /dev/null +++ b/pkg/tools/benchmark/job/config/config_test.go @@ -0,0 +1,127 @@ +// +// Copyright (C) 2019-2022 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 setting stores all server application settings +package config + +import ( + "context" + "io/fs" + "os" + "testing" + + "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/file" + "github.com/vdaas/vald/internal/test/comparator" + "github.com/vdaas/vald/internal/test/goleak" +) + +func TestNewConfig(t *testing.T) { + t.Parallel() + type args struct { + path string + } + type want struct { + wantCfg *Config + err error + } + type test struct { + name string + args args + want want + checkFunc func(want, *Config, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotCfg *Config, 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 diff := comparator.Diff(gotCfg, w.wantCfg, + comparator.IgnoreTypes(config.Observability{})); diff != "" { + return errors.New(diff) + } + return nil + } + tests := []test{ + func() test { + name := "/home/vankichi/Documents/vald-read-test.yaml" + path := name + return test{ + name: "return error when can't read file", + args: args{ + path: path, + }, + beforeFunc: func(t *testing.T, a args) { + t.Helper() + f, err := file.Open(a.path, os.O_CREATE, fs.ModeIrregular) + if err != nil { + if errors.Is(err, fs.ErrPermission) { + return + } + t.Error(err) + } + if err := f.Close(); err != nil { + t.Error(err) + } + }, + checkFunc: func(w want, gotCfg *Config, err error) error { + if errors.Is(err, fs.ErrPermission) { + return nil + } + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + }, + afterFunc: func(t *testing.T, a args) { + t.Helper() + if err := os.Remove(a.path); err != nil { + t.Fatal(err) + } + }, + want: want{ + wantCfg: nil, + err: errors.ErrUnsupportedConfigFileType(".yaml"), + }, + } + }(), + } + + 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 + } + + gotCfg, err := NewConfig(context.Background(), test.args.path) + if err := checkFunc(test.want, gotCfg, err); err != nil { + tt.Errorf("error = %v, got = %#v", err, gotCfg) + } + }) + } +} diff --git a/pkg/tools/benchmark/job/config/doc.go b/pkg/tools/benchmark/job/config/doc.go new file mode 100644 index 00000000000..57ef74c464a --- /dev/null +++ b/pkg/tools/benchmark/job/config/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 stores all server application settings +package config diff --git a/pkg/tools/benchmark/job/handler/doc.go b/pkg/tools/benchmark/job/handler/doc.go new file mode 100644 index 00000000000..f1014141ea4 --- /dev/null +++ b/pkg/tools/benchmark/job/handler/doc.go @@ -0,0 +1,17 @@ +// +// Copyright (C) 2019-2022 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/tools/benchmark/job/handler/grpc/handler.go b/pkg/tools/benchmark/job/handler/grpc/handler.go new file mode 100644 index 00000000000..85be642c7d8 --- /dev/null +++ b/pkg/tools/benchmark/job/handler/grpc/handler.go @@ -0,0 +1,56 @@ +// +// Copyright (C) 2019-2022 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 grpc server logic +package grpc + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/benchmark" + "github.com/vdaas/vald/internal/singleflight" + "github.com/vdaas/vald/pkg/tools/benchmark/job/service" +) + +type Benchmark interface { + benchmark.JobServer + Start(context.Context) +} + +type server struct { + benchmark.UnimplementedJobServer + + job service.Job + group singleflight.Group +} + +func New(opts ...Option) (bm Benchmark, err error) { + b := new(server) + + for _, opt := range append(defaultOpts, opts...) { + err = opt(b) + if err != nil { + return nil, err + } + } + + b.group = singleflight.New() + + return b, nil +} + +func (s *server) Start(ctx context.Context) { +} diff --git a/pkg/tools/benchmark/job/handler/grpc/option.go b/pkg/tools/benchmark/job/handler/grpc/option.go new file mode 100644 index 00000000000..4319ef3a748 --- /dev/null +++ b/pkg/tools/benchmark/job/handler/grpc/option.go @@ -0,0 +1,22 @@ +// +// Copyright (C) 2019-2022 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 grpc server logic +package grpc + +type Option func(*server) error + +var defaultOpts = []Option{} diff --git a/pkg/tools/benchmark/job/handler/rest/handler.go b/pkg/tools/benchmark/job/handler/rest/handler.go new file mode 100644 index 00000000000..f752dbbbbf4 --- /dev/null +++ b/pkg/tools/benchmark/job/handler/rest/handler.go @@ -0,0 +1,38 @@ +// +// Copyright (C) 2019-2022 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 provides rest api logic +package rest + +import ( + "github.com/vdaas/vald/apis/grpc/v1/benchmark" +) + +type Handler interface{} + +type handler struct { + js benchmark.JobServer +} + +func New(opts ...Option) Handler { + h := new(handler) + + for _, opt := range append(defaultOpts, opts...) { + opt(h) + } + + return h +} diff --git a/pkg/tools/benchmark/job/handler/rest/option.go b/pkg/tools/benchmark/job/handler/rest/option.go new file mode 100644 index 00000000000..ca3aee436d8 --- /dev/null +++ b/pkg/tools/benchmark/job/handler/rest/option.go @@ -0,0 +1,22 @@ +// +// Copyright (C) 2019-2022 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 provides rest api logic +package rest + +type Option func(*handler) + +var defaultOpts = []Option{} diff --git a/pkg/tools/benchmark/job/router/doc.go b/pkg/tools/benchmark/job/router/doc.go new file mode 100644 index 00000000000..053c9aeb49c --- /dev/null +++ b/pkg/tools/benchmark/job/router/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 provides implementation of Go API for routing http Handler wrapped by rest.Func +package router diff --git a/pkg/tools/benchmark/job/router/option.go b/pkg/tools/benchmark/job/router/option.go new file mode 100644 index 00000000000..063f5ce6f2c --- /dev/null +++ b/pkg/tools/benchmark/job/router/option.go @@ -0,0 +1,47 @@ +// +// Copyright (C) 2019-2022 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 provides implementation of Go API for routing http Handler wrapped by rest.Func +package router + +import ( + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/pkg/tools/benchmark/job/handler/rest" +) + +type Option func(*router) + +var defaultOpts = []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 + } +} + +func WithErrGroup(eg errgroup.Group) Option { + return func(r *router) { + r.eg = eg + } +} diff --git a/pkg/tools/benchmark/job/router/router.go b/pkg/tools/benchmark/job/router/router.go new file mode 100644 index 00000000000..e032578e97b --- /dev/null +++ b/pkg/tools/benchmark/job/router/router.go @@ -0,0 +1,52 @@ +// +// Copyright (C) 2019-2022 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 provides implementation of Go API for routing http Handler wrapped by rest.Func +package router + +import ( + "net/http" + + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/net/http/middleware" + "github.com/vdaas/vald/internal/net/http/routing" + "github.com/vdaas/vald/pkg/tools/benchmark/job/handler/rest" +) + +type router struct { + handler rest.Handler + eg errgroup.Group + timeout string +} + +// New returns REST route&method information from handler interface +func New(opts ...Option) http.Handler { + r := new(router) + + for _, opt := range append(defaultOpts, opts...) { + opt(r) + } + + return routing.New( + routing.WithMiddleware( + middleware.NewTimeout( + middleware.WithTimeout(r.timeout), + middleware.WithErrorGroup(r.eg), + )), + routing.WithRoutes([]routing.Route{ + // TODO add REST API interface here + }...)) +} diff --git a/pkg/tools/benchmark/job/service/doc.go b/pkg/tools/benchmark/job/service/doc.go new file mode 100644 index 00000000000..eb86a28c5bf --- /dev/null +++ b/pkg/tools/benchmark/job/service/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service diff --git a/pkg/tools/benchmark/job/service/job.go b/pkg/tools/benchmark/job/service/job.go new file mode 100644 index 00000000000..5287d9c7f1f --- /dev/null +++ b/pkg/tools/benchmark/job/service/job.go @@ -0,0 +1,188 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service + +import ( + "context" + "os" + "reflect" + "syscall" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/internal/client/v1/client/vald" + "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" + "github.com/vdaas/vald/internal/test/data/hdf5" +) + +type Job interface { + PreStart(context.Context) error + Start(context.Context) (<-chan error, error) + Stop(context.Context) error +} + +type jobType int + +const ( + USERDEFINED jobType = iota + SEARCH +) + +func (jt jobType) String() string { + switch jt { + case USERDEFINED: + return "userdefined" + case SEARCH: + return "search" + } + return "" +} + +type job struct { + eg errgroup.Group + dimension int + dataset *config.BenchmarkDataset + jobType jobType + jobFunc func(context.Context, chan error) error + insertConfig *config.InsertConfig + updateConfig *config.UpdateConfig + upsertConfig *config.UpsertConfig + searchConfig *config.SearchConfig + removeConfig *config.RemoveConfig + client vald.Client + hdf5 hdf5.Data +} + +func New(opts ...Option) (Job, error) { + j := new(job) + for _, opt := range append(defaultOpts, opts...) { + if err := opt(j); err != nil { + return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + } + } + if j.jobFunc == nil { + switch j.jobType { + case USERDEFINED: + opt := WithJobFunc(j.jobFunc) + err := opt(j) + return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + case SEARCH: + j.jobFunc = j.search + } + } else if j.jobType != USERDEFINED { + log.Warnf("[benchmark job] userdefined jobFunc is set but jobType is set %s", j.jobType.String()) + } + return j, nil +} + +func (j *job) PreStart(ctx context.Context) error { + log.Infof("[benchmark job] start download dataset of %s", j.hdf5.GetName().String()) + if err := j.hdf5.Download(); err != nil { + return err + } + log.Infof("[benchmark job] success download dataset of %s", j.hdf5.GetName().String()) + log.Infof("[benchmark job] start load dataset of %s", j.hdf5.GetName().String()) + if err := j.hdf5.Read(); err != nil { + return err + } + log.Infof("[benchmark job] success load dataset of %s", j.hdf5.GetName().String()) + return nil +} + +func (j *job) Start(ctx context.Context) (<-chan error, error) { + ech := make(chan error, 3) + cech, err := j.client.Start(ctx) + if err != nil { + log.Error("[benchmark job] failed to start connection monitor") + return nil, err + } + j.eg.Go(func() error { + for { + select { + case <-ctx.Done(): + return nil + case ech <- <-cech: + } + } + }) + + j.eg.Go(func() (err error) { + defer func() { + p, perr := os.FindProcess(os.Getpid()) + if perr != nil { + log.Error(perr) + return + } + if err != nil { + select { + case <-ctx.Done(): + ech <- errors.Wrap(err, ctx.Err().Error()) + case ech <- err: + } + } + if err := p.Signal(syscall.SIGTERM); err != nil { + log.Error(err) + } + }() + err = j.jobFunc(ctx, ech) + if err != nil { + log.Errorf("[benchmark job] failed to job: %v", err) + } + return + }) + + return ech, nil +} + +func (j *job) Stop(ctx context.Context) (err error) { + err = j.client.Stop(ctx) + return +} + +func calcRecall(linearRes, searchRes []*payload.Object_Distance) (recall float64) { + if len(linearRes) == 0 || len(searchRes) == 0 { + return + } + linearIds := map[string]struct{}{} + for _, v := range linearRes { + linearIds[v.Id] = struct{}{} + } + for _, v := range searchRes { + if _, ok := linearIds[v.Id]; ok { + recall++ + } + } + return recall / float64(len(linearRes)) +} + +func genVec(data [][]float32, cfg *config.BenchmarkDataset) [][]float32 { + start := cfg.Range.Start + end := cfg.Range.End + if (end - start) < cfg.Indexes { + end = cfg.Indexes + } + num := end - start + 1 + if len(data) < num { + num = len(data) + end = start + num + 1 + } + vectors := data[start : end+1] + return vectors +} diff --git a/pkg/tools/benchmark/job/service/option.go b/pkg/tools/benchmark/job/service/option.go new file mode 100644 index 00000000000..38013805ebc --- /dev/null +++ b/pkg/tools/benchmark/job/service/option.go @@ -0,0 +1,164 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service + +import ( + "context" + + "github.com/vdaas/vald/internal/client/v1/client/vald" + "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/test/data/hdf5" +) + +type Option func(j *job) error + +var defaultOpts = []Option{ + WithDimension(748), + // TODO: set default config for client +} + +func WithDimension(dim int) Option { + return func(j *job) error { + if dim > 0 { + j.dimension = dim + } + return nil + } +} + +func WithInsertConfig(c *config.InsertConfig) Option { + return func(j *job) error { + if c != nil { + j.insertConfig = c + } + return nil + } +} + +func WithUpdateConfig(c *config.UpdateConfig) Option { + return func(j *job) error { + if c != nil { + j.updateConfig = c + } + return nil + } +} + +func WithUpsertConfig(c *config.UpsertConfig) Option { + return func(j *job) error { + if c != nil { + j.upsertConfig = c + } + return nil + } +} + +func WithSearchConfig(c *config.SearchConfig) Option { + return func(j *job) error { + if c != nil { + j.searchConfig = c + } + return nil + } +} + +func WithRemoveConfig(c *config.RemoveConfig) Option { + return func(j *job) error { + if c != nil { + j.removeConfig = c + } + return nil + } +} + +func WithValdClient(c vald.Client) Option { + return func(j *job) error { + if c == nil { + return errors.NewErrInvalidOption("client", c) + } + j.client = c + return nil + } +} + +func WithErrGroup(eg errgroup.Group) Option { + return func(j *job) error { + if eg == nil { + return errors.NewErrInvalidOption("client", eg) + } + j.eg = eg + return nil + } +} + +func WithHdf5(d hdf5.Data) Option { + return func(j *job) error { + if d == nil { + return errors.NewErrInvalidOption("hdf5", d) + } + j.hdf5 = d + return nil + } +} + +func WithDataset(d *config.BenchmarkDataset) Option { + return func(j *job) error { + if d == nil { + return errors.NewErrInvalidOption("dataset", d) + } + j.dataset = d + return nil + } +} + +func WithJobTypeByString(t string) Option { + var jt jobType + switch t { + case "userdefined": + jt = USERDEFINED + case "search": + jt = SEARCH + } + return WithJobType(jt) +} + +func WithJobType(jt jobType) Option { + return func(j *job) error { + switch jt { + case USERDEFINED: + j.jobType = jt + case SEARCH: + j.jobType = jt + default: + return errors.NewErrInvalidOption("jobType", jt) + } + return nil + } +} + +func WithJobFunc(jf func(context.Context, chan error) error) Option { + return func(j *job) error { + if jf == nil { + return errors.NewErrInvalidOption("jobFunc", jf) + } + j.jobFunc = jf + return nil + } +} diff --git a/pkg/tools/benchmark/job/service/search.go b/pkg/tools/benchmark/job/service/search.go new file mode 100644 index 00000000000..2d14a6d84bf --- /dev/null +++ b/pkg/tools/benchmark/job/service/search.go @@ -0,0 +1,105 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service + +import ( + "context" + "testing" + "time" + + "github.com/vdaas/vald/apis/grpc/v1/payload" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" +) + +func (j *job) search(ctx context.Context, ech chan error) error { + log.Info("[benchmark job] Start benchmarking search") + if j.searchConfig == nil { + err := errors.NewErrInvalidOption("searchConfig", j.searchConfig) + select { + case <-ctx.Done(): + if err != context.Canceled { + ech <- errors.Wrap(err, ctx.Err().Error()) + } else { + ech <- err + } + case ech <- err: + } + return err + } + + // create data + vecs := genVec(j.hdf5.GetTest(), j.dataset) + timeout, _ := time.ParseDuration(j.searchConfig.Timeout) + cfg := &payload.Search_Config{ + Num: uint32(j.searchConfig.Num), + MinNum: uint32(j.searchConfig.MinNum), + Radius: float32(j.searchConfig.Radius), + Epsilon: float32(j.searchConfig.Epsilon), + Timeout: timeout.Nanoseconds(), + } + for i := 0; i < len(vecs); i++ { + log.Infof("[benchmark job] Start search: iter = %d", i) + lres, err := j.client.LinearSearch(ctx, &payload.Search_Request{ + Vector: vecs[i], + Config: cfg, + }) + if err != nil { + select { + case <-ctx.Done(): + if !errors.Is(err, context.Canceled) { + ech <- errors.Wrap(err, ctx.Err().Error()) + } else { + ech <- err + } + case ech <- err: + } + return err + } + bres := testing.Benchmark(func(b *testing.B) { + b.Helper() + b.ResetTimer() + start := time.Now() + sres, err := j.client.Search(ctx, &payload.Search_Request{ + Vector: vecs[i], + Config: cfg, + }) + if err != nil { + select { + case <-ctx.Done(): + if errors.Is(err, context.Canceled) { + ech <- errors.Wrap(err, ctx.Err().Error()) + } else { + ech <- err + } + case ech <- err: + break + } + } + latency := time.Since(start) + recall := calcRecall(lres.Results, sres.Results) + b.ReportMetric(recall, "recall") + b.ReportMetric(float64(latency.Microseconds()), "latency") + }) + // TODO: send metrics to the Prometeus + log.Infof("[benchmark job] Finish search bench: iter= %d \n%#v\n", i, bres) + } + + log.Info("[benchmark job] Finish benchmarking search") + return nil +} diff --git a/pkg/tools/benchmark/job/usecase/benchmarkd.go b/pkg/tools/benchmark/job/usecase/benchmarkd.go new file mode 100644 index 00000000000..4ceafcbfa9d --- /dev/null +++ b/pkg/tools/benchmark/job/usecase/benchmarkd.go @@ -0,0 +1,245 @@ +// +// Copyright (C) 2019-2022 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 provides usecases +package usecase + +import ( + "context" + "strconv" + + "github.com/vdaas/vald/internal/client/v1/client/vald" + iconf "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errgroup" + "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/interceptor/server/recover" + "github.com/vdaas/vald/internal/observability" + infometrics "github.com/vdaas/vald/internal/observability/metrics/info" + "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/test/data/hdf5" + "github.com/vdaas/vald/pkg/tools/benchmark/job/config" + handler "github.com/vdaas/vald/pkg/tools/benchmark/job/handler/grpc" + "github.com/vdaas/vald/pkg/tools/benchmark/job/handler/rest" + "github.com/vdaas/vald/pkg/tools/benchmark/job/router" + "github.com/vdaas/vald/pkg/tools/benchmark/job/service" +) + +type run struct { + eg errgroup.Group + cfg *config.Config + job service.Job + h handler.Benchmark + server starter.Server + observability observability.Observability +} + +func New(cfg *config.Config) (r runner.Runner, err error) { + log.Info("pkg/tools/benchmark/job/cmd start") + eg := errgroup.Get() + + if cfg.Job.Target != nil { + addr := cfg.Job.Target.Host + ":" + strconv.Itoa(cfg.Job.Target.Port) + if cfg.Job.ClientConfig.Addrs == nil { + cfg.Job.ClientConfig.Addrs = []string{addr} + } else { + cfg.Job.ClientConfig.Addrs = append(cfg.Job.ClientConfig.Addrs, addr) + } + } + + copts, err := cfg.Job.ClientConfig.Opts() + if err != nil { + return nil, err + } + if cfg.Job.ClientConfig.DialOption == nil { + copts = append(copts, grpc.WithInsecure(true)) + } + gcli := grpc.New(copts...) + vcli, err := vald.New( + vald.WithAddrs(cfg.Job.ClientConfig.Addrs...), + vald.WithClient(gcli), + ) + if err != nil { + return nil, err + } + + d, err := hdf5.New( + hdf5.WithNameByString(cfg.Job.Dataset.Name), + ) + if err != nil { + return nil, err + } + log.Info("pkg/tools/benchmark/job/cmd success d") + + job, err := service.New( + service.WithErrGroup(eg), + service.WithValdClient(vcli), + service.WithDataset(cfg.Job.Dataset), + service.WithJobTypeByString(cfg.Job.JobType), + service.WithDimension(cfg.Job.Dimension), + service.WithInsertConfig(cfg.Job.InsertConfig), + service.WithUpdateConfig(cfg.Job.UpdateConfig), + service.WithUpsertConfig(cfg.Job.UpsertConfig), + service.WithSearchConfig(cfg.Job.SearchConfig), + service.WithRemoveConfig(cfg.Job.RemoveConfig), + service.WithHdf5(d), + ) + if err != nil { + return nil, err + } + + h, err := handler.New() + if err != nil { + return nil, err + } + + grpcServerOptions := []server.Option{ + server.WithGRPCRegistFunc(func(srv *grpc.Server) { + // TODO register grpc server handler here + }), + server.WithGRPCOption( + grpc.ChainUnaryInterceptor(recover.RecoverInterceptor()), + grpc.ChainStreamInterceptor(recover.RecoverStreamInterceptor()), + ), + server.WithPreStartFunc(func() error { + // TODO check unbackupped upstream + return nil + }), + server.WithPreStopFunction(func() error { + // TODO backup all index data here + return nil + }), + } + + var obs observability.Observability + if cfg.Observability.Enabled { + obs, err = observability.NewWithConfig( + cfg.Observability, + infometrics.New("vald_benchmark_job_info", "Benchmark Job info", *cfg.Job), + ) + if err != nil { + return nil, err + } + } + + srv, err := starter.New( + starter.WithConfig(cfg.Server), + starter.WithREST(func(sc *iconf.Server) []server.Option { + return []server.Option{ + server.WithHTTPHandler( + router.New( + router.WithTimeout(sc.HTTP.HandlerTimeout), + router.WithErrGroup(eg), + router.WithHandler( + rest.New( + // TODO pass grpc handler to REST option + ), + ), + )), + } + }), + starter.WithGRPC(func(sc *iconf.Server) []server.Option { + return grpcServerOptions + }), + ) + if err != nil { + return nil, err + } + log.Info("pkg/tools/benchmark/job/cmd end") + + return &run{ + eg: eg, + cfg: cfg, + job: job, + h: h, + server: srv, + observability: obs, + }, nil +} + +func (r *run) PreStart(ctx context.Context) error { + if r.observability != nil { + if err := r.observability.PreStart(ctx); err != nil { + return err + } + } + if r.job != nil { + return r.job.PreStart(ctx) + } + return nil +} + +func (r *run) Start(ctx context.Context) (<-chan error, error) { + ech := make(chan error, 3) + var oech, dech, sech <-chan error + r.eg.Go(safety.RecoverFunc(func() (err error) { + defer close(ech) + if r.observability != nil { + oech = r.observability.Start(ctx) + } + dech, err = r.job.Start(ctx) + + if err != nil { + ech <- err + return err + } + + r.h.Start(ctx) + + sech = r.server.ListenAndServe(ctx) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case err = <-oech: + case err = <-dech: + case err = <-sech: + } + if err != nil { + select { + case <-ctx.Done(): + log.Error(err) + return errors.Wrap(ctx.Err(), err.Error()) + case ech <- err: + } + } + } + })) + return ech, nil +} + +func (r *run) PreStop(ctx context.Context) error { + return nil +} + +func (r *run) Stop(ctx context.Context) error { + if r.observability != nil { + r.observability.Stop(ctx) + } + if r.job != nil { + r.job.Stop(ctx) + } + return r.server.Shutdown(ctx) +} + +func (r *run) PostStop(ctx context.Context) error { + return nil +} diff --git a/pkg/tools/benchmark/operator/README.md b/pkg/tools/benchmark/operator/README.md new file mode 100644 index 00000000000..3300ab85a98 --- /dev/null +++ b/pkg/tools/benchmark/operator/README.md @@ -0,0 +1 @@ +# vald benchmark operator diff --git a/pkg/tools/benchmark/operator/config/config.go b/pkg/tools/benchmark/operator/config/config.go new file mode 100644 index 00000000000..915ec1251a4 --- /dev/null +++ b/pkg/tools/benchmark/operator/config/config.go @@ -0,0 +1,175 @@ +// +// Copyright (C) 2019-2022 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 stores all server application settings +package config + +import ( + "github.com/vdaas/vald/internal/config" +) + +// GlobalConfig is type alias for config.GlobalConfig +type GlobalConfig = config.GlobalConfig + +// Config represent a application setting data content (config.yaml). +// In K8s environment, this configuration is stored in K8s ConfigMap. +type Config 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"` + + // Scenario represents benchmark scenario configurations + Scenario *config.BenchmarkScenario `json:"scenario" yaml:"scenario"` +} + +// NewConfig represents the set config from the given setting file path. +func NewConfig(path string) (cfg *Config, err error) { + err = config.Read(path, &cfg) + if err != nil { + return nil, err + } + + if cfg != nil { + cfg.Bind() + } + + if cfg.Server != nil { + cfg.Server = cfg.Server.Bind() + } + + if cfg.Observability != nil { + cfg.Observability = cfg.Observability.Bind() + } + + if cfg.Scenario != nil { + cfg.Scenario = cfg.Scenario.Bind() + } else { + cfg.Scenario = new(config.BenchmarkScenario) + } + + return cfg, nil +} + +// func FakeData() { +// d := Config{ +// Version: "v0.0.1", +// Server: &config.Servers{ +// Servers: []*config.Server{ +// { +// Name: "agent-rest", +// Host: "127.0.0.1", +// Port: 8080, +// Mode: "REST", +// ProbeWaitTime: "3s", +// ShutdownDuration: "5s", +// HandlerTimeout: "5s", +// IdleTimeout: "2s", +// ReadHeaderTimeout: "1s", +// ReadTimeout: "1s", +// WriteTimeout: "1s", +// }, +// { +// Name: "agent-grpc", +// Host: "127.0.0.1", +// Port: 8082, +// Mode: "GRPC", +// }, +// }, +// MetricsServers: []*config.Server{ +// { +// Name: "pprof", +// Host: "127.0.0.1", +// Port: 6060, +// Mode: "REST", +// ProbeWaitTime: "3s", +// ShutdownDuration: "5s", +// HandlerTimeout: "5s", +// IdleTimeout: "2s", +// ReadHeaderTimeout: "1s", +// ReadTimeout: "1s", +// WriteTimeout: "1s", +// }, +// }, +// HealthCheckServers: []*config.Server{ +// { +// Name: "livenesss", +// Host: "127.0.0.1", +// Port: 3000, +// }, +// { +// Name: "readiness", +// Host: "127.0.0.1", +// Port: 3001, +// }, +// }, +// StartUpStrategy: []string{ +// "livenesss", +// "pprof", +// "agent-grpc", +// "agent-rest", +// "readiness", +// }, +// ShutdownStrategy: []string{ +// "readiness", +// "agent-rest", +// "agent-grpc", +// "pprof", +// "livenesss", +// }, +// FullShutdownDuration: "30s", +// TLS: &config.TLS{ +// Enabled: false, +// Cert: "/path/to/cert", +// Key: "/path/to/key", +// CA: "/path/to/ca", +// }, +// }, +// Scenario: &config.BenchmarkScenario{ +// Target: &config.BenchmarkTarget{ +// Host: "localhost", +// Port: 8081, +// }, +// Dataset: &config.BenchmarkDataset{ +// Name: "fashion-mnist-784-euc", +// Group: "Train", +// Indexes: 10000, +// Range: &config.BenchmarkDatasetRange{ +// Start: 100000, +// End: 200000, +// }, +// }, +// Jobs: []*config.BenchmarkJob{ +// { +// JobType: "search", +// Replica: 1, +// Repetition: 1, +// Dimension: 784, +// Iter: 10000, +// Num: 10, +// MinNum: 100, +// Radius: -1, +// Epsilon: 0.1, +// Timeout: "5s", +// }, +// }, +// }, +// } +// fmt.Println(config.ToRawYaml(d)) +// } diff --git a/pkg/tools/benchmark/operator/config/doc.go b/pkg/tools/benchmark/operator/config/doc.go new file mode 100644 index 00000000000..57ef74c464a --- /dev/null +++ b/pkg/tools/benchmark/operator/config/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 stores all server application settings +package config diff --git a/pkg/tools/benchmark/operator/handler/doc.go b/pkg/tools/benchmark/operator/handler/doc.go new file mode 100644 index 00000000000..f1014141ea4 --- /dev/null +++ b/pkg/tools/benchmark/operator/handler/doc.go @@ -0,0 +1,17 @@ +// +// Copyright (C) 2019-2022 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/tools/benchmark/operator/handler/grpc/handler.go b/pkg/tools/benchmark/operator/handler/grpc/handler.go new file mode 100644 index 00000000000..2ac0cd13465 --- /dev/null +++ b/pkg/tools/benchmark/operator/handler/grpc/handler.go @@ -0,0 +1,56 @@ +// +// Copyright (C) 2019-2022 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 grpc server logic +package grpc + +import ( + "context" + + "github.com/vdaas/vald/apis/grpc/v1/benchmark" + "github.com/vdaas/vald/internal/singleflight" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/service" +) + +type Benchmark interface { + benchmark.JobServer + Start(context.Context) +} + +type server struct { + benchmark.UnimplementedJobServer + + scenario service.Scenario + group singleflight.Group +} + +func New(opts ...Option) (bm Benchmark, err error) { + b := new(server) + + for _, opt := range append(defaultOpts, opts...) { + err = opt(b) + if err != nil { + return nil, err + } + } + + b.group = singleflight.New() + + return b, nil +} + +func (s *server) Start(ctx context.Context) { +} diff --git a/pkg/tools/benchmark/operator/handler/grpc/option.go b/pkg/tools/benchmark/operator/handler/grpc/option.go new file mode 100644 index 00000000000..4319ef3a748 --- /dev/null +++ b/pkg/tools/benchmark/operator/handler/grpc/option.go @@ -0,0 +1,22 @@ +// +// Copyright (C) 2019-2022 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 grpc server logic +package grpc + +type Option func(*server) error + +var defaultOpts = []Option{} diff --git a/pkg/tools/benchmark/operator/handler/rest/handler.go b/pkg/tools/benchmark/operator/handler/rest/handler.go new file mode 100644 index 00000000000..f752dbbbbf4 --- /dev/null +++ b/pkg/tools/benchmark/operator/handler/rest/handler.go @@ -0,0 +1,38 @@ +// +// Copyright (C) 2019-2022 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 provides rest api logic +package rest + +import ( + "github.com/vdaas/vald/apis/grpc/v1/benchmark" +) + +type Handler interface{} + +type handler struct { + js benchmark.JobServer +} + +func New(opts ...Option) Handler { + h := new(handler) + + for _, opt := range append(defaultOpts, opts...) { + opt(h) + } + + return h +} diff --git a/pkg/tools/benchmark/operator/handler/rest/option.go b/pkg/tools/benchmark/operator/handler/rest/option.go new file mode 100644 index 00000000000..ca3aee436d8 --- /dev/null +++ b/pkg/tools/benchmark/operator/handler/rest/option.go @@ -0,0 +1,22 @@ +// +// Copyright (C) 2019-2022 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 provides rest api logic +package rest + +type Option func(*handler) + +var defaultOpts = []Option{} diff --git a/pkg/tools/benchmark/operator/router/doc.go b/pkg/tools/benchmark/operator/router/doc.go new file mode 100644 index 00000000000..053c9aeb49c --- /dev/null +++ b/pkg/tools/benchmark/operator/router/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 provides implementation of Go API for routing http Handler wrapped by rest.Func +package router diff --git a/pkg/tools/benchmark/operator/router/option.go b/pkg/tools/benchmark/operator/router/option.go new file mode 100644 index 00000000000..fec24bfa260 --- /dev/null +++ b/pkg/tools/benchmark/operator/router/option.go @@ -0,0 +1,47 @@ +// +// Copyright (C) 2019-2022 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 provides implementation of Go API for routing http Handler wrapped by rest.Func +package router + +import ( + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/handler/rest" +) + +type Option func(*router) + +var defaultOpts = []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 + } +} + +func WithErrGroup(eg errgroup.Group) Option { + return func(r *router) { + r.eg = eg + } +} diff --git a/pkg/tools/benchmark/operator/router/router.go b/pkg/tools/benchmark/operator/router/router.go new file mode 100644 index 00000000000..9ca7f60c958 --- /dev/null +++ b/pkg/tools/benchmark/operator/router/router.go @@ -0,0 +1,52 @@ +// +// Copyright (C) 2019-2022 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 provides implementation of Go API for routing http Handler wrapped by rest.Func +package router + +import ( + "net/http" + + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/net/http/middleware" + "github.com/vdaas/vald/internal/net/http/routing" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/handler/rest" +) + +type router struct { + handler rest.Handler + eg errgroup.Group + timeout string +} + +// New returns REST route&method information from handler interface +func New(opts ...Option) http.Handler { + r := new(router) + + for _, opt := range append(defaultOpts, opts...) { + opt(r) + } + + return routing.New( + routing.WithMiddleware( + middleware.NewTimeout( + middleware.WithTimeout(r.timeout), + middleware.WithErrorGroup(r.eg), + )), + routing.WithRoutes([]routing.Route{ + // TODO add REST API interface here + }...)) +} diff --git a/pkg/tools/benchmark/operator/service/doc.go b/pkg/tools/benchmark/operator/service/doc.go new file mode 100644 index 00000000000..eb86a28c5bf --- /dev/null +++ b/pkg/tools/benchmark/operator/service/doc.go @@ -0,0 +1,18 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service diff --git a/pkg/tools/benchmark/operator/service/option.go b/pkg/tools/benchmark/operator/service/option.go new file mode 100644 index 00000000000..43b10ec5813 --- /dev/null +++ b/pkg/tools/benchmark/operator/service/option.go @@ -0,0 +1,55 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service + +import ( + "time" + + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/errors" +) + +// Option represents the functional option for scenario struct. +type Option func(sc *scenario) error + +var defaultOpts = []Option{ + WithReconcileCheckDuration("10s"), +} + +// WithErrGroup sets the error group to scenario. +func WithErrGroup(eg errgroup.Group) Option { + return func(sc *scenario) error { + if eg == nil { + return errors.NewErrInvalidOption("client", eg) + } + sc.eg = eg + return nil + } +} + +// WithReconcileCheckDuration sets the reconcile check duration from input string. +func WithReconcileCheckDuration(ts string) Option { + return func(sc *scenario) error { + t, err := time.ParseDuration(ts) + if err != nil { + return err + } + sc.rcd = t + return nil + } +} diff --git a/pkg/tools/benchmark/operator/service/scenario.go b/pkg/tools/benchmark/operator/service/scenario.go new file mode 100644 index 00000000000..dcd15ceab04 --- /dev/null +++ b/pkg/tools/benchmark/operator/service/scenario.go @@ -0,0 +1,372 @@ +// +// Copyright (C) 2019-2022 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 manages the main logic of benchmark job. +package service + +import ( + "context" + "reflect" + "strconv" + "sync/atomic" + "time" + + "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/k8s" + "github.com/vdaas/vald/internal/k8s/job" + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + benchjob "github.com/vdaas/vald/internal/k8s/vald/benchmark/job" + benchscenario "github.com/vdaas/vald/internal/k8s/vald/benchmark/scenario" + "github.com/vdaas/vald/internal/log" + corev1 "k8s.io/api/core/v1" + k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type Scenario interface { + PreStart(context.Context) error + Start(context.Context) (<-chan error, error) +} + +type scenario struct { + jobs atomic.Value + jobName string + jobNamespace string + jobTemplate string // row manifest template data of rebalance job. + jobObject *job.Job // object generated from template. + currentDeviationJobName atomic.Value + + scenarios atomic.Value + benchjobs atomic.Value + + rcd time.Duration // reconcile check duration + eg errgroup.Group + ctrl k8s.Controller +} + +// New creates the new scenario struct to handle vald benchmark job scenario. +// When the input options are invalid, the error will be returned. +func New(opts ...Option) (Scenario, error) { + sc := new(scenario) + for _, opt := range append(defaultOpts, opts...) { + if err := opt(sc); err != nil { + return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + } + } + + err := sc.initCtrl() + if err != nil { + return nil, err + } + return sc, nil +} + +// initCtrl creates the controller for reconcile k8s objects. +func (sc *scenario) initCtrl() (err error) { + // watcher of vald benchmark scenario resource + bs, err := benchscenario.New( + benchscenario.WithControllerName("benchmark scenario resource"), + benchscenario.WithNamespaces(sc.jobNamespace), + benchscenario.WithOnErrorFunc(func(err error) { + log.Errorf("failed to reconcile:", err) + }), + benchscenario.WithOnReconcileFunc(sc.benchScenarioReconcile), + ) + if err != nil { + return + } + + // watcher of vald benchmark job resource + bj, err := benchjob.New( + benchjob.WithControllerName("benchmark job resource"), + benchjob.WithOnErrorFunc(func(err error) { + log.Errorf("failed to reconcile:", err) + }), + benchjob.WithNamespaces(sc.jobNamespace), + benchjob.WithOnErrorFunc(func(err error) { + log.Error(err) + }), + benchjob.WithOnReconcileFunc(sc.benchJobReconcile), + ) + if err != nil { + return + } + + // watcher of job resource + job, err := job.New( + job.WithControllerName("benchmark job"), + job.WithNamespaces(sc.jobNamespace), + job.WithOnErrorFunc(func(err error) { + log.Errorf("failed to reconcile:", err) + }), + job.WithOnReconcileFunc(sc.jobReconcile), + ) + if err != nil { + return + } + + // create reconcile controller which watches valdbenchmarkscenario resource, valdbenchmarkjob resource, and job resource. + sc.ctrl, err = k8s.New( + k8s.WithControllerName("vald benchmark operator"), + k8s.WithResourceController(bs), + k8s.WithResourceController(bj), + k8s.WithResourceController(job), + ) + return +} + +// jobReconcile gets k8s job list and watches theirs STATUS. +// Then, it processes according STATUS. +func (sc *scenario) jobReconcile(ctx context.Context, jobList map[string][]job.Job) { + // TODO: impl logic + // for k, v := range jobList { + // log.Warnf("key: %s, value: %v", k, v) + // } + return +} + +// benchmarkJobReconcile gets the vald benchmark job resource list and create Job for running benchmark job. +func (sc *scenario) benchJobReconcile(ctx context.Context, jobList map[string]v1.ValdBenchmarkJob) { + log.Debugf("[reconcile benchmark job resource] job list: %#v", jobList) + if len(jobList) == 0 { + sc.benchjobs.Store(make(map[string]*v1.ValdBenchmarkJob, 0)) + log.Info("[reconcile benchmark job resource] job resource not found") + return + } + var cbjl map[string]*v1.ValdBenchmarkJob + if ok := sc.benchjobs.Load(); ok == nil { + cbjl = make(map[string]*v1.ValdBenchmarkJob, 0) + } else { + cbjl = ok.(map[string]*v1.ValdBenchmarkJob) + } + for k, job := range jobList { + if oldJob := cbjl[k]; oldJob != nil { + if oldJob.GetGeneration() != job.GetGeneration() { + // TODO: delete old version job + cbjl[k] = &job + } + } else { + log.Info("create job: ", k) + err := sc.createJob(ctx, job) + if err != nil { + log.Errorf("[reconcile benchmark job resource] failed to create job: %s", err.Error()) + } + cbjl[k] = &job + } + } + sc.benchjobs.Store(cbjl) +} + +// benchScenarioReconcile gets the vald benchmark scenario list and create vald benchmark job resource according to it. +func (sc *scenario) benchScenarioReconcile(ctx context.Context, scenarioList map[string]v1.ValdBenchmarkScenario) { + log.Debugf("[reconcile benchmark scenario resource] scenario list: %#v", scenarioList) + if len(scenarioList) == 0 { + sc.scenarios.Store(make(map[string]*v1.ValdBenchmarkScenario, 0)) + sc.benchjobs.Store(make(map[string]*v1.ValdBenchmarkJob, 0)) + log.Info("[reconcile benchmark scenario resource]: scenario not found") + return + } + var cbsl map[string]*v1.ValdBenchmarkScenario + if ok := sc.scenarios.Load(); ok == nil { + cbsl = make(map[string]*v1.ValdBenchmarkScenario, len(scenarioList)) + } else { + cbsl = ok.(map[string]*v1.ValdBenchmarkScenario) + } + for k, scenario := range scenarioList { + if oldScenario := cbsl[k]; oldScenario == nil { + err := sc.createBenchmarkJob(ctx, scenario) + if err != nil { + log.Errorf("[reconcile scenario] failed to create job: %s", err.Error()) + } + cbsl[k] = &scenario + } else { + // TODO delete old jobresource and job + if oldScenario.GetGeneration() != scenario.GetGeneration() { + cbsl[k] = &scenario + } + } + } + sc.scenarios.Store(cbsl) +} + +// createBenchmarkJob creates the ValdBenchmarkJob crd for running job. +func (sc *scenario) createBenchmarkJob(ctx context.Context, scenario v1.ValdBenchmarkScenario) error { + ownerRef := []k8smeta.OwnerReference{ + { + APIVersion: scenario.APIVersion, + Kind: scenario.Kind, + Name: scenario.Name, + UID: scenario.UID, + }, + } + for _, job := range scenario.Spec.Jobs { + bj := new(v1.ValdBenchmarkJob) + // set metadata.name, metadata.namespace + bj.Name = scenario.GetName() + "-" + job.JobType + "-" + strconv.FormatInt(time.Now().UnixNano(), 10) + bj.Namespace = scenario.GetNamespace() + bj.SetOwnerReferences(ownerRef) + + // set specs + bj.Spec = *job + if bj.Spec.Target == nil { + bj.Spec.Target = scenario.Spec.Target + } + if bj.Spec.Dataset == nil { + bj.Spec.Dataset = scenario.Spec.Dataset + } + // create benchmark job resource + c := sc.ctrl.GetManager().GetClient() + if err := c.Create(ctx, bj); err != nil { + // TODO: create new custom error + return err + } + } + return nil +} + +// createJobTemplate creates the job template for crating k8s job resource. +// ns and name are required to set job environment value. +func createJobTemplate(ns, name string) job.Job { + j := new(job.Job) + backoffLimit := int32(0) + j.Spec.BackoffLimit = &backoffLimit + j.Spec.Template.Spec.ServiceAccountName = "vald-benchmark-operator" + j.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyNever + j.Spec.Template.Spec.Containers = []corev1.Container{ + { + Name: "vald-benchmark-job", + Image: "local-registry:5000/vdaas/vald-benchmark-job:latest", + ImagePullPolicy: corev1.PullAlways, + LivenessProbe: &corev1.Probe{ + InitialDelaySeconds: int32(60), + PeriodSeconds: int32(10), + TimeoutSeconds: int32(300), + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/go/bin/job", + "-v", + }, + }, + }, + }, + StartupProbe: &corev1.Probe{ + FailureThreshold: int32(30), + PeriodSeconds: int32(10), + TimeoutSeconds: int32(300), + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/go/bin/job", + "-v", + }, + }, + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "liveness", + Protocol: corev1.ProtocolTCP, + ContainerPort: int32(3000), + }, + { + Name: "readiness", + Protocol: corev1.ProtocolTCP, + ContainerPort: int32(3001), + }, + }, + Env: []corev1.EnvVar{ + { + Name: "CRD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "CRD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.labels['job-name']", + }, + }, + }, + }, + }, + } + return *j +} + +// createJob creates benchmark job from benchmark job resource. +func (sc *scenario) createJob(ctx context.Context, bjr v1.ValdBenchmarkJob) (err error) { + bj := createJobTemplate(bjr.Namespace, bjr.Name) + bj.Name = bjr.Name + bj.Namespace = bjr.Namespace + bj.SetOwnerReferences(bjr.GetOwnerReferences()) + // create job + c := sc.ctrl.GetManager().GetClient() + if err = c.Create(ctx, &bj); err != nil { + // TODO: create new custom error + return err + } + if ok := sc.jobs.Load(); ok == nil { + sc.jobs.Store([]string{bj.Name}) + return + } else { + jobs := sc.jobs.Load().([]string) + jobs = append(jobs, bj.Name) + sc.jobs.Swap(jobs) + } + return +} + +func (sc *scenario) PreStart(ctx context.Context) error { + log.Infof("[benchmark scenario] start vald benchmark scenario") + return nil +} + +func (sc *scenario) Start(ctx context.Context) (<-chan error, error) { + scch, err := sc.ctrl.Start(ctx) + if err != nil { + return nil, err + } + ech := make(chan error, 2) + sc.eg.Go(func() error { + defer close(ech) + dt := time.NewTicker(sc.rcd) + defer dt.Stop() + for { + select { + case <-ctx.Done(): + return nil + case <-dt.C: + // TODO: Get Resource + _, ok := sc.scenarios.Load().(map[string]*v1.ValdBenchmarkScenario) + if !ok { + log.Info("benchmark scenario resource is empty") + continue + } + case err = <-scch: + if err != nil { + ech <- err + } + } + } + }) + + return ech, nil +} diff --git a/pkg/tools/benchmark/operator/usecase/benchmarkd.go b/pkg/tools/benchmark/operator/usecase/benchmarkd.go new file mode 100644 index 00000000000..116dfd6cd79 --- /dev/null +++ b/pkg/tools/benchmark/operator/usecase/benchmarkd.go @@ -0,0 +1,200 @@ +// +// Copyright (C) 2019-2022 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 provides usecases +package usecase + +import ( + "context" + + iconf "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errgroup" + "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/interceptor/server/recover" + + "github.com/vdaas/vald/internal/observability" + infometrics "github.com/vdaas/vald/internal/observability/metrics/info" + "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/pkg/tools/benchmark/operator/config" + handler "github.com/vdaas/vald/pkg/tools/benchmark/operator/handler/grpc" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/handler/rest" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/router" + "github.com/vdaas/vald/pkg/tools/benchmark/operator/service" +) + +type run struct { + eg errgroup.Group + cfg *config.Config + scenario service.Scenario + h handler.Benchmark + server starter.Server + observability observability.Observability +} + +func New(cfg *config.Config) (r runner.Runner, err error) { + log.Info("pkg/tools/benchmark/scenario/cmd start") + + eg := errgroup.Get() + + log.Info("pkg/tools/benchmark/scenario/cmd success d") + + sc, err := service.New( + service.WithErrGroup(eg), + ) + if err != nil { + return nil, err + } + + h, err := handler.New() + if err != nil { + return nil, err + } + + grpcServerOptions := []server.Option{ + server.WithGRPCRegistFunc(func(srv *grpc.Server) { + // TODO register grpc server handler here + }), + server.WithGRPCOption( + grpc.ChainUnaryInterceptor(recover.RecoverInterceptor()), + grpc.ChainStreamInterceptor(recover.RecoverStreamInterceptor()), + ), + server.WithPreStartFunc(func() error { + // TODO check unbackupped upstream + return nil + }), + server.WithPreStopFunction(func() error { + // TODO backup all index data here + return nil + }), + } + + var obs observability.Observability + if cfg.Observability.Enabled { + obs, err = observability.NewWithConfig( + cfg.Observability, + infometrics.New("vald_benchmark_scenario_info", "Benchmark Scenario info", *cfg.Scenario), + ) + if err != nil { + return nil, err + } + } + + srv, err := starter.New( + starter.WithConfig(cfg.Server), + starter.WithREST(func(sc *iconf.Server) []server.Option { + return []server.Option{ + server.WithHTTPHandler( + router.New( + router.WithTimeout(sc.HTTP.HandlerTimeout), + router.WithErrGroup(eg), + router.WithHandler( + rest.New( + // TODO pass grpc handler to REST option + ), + ), + )), + } + }), + starter.WithGRPC(func(sc *iconf.Server) []server.Option { + return grpcServerOptions + }), + ) + if err != nil { + return nil, err + } + log.Info("pkg/tools/benchmark/scenario/cmd end") + + return &run{ + eg: eg, + cfg: cfg, + scenario: sc, + h: h, + server: srv, + observability: obs, + }, nil +} + +func (r *run) PreStart(ctx context.Context) error { + if r.observability != nil { + if err := r.observability.PreStart(ctx); err != nil { + return err + } + } + if r.scenario != nil { + return r.scenario.PreStart(ctx) + } + return nil +} + +func (r *run) Start(ctx context.Context) (<-chan error, error) { + ech := make(chan error, 3) + var oech, dech, sech <-chan error + r.eg.Go(safety.RecoverFunc(func() (err error) { + defer close(ech) + if r.observability != nil { + oech = r.observability.Start(ctx) + } + + dech, err = r.scenario.Start(ctx) + if err != nil { + ech <- err + return err + } + + r.h.Start(ctx) + + sech = r.server.ListenAndServe(ctx) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case err = <-oech: + case err = <-dech: + case err = <-sech: + } + if err != nil { + select { + case <-ctx.Done(): + log.Error(err) + return errors.Wrap(ctx.Err(), err.Error()) + case ech <- err: + } + } + } + })) + return ech, nil +} + +func (r *run) PreStop(ctx context.Context) error { + return nil +} + +func (r *run) Stop(ctx context.Context) error { + if r.observability != nil { + r.observability.Stop(ctx) + } + return r.server.Shutdown(ctx) +} + +func (r *run) PostStop(ctx context.Context) error { + return nil +}